零、学习路线
一、什么是进程控制?
进程控制是进程管理中最基本的功能,主要包括创建新进程、终止已完成的进程、将因发生异常情况而无法继续运行的进程置于阻塞状态、负责进程运行中的状态转换等功能。如当一个正在执行的进程因等待某事件而暂时不能继续执行时,将其转变为阻塞状态,而在该进程所期待的事件出现后,又将该进程转换为就绪状态等。进程控制一般是由OS的内核中的原语来实现的。
简化:进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。
简化plus: 进程控制就是要实现进程状态转换。
二、如何实现进程控制?(用原语来实现)
原语介绍
计算机进程的控制通常由原语完成。所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。在操作系统中,某些被进程调用的操作,如队列操作、对信号量的操作、检查启动外设操作等,一旦开始执行,就不能被中断,否则就会出现操作错误,造成系统混乱。所以,这些操作都要用原语来实现 原语是操作系统核心(不是由进程,而是由一组程序模块组成)的一个组成部分,并且常驻内存,通常在管态下执行。原语一旦开始执行,就要连续执行完,不允许中断。
问题:根据上图,那么为何进程控制(状态转换)过程要“一气呵成”?
答案:如果不能“一气呵成”,就有可能导致操作系统中的某些关键数据结构信息不统一的情况,这会影响操作系统进行别的管理工作。
分析如下:
假设PCB中的变量state表示进程当前所处状态,1表示就绪态,2表示阻塞态,....
那么如果一个进程处于就绪态,state = 1的话,那么这个进程的PCB肯定是需要挂在就绪队列里的;如果一个进程处于阻塞态,state = 2 的话,那么该进程的PCB必然是被挂在阻塞队列里的。
那么这个时候我们考虑这样一件事情:
我们能知道,处于阻塞队列的进程表示该进程肯定是在等待某些事件的发生,假设此时进程2等待的事件发生,则操作系统中,负责进程控制的内核程序至少需要做这样两件事:①将PCB2的state设为1;②将PCB2从阻塞队列放到就绪队列;
假设现在进程2的state已经被设置为1,而在完成了这一步之后,操作系统检查到中断信号,那系统肯定要对这个中断进行处理,而此时,该进程的state=1,从state的角度来看,进程2处于就绪态;但从该进程的PCB(PCB2)所处的队列来看,该进程又处于阻塞态。所以这就导致了PCB2中的state变量所表示的进程状态和PCB2所处的队列不一致。
因此,如果不能“一气呵成”,就有可能导致操作系统中的某些关键数据结构信息不统一的情况,这会影响操作系统进行别的管理工作。
那么为什么原语这种特殊的程序,他能够一气呵成,不被中断呢?这就是接下来我们要探讨的问题,即:如何实现原语的原子性。
三、如何实现原语的原子性?
原语的执行具有原子性,即执行过程只能一气呵成,期间不允许被中断。
可以用“关中断指令”和“开中断指令”这两个特权指令实现原子性。
正常情况: CPU每执行完一条指令都会例行检查是否有中断信号需要处理,如果有,则暂停运行当前这段程序,转而执行相应的中断处理程序。该流程如下图所示:
接下来我们来看如果执行了关中断指令,会发生什么情况?
假设CPU正在依次执行指令, 然后当CPU执行了关中断指令之后,就不再例行检查中断信号,直到执行开中断指令之后才会恢复检查。
假设CPU在执行完关中断指令后,紧接着执行指令a,执行完指令a,即使外部有中断信号,但是CPU也不会检查中断信号是否存在,CPU会紧接着执行指令b,等执行完开中断指令后,CPU恢复例行检查过程,发现在之前还有一个外部中断信号,那么接下来他就会执行外部中断,执行完成后返回并执行指令3。流程如下图所示:
这样,关中断、开中断之间的这些指令序列就是不可被中断的,这就实现了“原子性”。
思考: 如果这两个特权指令允许用户程序使用的话,会发生什么情况?
意味着在执行该用户程序开始时就可以先运行关中断指令,然后一直到该用户程序末尾在执行关中断指令,这样的话,只要我的程序上CPU运行,那么我的程序会一直霸占着CPU而不会被中断,那显然这种情况是不应该发生的。所以关中断指令和开中断指令是特权指令,只允许内核程序执行,而不允许普通用户使用。
四、进程控制相关的原语
4.1 创建原语
在系统中每当出现了创建新进程的请求后,OS便调用进程创建原语Creat按下述步骤创建一个新进程:
(1) 申请空白PCB,为新进程申请获得唯一的数字标识符,并从PCB集合中索取一个空白PCB。 (2) 为新进程分配其运行所需的资源,包括各种物理和逻辑资源,如内存、文件、I/O设备和CPU时间等。
(3) 初始化进程控制块(PCB)。
(4) 如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列(创建态 --> 就绪态)。
4.2 撤销原语
如果系统中发生了要求终止进程的某事件,OS便调用进程终止原语,按下述过程去终止指定的进程:
(1) 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态;
(2) 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度;
(3) 若该进程还有子孙进程,还应将其所有子孙进程也都予以终止,以防它们成为不可控的进程;
(4) 将被终止进程所拥有的全部资源或者归还给其父进程,或者归还给系统;
(5) 将被终止进程(PCB)从所在队列(或链表)中移出,等待其它程序来搜集信息。
4.3 进程的阻塞与唤醒过程
有下述几类事件会引起进程阻塞或被唤醒:
(1) 向系统请求共享资源失败。
(2) 等待某种操作的完成。
(3) 新数据尚未到达。
(4) 等待新任务的到达。
4.3.1 进程的阻塞过程
正在执行的进程,如果发生了上述某事件,进程便通过调用阻塞原语block将自己阻塞。可见,阻塞是进程自身的一种主动行为。进入block过程后,由于该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由“执行”改为阻塞,并将PCB插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞队列。最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换,亦即,保留被阻塞进程的处理机状态,按新进程的PCB中的处理机状态设置CPU的环境。
4.3.2 进程的唤醒过程
当被阻塞进程所期待的事件发生时,比如它所启动的I/O操作已完成,或其所期待的数据已经到达,则由有关进程(比如提供数据的进程)调用唤醒原语wakeup,将等待该事件的进程唤醒。wakeup执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。
4.4 切换原语
切换原语会让此时处在运行态的进程下处理机,让该进程回到就绪状态,并且从就绪队列当中,选择一个处于就绪态的进程,让他上处理机运行,所以切换原语会让两个进程的状态发生改变。
切换原语需要干这些事情:
(1)首先将进程的运行环境信息存入到PCB当中;
(2)将下处理机的进程的PCB移入到就绪队列;
(3)将上处理机的进程的PCB移入到运行队列;
(4)根据PCB恢复新进程所需的运行环境。
进程的运行环境指的是操作系统为进程提供的所有资源、上下文和数据,使进程能够正常执行其任务。每个进程在操作系统中都运行在独立的环境中,该环境包含进程需要的各种资源和信息。
以下是进程运行环境的主要组成部分:
1. 内存空间
进程的内存空间是操作系统为每个进程分配的虚拟地址空间,用于存储代码、数据和栈等。每个进程有独立的内存空间,防止一个进程访问另一个进程的内存,保证了进程的隔离性和安全性。
内存空间通常分为以下几个部分:
- 代码段(Text Segment):存储进程的可执行指令,即程序的二进制代码。
- 数据段(Data Segment):用于存储已初始化的全局变量和静态变量。
- BSS段:存储未初始化的全局变量和静态变量。
- 堆区(Heap):用于动态分配的内存,通常通过
malloc()
等函数分配,随着程序的执行动态增长和缩减。- 栈区(Stack):用于存储函数调用、局部变量等,遵循“后进先出”的原则。
2. 寄存器
寄存器是CPU中的存储单元,用于存储临时数据和处理指令。在进程的运行过程中,CPU寄存器保存着进程的状态信息,如程序计数器(Program Counter)、栈指针(Stack Pointer)等。操作系统在进行进程切换时,会保存当前进程的寄存器内容,并在调度新的进程时恢复它的寄存器状态。
关键寄存器包括:
- 程序计数器(PC):保存进程当前执行的指令地址。
- 栈指针(SP):指向当前进程的栈顶,用于函数调用和返回。
- 基址寄存器(BP):用于管理栈帧。
3. 文件描述符表
文件描述符表是进程与文件系统、I/O设备等交互的桥梁。每个进程拥有一个文件描述符表,存储进程打开的文件、设备等的引用信息。典型的文件描述符包括:
- 标准输入(
stdin
):文件描述符0- 标准输出(
stdout
):文件描述符1- 标准错误(
stderr
):文件描述符2每个文件描述符关联着一个内核对象(如文件、设备、管道等),通过这些文件描述符,进程可以对文件系统或设备进行读写操作。
4. 进程控制块(PCB)
进程控制块是操作系统用来管理和调度进程的结构体,包含进程的基本信息和状态。每个进程都有一个独立的PCB,存储该进程的各种信息,如进程ID(PID)、进程状态、优先级、寄存器内容、内存分布、文件描述符表等。
PCB中的信息对于进程的切换和调度至关重要。操作系统通过PCB来保存和恢复进程的状态,确保多任务调度的正确性。
5. 系统资源
进程的运行需要各种系统资源,操作系统为每个进程分配这些资源,使其能够顺利执行。系统资源包括:
- CPU时间片:进程需要CPU的执行时间,操作系统通过进程调度算法分配CPU时间片。
- 内存:操作系统为进程分配虚拟内存,并管理其内存页的映射与换页。
- I/O资源:进程需要访问文件、设备或网络资源,操作系统通过设备驱动程序和文件系统为进程提供这些资源。
6. 信号与中断处理
进程在运行过程中可能会接收到来自操作系统、其他进程或硬件设备的信号或中断。信号是一种软中断,进程可以通过定义信号处理程序来响应特定的信号(如
SIGKILL
、SIGINT
等)。操作系统会在进程执行时根据需要传递信号,并切换到相应的信号处理函数。7. 环境变量
进程的环境变量是进程启动时传递给它的一组键值对,用于配置进程的运行环境。环境变量常用于传递系统配置信息、程序路径、库路径等。常见的环境变量包括:
PATH
:可执行程序的搜索路径。HOME
:用户的主目录路径。LD_LIBRARY_PATH
:动态链接库的搜索路径。8. 用户身份与权限
每个进程都有与之关联的用户身份和权限信息。操作系统通过进程的用户ID(UID)和组ID(GID)来限制进程对文件、设备等资源的访问权限。进程运行时的身份决定了它是否具有某些操作的权限。
9. 优先级与调度信息
进程的优先级影响操作系统如何调度它的运行。高优先级的进程通常会优先获得CPU时间,而低优先级进程则可能需要等待。操作系统使用不同的调度算法(如时间片轮转、优先级调度等)来分配CPU资源。
10. 虚拟内存和分页机制
大多数现代操作系统都使用虚拟内存机制来为每个进程提供独立的地址空间。操作系统通过分页机制将进程的虚拟地址映射到物理内存或磁盘上的页面文件。虚拟内存不仅隔离了进程的内存空间,还允许操作系统通过换页机制将不活跃的页面换出到磁盘,以提高内存使用效率。
11. 栈和堆管理
进程的栈和堆是动态内存管理的重要组成部分。
- 栈:用于函数调用和局部变量的存储。栈空间是有限的,并遵循“后进先出”原则。
- 堆:用于动态内存分配,通常通过
malloc
、free
等函数管理。堆空间可以动态增长,但也容易导致内存泄露。12. 执行上下文
执行上下文是进程当前执行的状态,包括程序计数器、寄存器状态、栈指针、堆指针等。操作系统在进行进程切换时,会保存当前进程的执行上下文,并在恢复时重新加载,确保进程从上次暂停的地方继续执行。
总结
进程的运行环境包括其所需的所有资源和上下文,如内存空间、寄存器、文件描述符、进程控制块、系统资源等。操作系统通过管理这些资源,确保进程能够顺利执行并在多任务系统中与其他进程隔离。
六、总结
无论哪个进程控制原语,要做的无非三类事情:
1.更新PCB中的信息(修改进程状态(state),保存/恢复运行环境)
2.将PCB插入合适的队列
3.分配/回收资源