内核资料收集
1.
进程终止了它们的执行代码. 从这种意义上说, 这些进程死了. 这时,必须通知内核以便内核释放进程所拥有的资源,包括
内存, 打开文件及其它零碎的东西, 如信号量.
进程终止的一般方式是调用exit()库函数, 该函数释放C函数所分配的资源,执行编程者所注册的每个函数, 并结束从系统回收
进程的那个系统调用.
exit()函数可能由编程者显示地插入.另外,C编译程序总是把exit()函数插入到main()函数的最后一条语句之后.
在以下两种情况下, 内核可以选择性的强迫整个线程组死掉:
a. 进程接收到一个不能忽视的信号
b. 内核正在代表进程运行时, 在内核态产生一个不可恢复的CPU异常.
2. 进程终止
有两个终止用户态应用的系统调用:
a. exit_group()系统调用, 它终止整个线程组. do_group_exit()是实现这个系统调用的主要内核函数. 这是C库函数
exit()应该调用的系统调用.
b. exit()系统调用,它终止某一个线程,而不是该线程所属线程组中的所有其它进程. do_exit()是实现这个系统调用的主
要内核函数. 这是被诸如pthread_exit()的linux线程库的函数所调用的系统调用.
2.1 do_group_exit()函数
do_group_exit()函数杀死属于current线程组的所有线程. 它接收进程终止代号作为参数,进程终止代号可能是系统调用
exit_group()指定的一个值, 也可能是内核提供的一个错误代号. 该函数执行下述操作:
a. 检查退出进程的SIGNAL_GROUP_EXIT标志是否不为0, 如果不为0, 说明内核已经开始为进程组执行退出的过
程. 这种情况下, 就把存放在current->signal->group_exit_code中的值作为退出码, 调用do_exit()函数
b. 否则, 设置进程的SIGNAL_GROUP_EXIT标志并把终止代号存放到current->signal_group_exit_code字段
c. 调用zap_other_threads()函数杀死current线程组中的其他进程
d. 调用do_exit()函数, 把进程的终止代号传递给它. do_exit()杀死进程而且不再返回.
2.2 do_exit()函数
所有进程的终止都是由do_exit()函数来处理的, 这个函数从内核数据结构中删除对终止进程的大部分引用. do_exit()函数
接受进程的终止代号作为参数执行操作:
a. 设置进程描述符的flag字段为PF_EXITING标志, 以表示进程正在被删除.
b. 如果需要, 通过del_timer_sync()从动态定时器队列中删除进程描述符.
c. 分别调用exit_mm(), exit_sem(), __exit_files(), __exit_fs(), exit_namespace()和exit_thread()函数从进程描述符中分离出
与分页/信号量/文件系统/打开文件描述符/命名空间以及I/O权限位图相关的数据结构. 如果没有其它进程共享这些数据
结构,那么这些函数还删除所有这些数据结构.
d. 如果实现了被杀死进程的执行域和可执行格式的内核函数包含在内核模块中,则函数递减它们的使用计数器.
e. 把进程描述符的exit_code()字段设置成进程的终止代号, 这个值要么是_exit()或exit_group()系统调用参数,要么是由内核
提供的一个错误代号(异常终止).
f. 调用exit_notify()函数执行下面的操作:
1>更新父子进程的亲属关系.
2>检查被终止进程其进程描述符的exit_signal字段是否不为-1, 并检查进程是否是其所属进程的最后一个成员. 在这种
情况下,函数通过给正被终止进程的父进程发送一个信号(通常是SIGCHLD), 通知父进程子进程死亡.
3>否则,也就是exit_signal字段等于-1, 或线程组中还有其他进程, 那么只要进程正在被跟踪, 就向父进程发送一个
SIGCHLD(此时父进程是调试程序)
4>如果进程描述符的exit_signal字段等于-1, 而且进程没有被跟踪,就把进程描述符的exit_state字段设置为EXIT_DEAD,
然后调用release_task()回收进程的其它数据结构占用的内存. 并递减进程描述符的使用计数器.使用记数器变为1. 使进程描述符本身正好不会被释放.
5> 否则,如果进程描述符exit_signal字段不等于-1, 或进程正在被跟踪, 就把exit_state字段置为EXIT_ZOMBIE.
6>把进程描述符的flags字段设置为PF_DEAD标志
g. 调用schedule()函数选择一个新进程运行. 调度程序忽略处于EXIT_ZOMBIE状态的进程, 所以这种进程正好在schedule()
中的宏switch_to被调用后停止执行. 调度程序将会检查被替换的僵死进程描述符的PF_DEAD标志并递减使用计数,从而
说明进程不再存活的事实.
3. 进程删除
unix允许进程查询内核以获得父进程的PID, 或者其任何子进程的执行状态. 如, 进程可以创建一个子进程来执行特定的
任务, 然后调用诸如wait()这样的一些库函数检查子进程是否终止. 如果子进程已经终止. 那么它的终止代号将告诉父进程这
个任务是否已经完成.
为了遵循这些设计选择, 不允许unix内核在进程一终止就丢弃包含在进程描述符字段中的数据. 只有父进程发出了与被
终止进程相关的wait()类系统调用之后,才允许这样做. 这就是引入僵死进程的原因: 进程已死,但它必须保存它的描述符,直到
父进程得到通知.
父进程在子进程结束前结束会发生的情况: 强迫所有孤儿进程成为init进程的子进程. init进程在用wait()类系统调用检查其合
法的子进程终止时, 会终撤消僵死的进程.
release_task()函数从僵死进程的的描述符中分离出最后的数据结构; 对僵死进程的处理有两种可能方式:
a. 如果父进程不需要接收来自子进程的信号,就调用do_exit();
b. 如果已经给父进程发送了一个信号, 就调用wait4()或waitpid()系统调用.
在调用wait4()或waitpid()系统调用的情景下, 函数还将回收进程描述符所占用的内存空间, 而在不需要接收子进程信号情
况下, 内存的回收由进程调度程序来完成, 其步骤如下:
a. 递减终止进程的个数
b. 如果进程正在被跟踪, 函数将它从调试程序的ptrace_children链表中删除, 并让该进程重新属于初始进程的父进程.
c. 调用__exit_signal()删除所有的挂起信号并释放进程的signal_struct描述符. 如果该描述符不再被其它轻量级进程使用,
函数进一步删除这个数据结构. 此外函数调用exit_itimers()从进程中剥离掉所有的POSIX时间间隔定时器
d. 调用__exit_sighand()信号处理函数
e. 调用__unhash_process(), 该函数依次执行下面操作:
1>变量nr_threads减一
2>两次调用detach_pid(), 分别从PIDTYPE_PID/PIDTYPE_TGID类型的PID散列表中删除进程描述符.
3>如果进程是线程组的领头进程,那么再调用两次detah_pid()从PIDTYPE_PGID/PIDTYPE_SID类型的散列表中删除
进程的描述符
4>用宏REMOVE_LINKS从进程链表中解除进程描述符的链接
f. 如果进程不是线程组的领头进程, 领头进程处于僵死状态, 而且进程是线程组的最后一个成员, 则该函数向领头进程的进
程发送一个信号, 通知它进程已经死亡.
g. 调用sched_exit()函数来调整父进程的时间片
h. 调用put_task_struct()递减进程描述符的使用计数器, 如果计数器变为0, 则函数终止所有残留的对进程的引用:
1>递减进程所有者的use_struct数据结构的使用计数器, 如果使用计数器变为0, 就释放该数据结构
2>释放进程描述符以及thread_info描述符和内核态堆栈所占用的内存区域.
阅读(838) | 评论(0) | 转发(0) |