Linux -- 信号编程

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

1. 在信号处理程序中,我们要保证调用”异步信号安全”的函数,即可重入的函数

不可重入的函数大多(a)已知它们使用静态数据结构。(b)它们调用malloc或free(c)它们是标准I/O函数

2. 由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。因此,所有信号处理程序应当在函数的起始保存errno,结尾恢复errno

3. 每个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞传递送到该进程的信号集

信号集signal set

int sigemptyset(sigset_t *set); //初始化由set指向的信号集,清除其中所有信号

int sigfillset(sigset_t *set);       //初始化由set指向的信号集,使其包括所有信号

所有应用程序在使用信号集前,要对该信号集调用sigemptyset或sigfillset一次。

信号集初始化之后,可在该信号中增删特定的信号。

int sigaddset(sigset_t *set,int signo)

int sigdelset(sigset_t *set,int signo)

进程的信号屏蔽字

int sigprocmask(int how,const sigset_t *restrict set, const sigset_t *restrict oset)

Oset若是非空指针,那么进程的当前信号屏蔽字通过oset返回

How的三种取值决定了如何修改当前信号屏蔽字:

SIG_BLOCK : 向当前信号屏蔽字中添加参数set包含的信号

SIG_UNBLOCK : 把当前信号屏蔽字中参数set包含的信号删去

SIG_SETMASK : 把参数set设为进程的信号屏蔽字。

请注意,sigprocmask仅为单线程进程定义的。处理多线程进程中信号的屏蔽使用另一个函数

执行信号的处理程序称为信号递达,信号从产生到递达之间的状态称为信号未决。被阻塞的信号将保持在未决状态,直到进程解决对此信号的阻塞。

int sigpending(sigset_t *set)

Set返回当前的未决信号

信号处理的范式

static int pipefd[2];

int signal_module_init()

{
         struct sigaction act;

         //信号处理程序指定为sig_handler
         act.sa_handler = sig_handler;

         //在进入信号处理程序前,把act.sa_mask信号集加到进程的信号屏蔽字中。调用sigfillset把所有信号加入这个信号集。这表示当进入信号处理程序后,阻塞一切信号
         sigfillset(&act.sa_mask)

         if(0 > sigaction(SIGINT,&act,0) ||
            0 > sigaction(SIGCHLD,&act,0) ||
            ...... ) {
                   write_log("failed to init signal:sigaction()");
                   return -1;
         }
         return signal_pipe_init();
}

 

static int signal_pipe_init()
{
         if( 0 < pipe(pipefd,O_CLOEXEC|O_NONBLOCK) ){
                   write_log("failed to init pipe");
                   return -1;
         }
         return 0;
}

 

static void sig_handler(int signo)
{
         //定义一个数组,将你注册的每个信号的signo映射成一个唯一的字符
         static const char sig_chars[NSIG+1] = {
                   [SIGINT] = 'I',
                   [SIGCHLD] = 'C',
                   .....
         };

         char s;
         int saved_errno;

         //保存当前的errno。每个线程仅有一个errno变量,不应让信号处理程序中的errno影响正常流程中的errno。因此我们需要在信号处理程序的起始存储errno,在末尾恢复errno
         saved_errno = errno;
         s = sig_chars[signo];
         write(pipefd[1],&s,sizeof(s));
         errno = saved_errno;

}

//然后在Reactor中监听pipefd[0]. 其回调函数如下:
void got_signal(ev)
{
         int res,ret;
         char c;
         int fd = ev->fd;
         for(;;){
                   //fd是非阻塞的
                   do {
                            res = read(fd,&c,1);
                   } while(res == -1 && errno == EINTR);

                   //pipe中没有可读数据
                   if(res <= 0){
                            break;
                   }

                   switch(c){
                            case 'I':
                                     dosomething1();
                                     break;
                           case 'C':
                                    dosomething2();
                                     break;
                            ......
                   }
         }
         return;
}
原文地址:https://www.cnblogs.com/tuowang/p/9398987.html