记录下我对这两个有关信号程序的理解,signal()和sigaction()主要用来设置针对某一信号的处理程序,这两个系统调用会首先会在进程自己的任务数据结构中设置sigaction[]结构数组,该元素结构如下图所示:
struct sigaction { void (*sa_handler)(int); //信号处理的句柄 sigset_t sa_mask; //信号屏蔽码,可以用来指定阻塞哪几位信号 int sa_flags; //信号选项 void (*sa_restorer)(void);//信号恢复函数指针,这个的用处要结合do_signal这个函数来理解 };数组中的每一项代表该信号的相关设置。在内核退出一个系统调用和某些中断过程中会检测当前进程是否收到信号,若收到用户指定的信号,内核就会根据进程中sigaction[]中对应信号的结构项执行用户定义的信号处理程序。接下来首先看一下signal()的源码:
int sys_signal(int signum, long handler, long restorer) { struct sigaction tmp; if (signum<1 || signum>32 || signum==SIGKILL) return -1; tmp.sa_handler = (void (*)(int)) handler; tmp.sa_mask = 0; tmp.sa_flags = SA_ONESHOT | SA_NOMASK; tmp.sa_restorer = (void (*)(void)) restorer; handler = (long) current->sigaction[signum-1].sa_handler; current->sigaction[signum-1] = tmp; return handler; }
分析:因为信号集是一个32位的位图,所以首先要检查信号在不在范围内,并且不能是SIGKILL信号。接着利用一个tmp,将其处理函数设置为输入的处理函数,信号屏蔽码为0,也就是不屏蔽任何信号,信号选项标志位设定为使用一次后就要恢复成默认值,并且在信号处理过程中该信号也能被接受,之后利用tmp设置好current->sigaction[signum-1]即可。再来看下sigaction(),源码如下:
int sys_sigaction(int signum, const struct sigaction * action, struct sigaction * oldaction) { struct sigaction tmp; if (signum<1 || signum>32 || signum==SIGKILL) return -1; tmp = current->sigaction[signum-1]; get_new((char *) action, (char *) (signum-1+current->sigaction)); if (oldaction) save_old((char *) &tmp,(char *) oldaction); if (current->sigaction[signum-1].sa_flags & SA_NOMASK) current->sigaction[signum-1].sa_mask = 0; else current->sigaction[signum-1].sa_mask |= (1<<(signum-1)); return 0; }分析:首先将current->sigaction[signum-1]保存在tmp中,再在sigaction[]中设置新的操作,如果传递进来的oldaction不为空,就可以利用这个内存暂存tmp,之后再根据flag来确定是否在句柄中屏蔽本信号,如果设置了SA_NOMASK,则不需要屏蔽,否则要屏蔽本信号。再来理解下最后这个屏蔽的意思,假如设置了这个屏蔽,那在整个信号处理过程中,即使又来了一个同样的信号在位图中置位,也不会立刻再次调用信号处理函数,而是要等待当前处理函数结束先。另外,如果有多个信号过来,但位图只有一位,只能保存1个信号,其他的会丢失。
其实他们的区别还是很容易看出的,signal()函数的话每次调用之后就会恢复成默认处理函数,需要在信号处理程序中再调用一次signal()函数才能继续使用用户自己设置的处理函数,但在下次设置之前,来的信号就会使用默认操作导致丢失。而sigaction()设置后能一直保持用户设置的处理函数。