3.3 撤销进程

1. 撤销进程

  • 很多进程终止了它们本该执行的代码,从这种意义上说,这些进程“死”了。当这种情况发生时,必须通知内核以便内核释放进程所拥有的资源,包括内存、打开文件及其他资源,如信号量。
  • 进程终止的一般方式是调用exit()库函数,该函数释放C函数库所分配的资源,执行编程者所注册的每个函数,并结束从系统回收进程的那个系统调用。exit()函数可能由编程者显式地插入。另外,C编译程序总是把exit()函数插入到main()函数的最后一条语句之后。
  • 内核可以有选择地强迫整个线程组死掉。这发生在以下两种典型情况下:当进程接收到一个不能处理或忽视的信号时, 或者当内核正在代表进程运行时在内核态产生一个不可恢复的CPU异常时。

2.进程终止

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

2.1 do_group_exit()函数

  • 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()杀死进程而且不再返回。

2.2 do_exit()函数

  • 所有进程的终止都是由do_exit()函数来处理的,这个函数从内核数据结构中删除对终止进程的大部分引用。do_exit()函数接受进程的终止代号作为参数并执行下列操作:
    • 1.把进程描述符的flag字段设置为PF_EXITING标志,以表示进程正在被删除。
    • 2.如果需要,通过函数del_timer_sync()从动态定时器队列中删除进程描述符
    • 3.分别调用exit_mm()、exit_sem() __exit_files() _exit_fs()、exit
      namespace()和exit_thread()函数从进程描述符中分离出与分页、信号量、文件系统、打开文件描述符、命名空间以及1/0权限位图相关的数据结构。如果没有其他进程共享这些数据结构,那么这些函数还删除所有这些数据结构中。
    • 4.如果实现了被杀死进程的执行域和可执行格式的内核函数包含在内核模块中,则函数递减它们的使用计数器。
    • 5.把进程描述符的exit_code字段设置成进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)。
    • 6.调用exit_notify()函数执行下面的操作:
      • a.更新父进程和子进程的亲属关系。如果同一线程组中有正在运行的进程,就让终止进程所创建的所有子进程都变成同一线程组中另外一个进程的子进程,否则让它们成为init的子进程。
      • b.检查被终止进程其进程描述符的exit_signal字段是否不等于-1,并检查进程是否是其所属进程组的最后一个成员(注意:正常进程都会具有这些条件,参见前面“clone().fork()和vfork()系统调用”一节中对copy.process()的描述,第16步)。在这种情况下,函数通过给正被终止进程的父进程发送一个信号(通常是SIGCHLD),以通知父进程子进程死亡。
      • c.否则,也就是exit_signal字段等于-1,或者线程组中还有其他进程,那么只要进程正在被跟踪,就向父进程发送一个SIGCHLD信号(在这种情况下,父进程是调试程序,因而,向它报告轻量级进程死亡的信息)。
      • d.如果进程描述符的exit_signal字段等于-1, 而且进程没有被跟踪,就把进程描述符的exit_state字段置为EXIT_DEAD 然后调用release_task()回收
        进程的其他数据结构占用的内存,并递减进程描述符的使用计数器(见进程删除)。使用记数器变为1(参见copy_process()函数的第3f步),以使进程描述符本身正好不会被释放。
      • e.否则,如果进程描述符的exit_signal字段不等于-1,或进程正在被跟踪,就把exit_state字段置为EXTTZOMBTE。在下一节我们将看到如何处理僵死进程。
      • f.把进程描述符的flags字段设置为PF_DEAD标志(可以参看schedule()函数)。
    • 7.调用schedule()函数选择一个新进程运行。调度程序忽略处于 EXIT_ZOMBIE状态的进程,所以这种进程正好在schedule()中的宏switch_to被调用之后停止执行。正如:调度程序将检查被替换的僵死进程描述符的PF_DEAD标志并递减使用计数器,从而说明进程不再存活的事实。

3.进程删除

  • 1.Unix允许进程查询内核以获得其父进程的PID.或者其任何子进程的执行状态。例如,进程可以创建一个子进程来执行特定的任务,然后调用诸如wait()这样的一些库函数检查子进程是否终止。如果子进程已经终止,那么,它的终止代号将告诉父进程这个任务是否已成功地完成。
  • 2.为了遵循这些设计选择,不允许Unix内核在进程一终止后就丢弃包含在进程描述符字段中的数据。只有父进程发出了与被终止的进程相关的wait()类系统调用之后,才允许这样做。这就是引人僵死状态的原因:尽管从技术上来说进程已死,但必须保存它的描述符,直到父进程得到通知。
  • 3.如果父进程在子进程结束之前结束会发生什么情况呢?在这种情况下,系统中会到处是僵死的进程,而且它们的进程描述符永久占据着RAM。如前所述,必须强迫所有的孤儿进程成为init进程的子进程来解决这个问题。 这样,init进程在用wait()类系统调用检查其合法的子进程终止时,就会撒消僵死的进程。
  • 4.release_task()函数从僵死进程的描述符中分离出最后的数据结构;对僵死进程的处理有两种可能的方式:如果父进程不需要接收来自子进程的信号,就调用do_exit();如果已经给父进程发送了一个信号,就调用wait4()或waitpid()系统调用。在后一种情况下,函数还将回收进程描述符所占用的内存空间,而在前一种情况下,内存的回收将由进程调度程序来完成(参见schedule()函数) 该函数执行下述步骤:
    • 1. 递减终止进程拥有者的进程个数。这个值存放在本章前面提到的user_struct 结构中(参见copy_process()的第4步)。
    • 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()函数来调整父进程的时间片(这一步在逻辑上作为对 copy_process()第17 步的补充)。
    • 8.调用put_task_struct()递减进程描述符的使用计数器,如果计数器变为0,则函数终止所有残留的对进程的引用。
      • a.递减进程所有者的user_struct数据结构的使用计数器(__count字段)(参见copy_process()的第5步),如果使用计数器变为0,就释放该数据结构。
      • b.释放进程描述符以及thread_info描述符和内核态堆栈所占用的内存区域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值