首页 > 代码库 > 孤儿进程退出分析
孤儿进程退出分析
如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,,白白地耗费内存.僵尸状态是在调用do_exit()函数时,最后会调用exit_notify()函数向父进程发送信号,给子进程重新找养父,养父为线程组中的其他线程或者为init进程,并把进程(存放在task_struct结构的exit_state中)设成EXIT_ZOMBIE。对于这个问题,解决方法是给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程。在do_exit()中会调用exit_notify(),该函数会调用forget_original_parent(),而后者会调用find_new_reaper()来执行寻父过程:
了解forget_original_parent()需要以下几个基本知识:forget_original_parent()定义在:linux/kernel/exit.c
1 parent == real_parent 的子进程
这是本进程的子进程,并且没有被ptrace,处于本进程的children list上。
2 parent == father && parent != real_parent的子进程
被本进程ptrace的其它进程的子进程,处于本进程的children list上。
3 real_parent == father && parent != real_parent的子进程
本进程的子进程,但正在被其它进程ptrace,处于本进程的ptrace_children list上。
/* * When we die, we re-parent all our children. * Try to give them to another thread in our thread * group, and if no such member exists, give it to * the child reaper process (ie "init") in our pid * space. */ static struct task_struct *find_new_reaper(struct task_struct *father) { struct pid_namespace *pid_ns = task_active_pid_ns(father); struct task_struct *thread; thread = father; while_each_thread(father, thread) { //依次遍历该结束的进程所在线程组的下一个进程 if (thread->flags & PF_EXITING) <span style="font-family: Verdana;">continue;</span>
<span style="white-space:pre"> </span>//如果得到的下一个进程被标记了 PF_EXITING ,就不符合要求,需要继续遍历
<span style="white-space:pre"> </span>//task_struct结构体有一个flags标志,表示进程的当前状态
<span style="white-space:pre"> </span>//<span style="font-family: Verdana;">PF_STARTING:<span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14.44444465637207px; line-height: 23.99305534362793px;">进程是否正在被创建 <span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14.44444465637207px; line-height: 23.99305534362793px;">PF_EXITING:进程退出 <span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14.44444465637207px; line-height: 23.99305534362793px;">PF_MEMALLOC:<span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14.44444465637207px; line-height: 23.99305534362793px;">当前是否在分配内存</span></span></span></span></span> if (unlikely(pid_ns->child_reaper == father))//unlikely如果相同进程返回true /*child_reaper 的作用是在当前线程组如果没有找到养父的话,需要通过托管表示进程结束后,需要这个child_reaper指向的进程对这个结束的进程进行托管,其中的一个目的是对孤儿进程进行回收。若该托管进程是该结束进程本身,就需要重新设置托管进程,就是将该托管进程设置为当前进程的养父进程thread。*/ pid_ns->child_reaper = thread; return thread;//在该结束进程所在的线程组中找到符合要求的进程,返回即可 } /* 如果该结束进程所在的线程组中没有其他的进程, 函数就返回该结束进程所在命名空间的 child_reaper 指向的托管进程 (前提是该托管进程不是该结束进程本身) */ if (unlikely(pid_ns->child_reaper == father)) { /* 如果该结束进程所在命名空间的 child_reaper 指向的托管进程就是该结束进程本身, 而程序运行至此,说明在该线程组中已经找不到符合要求的进程, 此时,需要将托管进程设置为 init 进程,供函数返回 */ write_unlock_irq(&tasklist_lock); if (unlikely(pid_ns == &init_pid_ns))//如果是init进程需要给出error,init进程不能终止 panic("Attempted to kill init!"); zap_pid_ns_processes(pid_ns); write_lock_irq(&tasklist_lock); /* * We can not clear ->child_reaper or leave it alone. * There may by stealth EXIT_DEAD tasks on ->children, * forget_original_parent() must move them somewhere. */ pid_ns->child_reaper = init_pid_ns.child_reaper;//把当前进程空间的托管进程设置为init进程的托管进程 } return pid_ns->child_reaper;//返回找到的托管进程也就是养父进程 }
reaper = find_new_reaper(father);//在该函数中找到养父进程,要么是线程组内的其他进程,要么是init进程,最后该进程的描述符删除操作就由养父进程进行处理,如果父进程先退出,该进程已经成为孤儿进程的话
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px; text-align: justify;">在给子进程找到合适的新父进程之后,需要对该进程的所有子进程设置新的父进程</span><span style="color:#111111;">list_for_each_entry_safe(p, n, &father->children, sibling) {</span><span style="color: rgb(17, 17, 17); white-space: pre;"> </span><span style="color:#111111;">p->real_parent = reaper;</span><span style="color: rgb(17, 17, 17); white-space: pre;"> </span><span style="color:#111111;">if (p->parent == father) {</span><pre name="code" class="cpp" style="font-size: 14.44444465637207px; line-height: 23.99305534362793px;"><span style="color:#111111;">//</span><span style="font-family: 黑体;font-size:12px; line-height: 26px; background-color: rgb(255, 255, 255);">当前退出的线程不是其真正的父亲,那么必然是被跟踪的进程.否则在系统日志打印错误</span>BUG_ON(task_ptrace(p));
<span style="white-space: pre;"> </span> <span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif;">p->parent =</span><span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif;"> p->real_parent;</span><span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif;"> </span>//将当前进程的父进程设置为真正的父进程
<span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14px; line-height: 24px; white-space: pre;"> </span><span style="font-family:Verdana;color:#111111;"><span style="font-size: 14px; line-height: 24px;">}</span></span><span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14px; line-height: 24px; white-space: pre;"> </span><span style="font-family:Verdana;color:#111111;"><span style="font-size: 14px; line-height: 24px;">reparent_thread(father, p, &dead_children);</span></span><span style="color: rgb(17, 17, 17); font-family: Verdana; font-size: 14px; line-height: 24px; white-space: pre;"> </span><span style="font-family:Verdana;color:#111111;"><span style="font-size: 14px; line-height: 24px;">}</span></span>
最后调用exit_ptrace()函数对ptrace的子进程寻找父亲
孤儿进程退出分析