Chinaunix首页 | 论坛 | 博客
  • 博客访问: 217150
  • 博文数量: 59
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 424
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-25 12:57
文章分类

全部博文(59)

文章存档

2016年(23)

2015年(30)

2014年(6)

我的朋友

分类: C/C++

2015-12-21 08:51:38

init进程会托管zombie进程,并且在它退出的时候,init进程会去回收zombie进程中的内存结构。这个回收工作肯定是调用wait函数。很好奇,这段wait代码到底在哪,翻了一下代码,终于找到了这段init进程调用wait函数,来结束子进程的代码。

    进程在结束的时候,如果父进程先于子进程结束了,那么父进程结束的时候会把重新设置它的子进程的父子关系,就是把它的所有子进程的父进程改为当前pid namespace中的child_reaper,那么这些子进程结束的时候向child_reaper进程发送信号SIGCHLD,那么在初始pid namespacechild_reaper就是init进程,init进程是进程号为1的进程,它是在内核中创造的第一个进程。


代码:init/main.c  linux-2.6.35.13


  1. 429 static noinline void __init_refok rest_init(void)
  2. 430         __releases(kernel_lock)
  3. 431 {
  4. 432         int pid;
  5. 433 
  6. 434         rcu_scheduler_starting();
  7. 435         /*
  8. 436          * We need to spawn init first so that it obtains pid 1, however
  9. 437          * the init task will end up wanting to create kthreads, which, if
  10. 438          * we schedule it before we create kthreadd, will OOPS.
  11. 439          */
  12. 440         kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

     可以看到start_kernel函数调用了rest_init函数,这个函数中会启动第一个进程,执行函数kernel_init,这个进程就是init进程,下面是init进程的启动过程。


  1. 866 static int __init kernel_init(void * unused)
  2. 867 {
  3. 868         /*
  4. 869          * Wait until kthreadd is all set-up.
  5. 870          */
  6. 871         wait_for_completion(&kthreadd_done);
  7. 872         lock_kernel();
  8. 873 
  9. 874         /*
  10. 875          * init can allocate pages on any node
  11. 876          */
  12. 877         set_mems_allowed(node_states[N_HIGH_MEMORY]);
  13. 878         /*
  14. 879          * init can run on any cpu.
  15. 880          */
  16. 881         set_cpus_allowed_ptr(current, cpu_all_mask);
  17. 882         /*
  18. 883          * Tell the world that we're going to be the grim
  19. 884          * reaper of innocent orphaned children.
  20. 885          *
  21. 886          * We don't want people to have to make incorrect
  22. 887          * assumptions about where in the task array this
  23. 888          * can be found.
  24. 889          */
  25. 890         init_pid_ns.child_reaper = current;  //init_pid_ns就是最初的pid命名空间,把这个空间的child_reaper设置为当前进程,就是init进程。

     init进程中会去执行/sbin/init程序


  1. 857         run_init_process("/sbin/init");

     也就是说init进程在这里后面的工作就是在/sbin/init程序中了,这个init是用户态的程序,init进程的有很多重要的任务,但是在这里我只关心它是如何处理处理zombie进程的,它有多种实现方式,看了一种基于事件的实现,,同时它依赖于库,找到了upstartmain函数,最后的调用了nih的轮询事件的函数。




代码:Upstart-1.6.1 init/main.c


  1. 606         /* Run through the loop at least once to deal with signals that were
  2. 607          * delivered to the previous process while the mask was set or to
  3. 608          * process the startup event we emitted.
  4. 609          */
  5. 610         nih_main_loop_interrupt ();
  6. 611         ret = nih_main_loop ();     //进入nih库中的时间循环函数

     在nih中,找到 nih/main.c


  1. 543         nih_signal_set_handler (SIGCHLD, nih_signal_handler); //设置sig handler
  2. 544 
  3. 545         while (! exit_loop) {
  4. 546                 NihTimer       *next_timer;
  5. 547                 struct timespec now;
  6. 548                 struct timeval  timeout;
  7. 549                 fd_set          readfds, writefds, exceptfds;

     可以看到,这个nih_main_loop就是一个循环事件的函数,nih_signal_handler 是信号SIGCHLD的处理函数,这个函数通过管道interrupt_pipe nih_main_loop函数联系起来,这个函数就是向管道中写数据。


  1. 624 void
  2. 625 nih_main_loop_interrupt (void)
  3. 626 {
  4. 627         nih_main_loop_init ();
  5. 628 
  6. 629         if (interrupt_pipe[1] != -1)
  7. 630                 while (write (interrupt_pipe[1], "", 1) < 0)  //向管道写入数据
  8. 631                         ;
  9. 632 }

     主循环在循环过程中会阻塞在read函数上,此时管道的一端写入了数据,同时管道的令一端也就是main函数中会在read函数中唤醒,执行后面的nih_child_poll函数,来处理子进程的结束退出。


  1. 595                 while (read (interrupt_pipe[0], buf, sizeof (buf)) > 0)  //阻塞读
  2. 596                         ;
  3. 597                 nih_signal_poll ();
  4. 598 
  5. 599                 /* Deal with terminated children */
  6. 600                 nih_child_poll ();      //处理子进程退出,里面时wait函数

nih_child_poll 函数就是处理任意进程结束的,可以想到这里肯定是调用wait函数了。

代码:ntl/child.c


  1. 141 void
  2. 142 nih_child_poll (void)
  3. 143 {
  4. 144         siginfo_t info;
  5. 145 
  6. 146         nih_child_init ();
  7. 147 
  8. 148         /* NOTE: there's a strange kernel inconsistency, when the waitid()
  9. 149          * syscall is native, it takes special care to zero this struct
  10. 150          * before returning ... but when it's a compat syscall, it
  11. 151          * specifically *doesn't* zero the struct.
  12. 152          *
  13. 153          * So we have to take care to do it ourselves before every call.
  14. 154          */
  15. 155         memset (&info, 0, sizeof (info));
  16. 156 
  17. 157         while (waitid (P_ALL, 0, &info, WAITOPTS | WNOHANG) == 0) {
  18. 158                 pid_t          pid;            
  19. 159                 NihChildEvents event;   

     从代码中看到了会循环调用waitid函数,返回值0表示wait函数的回收工作成功(回收工作就是删除进程中其他的内存数据结构,zombie进程在结束时会保留一些数据结构),子进程结束了,同时可能是有多个子进程结束了,这时需要循环调用waitid,以便让所有结束的子进程完成回收工作。

总结:

     父进程是init进程的进程在退出的时候,init进程会调用wait函数来处理所有子进程的退出,来清理进程剩下的内存结构。在不同的系统中,可能init进程的实现不相同,但是肯定子进程结束发出信号SIGCHLD,然后init进程调用wait函数来回收进程其他的内存结构。

参考文献:

1.http://blog.chinaunix.net/uid-23769728-id-3127671.html

2.

阅读(1823) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~