首页 > 学技术 > 技术网文 > C/C++ > 正文

[保留] 关于SIGCHLD的不排队,丢弃的问题


来源 chinaunix.net kuqin整理

看unix网络编程第一卷的时候,碰到书上这样一个例子:
一个并发服务器, 每一个客户端连接服务器就fork一个子进程.书上讲到当同时有n多个客户端断开连接时,
服务器端同时有n多个子进程终止, 这时候内核同时向父进程发送n多个sigchld信号.它的sigchld信号处理
函数如下:
void sig_chld(int signo)
{
       pid_t   pid;
       int     stat;
       
       while((pid = waitpid(-1, &stat, WNOHANG)) > 0){
               printf("child %d terminated\n", pid);
       }
        return;
}

我的问题是:既然sigchld是不可靠的信号,进程就不可能对sigchld进行排队, 直接丢弃了sigchld信号(当进程注册信号的时候,发现已有sigchld注册进未决信号, 因为内核同时发送多个sigchld).请问大家上面的代码是如何保证不产生僵尸进程的.谢谢!



 xltao 回复于:2006-09-15 15:28:58

高人指点啊


 linternt 回复于:2006-09-15 16:03:58

用消息队列吧,子进程退出前把自已的PID写进队列
然后父进程去读,不过pid = waitpid(-1, &stat, 0);
让父进程阻塞在调用这块,不过如果你想让waitpid快带返回也行, 如果没接到子进程退出信号,你可以把这个PID再写进去,然后继续读,直到接到SIGCHLD为止。

以前在项目中遇到过此问题,大量的并发进程往往造成很多的僵死进程,以至于程序无法响应,当时被这个问题弄得很头痛,直到想到这个方法以后才有效的解决了上述问题。

为方法就等于实现了一个对SIGCHLD的排队机制。我一直在大型的项目中用这个,很不错。


 思一克 回复于:2006-09-15 16:08:45

你能给出一简短的例子引起你说的那个问题的程序?


 susesuse 回复于:2006-09-15 16:36:42

我的看法,不一定对啊,呵呵.先看看下面的程序.


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void sig_child(int signo)
{ pid_t pid;
int stat;
while( (pid = waitpid(-1,&stat,WNOHANG)) > 0)
{
printf("child %d exit\n",pid);
sleep(5);//这应该导致信号丢失吧?
}
return;
}

void child_func()
{
return;
}

int main()
{
pid_t pid;
int i = 0;

signal(SIGCHLD,sig_child);
for(;i < 5;i ++)
{
if( (pid = fork()) == 0)
{
child_func();
printf("child function finished\n");
exit(0);
}
else if(pid > 0)
{
continue;
}
else
{
printf("fork failed\n");
exit(1);
}
}
return 0;




linux:~/test # gcc -o waitpid waitpid.c
linux:~/test # ./waitpid
child function finished
child 7449 exit
child function finished
child 7450 exit
child function finished
child 7454 exit
child function finished
child 7455 exit
child function finished
child 7459 exit

对于SIGCHILD信号(属于不可靠信号,不支持信号排队.),按理说应该有信号丢失,可为什么还是输出5次呢?这实际上没有把握清楚waitpid(-1,&stat,WNOHANG)函数的作用.它是用来用来检测进程是否已结束并回收僵尸进程的,在这个程序里信号确实会丢失的.但waitpid函数不是由SIGCHILD信号驱动的.


 flw 回复于:2006-09-15 16:51:22

丢失了怕什么。
while(waitpid()) 是个循环,只要下次不丢失,照样会把以前丢失了的都一起回收的嘛!
不然也就不会写成一个循环了。
写成循环的原因,就是怕丢失。


 xltao 回复于:2006-09-15 16:59:13

to susesuse:
不太同意你的看法, 这是个本来就很难重现的问题, 不能因为只举个例子就推出结论.
我想问你当父进程正在执行sigchld的信号处理程序, 内核这时向父进程发送一个sigchld,
因为sigchld是不可靠信号,进程就丢弃这个信号.既然丢弃,就是说父进程没有进行sigchld信号处理,那不就会产生僵尸进程了吗?
不知道我的理解对不对?
谢谢linternt


 xltao 回复于:2006-09-15 17:02:54

愚钝,不太明白flw的意思,既然丢了,是怎么找回来的.执教


 xltao 回复于:2006-09-15 17:15:11

不知道linternt可不可以贴一下代码!


 思一克 回复于:2006-09-15 17:16:17

这也基本是个伪命题。不存在丢失,还有排队的问题


 flw 回复于:2006-09-15 17:20:00

引用:原帖由 xltao 于 2006-9-15 17:02 发表
愚钝,不太明白flw的意思,既然丢了,是怎么找回来的.执教 


根本就不需要找回来!
好比有五个进程,
不妨分别称为 p1 p2 p3 p4 p5,
一开始 p1 结束了,发了一个 SIGCHLD(s1),
这时父进程可能空闲了,于是开始处理这个信号,假设处理的过程中 p2 又结束了,又发了一个 SIGCHLD(s2),
这时候已经有两个信号了(一个正在处理,一个待处理),这时如果 p3 又结束了,那么它发的那个 SIGCHLD(s3) 势必会丢失,
丢失了怎么办?
没关系,因为那个信号处理函数是个循环嘛,
所以 while(waitpid()) 的时候,会把 p1 p2 p3 都处理的。
即使是很不幸,因为十分凑巧的原因,p3 没有被回收,导致变成僵尸进程了,也没关系,
因为还有 p4 p5 嘛,等到 p4 或者 p5 结束的时候,
又会再一次调用 while(waitpid()),到时候虽说这个 while(waitpid()) 是由 p4/p5 引起的,但是它也会一并把 p3 也处理的,因为它是个循环嘛!

如果还搞不懂,你就再看看 waitpid 的 man。

记住一点:
waitpid 和 SIGCHLD 没关系,即使是某个子进程对应的 SIGCHLD 丢失了,只要父进程在任何一个时刻调用了 waitpid,那么这个进程还是可以被回收的。

哎呀呀,简直费劲死了,其实说白了,就是一个“生产者-消费者”问题。
子进程结束的时候,系统“生产”出一个僵尸进程,
同时用 SIGCHLD 通知父进程来“消费”这个僵尸进程,
即使是 SIGCHLD 丢失了,没有来得及消费,
但是只要有一次消费,就会把所有的僵尸进程都处理光光!
(我再说一遍:因为,while(waitpid()) 是个循环嘛!)

[ 本帖最后由 flw 于 2006-9-15 17:24 编辑 ]


 思一克 回复于:2006-09-15 17:22:45

这个帖子有可能成为精华。继续讨论


 susesuse 回复于:2006-09-15 17:24:36

引用:原帖由 xltao 于 2006-9-15 16:59 发表
to susesuse:
不太同意你的看法, 这是个本来就很难重现的问题, 不能因为只举个例子就推出结论.
我想问你当父进程正在执行sigchld的信号处理程序, 内核这时向父进程发送一个sigchld,
因为sigchld是不可靠信号,进 ... 



我的意思就是waitpid和信号没关系.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

void child_func()
{
    return;
}

int main(void)
{
    pid_t pid;
    int stat;

    if((pid = fork()) == 0)
    {
        child_func();
        printf("child process %d exit!\n",getpid());
        exit(0);
    }
    else if(pid > 0)
    {
        sleep(5);
        pid = waitpid(-1,&stat,WNOHANG);
        printf("child %d exit!\n",pid);
    }
    else
    {
        printf("fork failed\n");
        exit(-1);
    }

    return 0;
}


运行结果:
child process 23507 exit!
child 23507 exit!

这个程序里child信号不早就丢了吗,但子进程不照样被回收了吗?


 flw 回复于:2006-09-15 17:32:31

susesuse 说的对。
我也给出一个简单的例子。
(下面这个经过改进的例子清晰地说明,SIGCHLD 信号是可以丢失的,但是子进程是不会不回收的)
flw@Sleeper:~$ cat ttt.c

# include <stdio.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <unistd.h>

void sigchld_handler( int signo ){
    printf( "我就不在这里回收,看你能怎的?丢吧!丢吧!都丢光都没关系!\n" );
    printf( "我就故意在这里等它三秒钟,让它丢!都丢光才好呢。\n" );
    sleep(3);
    return;
}

int main()
{
    int i;
    pid_t pid;

    signal( SIGCHLD, sigchld_handler );

    for( i=0; i<5; i++ ){
        pid = fork();
        if ( pid == 0 ){
            sleep(2);
            exit(0); // child exit.
        }
        else if ( pid == -1 ){
            perror( "fork" ); // fork failed.
            exit(-1);
        }
    }

    system( "ps -aef | grep ttt" );
    while(waitpid(-1,NULL,0)!=-1);
    system( "ps -aef | grep ttt" );

    return(0);
}
flw@Sleeper:~$ cc -Wall -o ttt ttt.c
flw@Sleeper:~$ ./ttt
flw       4312  3683  0 17:45 pts/1    00:00:00 ./ttt
flw       4313  4312  0 17:45 pts/1    00:00:00 ./ttt
flw       4314  4312  0 17:45 pts/1    00:00:00 ./ttt
flw       4315  4312  0 17:45 pts/1    00:00:00 ./ttt
flw       4316  4312  0 17:45 pts/1    00:00:00 ./ttt
flw       4317  4312  0 17:45 pts/1    00:00:00 ./ttt
flw       4318  4312  0 17:45 pts/1    00:00:00 sh -c ps -aef | grep ttt
我就不在这里回收,看你能怎的?丢吧!丢吧!都丢光都没关系!
我就故意在这里等它三秒钟,让它丢!都丢光才好呢。
我就不在这里回收,看你能怎的?丢吧!丢吧!都丢光都没关系!
我就故意在这里等它三秒钟,让它丢!都丢光才好呢。
flw       4312  3683  0 17:45 pts/1    00:00:00 ./ttt
flw       4321  4312  0 17:45 pts/1    00:00:00 sh -c ps -aef | grep ttt
我就不在这里回收,看你能怎的?丢吧!丢吧!都丢光都没关系!
我就故意在这里等它三秒钟,让它丢!都丢光才好呢。
flw@Sleeper:~$


[ 本帖最后由 flw 于 2006-9-15 17:42 编辑 ]


 susesuse 回复于:2006-09-15 17:36:19

呵呵,版主就是有意思.


 flw 回复于:2006-09-15 17:43:19

算了,不说废话了,其实就是个很简单的问题。


 dzbjet 回复于:2006-09-15 17:54:44

1。man
2。不能解决, 看看linux内核代码,就知道了,费着这劲干嘛!?


 mingyanguo 回复于:2006-09-15 17:59:36

信号与僵尸没什么必然的联系。
你就是压根不理信号,就在那里傻等,一样没有僵尸。


 xltao 回复于:2006-09-15 18:04:10

谢谢各位


 rock_jq 回复于:2006-09-15 18:19:35

受益,
while ( (exit_pid=wait(&status)) != -1 ) ctrl_c_op();
这样可否获取所有子进程?


 frstq 回复于:2006-09-15 20:53:07

引用:原帖由 flw 于 2006-9-15 17:20 发表

根本就不需要找回来!
好比有五个进程,
不妨分别称为 p1 p2 p3 p4 p5,
一开始 p1 结束了,发了一个 SIGCHLD(s1),
这时父进程可能空闲了,于是开始处理这个信号,假设处理的过程中 p2 又结束了,又发了一 ... 




关键是一句话信号和waitpid没有关系!一个触发条件,一个是动作,程序写为while(...),就是考虑可能的收到一个信号,释放多个僵尸进程。


 lovesaka 回复于:2006-09-17 01:04:08

引用:原帖由 rock_jq 于 2006-9-15 18:19 发表
受益,
while ( (exit_pid=wait(&status)) != -1 ) ctrl_c_op();
这样可否获取所有子进程? 


可以的
你试试就知道了


 linternt 回复于:2006-09-18 10:55:01

不敢苟同的意见,僵死进程在某些应用中必须很好的解决。
 
我们就是在进程池中没处理好子进程的退出状态,而使整个系统不能工作。

一个进程回收不到就会占用一个我们的一个进程表,也就是占用一个进程池的位置,当僵死进程达到一定限度后,程序就会不再FORK新进程,而这些僵死进程已经退出,无法处理请求,所以整个系统不在工作。

以前我们的程序就是按版主所说的方式进行循环接收,但根本无法避免大批量并发快速请求。

事实证明我所采用的让SIGCHLD进行排队回收机制可以很好的处理了这个问题。

我觉得这个问题要看应用在什么系统中,以前的程序我也是循环接收,不是太多的并发请求也是没有问题的,所以也没怀疑过这个办法。

代码我就不贴了,我只说说原理吧:

主进程创建一个队列,我用的是一个线程循环接收队列的信息,没有的时候阻塞。waitpid的时候也是采用阻塞的方式,防止子进程退出的时间大于父进程接到信号的时间(队列结构包含一个进程号)
waitpid(队列中的PID号, &status, 0);

子进程退出之前要往队列发送自已的PID,然后才退出。

[ 本帖最后由 linternt 于 2006-9-18 10:59 编辑 ]


 flw 回复于:2006-09-18 11:00:32

引用:原帖由 linternt 于 2006-9-18 10:55 发表
不敢苟同的意见,僵死进程在某些应用中必须很好的解决。
 
我们就是在进程池中没处理好子进程的退出状态,而使整个系统不能工作。

一个进程回收不到就会占用一个我们的一个进程表,也就是占用一个进程池的位 ... 


特殊情况特殊处理,本来就没有什么一劳永逸的做法。
BTW:如果回收没有什么特别的动作的话,像你所说的那种情况,为什么不 signal( SIGCHLD, SIG_IGN ) 呢?


 linternt 回复于:2006-09-18 11:30:15

引用:原帖由 flw 于 2006-9-18 11:00 发表

特殊情况特殊处理,本来就没有什么一劳永逸的做法。
BTW:如果回收没有什么特别的动作的话,像你所说的那种情况,为什么不 signal( SIGCHLD, SIG_IGN ) 呢? 



因为我们要维护一个自已的进程池。


 思一克 回复于:2006-09-18 11:53:32

从原理上没有回收不到的问题。
那问题出在哪里? 内核本身? 还是进程?


 mingyanguo 回复于:2006-09-18 12:37:30

引用:原帖由 linternt 于 2006-9-18 10:55 发表
不敢苟同的意见,僵死进程在某些应用中必须很好的解决。
 
我们就是在进程池中没处理好子进程的退出状态,而使整个系统不能工作。

一个进程回收不到就会占用一个我们的一个进程表,也就是占用一个进程池的位 ... 


为什么会回收不到?


 flw 回复于:2006-09-18 12:52:13

to 楼上两位:
我觉得他所说的“回收不到”应该指的是“回收不够及时”。应该是对性能的影响而不是对功能的影响。
参考:
引用:以前我们的程序就是按版主所说的方式进行循环接收,但根本无法避免大批量并发快速请求。



 isnowran 回复于:2006-09-18 13:13:37

引用:原帖由 flw 于 2006-9-18 11:00 发表

特殊情况特殊处理,本来就没有什么一劳永逸的做法。
BTW:如果回收没有什么特别的动作的话,像你所说的那种情况,为什么不 signal( SIGCHLD, SIG_IGN ) 呢? 


POSIX规定不能忽略SIGCHLD


 flw 回复于:2006-09-18 13:15:22

引用:原帖由 isnowran 于 2006-9-18 13:13 发表

POSIX规定不能忽略SIGCHLD 


出处?


 flw 回复于:2006-09-18 13:18:11

引用:原帖由 isnowran 于 2006-9-18 13:13 发表

POSIX规定不能忽略SIGCHLD 


哦,看来你的知识库得更新了。
引用:       POSIX.1-1990  disallowed  setting  the  action for SIGCHLD to SIG_IGN.  [color=red]POSIX.1-2001 allows this possibility, so that ignoring SIGCHLD can be used to
       prevent the creation of zombies (see wait(2)). [/color] Nevertheless, the historical BSD and System V behaviours for ignoring SIGCHLD  differ,  so  that  the
       only  completely  portable  method of ensuring that terminated children do not become zombies is to catch the SIGCHLD signal and perform a wait(2) or
       similar.




 mingyanguo 回复于:2006-09-18 13:29:06

引用:原帖由 flw 于 2006-9-18 12:52 发表
to 楼上两位:
我觉得他所说的“回收不到”应该指的是“回收不够及时”。应该是对性能的影响而不是对功能的影响。
参考:
 


哦,我理解有误。


 飞灰橙 回复于:2006-09-18 13:30:54

是不是在bash裏敲敲回車,就會去嘗試一下waitpid啊?


 东门之杨 回复于:2006-09-18 13:54:29

man waitpid @linux. 有这样一段:
       POSIX.1-2001 specifies that if the disposition of SIGCHLD is set to SIG_IGN or the SA_NOCLDWAIT flag is set for  SIGCHLD  (see  sigaction(2)), then children that terminate do not become zombies and a call to wait() or waitpid() will block until all children have ter minated, and then fail with errno set to ECHILD.  (

反之考虑,如果SIGCHLD没有被设置为SIG_IGN  或者SA_NOCLDWAIT 没有被设置为SIGCHLD, 子进城是不是要成为zombie. 这样的情况应该怎么处理呢


 linternt 回复于:2006-09-18 14:41:15

引用:原帖由 flw 于 2006-9-18 12:52 发表
to 楼上两位:
我觉得他所说的“回收不到”应该指的是“回收不够及时”。应该是对性能的影响而不是对功能的影响。
参考:
 



flw说得没错。


 linternt 回复于:2006-09-18 14:45:11

父进程对SIGCHLD信号即使不处理,过一定的时间1号进程也会去处理这个信号,回收它的资源。

但大部分程序,做为一个商业软件,好像都会对此信号做一个妥善的处理。


 flw 回复于:2006-09-18 14:50:02

引用:原帖由 linternt 于 2006-9-18 14:45 发表
父进程对SIGCHLD信号即使不处理,过一定的时间1号进程也会去处理这个信号,回收它的资源。

但大部分程序,做为一个商业软件,好像都会对此信号做一个妥善的处理。 


你这个观点是错误的。
只有父进程死亡了(两次 fork 也属于父进程死亡的情形),
init 才会去回收它的资源的。
否则这事和 init 无关。


 linternt 回复于:2006-09-18 15:04:20

我说的意思是:如果进程表用完了的时候。
flw说得也对,我只是没说明白。(fork两次,子进程就由INIT接管了,守护进程就是这样)
这块理解的也不是太深刻。


 isnowran 回复于:2006-09-18 15:44:14

引用:原帖由 flw 于 2006-9-18 13:18 发表

哦,看来你的知识库得更新了。
 


看来我的手册是该更新了。。。不过,手册里描述的应该是当前系统的行为,应该是glibc相关的库的行为吧?那我岂不是连glibc也要更新了?手册描述就跟系统行为不一致了,那就更糟糕了,呵呵


 googleboy 回复于:2006-09-19 08:27:04

引用:原帖由 linternt 于 2006-9-18 15:04 发表
我说的意思是:如果进程表用完了的时候。
flw说得也对,我只是没说明白。(fork两次,子进程就由INIT接管了,守护进程就是这样)
这块理解的也不是太深刻。 



进程表用完了之后,应该就不会再fork进程了,跟init进程和父进程应该没有什么关系吧。

可以详细地说说你的意思吗?谢谢。


 linternt 回复于:2006-09-19 10:09:58

引用:原帖由 googleboy 于 2006-9-19 08:27 发表


进程表用完了之后,应该就不会再fork进程了,跟init进程和父进程应该没有什么关系吧。

可以详细地说说你的意思吗?谢谢。 



你说的没错,

我记得我们老师好像说过:(不对请批评我)
进程表用完的时候系统会进行一次清理(或者是叫别的),回收一些已经是退出状态而没被清理的注册表项。然后重新使用。(操作系统应该有这方面的详细解释)

其实我们在做开发过程中也可以看到,进程号大到一定程序就又回到小值了。也就是一个循环使用的过程。

当然,这只是我的理解,因为理论知识学得不好,不敢说对的。(不对请拍砖)


 isnowran 回复于:2006-09-19 10:14:35

引用:原帖由 linternt 于 2006-9-19 10:09 发表


你说的没错,

我记得我们老师好像说过:(不对请批评我)
进程表用完的时候系统会进行一次清理(或者是叫别的),回收一些已经是退出状态而没被清理的注册表项。然后重新使用。(操作系统应该有这方面的详 ... 


进程号是一个循环累加的过程,但是新进程的基本规则是进程号唯一,而你的说法显然违反了这条规则,不仅如此,还敢“自动清理”,试问,谁给系统自动清理的权利了?


 linternt 回复于:2006-09-19 14:30:49

我知道进程是累加的,可是到一定数值后就又回到很小的值了。
照你所说,进程只无限增大的了。

另外的自动清理是我个人的理解,就是说如果进程没被回收的话,INIT进程会去处理。然后重用这个进程表。


 mingyanguo 回复于:2006-09-19 14:56:54

引用:原帖由 linternt 于 2006-9-19 14:30 发表
我知道进程是累加的,可是到一定数值后就又回到很小的值了。
照你所说,进程只无限增大的了。

另外的自动清理是我个人的理解,就是说如果进程没被回收的话,INIT进程会去处理。然后重用这个进程表。 


重用小的PID的前提是这个PID是空闲的。


 googleboy 回复于:2006-09-19 16:03:50

如果又回到小的值得话,应该是数值溢出了吧。


 cjaizss 回复于:2006-09-19 17:28:56

引用:原帖由 思一克 于 2006-9-15 17:16 发表
这也基本是个伪命题。不存在丢失,还有排队的问题 


存在


 isnowran 回复于:2006-09-19 22:34:21

引用:原帖由 linternt 于 2006-9-19 14:30 发表
我知道进程是累加的,可是到一定数值后就又回到很小的值了。
照你所说,进程只无限增大的了。

另外的自动清理是我个人的理解,就是说如果进程没被回收的话,INIT进程会去处理。然后重用这个进程表。 


循环累加的意思就是从1到100,头尾连起来


 塑料袋 回复于:2006-09-19 23:21:28

在忽略情况下,一旦一个进程收到一个SIG_CHLD,这个进程就会干掉:

current进程所在的thread_group中, 所有进程的所有子进程里的,exit_signal == SIG_CHLD,并且处于僵尸状态的那部分子进程


以下摘自2.4.32

int fastcall do_signal(struct pt_regs *regs, sigset_t *oldset)
{
siginfo_t info;
struct k_sigaction *ka;


if ((regs->xcs & 3) != 3)
return 1;

if (!oldset)
oldset = &current->blocked;

for (;;) {
unsigned long signr;

spin_lock_irq(&current->sigmask_lock);
signr = dequeue_signal(&current->blocked, &info);
spin_unlock_irq(&current->sigmask_lock);

if (!signr)
break;


if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {

                                        //进程被跟踪,在这里处理,不考虑

}

ka = &current->sig->action[signr-1];




                             [color=Red] /*SIG_IGN 时的处理, 这里是关键*/[/color]


if (ka->sa.sa_handler == SIG_IGN) {

[color=Red]/*SIGCHLD以外的信号忽略,但是SIGCHLD不忽略,执行这里*/[/color]
if (signr != SIGCHLD)
continue;

while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
;
continue;
}

                                
                                /*默认的信号处理函数*/
if (ka->sa.sa_handler == SIG_DFL) {
int exit_code = signr;

if (current->pid == 1)
continue;

                                             /* 这里执行默认的信号处理函数*/ 
}


__asm__("movl %0,%%db7" : : "r" (current->thread.debugreg[7]));

/* Whee!  Actually deliver the signal.  */
handle_signal(signr, ka, &info, oldset, regs);
return 1;
}

/* Did we come from a system call? */
if (regs->orig_eax >= 0) {
/* Restart the system call - no handlers present */
if (regs->eax == -ERESTARTNOHAND ||
    regs->eax == -ERESTARTSYS ||
    regs->eax == -ERESTARTNOINTR) {
regs->eax = regs->orig_eax;
regs->eip -= 2;
}
}
return 0;
}




这个就是对SIGCHLD的处理

while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
;

  反复执行sys_wait4([color=Red]-1, NULL, WNOHANG, NULL)[/color] ,直到返回<=0







asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)
{
int flag, retval;
DECLARE_WAITQUEUE(wait, current);
struct task_struct *tsk;

if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL))
return -EINVAL;

add_wait_queue(&current->wait_chldexit,&wait);
                /* 以上语句都不用考虑*/ 

repeat:
flag = 0;
current->state = TASK_INTERRUPTIBLE;
read_lock(&tasklist_lock);
tsk = current;

do {
struct task_struct *p;
  for (p = tsk->p_cptr ; p ; p = p->p_osptr) {
                          
                                                /*  这里省略了一部分指令,以参数pid = -1, option = WNOHANG
                                                 *   来执行这里的语句时,效果就是
                                 *
                                                 *   过滤掉所有exit_signal != SIG_CHLD的,进程task的子进程
                                 *
                                                 *   进程task的所有exit_signal == SIG_CHLD的子进程进入下边的
                                 *  flag = 1;
                                                  * switch (p->state) {
                                                 *
                                                 */

                                                flag = 1;

switch (p->state) {
case TASK_STOPPED:
                                                                
                                                                /* 对被跟踪进程的处理,我们不考虑*/
goto end_wait4;


case TASK_ZOMBIE:

read_unlock(&tasklist_lock);

retval = p->pid;
  
                                                                /* 对exit_signal == SIG_CHLD的子进程的处理*/

if (p->p_opptr != p->p_pptr) {

                                                                     /*进程task的继子进程归还给继子进程的原始父进程,
                                                                       *并向原始父进程发送SIG_CHLD*/
} else
release_task(p); 
                                                                               /*回收僵尸进程的task_struct*/

goto end_wait4;
default:
continue;
}
}
                              

                                /* 以下3句指令
                      *
                                 * 因为参数中没有设置__WNOTHREAD,所以会循环扫描整个thread_group中的
                      * 所有进程的所有子进程,当然,子进程必须exit_signal == SIG_CHLD
                                 */
if (options & __WNOTHREAD)
break;
tsk = next_thread(tsk);

} while (tsk != current);


read_unlock(&tasklist_lock);

                
                /* 一旦执行这个if语句,证明当前进程所在的thread_group中,所有进程的所有子进程
            *(子进程还要满足 exit_signal == SIG_CHLD)只有就绪,可中断,不可中断三种状态
            *
                  * 而没有僵尸,或者被跟踪而停止的子进程 
                  *
                  * 这样会返回0
                  */
if (flag) {
retval = 0;
if (options & WNOHANG)
goto end_wait4;
                    
                                /*以下省略一些指令,因为根本不会执行,参数中option == WNOHANG*/
}

                
               /* thread_group中,所有进程都没有exit_signal == SIG_CHLD的子进程,才会执行
           * retval = -ECHILD,返回负值
                 */

retval = -ECHILD;


  
[color=Red]               /* 三种情况会到达这里
           *
                * 1) 处理了一个僵尸进程,retval = 僵尸进程的pid
                *
                * 2) thread_group组中所有进程的子进程们,虽然有的exit_signal == SIG_CHLD,
                *     但是没有僵尸进程,retval = 0
                *
                * 3) thread_group组中进程的子进程们,没有任何一个exit_signal == SIG_CHLD,
                *     这时retval = -ECHILD,负值
                */[/color]end_wait4:
current->state = TASK_RUNNING;
remove_wait_queue(&current->wait_chldexit,&wait);
return retval;
}

[ 本帖最后由 塑料袋 于 2006-9-19 23:27 编辑 ]


 塑料袋 回复于:2006-09-19 23:34:58

你的这段程序
void sig_chld(int signo)
{
       pid_t   pid;
       int     stat;
       
       while((pid = waitpid(-1, &stat, WNOHANG)) > 0){
               printf("child %d terminated\n", pid);
       }
        return;
}


跟Linux Kernel中对SIGCHLD的处理基本一模一样

waitpid(-1, &stat, WNOHANG)) 类似于sys_wait4

只要当前进程所在的线程组中,有任何一个线程还有子进程,并且这个子进程的exit_signal == SIGCHLD,还处于ZOMBIE状态,那么就会返回一个>0的ZOMBIE进程的pid

直到干掉了所有这样的子进程,才返回<=0 的值

象这么用的话,一个SIGCHLD就足够干掉一大片子进程了

[ 本帖最后由 思一克 于 2006-9-20 09:49 编辑 ]


 塑料袋 回复于:2006-09-20 09:21:57

引用:原帖由 flw 于 2006-9-20 09:17 发表
塑料袋兄弟,以您目前的水平,还不适合在 ChinaUnix C/C++ 版回答别人的问题。希望您在这一点上有足够的自知之明,否则只会毒害别人,并没有什么好处。 




塑料袋兄弟,以您目前的水平,还不适合在 ChinaUnix C/C++ 版回答别人的问题。希望您在这一点上有足够的自知之明,否则只会根一帮弱智们对牛弹琴,并没有什么好处。


 flw 回复于:2006-09-20 09:22:02

塑料袋兄弟,以您目前的水平,还不适合在 ChinaUnix C/C++ 版回答别人的问题。希望您在这一点上有足够的自知之明,否则只会毒害别人,并没有什么好处。
哦,忘了说了,Confirming to POSIX.1,SIGCHLD 的默认行为是 ignore the signal。


 linternt 回复于:2006-09-20 09:50:32

这个问题说了这么多,我觉得还是针对性问题做一些处理吧。
原理和应用要各自对待才是。

观注一下高手的精彩回答


 ccjjhua 回复于:2006-09-20 10:27:52

建议有重大争议的论题,能有高手作最后的总结陈词。以后看话题的时候只要看最后的发言就知道答案了。省时间。


 ccjjhua 回复于:2006-09-20 10:48:19

我觉得父进程要等待子进程结束的信号,不仅仅是为了回收它 ,更重要的是有些应用需要父进程在子进程退出的时候做些相应的操作。如果只是为了回收,父进程可以忽略该信号。当子进程退出时,父进程是有可能接受不到该信号,就是“丢失”的情况发生了,如果只是为了不让有僵尸进程发生,父进程可以继续等待下一个(其他子进程产生的退出信号)信号,就可以把原来的子进程进行回收,但如果父进程需要针对该子进程做些特别的操作,就不可能完成了。不知道我说的对不对。

[ 本帖最后由 ccjjhua 于 2006-9-20 10:54 编辑 ]


 linternt 回复于:2006-09-20 11:11:28

引用:原帖由 ccjjhua 于 2006-9-20 10:48 发表
我觉得父进程要等待子进程结束的信号,不仅仅是为了回收它 ,更重要的是有些应用需要父进程在子进程退出的时候做些相应的操作。如果只是为了回收,父进程可以忽略该信号。当子进程退出时,父进程是有可能接受不到 ... 



说的对,呵呵,高手:mrgreen:


 zleil 回复于:2006-09-23 23:13:05

引用:原帖由 flw 于 2006-9-15 17:20 发表
根本就不需要找回来!
好比有五个进程,
不妨分别称为 p1 p2 p3 p4 p5,
一开始 p1 结束了,发了一个 SIGCHLD(s1),
这时父进程可能空闲了,于是开始处理这个信号,假设处理的过程中 p2 又结束了,又发了一个 SIGCHLD(s2),
这时候已经有两个信号了(一个正在处理,一个待处理),这时如果 p3 又结束了,那么它发的那个 SIGCHLD(s3) 势必会丢失,
丢失了怎么办?
没关系,因为那个信号处理函数是个循环嘛,
所以 while(waitpid()) 的时候,会把 p1 p2 p3 都处理的。
即使是很不幸,因为十分凑巧的原因,p3 没有被回收,导致变成僵尸进程了,也没关系,
因为还有 p4 p5 嘛,等到 p4 或者 p5 结束的时候,



也就是所有被阻塞的SIGCHLD信号都是被一个待处理的SIGHLD信号阻塞,对不对?
这样,所有丢失信号SIGCHLD对应的僵死进程将被那个待处理的SIGHLD信号的响应代码回收,因为响应代码里有循环。对不对?

所以,
引用:即使是很不幸,因为十分凑巧的原因,p3 没有被回收,导致变成僵尸进程了,也没关系,
因为还有 p4 p5 嘛,等到 p4 或者 p5 结束的时候


这种情况下,还是由P2产生的SIGCHLD信号的响应处理过程来回收P3僵死进程。flw看看是不是这样?


 yuanyawei 回复于:2006-10-16 16:35:45

引用:原帖由 ccjjhua 于 2006-9-20 10:48 发表
我觉得父进程要等待子进程结束的信号,不仅仅是为了回收它 ,更重要的是有些应用需要父进程在子进程退出的时候做些相应的操作。如果只是为了回收,父进程可以忽略该信号。当子进程退出时,父进程是有可能接受不到 ... 




以前作一个定时器时(秒)时,在大量任务进程同时结束时出现过这种丢信号的情况,原设计在接受到SIGCHLD信号后父进程马上做了些处理,但不到SLEEP(5)这么长,在不考虑极端的情况下,我是这么做的。处理信号的工作尽量简单化,我只是把它加入到一个链表里暂存,在父进程闲时才去处理。当时还是用的2.4的内核,应该是父进程处理时间过长导致的问题,用子进程自己记录ID到消息队列或共享内存里的方法也是可以的,但我不能保证我的子进程是否会core,那样就麻烦了。顺便补充一句,用在linux下丢信号的代码在aix4和hpux10下没有发现类似现象,但可能是配置高的原因,所以我还是更新了,也没有具体去测试aix和hp下sigchld是否排队?


 googleboy 回复于:2006-10-17 18:49:48

好帖子,希望高手们能继续。


 aaaaal 回复于:2006-12-06 11:47:12

继续关注


 柳五随风 回复于:2006-12-09 13:31:53

单纯用waitpid处理,存在p1~pn,当pn~pm的SIGCHLD丢失的时候,不能捕获child processes的状态的问题,在有些环境下(比如QT/QProcess)里面要求必须处理,不能忽略,那么一半采用的就是上面有人说的K-Queue的办法。
顺便问问,那位有在QT3.11上碰到过大量QProcess crash的问题?




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=828942
转载请注明作者名及原文出处



收藏本页到: