操作系统
进程
进程是程序的一次执行过程,是系统进行资源分配和调度的一个独立单位,目的是为了更好地描述和控制程序的并发执行。
结构 | 说明 |
---|---|
进程控制块 PCB | 进程存在的唯一标识,包括进程描述信息、控制信息、资源分配信息等。 |
程序段 | 能被进程调度到 CPU 执行的代码。 |
数据段 | 进程对应的程序加工处理的原始数据。 |
进程特征
特征 | 说明 |
---|---|
动态性 | 进程最基本的特征,进程是程序的一次执行,具有一定的生命周期。 |
并发性 | 多个进程可以同时存在于内存中,在一段时间内同时运行。 |
独立性 | 进程是一个能独立运行、独立接受调度的单位。 |
异步性 | 进程按不可预知的速度推进。 |
进程状态
状态 | 说明 |
---|---|
创建态 | 进程正在被创建,尚未转到就绪态。 |
就绪态 | 进程已处于准备运行的状态,获得了除处理机外的一切资源。 |
运行态 | 进程正在处理机上运行。 |
阻塞态 | 进程正在等待某一事件而暂停运行,如等待某资源可用或等待 IO 流完成。 |
结束态 | 进程正常结束或中断退出。 |
进程控制
进程的创建
过程:
为新进程分配一个唯一的进程标识号,并申请一个空白的 PCB,若申请失败则创建失败。
为新进程的程序和数据分配内存空间,若资源不足会进入阻塞态。
初始化 PCB,主要包括标志信息、处理机状态信息、以及设置进程优先级等。
若进程就绪队列未满,就将新进程插入就绪队列,等待被调度运行。
进程的终止
进程终止包括:正常结束,表示进程已经完成并准备退出;异常结束,表示进程在运行时发生异常,程序无法继续运行,例如非法指令,IO 故障等;外界干预,指进程因为外界请求而终止,例如操作系统干预等。
过程:
- 根据被终止进程的标识符,检索 PCB,读出该进程的状态。
- 若被终止的进程处于执行状态,终止执行,将处理机资源分配给其他进程。
- 若进程还有子进程,将所有子进程终止。
- 将该进程的全部资源归还给父进程或操作系统,并将 PCB 从队列删除。
进程的阻塞与唤醒
正在执行的进程由于等待的事件未发生,由系统执行阻塞原语,由运行态变为阻塞态。
阻塞过程:
- 找到将要被阻塞进程的 PCB。
- 如果进程为运行态,保护现场并转为阻塞态,停止运行。
- 把 PCB 插入相应事件的等待队列,当被阻塞进程期待的事件发生时,由相关进程调用唤醒原语,将进程唤醒。
唤醒过程:
- 在该事件的等待队列中找到进程对应的 PCB。
- 将其从等待队列中移除,设置状态为就绪态。
- 将 PCB 插入就绪队列,等待调度程序调度。
进程切换
进程切换是指处理机从一个进程的运行转到另一个进程上运行。
进程切换过程:
- 保存处理机上下文,包括程序计数器和其他寄存器。
- 更新 PCB 信息,并把 PCB 移入相应的阻塞队列。
- 选择另一个进程执行并更新其 PCB。
- 更新内存管理的数据结构,恢复处理机上下文。
进程通信⭐
管道通信
Linux 里的 |
就是一个管道,功能是将前一个命令的输出作为后一个命令的输入。
管道通信中存储空间是内核缓冲区,只允许一边写、另一边读,只要缓冲区有数据,进程就能读出。写进程会先将缓冲区写满才让读进程读,当缓冲区还有数据时,写进程不会往缓冲区写数据。因此管道是半双工通信,效率低,不适合进程间频繁交换数据。
消息队列
消息队列是保存在内核中的消息链表,消息的发送方和接收方要约定好消息体的数据类型,每个消息体都是固定大小的存储块,不像管道是无格式的字节流。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。消息队列的通信效率高于管道,进程发送消息时,把数据放在消息队列后就可以正常返回。
消息队列不适合较大数据的传输,因为内核中每个消息体都有最大长度限制。此外,消息队列通信存在数据拷贝开销,进程写数据到消息队列时,会发生从用户态拷贝数据到内核态的过程,读取数据时,会发生从内核态拷贝数据到用户态的过程。
共享内存
共享内存解决了消息队列中用户态与内核态间的数据拷贝问题,将虚拟地址空间映射到相同的物理内存,当某个进程写数据时,另一个进程马上就能看到,不需要拷贝,提高通信效率。
线程
线程是进程中的一个实体,是操作系统独立调度和分配的基本单位,由线程 ID、程序计数器、寄存器集合和堆栈组成。引入线程是为了减少程序并发执行的开销,进一步提高操作系统的并发性能。
线程和进程的区别⭐
调度:进程是分配资源的基本单位,而线程是独立调度的基本单位。
资源:进程拥有系统资源,而线程只有一点运行必需的资源。如果线程也是分配资源的单位,切换就需要较大开销,引入没有意义。
开销:进程切换涉及当前 CPU 环境的保存和设置,但线程切换只需要保存和设置少量的寄存器容量。
地址空间:进程的地址空间互相独立,同一进程的线程共享进程资源,进程内的线程对其他进程不可见。
通信:进程通信需要同步和互斥手段的辅助,保证数据一致性。线程可以直接读写进程数据段(全局变量)来进行通信。
线程实现
内核级线程 1:1 实现
内核通过操纵调度器对线程进行调度,并将线程的任务映射到处理器上。程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口,轻量级进程,即通常意义上的线程。
优点:当一个线程被阻塞时,允许其他线程继续执行。
缺点:代价相对较高,需要在用户态和内核态来回切换。
用户级线程 1:N 实现
从广义上讲,一个线程只要不是内核线程,就可以认为是用户线程。狭义上的用户线程指的完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及其是如何实现的。
优点:由于线程管理在用户空间进行,不需要切换到内核态,开销小,支持大规模并发。
缺点:一个线程在使用内核服务时被阻塞,整个进程都会被阻塞。
混合方式 N:M 实现
混合模式下既存在用户线程,也存在轻量级进程。用户线程完全建立在用户空间中,因此开销依然很小,可以支持大规模并发。轻量级进程作为用户线程和内核线程之间的桥梁,使用内核提供的线程调度功能及处理器映射,降低整个进程阻塞的风险。
死锁⭐
死锁就是指多个进程因为互相竞争资源而造成的一种互相等待的僵局,若无外力作用,这些进程都无法继续向前推进。
死锁的原因
不可剥夺资源数量的不足,如打印机,对可剥夺资源的竞争不会造成死锁。
进程请求和释放资源的顺序不当,例如进程 P1 和 P2 分别占用资源 R1 和 R2,而此时 P1 和 P2 又分别申请资源 R2 和 R1。
信号量的使用不当,进程间彼此互相等待对方发来的消息,也会使进程无法推进。
必要条件
互斥条件:进程对资源的占有具有排它性,如果进程请求的资源已被占用,请求就会被阻塞。
不可剥夺条件:进程获得的资源没有使用完成前,不能被其它进程强行获取,只能由占有它的进程主动释放。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求被阻塞,但进程也不会释放自己已经占有的资源。
循环等待条件:存在一个进程资源的循环等待链,链中每个进程已经占有的资源同时是其他进程请求的资源。
死锁处理
预防
-
破坏互斥条件
允许系统资源共享,但有的资源不可能同时访问,如打印机等临界资源。
-
破坏不可剥夺条件
允许剥夺其他进程已占有的资源,但释放已获得的资源可能会造成前一段工作的失效。
-
破坏请求和保持条件
采用预先资源分配法,在进程运行前一次性分配它需要的所有资源,缺点是有些资源可能仅在运行初期或快结束时才使用。
-
破坏循环等待条件
采用顺序资源分配法, 给系统资源编号,规定每个进程必须按编号递增的顺序请求资源。
避免
同样属于事先预防,但并不是事先采取某种限制措施,而是动态地根据情况处理。
-
系统安全状态
不安全状态可能会导致死锁,如果一次分配不会导致系统进入不安全状态,则将资源分配给进程,否则就让进程等待。
安全状态是指系统能按照某种进程推进顺序为每个进程分配资源,直到满足每个进程对资源的需求。
-
银行家算法
把操作系统视为银行家,资源视为资金,进程向操作系统申请资源相当于用户向银行家贷款。操作系统按照规则为进程分配资源,当进程首次申请资源时,要测试系统现存资源能否满足其最大需求量,可以则按申请量分配,否则推迟分配。
当进程在执行中继续申请资源时,先测试该进程已占有的资源数与申请的资源数之和是否超过该进程对资源的最大需求量,如果超过则拒绝分配,否则再测试系统现存的资源能否满足该进程尚需的最大资源量,如果满足则按申请量分配,否则推迟分配。
检测
系统死锁可用资源分配图描述,圆圈表示进程,框表示资源。从进程到资源的有向边是请求边,从资源到进程的边是分配边。
简化资源分配图可以检测系统状态是否为死锁状态。在资源分配图中,找出既不阻塞也不孤立的进程,消去它的所有请求边和分配边,使之成为孤立的点。如果系统状态不可被完全简化,那么代表死锁。
解除
-
资源剥夺法
挂起某些死锁进程,抢占其资源,分配给其它死锁进程。
-
撤销进程法
强制撤销部分甚至全部死锁进程,可以按进程优先级和撤销代价进行。
-
进程回退法
让一个或多个进程回退到足以避免死锁的地步,要求系统保持进程的历史信息,设置还原点。