码迷,mamicode.com
首页 > 其他好文 > 详细

10.信号

时间:2014-05-10 19:54:21      阅读:354      评论:0      收藏:0      [点我收藏+]

标签:style   blog   class   code   java   tar   

1、概念

信号提供了一种处理异步事件的方法。

不存在编号为0的信号,kill函数对信号编号0有特殊的应用。POSIX.1 将此种信号编号值称为空信号。

 

2、信号的相关动作

a、忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略,它们是SIGKILL和SIGSTOP。这两种信号不能不忽略的原因是:它们向超级用户提供了使进程终止和停止的可靠方法;

b、捕捉信号。为了做到这一点,要通知内核在某种信号发生时调用一个用户函数。(SIGTERM是终止信号,kill命令传送的系统默认信号是终止信号)。注意,不能捕捉SIGKILL和SIGSTOP信号;

c、执行系统默认动作。针对大多数信号的系统默认动作是终止进程。

 

3、程序启动(exec处理信号的方式)

当执行一个程序时,所有信号的状态都是系统默认或忽略,除非调用exec的进程忽略该信号。确切地说,exec函数将原先设置为要捕捉的信号都更改为它们的默认动作,其他信号的状态不变。如在一个交互式shell如何处理针对后台进程的中断和退出信号,如:

cc main.c &

shell自动将后台进程对中断和退出信号的处理方式设置为忽略,于是,当按中断键时就不会影响到后台进程。

 

进程启动(fork处理信号的方式)

当一个进程调用fork时,其子进程继承父进程的信号处理方式,因为子进程在开始时复制了父进程的存储映像,所以信号捕捉函数的地址在子进程中是有意义的。

 

4、不可靠信号的两个例子

前提,早期版本中的问题是在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值。如:

bubuko.com,布布扣
    int sig_init();                    // my signal handling function
    ...
    signal(SIGINT,sig_init);        // establish handler 
    ...
    
    sig_init()
    {
        signal(SIGINT,sig_init);    // reestablish handler for next time
        ...                            // process the signal
    }
bubuko.com,布布扣

问题:从信号发生之后到在信号处理程序中调用signal函数之前这段时间中有一个时间窗口。在此段时间中,可能发生另一次中断信号。第二个信号会导致执行默认动作,而针对终端信号的默认动作是终止该进程。

 

另一个问题:在进程不希望某种信号发生时,它不能关闭该信号。进程能做的一切就是忽略该信号。有时希望通知系统“阻止下列信号发生,如果它们确实发生了,请记住了它们。”能够显现这种缺陷的经典实例有下列程序段,它捕捉了一个信号,然后设置一个表示该信号已发生的标志:

bubuko.com,布布扣
    int sig_init_flag;                // set nonzero when signal occurs
    main()
    {
        int sig_init();                // my signal handling function
        ...
        signal(SIGINT,sig_init);    // establish handler
        ...
        while(sig_init_flag == 0)
            pause();                // go to sleep,waiting for signal
        ...
    }
    sig_init()
    {
        signal(SIGINT,sig_init);    // reestablish handler for next time
        sig_init_flag = 1;            // set flag for main loop to examine
    }
bubuko.com,布布扣

其中,进程调用pause函数使自己休眠,直至捕捉到一个信号。当捕捉到信号时,信号处理程序将标志sig_init_flag设置为非0值。从信号处理程序返回后,内核自动将该进程唤醒,它检测到该标志为非0值,然后执行它所需做的工作。但是这里也有一个时间窗口,在此窗口中操作可能失误。如果在测试sig_init_flag(while语句)之后和调用pause之前发生SIGINT信号,则此进程在调用pause时入睡,并且长眠不醒(假定此信号不会再次发生)。于是,这次发生的信号也就丢失了。pause函数使调用进程在接到一个信号前挂起。

 

5、中断的系统调用

早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断。该系统调用返回出错,其errno被设置为EINTR,这样处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个理应当唤醒其他被阻塞的系统调用的好机会。

 

与被中断的系统调用相关的问题是必须显式地处理出错返回。典型的代码序列(假定进行一个读操作,它被中断,我们希望重新启动它)可能如下所示:

bubuko.com,布布扣
    again:
        if((n = read(fd,buf,BUFFSIZE)) < 0) {
            if(errno == EINTR)
                goto again;        // just an interrupted system call
                
            // handler other errors
        }
bubuko.com,布布扣

为了帮助应用程序使其不必处理被中断的系统调用,4.2 BSD 引入了某些被中断的系统调用的自动重启动。自动重启动的系统调用包括ioctl、read、readv、write、writev、wait和waitpid。正如前述,其中前5个函数只有对低速设备进行操作时才会被信号中断。而wait和waitpid在捕捉到信号时总是被中断。因为这种自动重启动的处理方式会带来问题,所以某些应用程序并不希望这些函数被中断后重启动。为此4.3 BSD允许进程基于每个信号禁用此功能(见程序清单10-12)。

引入自动重启动的理由是:有时用户并不知道所使用的输入、输出设备是否是低速设备,如果我们编写的程序可以用交互方式运行,则它可能读、写低速终端设备。如果在程序中捕捉信号,而且系统不提供重启动功能,则对每次读、写系统调用都要进行是否出错返回的测试,如果是被中断的,则再调用读、写系统调用。

 

6、可重入函数

进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用exit或longjmp),则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉到信号时进程在何处执行。

a、如果进程正在执行malloc,在其堆中分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc,这时会发生什么?

b、若进程正在执行getpwnam,这种将其结果存放在静态存储单元(可能是静态变量,当发生两次getpwnam时,该变量值只保存最后一次getpwnam的值,并且恢复不了第一次getpwnam时的值)中的函数,期间插入执行信号处理程序,它又调用这样的函数,这时又会发生什么?

 

在malloc中,可能会对进程造成破坏,因为malloc通常为它所分配的存储区维护一个链接表,而插入执行信号处理程序时,进程可能正在更改此链接表。

在getpwnam中,返回给正常调用者的信息可能被返回给信号处理程序的信息覆盖。

 

函数时不可重入的原因为:

a、已知它们使用静态数据结构;

b、它们调用malloc或free;

c、它们是标准IO函数;

标准IO库的很多实现都以不可重入方式使用全局数据结构。

 

可重入函数另一个说法,当函数正在执行而被中断,而且中断返回,函数继续执行后的结果,与没有发生中断执行后的结果一样,那么这个函数就是可重入函数。

 

应当了解,即使在信号处理程序中使用了可重入函数(不会破坏被中断的进程的函数),但是由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。如,一个信号处理程序,它恰好在main刚刚设置errno之后被调用,而该信号处理程序调用read这类函数,则它可能更改errno的值,从而取代了刚刚由main设置的值。因此,作为一个通用的规则,当在信号处理程序中调用了可重入函数时,应当在其前保存,在其后恢复errno。

 

例子:

bubuko.com,布布扣
    static void my_alarm(void)
    {
        struct passwd *rootptr;
 
        printf("in signal handler\n");
        if((rootptr = getpwnam("root")) == NULL)
            printf("getpwnam(root) error!\n");
    
        alarm(1);
    }
    
    int main()
    {
        struct passwd *ptr;                        // 只是指针,内存由getpwnam分配,并放在静态存储区中
    
        signal(SIGALRM,my_alarm);
        alarm(1);

        for(;;) {
            if((ptr = getpwnam("zzc")) == NULL)
                printf("getpwnam error!\n");
    
            if(strcmp(ptr->pw_name,"zzc") != 0)
                printf("return value corrupted!,pw_name = %s\n",ptr->pw_name);
        }
    }
bubuko.com,布布扣

结果:

bubuko.com,布布扣
    :~/program/c$ ./test
    in signal handler

    ^C(强制中断进程运行)
bubuko.com,布布扣

如果把信号处理函数中的

bubuko.com,布布扣
    if((rootptr = getpwnam("root")) == NULL)
            printf("getpwnam(root) error!\n");
bubuko.com,布布扣

注释掉的话(没有覆盖main中getpwnam函数的内容),结果为:

bubuko.com,布布扣
    :~/program/c$ ./test
    in signal handler
    in signal handler
    in signal handler
    in signal handler
    in signal handler
    ...
    ^C
bubuko.com,布布扣

 

7、可靠信号术语和语义

在产生了信号时,内核在进程表中设置一个某种形式的标志。当对信号采取了这种行动时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的(pending)。

如果为进程产生了一个设置为阻塞(该信号在信号屏蔽字中的对应位被设置)的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程

a、对此信号解除阻塞;

b、将此信号的动作更改为忽略。注意,内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作(即忽略该函数或者默认动作或者进行信号处理)。

进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。

 

每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集,对于每种可能产生的信号,该屏蔽字中都有一位与之对应。对于某种信号,若其对应位已设置,则它当前是被阻塞的。进程可以调用sigprocmask来检测和更改其当前信号屏蔽字。

 

8、kill、raise、alarm、pause函数

kill函数将信号发送给进程或进程组。

 

raise函数则允许进程向自身发送信号。

 

alarm函数

如果我们向捕捉SIGALRM信号,则必须在调用alarm之前设置该信号的处理程序。如果我们先调用alarm,然后在我们能够设置SIGALRM处理程序之前已接收到该信号,那么进程将终止。

 

pause函数

pause函数使调用进程挂起, 直到捕捉到一个信号,执行了信号处理程序并从其返回时,pause才返回。

 

9、sleep简单而不完整的实现

使用alarm和pause,进程可使自己休眠一段指定的时间。如下面的sleep1函数:

bubuko.com,布布扣
    static void sig_alarm()
    {
        ...
    }
    unsigned int sleep1(unsigned int nsecs)
    {
        if(signal(SIGALRM,sig_alarm) == SIG_ERR)
            return (nsecs);
            
        alarm(nsecs);            // start the timer
        pause();                // next caught signal wakes us up
        return (alarm(0));        // turn off timer,return unslept time
    }
bubuko.com,布布扣

在第一次调用alarm和调用pause之间有一个竞争条件:在一个繁忙的系统中,可能alarm在调用pause之前超时,并调用了信号处理程序。如果发生这种情况,则在调用pause后,如果没有捕捉到其他信号,则调用者将永远被挂起。

解决方法:

a、使用setjmp;

b、使用sigprocmask和sigsuspend;

 

方法a:

bubuko.com,布布扣
    static jmp_buf env_alrm;
    static void sig_alrm(int signo)
    {
        longjmp(env_alrm,1);
    }
    unsigned int sleep2(unsigned int nsecs)
    {
        if(signal(SIGALRM,sig_alrm) == SIG_ERR)
            return (nsecs);
            
        if(setjmp(env_alrm) == 0) {
            alarm(nsecs);                // start the timer
            pause();                    // next caught signal wakes us up
        }
        
        return (alarm(0));                // turn off timer,return unslept time
    }
bubuko.com,布布扣

在此,竞争条件已被避免。即使pause从未执行,在发生SIGALRM时,sleep2也会返回。

出现的另一个问题:它涉及与其他信号的交互,如果SIGALRM中断了某个其他信号处理程序,则调用longjmp会提早终止该信号处理程序。如:

bubuko.com,布布扣
    // 超过5s
    static void sig_int(int signo)
    {
        int i,j;
        volatile int k;
 
        printf("\nsig_int starting!\n");
        for(i = 0;i < 300000;i++)
            for(j = 0;j < 40000;j++)
                k += i * j;
 
        printf("sig_int finished!\n");
    }
 
    int main()
    {
        unsigned int unslept;
 
        if(signal(SIGINT,sig_int) == SIG_ERR) {
            printf("signal(SIGINT) error!\n");
            exit(1);
        }
        unslept = sleep2(5);
        printf("sleep2 returnd :%u\n",unslept);
 
        exit(0);
    }
bubuko.com,布布扣

输出结果为:

bubuko.com,布布扣
    :~/program/c$ ./test
    ^?                      // we type the interrupt character
    sig_int starting
    sleep2 returnd :0
bubuko.com,布布扣

除了用来实现sleep函数外,alarm还常用于对可能阻塞的操作设置时间上限值。例如,程序中有一个读低速设备的可能阻塞的操作,我们希望超过一定时间量后就停止执行该操作:

bubuko.com,布布扣
    static void sig_alrm(int);
    
    int main()
    {
        int n;
        char line[MAXLINES];
        
        if(signal(SIGALRM,sig_alrm) == SIG_ERR) {
            printf("signal(SIGALRM) error!\n");
            exit(1);
        }
        
        alarm(10);
        if((n = read(STDIN_FILENO,line,MAXLINE)) < 0) {
            printf("read error!\n");
            exit(1);
        }
        alarm(0);
        
        write(STDOUT_FILENO,line,n);
        exit(0);
    }
    
    static void sig_alrm(int signo)
    {
        // nothing to do,just return to interrupt the read
    }
bubuko.com,布布扣

遇到的问题:①跟上次程序类似,在第一次alarm调用和read调用之间有一个竞争条件。如果内核在这两个函数之间使进程阻塞,而其时间长度又超过闹钟时间,则read可能永远阻塞。②在read时候被alarm中断,如果系统调用是自动重启动的,则当从SIGALRM信号处理程序返回时,read并不被中断,在这种情形下,设置时间限制不起作用。

解决方法:

bubuko.com,布布扣
    static void sig_alrm(int);
    static jmp_buf env_alrm;
    
    int main()
    {
        int n;
        char line[MAXLINES];
        
        if(signal(SIGALRM,sig_alrm) == SIG_ALRM) {
            printf("signal(SIGALRM) error!\n");
            exit(1);
        }
        if(setjmp(env_alrm) != 0) {
            printf("read timeout!\n");
            exit(1);
        }
        
        alarm(10);
        
        if((n = read(STDIN_FILENO,line,MAXLINE)) < 0) {
            printf("read error!\n");
            exit(1);
        }
        
        alarm(0);
        
        write(STDOUT_FILENO,line,n);
        exit(0);
    }
    
    static void sig_alrm(int signo)
    {
        longjmp(env_alrm,1);
    }
bubuko.com,布布扣

不管系统是否重新启动中断的系统调用,该程序都会如期的那样工作,但是要知道,该程序跟上次的程序一样存在于其他信号处理程序交互的问题。

 

10、信号集

所有应用程序在使用信号集前,都要对信号集调用sigemptyset或sigfillset一次,因为C编译器把未赋初值的外部和静态变量都初始化为0,而这是否与给定系统上信号集的实现相对应却并不清楚。

 

11、sigprocmask函数

sigprocmask函数用来检测或获取或更改当前信号屏蔽字(即阻塞还是非阻塞)。

在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。

sigprocmask是仅为单线程的进程定义的。为处理多线程的进程中信号的屏蔽,提供了另一个单独的函数。

 

12、sigpending函数

sigpending函数返回未决的信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。如:

bubuko.com,布布扣
    static void sig_quit(int);
 
    int main()
    {
        sigset_t newmask,oldmask,pendmask;

        if(signal(SIGQUIT,sig_quit) == SIG_ERR)
            printf("can‘t catch SIGQUIT!\n");
    
        // block SIGQUIT and save current signal mask
        sigemptyset(&newmask);
        sigaddset(&newmask,SIGQUIT);
    
        if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)    //
            printf("SIG_BLOCK error!\n");                    
                                                            
        sleep(5);
    
        if(sigpending(&pendmask) < 0)
            printf("sigpending error!\n");
    
        if(sigismember(&pendmask,SIGQUIT))
            printf("SIGQUIT pending!\n");                    //// reset signal mask which unblocks SIGQUIT
        if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)        //
            printf("SIG_SETMASK error!\n");
        printf("SIGQUIT unblocked!\n");
    
        sleep(5);
        exit(0);
    }
    
    static void sig_quit(int signo)
    {
        printf("caught SIGQUIT!\n");
        if(signal(SIGQUIT,SIG_DFL) == SIG_ERR)
            printf("can‘t reset SIGQUIT!\n");
    }
bubuko.com,布布扣

运行结果:

bubuko.com,布布扣
    :~/program/c$ ./test
    ^\                                        产生信号一次(在5秒中之内)
    SIGQUIT pending!                        从sleep返回后
    caught SIGQUIT!                            在信号处理程序中
    SIGQUIT unblocked!                        从sigprocmask返回后
    :~/program/c$
bubuko.com,布布扣

①与②之间,SIGQUIT信号是被阻塞的,所以也是未决的。

③调用后,SIGQUIT信号的阻塞被释放,在sigprocmask返回前,任何未决的、不再阻塞的信号会被递送给该进程。所以,SIGQUIT的信号处理函数会被调用,最后才处理sigprocmask后面的程序。

bubuko.com,布布扣
    :~/program/c$ ./test
    SIGQUIT unblocked!                        没有产生信号
    :~/program/c$
bubuko.com,布布扣

SIGQUIT处理程序(sig_quit)中的printf语句先执行,然后再执行sigprocmask之后的printf语句,因为sigprocmask(SIG_SETMASK,&oldmask,NULL)执行完后,会立即递交SIGQUIT信号(之前被阻塞,现在解除阻塞),相应的信号处理函数会被立即调用。

然后该进程再休眠5秒钟,如果在此期间再产生退出信号,那么因为在上次捕捉到该信号时,已将其处理方式设置为默认动作,所以这一次它就会使该进程终止。

 

13、sigaction函数

此函数的功能是检查或修改与指定信号相关联的处理动作(或同时执行这两种操作)。此函数取代了UNIX早期版本使用的signal函数。

bubuko.com,布布扣
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);
bubuko.com,布布扣

其中,参数signo是检测或修改其具体动作的信号编号。

若act指针非空,则要修改其动作;

若oact指针非空,则系统经由oact指针返回该信号的上一个动作。此函数使用下列结构:

bubuko.com,布布扣
    struct sigaction {
        void (*sa_handler)(int);            // addr of signal handler,or SIG_IGN,or SIG_DEF
                                            
        sigset_t sa_mask;                    // additional signals to block
        int sa_flags;                        // signal options
        
        // alternative handler
        void (*sa_sigaction)(int,siginfo_t *,void *);
    };
bubuko.com,布布扣

当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址(与常量SIG_IGN或SIG_DFL相对),则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。

 

在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了五次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。

一旦对给定的信号设置了一个动作,那么在调用sigaction显式地改变它之前,该设置就一直有效。不像旧的不可靠信号语义signal函数,对给定的信号设置了一个动作后,需要在信号处理函数中再次设置一次。

sa_sigaction字段是一个替代的信号处理程序,当在sigaction结构中使用了SA_SIGNOF标志时,使用该信号处理程序。对于sa_sigaction字段和sa_handler字段这两者,其实现可能使用同一存储器,所以应用程序只能一次使用这两个字段中的一个。

 

下列程序用sigaction实现的signal函数

bubuko.com,布布扣
    Sigfunc *signal(int signo,Sigfunc *func)
    {
        struct sigaction act,oact;
        
        act.sa_handler = func;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        
        if(signo == SIGALRM) {
    #ifdef SA_INTERRUPT
            act.sa_flags |= SA_INTERRUPT;
    #endif
        } else {
    #ifdef SA_RESTART
            act.sa_flags |= SA_RESTART;
    #endif
        }
        
        if(sigaction(signo,&act,&oact) < 0)
            return (SIG_ERR);
        return (oact.sa_handler);
    }
bubuko.com,布布扣

某些早期系统定义了SA_INTERRUPT标志。这些系统的默认方式是重新启动被中断的系统调用,而指定此标志则使系统调用被中断后不再重启动。Linux定义了SA_UNTERRUPT标志,以便与使用该标志的应用程序兼容。但是,如若信号处理程序是用sigaction设置的,那么其默认方式是不重新启动系统调用。除非说明了SA_RESTART标志,否则sigaction函数不再重启动被中断的系统调用。

 

14、sigsetjmp和siglongjmp函数

调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢?

为了允许这两种形式存在,定义了两个新函数sigsetjmp和siglongjmp。

bubuko.com,布布扣
    int sigsetjmp(sigjmp_buf env,int savemask);
    void siglongjmp(sigjmp_buf env,int val);
bubuko.com,布布扣

如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。如:

下面程序演示了在信号处理程序被调用时,系统所设置的信号屏蔽字如何自动地包括刚被捕捉到的信号:

bubuko.com,布布扣
    static sigjmp_buf jmpbuf;
    static volatile sig_atomic_t canjump;
    static void sig_usr1(int),sig_alarm(int);

    static void pr_mask(const char *str)
    {
        sigset_t sigset;
        int errno_save;

        errno_save = errno;
        if(sigprocmask(0,NULL,&sigset) < 0) {
            printf("sigprocmask error!\n");
            exit(1);
        }

        printf("%s\n",str);

        if(sigismember(&sigset,SIGINT))        printf("SIGINT\n");
        if(sigismember(&sigset,SIGQUIT))    printf("SIGQUIT\n");
        if(sigismember(&sigset,SIGUSR1))    printf("SIGUSR1\n");
        if(sigismember(&sigset,SIGALRM))    printf("SIGALRM!\n");

        printf("\n");
        errno = errno_save;
    }

    int main()
    {
        if(signal(SIGUSR1,sig_usr1) == SIG_ERR) {
            printf("signal(SIGUSR1) error!\n");
            exit(1);
        }

        if(signal(SIGALRM,sig_alarm) == SIG_ERR) {
            printf("signal(SIGALRM) error!\n");
            exit(1);
        }

        if(sigsetjmp(jmpbuf,1)) {
            pr_mask("ending main:");
            exit(0);
        }

        canjump = 1;

        for(;;)
            pause();
    }

    static void sig_usr1(int signo)
    {
        time_t starttime;
    
        if(canjump == 0)
            return;

        pr_mask("starting sig_usr1:");
        alarm(3);

        starttime = time(NULL);
        for(;;) {
            if(time(NULL) > starttime + 5)
                break;
        }

        pr_mask("finishing sig_usr1:");

        canjump = 0;
        siglongjmp(jmpbuf,1);
    }

    static void sig_alarm(int signo)
    {
        pr_mask("in sig_alarm:");
    }
bubuko.com,布布扣

运行结果为:

bubuko.com,布布扣
    :~/program/c$ ./test &
    [1] 1280
    :~/program/c$ kill -USR1 1280
    :~/program/c$ starting sig_usr1:
    SIGUSR1

    in sig_alarm:
    SIGUSR1
    SIGALRM!

    finishing sig_usr1:
    SIGUSR1

    ending main:


    [1]+  Done                    ./test
bubuko.com,布布扣

 

15、sigsuspend函数

更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞,使用这种技术可以保护不希望由信号中断的代码临界区。如果希望对一个信号解除阻塞,然后pause以等待以前被阻塞的信号发生,则又将如何呢?假定信号是SIGINT,实现这一点的一种不正确的方法是:

bubuko.com,布布扣
    sigset_t newmask,oldmask;
    
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGINT);
    
    // block SIGINT and save current signal mask
    if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
        printf("SIG_BLOCK error!\n");
        
    // critical region of code
    if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
        printf("SIG_SETMASK error!\n");                    // ---------------- [1] -------------
        
    // window is open
    pause();        // wait for signal to occur            // ---------------- [2]--------------
    
    // continue to process
bubuko.com,布布扣

如果在信号阻塞时将其发送给进程,那么该信号的传递就被推迟直到对它解除了阻塞。对应用程序而言,该信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号(在[1]和[2]之间发生了SIGINT信号,则跳到信号处理函数中进行处理,处理后返回执行pause函数,pause因为等待SIGINT信号而导致阻塞,所以需要原子操作),所以从这种意义上说,在此时间窗口中发生的信号丢失了,这样就使得pause永远阻塞。

为了纠正此问题,需要一个原子操作中先恢复信号屏蔽字,然后使进程休眠。这种功能是由sigsuspend函数提供的:

bubuko.com,布布扣
int sigsuspend(const sigset_t *sigmask);
bubuko.com,布布扣

将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。

下面程序显示了保护临界区,使其不被特定信号中断的正确方法:

bubuko.com,布布扣
    static void sig_init(int signo);

    static void pr_mask(const char *str)
    {    
        sigset_t sigset;
        int errno_save;

        errno_save = errno;
        if(sigprocmask(0,NULL,&sigset) < 0) {
            printf("sigprocmask error!\n");
            exit(1);
        }

        printf("%s\n",str);

        if(sigismember(&sigset,SIGINT))        printf("SIGINT\n");
        if(sigismember(&sigset,SIGQUIT))    printf("SIGQUIT\n");
        if(sigismember(&sigset,SIGUSR1))    printf("SIGUSR1\n");
        if(sigismember(&sigset,SIGALRM))    printf("SIGALRM!\n");

        printf("\n");
        errno = errno_save;
    }

    int main()
    {
        sigset_t newmask,oldmask,waitmask;

        pr_mask("program start :");

        if(signal(SIGINT,sig_init) == SIG_ERR) {
            printf("signal(SIGINT) error!\n");
            exit(1);
        }
        sigemptyset(&waitmask);
        sigaddset(&waitmask,SIGUSR1);

        sigemptyset(&newmask);
        sigaddset(&newmask,SIGINT);

        // block SIGINT and save current signal mask
        if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) {
            printf("SIG_BLOCK error!\n");
            exit(1);
        }

        // critical region of code
        pr_mask("in critical region :");

        // pause,allowing all signals except SIGUSR1
        if(sigsuspend(&waitmask) != -1) {
            printf("sigsuspend error!\n");
            exit(1);
        }

        pr_mask("after return from sigsuspend:");

        // reset signal mask which unblocks SIGINT
        if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) {
            printf("SIG_SETMASK error!\n");
            exit(1);
        }

        pr_mask("program exit:");

        exit(0);
    }

    static void sig_init(int signo)
    {
        pr_mask("in sig_init:");
    }
bubuko.com,布布扣

输出结果为:

bubuko.com,布布扣
    :~/program/c$ ./test
    program start :

    in critical region :
    SIGINT

    ^C                                    // 键入中断字符
    in sig_init:
    SIGINT
    SIGUSR1

    after return from sigsuspend:
    SIGINT

    program exit:

    :~/program/c$
bubuko.com,布布扣

 

可以用信号实现父、子进程之间的同步:

bubuko.com,布布扣
    static void charactatime(char *str)
    {
        char *ptr;
        int c;

        setbuf(stdout,NULL);
        for(ptr = str;(c = *ptr++) != 0;)
            putc(c,stdout);
    }

    static volatile sig_atomic_t sigflag;
    static sigset_t newmask,oldmask,zeromask;

    static void sig_usr(int signo)
    {
        sigflag = 1;
    }

    void TELL_WAIT()
    {
        if(signal(SIGUSR1,sig_usr) == SIG_ERR) {
            printf("signal(SIGUSR1) error!\n");
            exit(1);
        }
        if(signal(SIGUSR2,sig_usr) == SIG_ERR) {
            printf("signal(SIGUSR2) error!\n");
            exit(1);
        }
        sigemptyset(&zeromask);
        sigemptyset(&newmask);
        sigaddset(&newmask,SIGUSR1);
        sigaddset(&newmask,SIGUSR2);

        // block SIGUSR1 and SIGUSR2,and save current signal mask
        if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) {
            printf("SIG_BLOCK error!\n");
            exit(1);
        }
    }

    void TELL_PARENT(pid_t pid)
    {
        kill(pid,SIGUSR2);
    }

    void WAIT_PARENT()
    {
        while(sigflag == 0)
            sigsuspend(&zeromask);

        sigflag = 0;

        // reset signal mask to original value
        if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) {
            printf("SIG_SETMASK error!\n");
            exit(1);
        }
    }

    void TELL_CHILD(pid_t pid)
    {
        kill(pid,SIGUSR1);
    }

    void WAIT_CHILD()
    {
        while(sigflag == 0)
            sigsuspend(&zeromask);

        sigflag = 0;

        // reset signal mask to original value.
        if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) {
            printf("SIG_SETMASK error!\n");
            exit(1);
        }
    }

    int main()
    {
        pid_t pid;

        TELL_WAIT();

        if((pid = fork()) < 0) {
            printf("fork error!\n");
            exit(1);
        } else if(pid == 0) {
            WAIT_PARENT();
            charactatime("output from child!\n");
        } else {
            charactatime("output from parent!\n");
            TELL_CHILD(pid);
        }

        exit(0);
    }
bubuko.com,布布扣

如果在等待信号发生时希望去休眠,则适用sigsuspend函数是非常适当的,但是如果在等待信号期间希望调用其他系统函数,那么将会怎样呢?不幸的是,在单线程环境下对此问题没有妥善的解决方法。如果可以使用多线程,则可专门安排一个线程处理信号。

如果不使用线程,那么我们能尽力做到最好的是,当信号发生时,在信号捕捉程序中对一个全局变量置1.

 

16、sleep函数

bubuko.com,布布扣
unsigned int sleep(unsigned int seconds);
bubuko.com,布布扣

此函数使调用进程被挂起,直到满足以下条件之一:

a、已经过了seconds所指定的墙上的时钟时间;

b、调用进程捕捉到一个信号并从信号处理程序返回;

 

在第一种情形中,返回值是0.当由于捕捉到某个信号sleep提早返回时(第二种情形),返回值是未睡够的秒数。

10.信号,布布扣,bubuko.com

10.信号

标签:style   blog   class   code   java   tar   

原文地址:http://www.cnblogs.com/sheshiji/p/3704179.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!