Linux入门:信号(三)——捕捉信号

来源:互联网 发布:php array indexof 编辑:程序博客网 时间:2024/04/28 18:13

      如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。


一、内核如何实现信号的捕捉,例如SIGQUIT函数:

1.用户注册了SIGQUIT信号的处理函数sighandler;

2.当前正在执行main函数,这时发生了中断或异常切换到内核态;

3.在中断处理完成后要返回到用户的main函数之前检查到有SIGQUIT信号递达;

4.内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler函数和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程;

5.sighandler函数返回后自动执行系统调用sigreturn再次进入内核态;

6.如果没有新的信号递达则返回用户态恢复main函数的上下文继续执行。


二、sigaction

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功返回0,出错则返回-1.


act 和oact指针指向struct sigaction结构体 :

struct sigaction { 
    void (*sa_handler)(int); 
    void (sa_sigaction)(int, siginfo_t , void *); 
    sigset_t sa_mask;  // 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)
    int sa_flags;   //默认设置为零
    void (*sa_restorer)(void); 
}; 
sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用) 
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL 表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册 了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信 号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main 函数调用,而是被系统所调用。

 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函 数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产 生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则 用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

三、pause

int pause(void);  //pause函数使调用进程挂起直到有信号递达(只有出错返回值)

下面用alarm和pause函数实现一个mysleep函数:

1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数 sig_alrm。 
2. 调用alarm(nsecs)设定闹钟。 
3. 调用pause等待,内核切换到别的进程运行。 
4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。 
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。 
6. 切换到用户态执⾏行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽, 从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用 sigreturn再次进入 内核,再返回用户态继续执行进程的主控制流程(main函数调用 的mysleep函数)。 
7. pause函数返回-1,然后调⽤用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理 动作。

程序代码:

#include <stdio.h>#include <signal.h>void handler(){}int mysleep(int time){struct sigaction act, oact;act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGALRM, &act, &oact);//调用alarm设定闹钟int ret = alarm(time);//调用pause等待,内核运行别的进程pause();sigaction(SIGALRM, &oact, NULL);//取消闹钟ret = alarm(0);//ret>0时表示休眠失败return ret;}int main(){while(1){mysleep(2);printf("I am waking up!\n");}    return 0;}
运行结果:



现在重新审视一下mysleep程序,虽然alarm(time)紧接着的下一行就是pause,但是无法保证pause函数一定会在alarm被调用time秒后执行。由于异步事件在任何时候都有可能发生,所以如果我们写程序时不考虑周密,就可能因为时序问题而发生错误。这叫做竞态条件。

因此在上述程序中,可以采用sigsuspend函数将“解除信号屏蔽”和“挂起等待信号”这两步合为一个原子操作。四个suspend包含了pause的等待功能同时解决了竞态条件问题。

int sigsuspend(const sigset_t *sigmask);   //执行了一个信号处理函数后才返回,返回值为-1。

调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时 解除对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原 来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

改进后的程序代码:

#include <stdio.h>#include <signal.h>void handler(){}int mysleep(int time){struct sigaction act, oact;sigset_t newmask, oldmask, susmask;act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGALRM, &act, &oact);sigemptyset(&newmask);sigprocmask(SIG_BLOCK, &newmask, &oldmask);//调用alarm设定闹钟int ret = alarm(time);susmask = oldmask;sigdelset(&susmask, SIGALRM);//调用pause等待,内核运行别的进程//pause();sigsuspend(&susmask);ret = alarm(0);sigaction(SIGALRM, &oact, NULL);sigprocmask(SIG_SETMASK, &oldmask, NULL);//取消闹钟//ret>0时表示休眠失败return ret;}int main(){while(1){mysleep(2);printf("I am waking up!\n");}    return 0;}