僵尸进程 & 孤儿进程
一句话解释;僵尸进程就是死了等父进程收尸;孤儿进程就是父亲挂了,再找个爸爸 (哈哈 )
1. 僵尸进程-退出,没有被回收处理
1.1 僵尸进程定义
任何一个子进程(init 除外)在 exit()/do_exit() 之后,都会称为僵尸进程(Zombie)。
1.2 僵尸进程未释放的资源
进程关联的资源(内存,文件,信号等)被释放掉了,但是它本身占用的内存还没有释放,比如创建时分配的内核栈,进程号,thread_info结构和task_struct结构等。
1.3 僵尸进程存在的目的
此时进程存在的唯一目的是向它的父进程提供信息。
父进程检索到信息后,或者通知内核那是无关的信息后,该进程所持有的剩余内存被释放,归还给系统使用。
1.4 父进程处理僵尸进程
子进程退出后,父进程调用 wait() 或 waitpid() 获取子进程的状态信息,通知内核将其资源释放。
wait 和 waitpid 详解及代码示例
https://blog.csdn.net/lqy971966/article/details/110818165
2. 孤儿进程-执行,没有父进程
孤儿进程是没有父进程的进程
孤儿进程这个重任就落到了 init 进程(或者 systemd 进程)身上
重复强调,任何一个子进程(init 除外)在 exit() 之后,并非马上就消失掉,
而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理 。
https://www.cnblogs.com/zzzwqh/p/13567004.html
3. 进程退出内核处理流程
3.1 进程退出 do_exit调用
当一个进程终结时,内核释放其资源并知其父进程。
不管进程怎么终结,该任务大部分要靠 do_exit(),其工作如下:
1.将task_struct中的标识成员设置为PF_EXITING;
2.调用del_timer_sync()删除内核定时器, 确保没有定时器在排队和运行;
3.调用exit_mm()释放进程占用的mm_struct;
4.调用sem__exit(),使进程离开等待IPC信号的队列;
5.调用exit_files()和exit_fs(),递减文件描述符、文件系统数据的引用计数。
6.把task_struct的exit_code设置为进程的返回值,供父进程随时检索;
7.调用exit_notify()向父进程发送信号,并把自己的状态设为EXIT_ZOMBIE;
8.调用schedule()切换到新进程继续执行。
因为进程的状态为EXIT_ZOMBIE,所以不再被调度,do_exit()永不返回。
至此,该进程关联的资源被释放掉了,但是它本身占用的内存还没有释放,比如创建时分配的内核栈,thread_info结构和task_struct结构等。
此时进程存在的唯一目的是向它的父进程提供信息。父进程检索到信息后,或者通知内核那是无关的信息后,该进程所持有的剩余内存被释放,归还给系统使用。
3.2 父进程处理僵尸进程内核处理流程 release_task
进程退出后,父进程调用 wait() 或 waitpid() 来检测子进程是否存在。
操作:挂起调用本进程,直到其中一个子进程退出,此时函数会返回子进程的PID。
释放进程描述符 release_task()
1.调用关系 _exit_signal()->_unhash_process()->detach_pid()从 pidhash 上删除该进程,
同时也要从任务列表中删除该进程。
2._exit_signal()释放目前僵死进程所使用的剩余资源,并进行最终统计和记录。
3.如果这个进程是线程组的最后一个进程,并且领头进程已经死掉,
那么 release_task()就要通知僵死的领头进程的父进程。
4.release_task()->put_task_struct()释放进程内核栈和thread_info结构所占的页,
并释放task_struct所占的slab高速缓存。
至此,进程描述符和所有进程独享的资源就全部释放掉了。
4. 孤儿进程内核处理流程
如果子进程的父进程已经退出了,那么子进程在退出时,
exit_notify()->forget_original_parent()->find_new_reaper()来寻找新的父进程。
find_new_reaper()函数先在当前线程组中找一个线程作为父亲,如果找不到,就让init做父进程。
init进程会例行调用wait()来检查其子进程,清除所有与其相关的僵死进程。