进程终止
当一个进程显示地调用*exit()函数,或者隐式地在main()函数中返回(C 编译器自动地在main()后调用exit()函数)时,进程即终止了。
进程也有可能非自愿地终止。当进程收到某些信号或产生异常,并且无法忽略,改变处理方式时。进程依照默认的做法终止。
不管进程以什么方式终止,都要调用do_exit()函数,这个函数定义在<kernel/exit.c>中。
1. 将进程描述符的flags成员设置为PF_EXITING。
2. 调用del_timer_sync()益处内核定时器。保证不会再有定时器和定时器处理程序运行。
3. profile_task_exit()效果不明。 ???
4. 对ptrace的操作暂时不知。 ???
5.acct_process()书写计数信息。
6.__exit_mm()释放进程描述符的mm_struct成员。当该地址空间不被其他进程分享时,顺便释放此块内存。
7.exit_sem()如果该进程正在排队等待IPC信号,那么退出等待队列。
8.exit_notify()向父进程发送信号,并且将子进程的父进程设置为同一线程组的其他线程,或者是init进程。
9.schedule()调度,运行到其他进程。
中止进程将转变为TASK_ZOMBLE状态,其唯一对内存空间的利用就是内核栈,因为保留了thread_info和task_struct两个数据结构,直到其父进程调用了wait4()。
移除进程描述符
wait()函数族基本做法,调用<kernel/exit.c>中的release_task()函数(详见《Linux内核设计与实现》第37页):
1.调用free_uid()来减少该进程拥有者的进程使用计数,当该计数为0,则该用户的cache被销毁。
2.调用unhash_process()从pidhash上删除该进程(其实并未显式地调用该函数,而是将其语句分散开调用了),同时也要从task_list中删除 该进程。
3.如果这个进程正在被ptrace追踪,将追踪进程的父进程重设为其最初的父进程并将它从ptrace_list上删除。
4.最后,调用put_task_struct释放进程内核栈和thread_info结构所占的页,并释放task_struct所占的slab高速缓存。
关于slab可在学习slab机制
无父进程的困境
与《Linux内核设计与实现》第二版中表述不同,do_exit()并未调用notify_parent(),而是调用了上文提过的exit_notify(),进而调用forget_original_parent()来完成改变父进程的工作。
上图中可以看到,exit_notify()函数甚至还处理了因为本进程退出而产生孤儿进程组的情况。其实exit_notify()还囊括了许多情况,不一一列举。下图为forget_original_parent()函数:
需要注意的是 1.child_reaptr 指向init进程。 2. list_for_each_safe(){} 作用未知,疑似为某种循环体。 ????