ui
操作系统(二)进程管理
- 一、进程
- 程序和进程
- 进程控制块(PCB)
- 进程的组成
- 进程的特征
- 进程的状态与转换
- 进程状态的转换
- 进程的组织
- 链接方式
- 索引方式
- 进程的控制
- 进程的创建
- 进程的终止
- 进程阻塞
- 进程唤醒
- 进程切换
- 进程通信
- 共享存储
- 消息传递
- 管道通信
- 二、线程
- 什么是线程
- 线程的属性
- 线程的实现
- 用户级线程
- 内核级线程
- 多线程模型
- 处理机调度
- 高级调度
- 低级调度
- 中级调度
- 调度算法
- 先来先服务(FCFS)
- 短作业优先
- 高响应比优先
- 时间片轮转
- 优先级调度算法
- 多级反馈队列调度算法
- 三、进程同步和互斥
- 四、死锁
- 死锁产生的必要条件
- 预防死锁
- 静态策略
- 破坏互斥条件
- 破坏不剥夺条件
- 破坏请求和保持条件
- 破坏循环等待条件
- 动态策略
- 安全序列
- 银行家算法
- 死锁的检测和解除
- 死锁的检测
- 死锁的解除
一、进程
程序和进程
程序:是静态的,编译好的二进制文件,存储在磁盘中,.exe程序
进程:是动态的,是程序的一次执行过程
进程控制块(PCB)
- 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
- 进程的状态,有就绪、运行、挂起、停止等状态。
- 进程切换时需要保存和恢复的一些CPU寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录(Current Working Directory)。
- umask掩码。
- 文件描述符表,包含很多指向file结构体的指针。
- 和信号相关的信息。
- 用户id和组id。
- 会话(Session)和进程组。
- 进程可以使用的资源上限(Resource Limit)。
进程的组成
PCB:
- 进程描述信息
- 进程控制和管理信息
- 资源分配清单
- 处理机相关信息
程序段:程序的代码(指令序列)
数据段:运行过程中产生的各种数据 (如:程序中定义的变量)
进程的特征
- 动态性:进程是程序的一次执行过程,是动态地产生、变化和消亡的
- 并发性:内存中有多个进程实体,各进程可并发执行
- 独立性:进程是能独立运行、独立获得资源、独立接受调度的基本单位
- 异步性:各进程按各自独立的、不可预知的速度向前推进,操作系统要提供"进程同步机制"来解决异步问题
- 结构性:每个进程都会配置一个PCB。 结构上看,进程由程序段、数据段、PCB组成
进程的状态与转换
进程基本的状态有5种。分别为运行状态、就绪状态、阻塞状态、创建状态、终止状态。
创建态:进程正在被创建时,它的状态是“创建态”,在这个阶段操作系统会为进程分配资源、初始化PCB。
就绪态:当进程创建完成后,便进入“就绪态”,处于就绪态的进程已经具备运行条件,但由于没有空闲CPU,就暂时不能运行。
运行态:如果一个进程此时在CPU上运行,那么这个进程处于“运行态”。CPU会执行该进程对应的程序(执行指令序列)
阻塞态:在进程运行的过程中,可能会请求等待某个事件的发生(如等待某种系统资源的分配,或者等待其他进程的响应)。在这个事件发生之前,进程无法继续往下执行,此时操作系统会
让这个进程下CPU,并让它进入“阻塞态”当CPU空闲时,又会选择另一个“就绪态”进程上CPU运行
终止态:一个进程可以执行 exit 系统调用,请求操作系统终止该进程。此时该进程会进入“终止态”,操作系统会让该进程下CPU,并回收内存空间等资源,最后还要回收该进程的PCB。当终止进程的工作完成之后,这个进程就彻底消失了
进程状态的转换
就绪态->运行态:进程被调度
运行态-> 就绪态:时间片到,或CPU被其他高优先级的进程抢占
运行态-> 阻塞态:等待系统资源分配,或等待某事件发生(主动行为)
阻塞态->就绪态:资源分配到位,等待的事件发生(被动行为)
创建态-> 就绪态:系统完成创建进程相关的工作
运行态->终止态:进程运行结束,或运行过程中遇到不可修复的错误
进程的组织
链接方式
索引方式
进程的控制
进程的创建
步骤:
- 申请空白PCB
- 为新进程分配所需资源
- 初始化PCB
- 将PCB插入就绪队列
引起进程创建的事件
-
用户登录:分时系统中,用户登录成功,系统会建立为其建立-个新的进程
-
作业调度:多道批处理系统中,有新的作业放入内存时,会为其建立-个新的进程
-
提供服务:用户向操作系统提出某些请求时,会新建一个进程处理该请求
-
应用请求:由用户进程主动请求创建一个子进程
进程的终止
步骤:
- 从PCB集合中找到终止进程的PCB
- 若进程正在运行,立即剥夺CPU,将CPU分配给其他进程
- 终止其所有子进程(进程间的关系是树形结构),将该进程拥有的所有资源归还给父进程或操作系统
- 删除PCB
引起进程终止的事件:
- 正常结束(进程自己请求终止(exit 系统调用)
- 异常结束(整数除以0、非法使用特权指令,然后被操作系统强行杀掉)
- 外界干预(Ctrl+Alt+delete,用户选择杀掉进程)
进程阻塞
步骤:
- 找到要阻塞的进程对应的PCB
- 保护进程运行现场,将PCB状态信息设置为“阻塞态",暂时停止进程运行
- 将PCB插入相应事件的等待队列
引起进程阻塞的事件:
- 需要等待系统分配某种资源
- 需要等待相互合作的其他进程完成工作
进程唤醒
- 在事件等待队列中找到PCB
- 将PCB从等待队列移除,设置进程为就绪态
- 将PCB插入就绪队列,等待被调度
引起进程唤醒的事件:
- 等待的事件发生
进程切换
步骤:
- 将运行环境信息(进程上下文)存入PCB
- PCB移入相应队列
- 选择另一个进程执行,并更新其PCB
- 根据PCB恢复新进程所需的运行环境
引起进程切换的事件:
- 当前进程时间片到
- 有更高优先级的进程到达
- 当前进程主动阻塞
- 当前进程终止
进程通信
共享存储
在linux中,使用共享内存的方式来实现共享内存。要让各个进程互斥的进行,可以采用加锁或者pv操作。
消息传递
进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。
步骤:
- 1:进程p发送消息
- 2:通过消息队列写入数据,通常需要从进程拷贝到内核(进程X的pcb消息队列中)。
- 3:进程X从内核拷贝到进程
- 4:然后再从进程中X拷贝到输出文件
管道通信
- 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
- 各进程要互斥地访问管道(由操作系统实现)
- 当管道写满时,写进程将阻塞,直到读进程将管道中的数据取走,即可唤醒写进程。
- 当管道读空时,读进程将阻塞,直到写进程往管道中写入数据,即可唤醒读进程。
- 管道中的数据一旦被读出,就彻底消失。因此,当多个进程读同一个管道时,可能会错乱。对此,通常有两种解决方案:①一个管道允许多个写进程,一个读进程②允
许有多个写进程,多个读进程,但系统会让各个读进程轮流从管道中读数据。
二、线程
什么是线程
线程可以理解为“轻量级进程”。线程是一个基本的CPU执行单元,也是程序执行流的最小单位。引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)引入线程后,进程只作为除CPU之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。线程则作为处理机的分配单元。
线程的属性
- 线程是处理机调度的单位
- 多CPU计算机中,各个线程可占用不同的CPU
- 每个线程都有一个线程ID、线程控制块(TCB)
- 线程也有就绪、阻塞、运行三种基本状态
- 线程几乎不拥有系统资源
- 同一进程的不同线程间共享进程的资源
- 由于共享内存地址空间,同-进程中的线程间通信甚至无需系统干预
- 同一进程中的线程切换,不会引起进程切换
- 不同进程中的线程切换,会引起进程切换
- 切换同进程内的线程,系统开销很小
- 切换进程,系统开销较大
线程的实现
用户级线程
从代码的角度看,线程其实就是一段代码逻辑。上述三段代码逻辑上可以看作三个“线程”。
while 循环就是一个最弱智的“线程库”,线程库完成了对线程的管理工作(如调度)。
- 用户级线程由应用程序通过线程库实现,所有的线程管理工作都由应用程序负责(包
括线程切换) - 用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预。
- 在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。“用户级线程”就是“从用户视角看能看到的线程”
优缺点
优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统
开销小,效率高
缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可
在多核处理机上并行运行。
内核级线程
- 内核级线程的管理工作由操作系统内核完成。
- 线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才
能完成。 - 操作系统会为每个内核级线程建立相应的TCB(Thread Control Block,线程控制块),
通过TCB对线程进行管理。“内核级线程”就是“从操作系统内核视角看能看到的线程”
优缺点
优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核
处理机上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到
核心态,因此线程管理的成本高,开销大。
多线程模型
一对一模型:一个用户级线程映射到一个内核级线程。每个用户进程有与用户级线程同
数量的内核级线程。
优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核
处理机上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到
核心态,因此线程管理的成本高,开销大
多对一模型:多个用户级线程映射到一个内核级线程。且一个进程只被分配一个内核级
线程。
优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统
开销小,效率高
缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可
在多核处理机上并行运行
多对多模型:n 用户及线程映射到 m 个内核级线程(n >= m)。每个用户进程对应 m 个内核级线程。克服了多对一模型并发度不高的缺点(一个阻塞全体阻塞),又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点
处理机调度
高级调度
高级调度(作业调度)。按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程。每个作业只调入一次,调出一次。作业调入时会建立PCB,调出时才撤销PCB。
低级调度
低级调度(进程调度/处理机调度):按照某种策略从就绪队列中选取一个进程,将处理机分配给它。
中级调度
内存不够时,可将某些进程的数据调出外存。等内存空闲或者进程需要运行时再重新调入内存。暂时调到外存等待的进程状态为挂起状态。被挂起的进程PCB会被组织成挂起队列
中级调度(内存调度)—按照某种策略决定将哪个处于挂起状态的进程重新调入内存。一个进程可能会被多次调出、调入内存,因此中级调度发生的频率要比高级调度更高。
三种调度的联系、对比
调度算法
先来先服务(FCFS)
短作业优先
高响应比优先
时间片轮转
优先级调度算法
多级反馈队列调度算法
三、进程同步和互斥
进程的同步:并发性带来了异步性,有时需要通过进程同步解决这种异步问题。有的进程之间需要相互配合地完成工作,各进程的工作推进需要遵循一定的先后顺序。
进程互斥:对临界资源的访问,需要互斥的进行。即同一时间段内只能允许一个进程访问该资源
临界资源分为四个部分:
- 进入区,检查是否可进入临界区, 若可进入,需要"上锁”
- 临界区,访问临界资源的那段代码
- 退出区,负责"解锁"
- 剩余区,其余代码部分
需要遵循的原则:
- 空闲让进,临界区空闲时, 应允许一个进程访问
- 忙则等待,临界区正在被访问时,其他试图访问的进程需要等待
- 有限等待,要在有限时间内进入临界区,保证不会饥饿
- 让权等待,进不了临界区的进程,要释放处理机,防止忙等
四、死锁
死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象
饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。
死循环:某进程执行过程中一直跳不出某个循环的现象。
死锁产生的必要条件
- 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁
- 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
预防死锁
静态策略
破坏互斥条件
互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。
如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如: SPOOLing技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用SPOOLing技术将打印机改造为共享设备
破坏不剥夺条件
不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。
- 方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)
破坏请求和保持条件
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。
破坏循环等待条件
循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源, 同类资源(即编号相同的资源)一次申请完。
原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。
动态策略
安全序列
所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。
如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。
如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是“银行家算法”的核心思想
银行家算法
死锁的检测和解除
死锁的检测
为了能对系统是否已发生了死锁进行检测,必须:
- 用某种数据结构来保存资源的请求和分配信息;
- 提供一种算法,利用上述信息来检测系统是否已进入死锁状态
死锁的解除
解除死锁的主要方法有:
- 资源剥夺法。挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿。
- 撤销进程法(或称终止进程法)。强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这种方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来。
- 进程回退法。让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点。