signal 函数只能接受一次信号? The naughty signal function

来源:互联网 发布:如何用java编写游戏 编辑:程序博客网 时间:2024/06/04 19:59

The naughty signal function



signal 函数的prototype:
       #include <signal.h>       typedef void (*sighandler_t)(int);       sighandler_t signal(int signum, sighandler_t handler);



在练习是时候发现一个问题: 学APUE都应该看过这个demo
#include"apue.h"#include"stdio.h"#include"myerr.h"static void sig_usr(int);int main(){        if(signal(SIGUSR1,sig_usr) == SIG_ERR)        {                err_sys("signal error\n");        }        if(signal(SIGUSR2,sig_usr) == SIG_ERR)        {                err_sys("can't catch SIGUSR2");        }        for(;;)        {                pause();        }        return 0;}static void sig_usr(int signo){        if(signo == SIGUSR1)        {                printf("received SIGUSR1\n");        }        else if(signo == SIGUSR2)        {                printf("received SIGUSR2\n");        }        else        {                err_dump("received signal %d\n",signo);        }}
我觉得还是先引出问题的比较好。
so ,test result:
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ gcc ./pro_10_2.c -g -o ./a.out
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ ./a.out &
[1] 3310
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ kill -USR1 3310
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ received SIGUSR1
kill -USR2 3310
received SIGUSR2
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ ps
  PID TTY          TIME CMD
 3187 pts/0    00:00:00 bash
 3310 pts/0    00:00:00 a.out
 3311 pts/0    00:00:00 ps
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ ps
  PID TTY          TIME CMD
 3187 pts/0    00:00:00 bash
 3310 pts/0    00:00:00 a.out
 3312 pts/0    00:00:00 ps
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ kill -USR1 3310
[1]+  User defined signal 1   ./a.out
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ ps
  PID TTY          TIME CMD
 3187 pts/0    00:00:00 bash
 3313 pts/0    00:00:00 ps


The question is coming!

为嘛我发送一次信号,ps,程序还在跑,没事,我发送第二次不同的信号SIGUSR2,没是,程序还在跑,
为嘛我发送第二次相同的信号SIGUSR1 程序就给死掉了。。。

为这个问题,各种请教,各种无果

最后还是自己想通了。。。



我们像一个侦探一样来慢慢剖析这段code,这是个非常享受,有意思的过程。Just enjoy it。

首先
for(;;){        pause();}

这就是一个死循环,然后里面调用了pause函数,可以把当前进程挂起。所谓的suspended
如果不知道神马叫做挂起:=_=

挂起是指在操作系统进程管理将前台的进程暂停并转入后台的动作。将进程挂起可以让用户在前台执行其他的进程。挂起的进程通常释放除CPU以外已经占有的系统资源,如内存等。

在需要时用户可以恢复进程的运行,将被挂起的进程从后台转入前台,并从暂停处开始继续运行。

                                                                                            -- wikipedia
 
 
这个时候如果把程序放在background group里面运行,通过shell 的kill 给个信号,可以触发signal handler来处理这个signal。于是就有:
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ ./a.out &
[1] 3310
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ kill -USR1 3310
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_10$ received SIGUSR1


但是为什么送第二次相同的信号就死掉了呢?

原因在这里

        if(signal(SIGUSR1,sig_usr) == SIG_ERR)        {                err_sys("signal error\n");        }        if(signal(SIGUSR2,sig_usr) == SIG_ERR)        {                err_sys("can't catch SIGUSR2");        }
这个就是创建signal handler函数的语句。

我们必须非常仔细的注意,这里的signal handler的declaration:

static void sig_usr(int);

static的作用并不是把函数放在 静态内存区域,而是限定函数的作用域。signal handler是在stack上面的!

这才是问题的关键!这么以来,上面的code就只将sig_usr 载入了两次,一次给SIGUSR 1,一次给SIGUSR2

触发signal handler之后是会被弹栈的!这个时候signal handler就木有了!

之后如果继续向signal handler 发送信号就会触发default behaviour for SIGUSR1 and SIGUSR2.

他们的default behaviour就是termination 。。。so 程序挂掉。




if(signal(SIGUSR1,sig_usr) == SIG_ERR){        err_sys("signal error\n");}if(signal(SIGUSR2,sig_usr) == SIG_ERR){       err_sys("can't catch SIGUSR2");}
这部分就是创建信号处理函数的signal handler


用我自己写的demo来印证我的explanation
/*********************************************************************code writer : EOFcode date : 2014.03.31e-mail : jasonleaster@gmail.comcode purpose :        I would like to share my code with someone like me.Just a demo for signal function. The signal would be loadedto stack.Load once, pop once.**********************************************************************/#include"apue.h"#include"stdio.h"#include"myerr.h"static void sig_usr(int);int main(){        int temp = 0;        if(signal(SIGUSR1,sig_usr) == SIG_ERR)        {                err_sys("signal error\n");        }        if(signal(SIGUSR2,sig_usr) == SIG_ERR)        {                err_sys("can't catch SIGUSR2");        }        for(temp = 0;1;temp++)        {                pause();                if(temp < 1)                {                        if(signal(SIGUSR1,sig_usr) == SIG_ERR)                        {                                err_sys("signal error\n");                        }                        if(signal(SIGUSR2,sig_usr) == SIG_ERR)                        {                                err_sys("can't catch SIGUSR2");                        }                }        }        return 0;}static void sig_usr(int signo){        if(signo == SIGUSR1)        {                printf("received SIGUSR1\n");        }        else if(signo == SIGUSR2)        {                printf("received SIGUSR2\n");        }        else        {                err_dump("received signal %d\n",signo);        }}



这个时候我载入四次sigusr 我发送超过两次SIGUSR1才会挂掉
liuzjian@ubuntu:/Ad_Pro_in_Unix/chapter_10$ ./a.out &
[1] 3372
liuzjian@ubuntu:/Ad_Pro_in_Unix/chapter_10$ kill -USR1 3372
received SIGUSR1
liuzjian@ubuntu:/Ad_Pro_in_Unix/chapter_10$ kill -USR1 3372
received SIGUSR1
liuzjian@ubuntu:/Ad_Pro_in_Unix/chapter_10$ kill -USR1 3372
[1]+  User defined signal 1   ./a.out




That‘s all .  Thank you。 

I wish this blog would help you if you come across the problem.





update:2014年4月1日(请看4月3日的更新,此次更新仅作为“引出问题”部分)

今天愚人节,有点郁闷。。。
没想明白
source_code.c
#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>void handler1(int sig){        pid_t pid;        if((pid = waitpid(-1,NULL,0)) < 0)        {                printf("waitpid error\n");        }        printf("Handler reaped child %d\n",(int)pid);        sleep(2);        return;}int main(){        int i,n;        pid_t pid;        char buf[BUFSIZ];        if(signal(SIGCHLD,handler1) == SIG_ERR)        {                printf("signal error\n");        }        for(i = 0;i < 3;i++)        {                if((pid = fork()) < 0)                {                        printf("fork error\n");                }                else if(pid == 0)                {                        printf("Hello from child%d\n",(int)getpid());                        sleep(2);                        exit(0);                }        }        if((n = read(STDIN_FILENO,buf,sizeof(buf)-1)) < 0)        {                printf("read error\n");        }        printf("parent processsing input\n");        while(1)        {        }        exit(0);}




fork的子进程和父进程大多数的数据是一样的,父进程的signal handler会copy给子进程

 waitpid(-1,NULL,0)

  If PID is (pid_t) -1, match any process.
stack里面应该有四个signal handler1

后面在百度百科看到这个下面这段话。。。我觉得如果遇到和我相同问题的人就不要为signal函数纠结了。。。完全木有意义,好好掌握sigaction就是了

signal函数是由ISO C定义的。因为ISO C不涉及多进程,进程组以及终端I/O等,所以他对信号的定义非常含糊,以至于对UNIX系统而言几乎毫无用处。
备注:因为signal的语义与现实有关,所以最好使用sigaction函数替代本函数。


update: 2014.04.03
此次更新删除了部分上次更新得到的错误结论。并在此次更新中对上面有疑惑的地方解释清楚。算是“结题”吧

过去几天了。各种讨论,无果。傍晚的时候还是领悟了。。。。


做一下背景知识的铺垫,这几乎就是解决问题的关键:

一下这个demo说明waitpid的 “神奇” 用法。。。至于waitpid的API和API说明我就不给了。。。
不过我必须说明一下,child的exit status和SIGCHLD信号只会传递给parent process。这是个很重要的概念,没搞清楚就容易动摇怀疑。。。。

#include <stdio.h>#include <stdlib.h>int main(){        pid_t pid;        int temp = 0;        for(temp = 0;temp < 3;temp++)        {                if((pid = fork()) == 0)                {                        sleep(1);                        exit(0);                }        }        while((pid = waitpid(-1,NULL,0)) > 0)        {                printf("Handler reaped child %d\n",(int)pid);        }        printf("pid %d\n",pid);        return 0;}

注意,fork出的child process对于parent process 继承的数据copy特性。如下图






此时每一个child里面都有signal handler ,也就是意味着,每个signal handler都有waitpid 函数。那么SIGCHLD会传递给child自身或者其他child process吗?上面我已经说过了,不会。waitpid只接受来自自身child process的SIGCHLD信号,这里的SIGCHLD都会被送给parent process

上面waitpid_test.c程序的运行结果:

jasonleaster@ubuntu:/CSAPP/chapter_8$ ./a.out
Handler reaped child 10228
Handler reaped child 10227
Handler reaped child 10229
pid -1

还有,sigaction  install signal handler 之后是不会自动卸载signal handler的!

       Once  we  install  an  action  for  a  given  signal,  that  action  remains  installed  until  we explicitly change it by calling sigaction.Unlike earlier systems with their unreliable signals, POSIX.1 requires that a signal handler remain installed until explicitly changed.

<span style="font-size:14px;">#include <stdio.h>#include <stdlib.h>#include <signal.h>void sig_handler(void){        printf("hello world!\n");}int main(){        int temp = 0;        struct sigaction act;        sigemptyset(&act.sa_mask);        act.sa_flags = 0;        act.sa_handler = sig_handler;        if(sigaction(SIGCHLD,&act,NULL) < 0)        {                printf("sigaction error\n");        }        for(;;)        {        }        return 0;}</span>
jasonleaster@ubuntu:/CSAPP/chapter_8$ ./a.out &
[1] 3461
jasonleaster@ubuntu:/CSAPP/chapter_8$ kill -CHLD 3461
hello world!
jasonleaster@ubuntu:/CSAPP/chapter_8$ kill -CHLD 3461
hello world!
jasonleaster@ubuntu:/CSAPP/chapter_8$ kill -CHLD 3461
hello world!
jasonleaster@ubuntu:/CSAPP/chapter_8$ kill -CHLD 3461
hello world!
jasonleaster@ubuntu:/CSAPP/chapter_8$ kill -CHLD 3461
hello world!




这是至关重要的铺垫。。。。

以前我们遇到的疑惑:

为了规范signal的使用,放弃使用signal函数,重写source_code.c.使用sigaction函数的版本
#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>#include <errno.h>void handler1(int sig){        pid_t pid;        if((pid = waitpid(-1,NULL,0)) < 0)        {                printf("waitpid error\n");        }        printf("Handler reaped child %d\n",(int)pid);        sleep(2);        return;}int main(){        int i,n;        pid_t pid;        struct sigaction act;        sigemptyset(&act.sa_mask);        act.sa_flags = 0;        act.sa_handler = handler1;        act.sa_flags |= SA_RESTART;//      sigaddset(&act.sa_mask,SIGCHLD);        char buf[BUFSIZ];        if(sigaction(SIGCHLD,&act,NULL) != 0)        {                printf("signal error\n");        }        for(i = 0;i < 3;i++)        {                if(fork() == 0)                {                        printf("Hello from child%d\n",(int)getpid());                        sleep(1);                        exit(0);                }        }        if((n = read(STDIN_FILENO,buf,sizeof(buf)-1)) < 0)        {                printf("read error\n");        }        printf("parent processsing input\n");        while(1)        {        }        exit(1);}
以后讨论均以这个sigaction函数版本的代码为讨论基础

运行source_code.c 生成的程序
jasonleaster@ubuntu:/CSAPP/chapter_8$ ./a.out
Hello from child10283
Hello from child10284
Hello from child10285
Handler reaped child 10284
Handler reaped child 10283
jasonleaster
parent processsing input


source_code.c 的运行结果为什么会reap两次child process ?为什么就是两次



         慢慢回答这个问题,注意到代码的作者把waitpid放在了signal handler里面,其实我一开始不理解,误会了,觉得怎么可以把waitpid放在signal handler里面,其实这是一种非常好的机制。


           The basic structure is that a parent process creates some children that run independently for a while and then terminate. The parent must reap the children to avoid leaving zombies in the system. But we also want the parent to be free to do other work while the children are running. So we decide to reap the children with a SIGCHLD handler, instead of explicitly waiting for the children to terminate.


           we note that although three SIGCHLD signals were sent to the parent, only two of these signals were received, and thus the parent only reaped two children. 


就这这里,为什么就是两次!


        

            Here’s what happened: The first signal is received and caught by the parent. While the handler is still processing the first signal, the second signal is delivered and added to the set of pending signals. However, since SIGCHLD signals are blocked by theSIGCHLD handler the second signal is not received. Shortly there after, while the handler is still processing the first signal, the third signal arrives. Sincethere is already a pending SIGCHLD, this third SIGCHLD signal is discarded. Sometimelater, after the handler hasreturned, the kernel notices that there is a pending SIGCHLD signal and forces the parent to receive the signal. The parent catches the signal and executes the handler a second time. After the handler finishes processing the second signal,there are no more pending SIGCHLD signals, and there never will be, because all knowledge of the third SIGCHLD has beenlost. 




上面只reap 了两个child process。另外一个呢? zombie咯。。。。
jasonleaster@ubuntu:/CSAPP/chapter_8$ ./a.out
Hello from child10315
Hello from child10316
Hello from child10317
Handler reaped child 10315
Handler reaped child 10316
jasonleaster
parent processsing input
^Z
[1]+  Stopped                 ./a.out
jasonleaster@ubuntu:/CSAPP/chapter_8$ ps
  PID TTY          TIME CMD
 9955 pts/4    00:00:00 bash
10314 pts/4    00:00:01 a.out
10317 pts/4    00:00:00 a.out <defunct>
10318 pts/4    00:00:00 ps

可以看见10317 pts/4    00:00:00 a.out <defunct>就是那个僵尸

稍稍修改代码就可以避免僵尸了,也就是利用我们之前的“铺垫”

#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>#include <errno.h>void handler1(int sig){        pid_t pid;        while((pid = waitpid(-1,NULL,0)) > 0)        {                printf("Handler reaped child %d\n",(int)pid);        }        if(errno != ECHILD)        {                printf("waitpid error\n");        }        sleep(2);        return;}int main(){        int i,n;        pid_t pid;        struct sigaction act;        sigemptyset(&act.sa_mask);        act.sa_flags = 0;        act.sa_handler = handler1;        act.sa_flags |= SA_RESTART;//      sigaddset(&act.sa_mask,SIGCHLD);        char buf[BUFSIZ];        if(sigaction(SIGCHLD,&act,NULL) != 0)        {                printf("sigaction error\n");        }        for(i = 0;i < 3;i++)        {                if(fork() == 0)                {                        printf("Hello from child%d\n",(int)getpid());                        sleep(1);                        exit(0);                }        }        if((n = read(STDIN_FILENO,buf,sizeof(buf)-1)) < 0)        {                printf("read error\n");        }        printf("parent processsing input\n");        while(1)        {        }        exit(0);}


jasonleaster@ubuntu:/CSAPP/chapter_8$ ./a.out
Hello from child10352
Hello from child10353
Hello from child10354
Handler reaped child 10352
Handler reaped child 10354
Handler reaped child 10353
jasonleaster
parent processsing input
^Z
[1]+  Stopped                 ./a.out
jasonleaster@ubuntu:/CSAPP/chapter_8$ ps
  PID TTY          TIME CMD
 9955 pts/4    00:00:00 bash
10351 pts/4    00:00:01 a.out
10355 pts/4    00:00:00 ps

可以看出。。。木有zombie咯。。。。



这是一种很好的机制,把waitpid 放在signal handler里面,reap child process


最后感谢那些和我一起交流讨论的programmer . 非常感谢。

Sharing make our world more beautiful.                          -- jasonleaster
                                                                             2014.04.03.night



update: 2014.04.03


得到一个新的问题,由这个问题应该能区分很难区分出的概念,waitpid靠什么来捕捉child process结束
 信号?signal
进程结束状态?status

由于进程结束的时候这两个都是同时发生的,一般情况下至于谁对waitpid作用,没什么人关心,反正waitpid会等child process挂掉就是了。
下面这个代码就说明了这个问题



#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>#include <errno.h>int number = 1;//global variblevoid handler1(int sig){        pid_t pid;        printf("pid :%d sig : %d\n",getpid(),sig);        while((pid = waitpid(-1,NULL,0)) > 0)        {                printf("Handler reaped child %d\n",(int)pid);        }        printf("number :%d pid :%d sig : %d\n",number,getpid(),sig);        fflush(NULL);        if(errno != ECHILD)        {                printf("waitpid error\n");        }        sleep(2);        number++;        return;}int main(){        int i,n;        pid_t pid;        struct sigaction act;        sigemptyset(&act.sa_mask);        act.sa_flags = 0;        act.sa_handler = handler1;        act.sa_flags |= SA_RESTART;//      sigaddset(&act.sa_mask,SIGCHLD);        char buf[BUFSIZ];        if(sigaction(SIGCHLD,&act,NULL) != 0)        {                printf("sigaction error\n");        }        for(i = 0;i < 5;i++)        {                if(fork() == 0)                {                        printf("Hello from child%d\n",(int)getpid());                        sleep(1);                        exit(0);                }        }        if((n = read(STDIN_FILENO,buf,sizeof(buf)-1)) < 0)        {                printf("read error\n");        }        printf("parent processsing input\n");        while(1)        {        }        exit(0);}


test result:

jasonleaster@ubuntu:/CSAPP/chapter_8$ ./a.out
Hello from child4330
Hello from child4331
Hello from child4334
Hello from child4332
Hello from child4333
pid :4329 sig : 17
Handler reaped child 4331
Handler reaped child 4332
Handler reaped child 4334
Handler reaped child 4330
Handler reaped child 4333
number :1 pid :4329 sig : 17
pid :4329 sig : 17
number :2 pid :4329 sig : 17
jasonleaster
parent processsing input
^C



这样的输出结果看似很难解释。。。
冷静分析一下,不难发现,这里number的变化说明了signal handler被调用了两次!
而第一次调用是因为有个child的SIGCHLD信号发送给parent process了,从而触发,signal handler。

while((pid = waitpid(-1,NULL,0)) > 0)        {                printf("Handler reaped child %d\n",(int)pid);        }



这个循环一直reap掉所有的child

然后第一次signal handler处理完毕。
接着又有
pid :4329 sig : 17
number :2 pid :4329 sig : 17
输出

这无疑是第二次signal handler。而此次signal handler必须要有触发信号。what's happened?

是这样的:
每个child 挂掉的时候,都会发生两个变化,第一个是exit status,就是返回值,第二个就是向parent process发送一个SIGCHLD的信号
而waitpid等待的不是那个SIGCHILD,是exit status 上面程序waitpid一直while循环reap掉五个child exit status然后while结束。这个时候SIGCHLD是blocked and discard。于是又一次触发了signal handler,blocked and discard的signal 被unblocked ,于是就没有信号了,而waitpid此时没有child process 更别谈child process  的exit status了。于是while没进去 第二个signal handler结束。
            
                                                                                                              ——jasonleaster




1 0
原创粉丝点击