进程管理
进程与线程
概念
定义:进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
组成:
- PCB:进程存在的唯一标志。PID不同。
- 程序段:存放程序代码。
- 数据段:存放程序运行中的数据。
组织形式:
- 链接方式:按照进程状态将PCB分为多个队列,链表连接方式。
- 索引方式:按照进程状态建立不同的索引表,表项指向PCB。
特征:
- 动态性:最基本的特征。
- 独立性:进程是系统进行资源分配、调度的独立单位。
- 异步性、并发性、结构性。
进程状态
三种状态:运行状态、就绪状态、阻塞状态。
状态转换:
- 不存在由(阻塞态——>运行态)和(就绪态——>阻塞态)的转换。
- (运行态——>阻塞态)是主动的过程,(阻塞态——>就绪态)是被动的过程。
进程控制
概念:
- 通过进程控制来实现进程状态转换。
- 原语需要一气呵成,不可中断。关中断、开中断。
相关原语:创建、终止、阻塞、唤醒、切换。
进程通信
共享存储:设置一个共享空间,但需要保证进程的互斥访问。
- 基于数据结构的共享:只能存储某种数据结构的数据。
- 基于存储区的共享:高级共享。
管道通信:设置一个管道,即缓冲区,进行读写操作。
- 进行半双工通信(单向),互斥访问。
- 写满时,不能再写。读空时,不能再读。
- 没写满,不能读。没读空,不能写。
消息传递:结构化消息,含消息头(发送方、接收方等进程信息)、消息体。
- 直接通信方式:消息挂到接收方的消息队列中。
- 间接通信方式:发到信箱。
- 使用发送/接收原语。
线程
线程属性:
- 线程成为CPU分配的基本单位,进程是资源分配的单位。
- 同一进程的不同线程共享资源,共享内存地址空间,不需要系统干预。
- 同一进程内的线程切换不会引起进程的切换。
- 也有就绪、阻塞等状态。
实现方式:
- 用户级线程 ;多对一模型:一个线程阻塞,会导致整个进程都被阻塞。
- 内核级线程 ;一对一模型:一个用户级线程对应一个内核级线程
本节错题
优先级可以分为静态和动态两种,动态优先级可根据运行情况随时调整,而不是不可改动的。
系统发生死锁时,有可能进程全都处于阻塞态,或无进程任务。因此,并非任何时刻都有进程处于运行态。
程序是静态的,进程是动态的。不可将两者相互描述。
新进程进入就绪态,不会导致运行态转变为其他状态。
进程的就绪数目越多,争夺CPU的进程就越多,但只要就绪队列不为空,CPU就总是可以调度进程运行,保持繁忙。这与就绪进程的数目没有关系,除非就绪队列为空,此时CPU进入等待态,导致CPU的效率下降。
由于引入线程后,线程变为处理机调度的基本单位,因此,同一进程或不同进程的线程都可以并发执行。
降低进程优先级的时机:进程的时间片用完。提高优先级的时机:进程完成IO操作进入就绪队列、长期处于就绪状态。
进程被唤醒,进入就绪状态,而不是运行态。优先级可能被适当提高。
进程可以创建进程或线程,线程也可以创建线程,但线程不能创建进程。
处理机调度
概念
调度:从就绪队列中按照一定的算法选择一个进程,并将CPU分配给他。
三种调度:
- 高级调度(作业调度):选择合适的作业调入内存。(外存 ==》内存,面向作业)
- 中级调度(内存调度):从挂起的队列中选择合适的进程调入内存。(外存 ==〉内存,面向进程)
- 低级调度:为进程分配处理机。
进程调度
进程调度的主动与被动:
- 主动放弃:进程正常终止、运行过程中发生异常而终止、主动阻塞。
- 被动放弃:时间片用尽、优先级更高的进程、更紧急的进程。
不能进行进程调度:进程在操作系统内核程序临界区;处理中断;原语过程。
评价指标
系统吞吐量:单位时间内完成作业的数量。
\[ 系统吞吐量 = \frac{总共完成了多少道作业}{总共花了多少时间} \]
周转时间:从作业被提交给系统,到最后完成为止的时间间隔。
带权周转时间:值越小,满意度越高。必然>=1。
\[ 周转时间 = 作业完成时间 - 作业提交时间 \\ 平均周转时间 = \frac{各作业周转时间之和}{作业数}\\ 带权周转时间 = \frac{作业周转时间}{作业实际运行时间}\\ 平均带权周转时间 = \frac{各作业带权周转时间和}{作业数} \]
调度算法
先来先服务FCFS:按照到达的先后顺序调度,事实上就是等待时间越久的越优先得到服务。
优点:公平、算法实现简单。非抢占式。
缺点:对长作业有理,对短作业不利。
短作业优先SJF:最短的作业或进程得到服务。如果运行时间相同,则选择先到达的进程运行。
最短剩余时间优先:比较新进程和正在运行的进程的剩余时间。(抢占式)
缺点:对短作业有利,长作业不利。会导致饥饿现象,源源不断的短作业等,则长作业长时间得不到服务。
高响应比优先HRRN:不会导致饥饿。
\[ 响应比 = \frac{等待时间+要求服务时间}{要求服务时间} \]
时间片轮转RR:
- 轮流让各个进程执行一个时间片,若在一个时间片内未执行完成,则剥夺处理机。
- 用于进程调度。(只有作业放入内存建立了相应的进程后,才能被分配处理机时间片)
- 抢占式算法;时钟中断宣布时间片到。
- 在运行过程中,如果同一时刻,既有进程下处理机,又有新进程到达,则默认新到达的进程先进入就绪队列。
- 如果时间片太大,时间片轮转调度算法退化为先来先服务调度算法,会增大进程相应时间。时间片太小,又会导致进程切换过于频繁。
- 不会导致饥饿。
优先级调度算法:
- 每个作业、进程都有各自的优先级,调度时选择优先级高的。
- 如果进程在就绪队列等了太长时间,可以适当提高其优先级。如果一个进程频繁进行IO操作,可以适当提高。
- 优点:用优先级区分紧急程度,适用于实时操作系统。可灵活地调整对各个作业的偏好程度。
- 会导致饥饿。
多级反馈队列调度算法:
- 用于进程调度;抢占式算法。
- 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大。
- 若用完时间片,进程还未结束,则进程进入下一级队列队尾。
- 只有第k级队列为空时,才会为k+1级队头的进程分配时间片。
- 被抢占处理机的进程重新放回原队列队尾。
- 会导致饥饿。源源不断短进程到达,被降级的优先级会长期得不到服务。
进程同步与互斥
概念
进程同步:进程的直接制约关系,各个进程的工作推进需要遵循一定的先后顺序。
进程互斥:对临界资源的访问,需要互斥的进行。即同一时间段内只能允许一个进程访问该资源。
- 进入区检查并上锁。退出去解锁。
- 临界区访问临界资源的代码。剩余区时其余代码部分。
- 遵循原则:空闲让进、忙则等待、有限等待、让权等待(while循环等待)。
进程互斥的软件实现方法
单标志法:
- 两个进程在访问完临界区后会把使用临界区的权限交给另一个进程。
- 根据例子可知,尽管临界区是空闲的,也有可能出现不允许另一进程访问的情况。
- 违背了空闲让进的原则。
双标志先检查法:
- 设置一个bool数组,数组中各个元素用来标记各进程是否想要进入临界区。
flag[0]=true
意味着0号进程现在想要进入临界区。 - 由于异步性,可能会导致进程同时访问临界区。违背了忙则等待原则。
- 原因在于:进入区的检查和上锁的处理不是一气呵成的。
双标志后检查法:
- 由于异步性,可能导致进程都无法进入临界区。
- 解决了忙则等待原则,但是又违背了空闲让进和有限等待的问题。会产生饥饿现象。
Peterson算法:
- 进入区:主动争取、主动谦让,检查对方是否谦让。
- 不遵循让权等待原则,会发生忙等。
进程互斥的硬件实现方法
中断屏蔽:“开/关中断指令”的实现。
缺点:
- 不适合多处理机。比如现有某一进程正在访问临界区,而另一进程正在运行且需要访问同一临界区,就会导致冲突。
- 只适用于内核进程,不适用于用户进程。因为两指令权限很大。
TestAndSet:
- TSL指令是用硬件实现的,执行过程不允许被中断,只能一气呵成。
- 缺点:不满足让权等待原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致忙等。
- 记录以前的lock值,修改lock值。
swap指令:逻辑上和TSL相同。
信号量机制
整型信号量:该整数表示某一资源的可用数量。
记录型信号量semaphore:
- P操作:该进程请求一个单位的资源,先执行
value--
,若结果<0,说明该资源之前已经分配完毕,因此该进程需要调用block原语
进行自我阻塞,即从当前运行的进程从(运行态 --> 阻塞态),主动放弃处理机,并插入该类资源的等待队列中。 - V操作:释放一个单位的资源,先执行
value++
,若结果<=0,说明此时仍然有进程在等待该类资源,应调用wakeup原语
唤醒等待该资源的进程。(阻塞态 --> 就绪态)
信号量实现互斥、同步
互斥实现步骤:
- 分析并发进程的关键活动,划定临界区。(比如说打印机是需要互斥访问,这类资源就应该放在临界区)
- 设置互斥信号量
mutex
,初值为1。(信号量的名字可以任意设置。而具体的值,则是根据资源数量而设置) - 在临界区之前执行
P(mutex)
。 - 在临界区之后执行
V(mutex)
。
注意:
- 对不同的临界资源,需要设置不同的互斥信号量。
- P、V操作需要成对出现。缺少P导致不能保证资源的互斥访问。缺少V导致资源不被释放,进程不被唤醒。
同步实现步骤(需要一前一后执行的操作):
- 分析什么地方需要实现同步关系。
- 在“前操作”之后执行
V(S)
- 在“后操作”之前执行
P(S)
注意:
- 执行完P、V操作后,S的结果还是0。
- 如果先执行P操作,会主动阻塞。然后在V操作中执行
wakeup
原语唤醒该阻塞进程。 - 如果先执行V操作,执行P操作时就知道,此时有可用资源。
实现前驱关系:
- 要为每一对前驱关系各设置一个同步变量。 S1 ---> S2; S1 ---> S3....
- 根据自身情况,执行相应的P、V操作。
生产者-消费者问题
生产者消费者问题是一个互斥、同步的综合问题。
解题要点:
两对同步关系:有时候是消费者需要等待生产者生产、有时候是生产者要等待消费者消费。因此是两个同步信号量。
如图所示,实际上,full 和 empty的名字不是我们的关注点。问题在于,根据箭头方向,决定两者的前后顺序。
一对互斥关系:对于临界区的访问,通常是为 1 的。
注意实现互斥和同步的两个P操作的先后顺序。如果任意的交换位置,可能会导致死锁现象,即相互等待。
多生产者-多消费者问题
- 不设置专门的mutex,也不会出现多个进程同时访问盘子的现象的原因:
答:当缓冲区的大小为1时,在任何时刻,多类同步信号量中最多只有一个是1,所以最多只有一个进程的P操作不会被阻塞,并且顺利地进入临界区。
如果把盘子(缓冲区)数量改成>=2:
答:如果没有mutex,可能会导致缓冲区数据覆盖的情况,即两个进程同时访问同一块缓冲区。
要把对“行为动作”的分析转变成对事件的分析。 盘子变空 ===》放入水果。(不是很明白)
吸烟者问题
假设一个系统有三个抽烟者进程和一个供应者进程。
同步关系:四个同步关系。
互斥关系:桌子可以抽象为容量为1的缓冲区。且有三种组合。
这里我有点疑问。为什么producer的P操作不是放在if语句之前。
读者-写者问题
问题描述:
- 允许多个读者同时对文件执行读操作。
- 只允许一个写者往文件中写信息。
- 任一写者在完成写操作之前不允许其他读者或写者工作。
- 写者执行写操作前,应让已有的读者和写者全部退出。
主要思想:
- 其核心思想在于设置了一个计数器count用来记录当前正在访问共享文件的读进程数。我们可以用count的值来判断当前进入的进程是否是第一个(进行加锁)/最后一个(进行解锁)进程。
- 又由于count变量的检查和赋值不能一气呵成,从而导致某些错误,因此要借助互斥信号量。
哲学家问题
问题描述:
- 哲学家进餐问题的关键在于解决进程死锁:进程之间只存在互斥关系,而此时每个进程都需要同时持有两个临界资源,因此就有死锁的可能。
- 对于解决方案:各哲学家拿筷子这件事必须互斥的进行。这就保证了即使一个哲学家在拿筷子拿到一半时被阻塞,也不会有别的哲学家会继续尝试拿筷子。这样的话,当前正在吃饭的哲学家放下筷子后,被阻塞的哲学家就可以获得等待的筷子了。
管程
管程的引入:解决信号量机制编程麻烦的问题,更好地实现同步互斥。
基本特征:
- 管程中需要定义共享数据结构,对其初始化,再定义一组用来访问该结构的函数。
- 各外部进程/线程只能通过管程提供的特定入口才能访问共享数据。
- 每次仅允许一个进程在管程内执行某个内部过程。
补充:
- 这种互斥特性是由编译器负责实现的。
- 可在管程中设置条件变量及等待/唤醒操作以解决同步问题。
死锁
概念
死锁、饥饿、死循环:
共同点:都是进程无法顺利向前推进
不同点:
- 死锁是“循环等待对方手里的资源”导致。因此必须要有两个或两个以上的进程同时发生死锁。发生死锁的进程一定处于阻塞态。
- 饥饿可能只有一个进程发生饥饿。发生饥饿的进程即可能是阻塞态(等待io设备),也可能是就绪态(长期得不到处理机)。
- 死循环是被管理者的问题,是可以上处理机上运行的。死锁和饥饿是管理者进行调度方案不佳的问题,处于核心态。
死锁产生的必要条件:
- 互斥条件:对必须互斥使用的资源争抢。
- 不剥夺条件:只能是进程主动释放资源,不可剥夺。
- 请求和保持条件:保持某种资源不放的同时,请求别的资源。
- 循环等待条件:存在一种进程资源的循环等待链条。循环等待未必死锁。
处理死锁的策略:
- 预防死锁:破坏产生条件。
- 避免死锁:避免系统进入不安全状态。
- 死锁的检测和解除:允许死锁发生,系统负责检测出死锁并解除。
预防死锁
四种方法与各自缺点
破坏互斥条件 | 方法 | 缺点 |
---|---|---|
破坏不剥夺条件 | 申请资源得不到满足时,立即释放拥有的所有资源;申请的资源被其他进程占用时,由操作系统协助剥夺。 | 可行性不高。 |
破坏请求和保持条件 | 运行前分配好所有需要的资源,之后一直保持。 | 剥夺资源可能导致部分工作实效;反复申请和释放导致系统开销大;导致饥饿。 |
破坏循环等待条件 | 给资源编号,必须按编号从小到大的顺序申请资源。 | 资源利用率低;可能导致饥饿。 |
破坏互斥条件 | 将临界资源改造为可共享使用的资源(SPOOLing技术)。 | 不方便增加新设备;会导致资源浪费。 |
避免死锁
银行家算法步骤:
- 检查此次申请是否超过了之前声明的最大需求数。
- 检查此时系统剩余的可用资源是否还能满足这次请求。
- 试探分配,修改数据结构
- 用安全性算法检查此次分配是否会导致系统进入不确定状态。
安全性算法步骤:
- 检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,则将他加入安全序列,并把该进程持有的资源全部回收。
- 不断重复上述过程,看最终是否能让所有进程都加入安全序列。
系统处于不安全状态未必死锁,但死锁一定处于不安全状态。
死锁的检测和解除
死锁检测算法:
- 依次消除与不阻塞进程相连的边,直到无边可消,求出安全序列。
- 若资源分配图时不可完全简化的,说明发生了死锁。
死锁的解除:
- 资源剥夺法:挂起死锁进程,并抢占它的资源。
- 撤销进程法:强制撤销部分或全部进程。又可能导致功亏一篑。
- 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步。这需要系统记录进程的历史信息,设置还原点。