一. 进程切换
进程在运行过程中,内核如何实现进程切换的。
进程切换图示:
进程P0处于运行态,进程P1处于空闲(就绪态或等待态),进程P0执行过程中遇到中断或系统调用(如时间片用完产生时钟中断),切换到内核,切换过程中要保护现场到PCB0。之后选择下一个就绪进程P1,需要恢复P1现场,恢复完之后切到进程P1。若P1的时间片又用完了,又产生时钟中断,又切换到内核,要保存P1的现场到对应的进程控制块PCB1。
进程控制块保存进程的相关信息:进程的标识信息、状态信息(状态寄存器的相关信息,地址空间的起始位置,第一级列表的起始地址,进程的状态),进程所占用的资源(占用的存储资源组织成进程的内存管理的数据结构,占用的内核堆栈),保护现场信息,指针用于描述当前进程在哪个状态队列中。
其中,进程的内存地址空间数据结构mm_struct包含映射到内存地址空间里头的哪些内存块,第一级列表的起始地址。
进程队列用双向链表组织。
二. 进程创建
进程创建是操作系统提供给用户使用的系统调用。不同操作系统提供的进程创建API(系统调用接口)不同。
Unix采用fork、exec两个系统调用来完成新进程的创建,fork后两个进程执行的程序是一样的,id不一样,相应变量就不一致,执行内容也不一样;exec把新程序加载到内存中重写新创建的子进程。系统调用返回时,变成两个进程,第二个进程已经变成新的程序在运行了。
- fork要进行准确的复制,并给子进程新的id,放到就绪队列里头。
fork在进行子进程的创建时,地址空间的复制
- 程序的加载和执行——把整个地址空间内容换掉
- 第一个进程是怎么样的创建的
(1)创建空闲进程:操作系统没有要执行的用户进程,系统没有新的任务要执行了,系统处于暂停状态,但CPU还在执行指令,执行空闲进程的处理。初始化空闲进程之后放入就绪队列里,优先级最低。
(2)创建内核线程
用户态进程初始化之前,先创建了一个内核线程,通过kernelthread函数实现(也会调用do fork(),进程创建和线程创建靠函数里的参数区别),要建立起内存共享(和其他的内核线程共享同一个内核地址空间)。 - 开销
三. 进程加载
是指用户的应用程序通过系统调用exec()加载
程序加载的过程:从外存把可执行文件加载进来,跳转到上面执行即可。主要工作就是可执行文件格式的识别。不同系统可以加在的可执行文件的格式不一样。
具体有三个主要的函数:分别负责获取相应的参数,核心加载功能(创建新进程所需的内存相关的段结构),识别可执行文件的格式并且在内存里加载对应的段然后开始执行。
四. 进程等待与退出
是父子进程之间的一种交互,完成子进程的资源回收。
(1)父进程先进行wait,之后子进程执行exit,唤醒父进程,等到父进程得到CPU的使用权可以执行时,父进程就处理子进程的返回值。
(2)子进程先执行exit,返回一个值等待父进程处理,父进程的wait直接返回一个值。
exit作用:释放空间,把自己的状态改为僵尸状态,唤醒他的父亲。
除了进程创建,切换,加载,等待,退出这些系统调用之外,对于进程控制还有一些其他的系统调用。如nice系统调用完成对进程初始优先级的设置;ptrace系统调用完成一个进程对另一个进程的执行过程的控制;sleep可以使进程进入等待状态,时间到了进入就绪状态。
上面所讲的进程控制的系统调用对三状态进程模型是有影响的,fork会导致新进程的创建,父进程执行wait导致父进程由运行状态变为等待状态,exit会导致进程由运行状态变为退出状态。子进程的exit会导致由于wait进入等待状态的父进程回到就绪状态。