一、进程的基本概念
1.1 进程控制块(PCB)
操作系统对这些进程只做两件事情:“描述”+“组织”。
描述:将进程描述出来,就需要描述出来当前进程的各种信息,我们的操作系统将这些各种信息放在一个结构体中,这个结构体就称为进程控制块。PCB包括:
- 进程管理相关
- 进程id
- 描述虚拟地址空间的信息(页表)
- 进程调度信息
- 进程的状态:有就绪,运行,挂起,停止等状态
- 进程的优先级信息
- 进程调度所需的其他信息:和相应的进程调度算法有关,如进程等待CPU的时间总和,进程已经执行的时间总和。
- 进程控制信息
- 程序和数据的地址:进程程序和数据所在的内存和外存地址,以便调度进程时使用
- 进程同步和通信机制
- 资源清单:除了CPU之外进程所需要的全部资源以及已经分配到该进程的资源清单。
- 链接指针:该进程所在队列的下一个进程的PCB首地址
- CPU状态信息:进程切换时需要保存和恢复的一些CPU寄存器信息
- 通用寄存器:可以被用户程序进程访问,用于暂存信息
- 指令计数器:存放者当前进程要访问的下一条指令地址
- 程序状态字PSW:含有状态信息
- 存储管理相关
- 代码段指针
- 数据段指针
- 堆栈段指针
- 文件管理相关
- 当前工作目录
- 文件描述符表,包含多个执行file结构体的指针
- 用户id和组id
组织:用我们所学过的数据结构的各种各样的数据结构知识将这些结构体组织起来
- 线性表方式:不论进程的状态如何,将所有的PCB连续地存放在内存的系统区。这种方式适用于系统中进程数目不多的情况
- 索引表方式:该方式时线性表的改进,系统按照进程的状态分别建立就绪索引表,阻塞索引表等
- 链接表方式:系统按照进程的状态将进程的PCB组成队列,从而形成就绪队列,阻塞队列和运行队列等。
索引表方式:
链接表方式:
1.2 Linux的进程控制块
Linux操作系统的进程控制块采用C语言结构task_struct来表示。这个结构包含用于表示进程的所以必要信息:进程状态、调度和内存管理信息、打开文件列表、指向父进程的指针及指向子进程和兄弟进程列表的指针等
long state;
struct sched_entity se;
struct task_struct *parent;
struct list_head children;
struct files_struct *files;
struct mm_struct *mm;
在Linux内核中,所有活动的进程都采用task_struct的双向链表。内核采用一个指针即current,指向当前系统正在执行的进程。
二、进程状态
2.1 进程五态模型
- 新建:对应于进程被创建时的状态,尚未进入就绪队列。
- 运行:进程占有处理器正在运行的状态。
- 等待:又称阻塞或者睡眠,进程等待某个事件发生(如I/O完成或收到信号)
- 就绪:进程具备运行条件,等待系统分配处理器以便运行的状态。
- 终止:指进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终止权的进程所终止时所处的状态。
2.2 进程七态模型
当系统资源尤其是内存资源已经不能满足进程运行的要求时,必须把某些进程挂起(suspend),对换到磁盘对换区中,释放它占有的某些资源,暂时不参与低级调度(线程调度)。起到平滑系统操作负荷的目的。
- 挂起就绪态:进程具备运行条件,但目前在外存中,只有它被对换到内存才能被调度执行。
- 挂起等待态:表明进程正在等待某一个事件发生且在外存中。
- 等待态→挂起等待态:操作系统根据当前资源状况和性能要求,可以决定把等待态进程对换出去成为挂起等待态。
- 挂起等待态→挂起就绪态:引起进程等待的事件发生之后,相应的挂起等待态进程将转换为挂起就绪态
- 挂起就绪态→就绪态:当内存中没有就绪态进程,或者挂起就绪态进程具有比就绪态进程更高的优先级,系统将把挂起就绪态进程转换成就绪态。
- 就绪态→挂起就绪态:操作系统根据当前资源状况和性能要求,也可以决定把就绪态进程对换出去成为挂起就绪态。
挂起进程等同于不在内存中的进程,因此挂起进程将不参与低级调度直到它们被调换进内存。
挂起进程具有如下特征:
- 该进程不能立即被执行
- 挂起进程可能会等待一个事件,但所等待的事件是独立于挂起条件的,事件结束并不能导致进程具备执行条件。(等待事件结束后进程变为挂起就绪态)
- 进程进入挂起状态是由于操作系统、父进程或进程本身阻止它的运行。
- 结束进程挂起状态的命令只能通过操作系统或父进程发出。
三、进程调度
3.1 调度程序
高级调度(外存-->内存)
按某种算法在外存中处于后备队列的作业中挑选一个(或多个)作业,给它分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竞争处理机的权利。
高级调度是外存与内存之间的调度。在这里,每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,而调出的时机必然是作业运行结束后,发生频率最低。
中级调度(外存-->内存)
被调到外存等待的进程处于挂起态。该进程的数据段和代码段会被调回外存,但PCB依旧会留在内存中的,并不会被调回外存,因为操作系统只有通过该进程的PCB,才能对其进行管理。被挂起进程的PCB会被操作系统放到挂起队列中。
中级调度,就是决定将哪个挂起状态的进程从外存重新调回内存。
注意和高级调度区分,虽然同样是从外存调到内存,但高级调度是调入,中级调度是调回。
由于一个进程可能会被多次调出、调回内存,因此中级调度发生的频率要比高级调度的高。
低级调度(内存-->CPU)
低级调度的主要任务是按照某种规则从就绪队列中选取一个进程,将CPU分配给它。低级调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置低级调度。而且低级调度的频率很高,一般几十毫秒一次。
总结:
3.2 调度队列
进程在进入系统时,会被加入到作业队列,这个队列包括系统内的所有进程。
驻留在内存中的,就绪态的线程保存在就绪队列中。这个队列通常用链表实现,其头节点有两个指针,用于指向链表的第一个和最后一个PCB块,每个PCB还包括指向下一个PCB块的指针。
系统还有其他队列。当一个进程被分配了 CPU 后,它执行一段时间,最终退出,或被中断,或等待特定事件发生如 I/O 请求的完成。假设进程向一个共享设备如磁盘发出 I/O 请求。由于系统具有许多进程,磁盘可能忙于其他进程的 I/O 请求,因此该进程可能需要等待磁盘。等待特定 I/O 设备的进程列表,称为设备队列。每个设备都有自己的设备队列
进程调度通常用队列图来表示,如图所示。每个矩形框代表一个队列;这里具有两种队列:就绪队列和设备队列。圆圈表示服务队列的资源;箭头表示系统内的进程流向。
最初,新进程被加到就绪队列;它在就绪队列中等待,直到被选中执行或被分派。当该进程分配到 CPU 并执行时,以下事件可能发生:
- 进程可能发出 I/O 请求,并被放到 I/O 队列。
- 进程可能创建一个新的子进程,并等待其终止。
- 进程可能由于中断而被强制释放 CPU,并被放回到就绪队列。
对于前面两种情况,进程最终从等待状态切换到就绪状态,并放回到就绪队列。进程重复这一循环直到终止;然后它会从所有队列中删除,其 PCB 和资源也被释放。
3.3 上下文切换
中断会导致 CPU 从执行当前任务改变到执行内核程序。这种操作在通用系统中经常发生。当中断发生时,系统需要保存当前运行在 CPU 上的进程的上下文,以便在处理后能够恢复上下文,即先挂起进程,再恢复进程。
切换 CPU 到另一个进程需要保存当前进程状态和恢复另一个进程的状态,这个任务称为上下文切换。
进行上下文切换时,内核会将旧进程状态保存在其 PCB 中,然后加载经调度而要执行的新进程的上下文。上下文切换的时间是纯粹的开销,因为在切换时系统并没有做任何有用工作。