Linux0.11内核--进程的结束

时间:2021-07-06 09:20:01

进程的结束

结束一个进程,就是要释放该进程所有的结构和资源,让系统从此之后再也感觉不到它的存在。如前面所说的,一个进程的结构包括:

  1. task[]数组中一项,指向了该进程的task_struct和内核堆栈所在页面;

  2. GDT中两项,一项是TSS描述符,一项是LDT描述符;

  3. 若干页目录项和若干页表。

一个进程拥有的资源包括:

  1. 进程拥有的所有物理页面(包括页表和task_struct所占页面);

  2. 进程打开的所有文件。

GDT中的两项不用特意清除,以后别的进程要用时直接覆盖上去就了。因此,进程要结束就要做好如下几件事情

  1. 释放所有物理页面;

  2. 关闭所有打开的文件;

  3. 清除task[]数组中相关项。

清除task[]数组项往往是最后一步工作。当该项被清除后,进程就不可能被调度函数schedule()再次选中了。同时,进程结束时还可能需要与父进程通信,所以子进程一般完成前面两个任务,然后通知父进程“子进程要结束了!!”,最后由父进程做最后的task[]数组项清除。子进程通过系统调用exit()完成前两项任务,把自己变成僵死状态(TASK_ZOMBIE)。父进程通过系统调用waitpit()完成最后的扫尾。

Linux0.11内核--进程的结束

 

 

 


一个进程eixt()后并没有完全消失,它的物理页面全部释放了,页表页目录项也全部清除。只剩进程控制块(task_struct)和内核堆栈占用的一页内存还保留着,在task[]数组中还有它的一项。同时进程进入了僵死状态,在也不会被调度执行。一个进程永远不会再次被调度,但是它却还占有task[]数组中的一项,这让人很难忍受,所以这时的进程就像僵尸一样让人讨厌。

最后一页内存以及task[]数组项的清除工作应该由父进程调用waitpid()完成,但是很有可能父进程无法完成,比如如下情况:

  1. 父进程早于子进程exit()

  2. 子进程僵死,但父进程没有调用waitpid()

  3. 父进程调用waitpid(),但因为种种原因没有释放子进程资源就退出了。

我们必须要在这样的情况下仍然能消灭掉掉僵死进程,否则它们永远占用task[]数组项,会使得find_empty_process()函数找不到空闲的task[]数组项,导致无法创建新的进程。

解决方法很简单,如果父进程无法完成,就让让进程1来做。当一个父进程早于子进程exit()时,它把所有的子进程过继给进程1。当父进程没有调用waitpid(),或调用waitpid()但还是没能消灭僵死进程时,僵死的子进程永远存在,直到父进程exit(),这时子进程过继给进程1,同时父进程向进程1发送SIGCHLD信号。

init()的源代码分析见系统初始化这章,这里我们主要看进程1如何接管父进程无法消灭的僵死进程。init()的伪代码如下:

Linux0.11内核--进程的结束

init()初始化应用环境后,进程1就运行在while(1)的死循环中。进程1创建进程2,进程2execve()系统调用运行shell。当用户与shell交互时,进程1运行在里层while(1)循环中,该循环的作用就是收拾所有的僵死进程。

wait()函数的定义在wait.c中,它封装了waitpid()函数,等待任何子进程结束。


wait()


 

pid_t wait(int * wait_stat)

{

return waitpid(-1,wait_stat,0);

}



 

父进程调用waitpid()最多只能处理一个满足条件的子进程,除非它像进程1那样在死循环中调用waitpid()。进程1死循环调用wait(),直到当前shell退出,保证处理完所有僵死进程。