进程退出

 

很多进程终止了他们本该执行的代码,从这种意义上说,他们已经“死”了。当这种情况发生时,必须通过内核以便内核释放进程所拥有的资源。

进程终止的一般方式是调用exit()库函数,该函数释放C函数库所分配的资源,执行编程者所注册的每个函数,并结束从系统回收进程所执行的那个系统调用。

内核可以有选择地强迫整个线程组死掉。这发生在以下两种典型情况下:当进程接收到一个不能处理或忽视的信号时,或者当内核正在代表进程运行在内核态产生一个不可恢复的CPU异常时。

 

1 进程终止


在Linux 2.6中有两个终止用户态应用的系统调用:
(1)exit_group()系统调用,它终止整个线程组,即整个基于多线程的应用。do_group_exit()函数是实现这个系统调用的内核函数。
(2)exit()系统调用,它终止某一个线程,而不管该线程所属线程组中其他进程。do_exit()函数是实现这个系统调用的内核函数。这是诸如pthread_exit()的Linux线程库的函数所调用的系统调用。

do_group_exit()函数杀死属于current线程组的所有进程。它接受进程终止代号作为参数,进程终止代号可能是系统调用exit_group()(正常结束)指定的一个值,也可能是内核提供的一个错误代号(异常结束)。

该函数执行下述操作:
1.    检查退出进程的SIGNAL_GROUP_EXIT标志是否不为0,如果不为0,说明内核已经开始为线程组执行退出的过程。在这种情况下,就把存放在current->signal->group_exit_code中的值当做退出码,然后跳转到第4步。

2.    否则,置SIGNAL_GROUP_EXIT位并把终止代号存放到current->signal->group_exit_code字段。

3.    调用zap_other_threads函数杀死current线程组所有进程。为了完成这个步骤,函数扫描与current-tgid对应的PIDTYPE_TGID类型的散列表中的每一个PID链表,向表中所有不同于current的进程发出SIGKILL信号,结果,所有这样的进程都将执行do_exit()函数,从而被杀死。

4.    调用do_exit()函数,把进程的终止代号传递给它。正如我们将要看到的,do_exit()杀死进程而不再返回。

所有的进程终止都是由do_exit函数来处理的,这个函数从内核数据结构中删除对终止进程的大部分引用。do_exit函数接受进程的终止代号作为参数并执行下列操作:

1.    把进程描述符的flags字段设置为PF_EXITING标志,表示进程正在被删除。

2.    如果需要,调用del_timer_sync()函数从动态定时器队列中删除进程描述符。

3.    分别调用exit_mm(tsk);    exit_sem(tsk);    __exit_files(tsk);__exit_fs(tsk);exit_namespace(tsk)和    exit_thread()函数从进程描述符中分离出与分页、信号量、文件系统、打开文件描述符、命名空间以及I/O权限位图相关的数据结构。如果没有其他进程共享这些数据结构,那么这些函数还将删除所有这些数据结构。

4.    如果实现了被杀死进程的执行域和可执行格式的内核函数包含在内核模块中,则函数递减它们的使用计数器。

5.    把进程描述符的exit_code字段设置成进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是内核提供的错误代号(异常终止)。

6.    调用exit_notify()函数执行下面的操作:


a.    更新父进程和子进程的亲属关系。如果同一线程组中有正在运行的进程,就让终止进程所创建的所有子进程都变成同一线程组中另外一个进程的子进程,否则让它们成为init的子进程。
b.    检查被终止进程其进程描述符的exit_signal字段是否不等于-1,并检查进程是否是其所属进程组的最后一个成员。在这种情况下,函数通过给正在被终止进程的父进程发送一个信号(通常是SIGCHLD),以通知父进程子进程已死。
c.    否则,也就是exit_signal字段等于-1,或者线程组中还有其他进程,那么只要进程正在被跟踪,就向父进程发送一个SIGCHLD信号(在这种情况下,父进程是调试程序,因而向它报告轻量级进程死亡的信息)。
d.    如果进程描述符的exit_signal字段等于-1,而且进程没有被跟踪,就把进程描述符的exit_signal字段设置为EXIT_DEAD,然后调用release_task()函数回收进程的其他数据结构占用的内存,并递减进程描述符的使用计数器,最后使其正好为1,以便正好不会被释放。
e.    否则,如果进程描述符的exit_signal字段不等于-1,或进程描述符正在被跟踪,就把exit_signal字段设置为EXIT_ZOMBIE,称为僵死进程。
f.    把进程描述符的flags字段置为PF_DEAD标志,后面调度程序会谈到其作用。


7.    调用schedule()函数选择一个新进程运行。调度程序忽略处于EXIT_ZOMBIE状态的进程,所以这种进程正好在schedule()中的宏switch_to被调用之后停止执行。

2 进程删除


release_task()函数从僵死进程的描述符分离出最后的数据结构以后,对僵死进程的处理有两种可能的方式:如果父进程不需要接收来自子进程的信号,就调用do_exit();如果已经给父进程发送了一个信号,就调用wait4()或waitpid()系统调用。在后一种情况下,函数还将回收进程描述符所占用的内存空间,而在前一种情况下,内存的回收将由调度程序来完成:

1.    递减终止进程拥有者的进程个数。这个值存放在user_struct结构中。

2.    如果进程正在被跟踪,函数将它从调度程序的ptrace_children链表中删除,并让该进程重新属于初始的父进程。

3.    调用__exit_signal()函数删除所有挂起信号并释放进程的signal_struct描述符。如果该描述符不再被其他的轻量级进程使用,函数进一步删除这个数据结构。此外,函数调用exit_itimers函数从进程中剥离掉所有的POSIX时间间隔定时器。

4.    调用__exit_sighand()删除信号处理函数。

5.    调用__unhash_process(),该函数一次处理下面的操作:


a.    变量nr_threads减1。
b.    两次调用detach_pid(),分别从PIDTYPE_PID和PIDTYPE_TGID类型的PID散列表中删除进程描述符。
c.    如果进程是线程组的领头进程,那么再调用两次detach_pid(),从PIDTYPE_PGID和PIDTYPE_SID类型的散列表中删除进程描述符。
d.    用宏REMOVE_LINKS从进程链表中解除进程描述符的链接。


6.    如果进程不是线程组的领头进程,领头进程处于僵死状态,而且进程是线程组的最后一个成员,该函数向领头进程的父进程发送一个信号,通知它进程已死亡。

7.    调用sched_exit()函数来调整父进程的时间片。


8.    调用put_task_struct()递减进程描述符的使用计数器,如果计数器变为0,则函数终止所有残留的对进程的引用:


a.    递减进程所有者的user_struct数据结构的使用计数器(__count字段),如果计数器变为0则释放该数据结构。
b.    释放进程描述符以及thread_info描述符和内核态堆栈所占用的内存区域。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值