最近在尝试使用epoll写一个类似libevent的库。那么,如何像libevent一样在事件循环中加入对信号事件的观察呢?查了资料,一个可行的方法是使用sigprocmask()及其相关函数。但请注意,此方法存在缺陷,请谨慎使用。个人继续研究后,暂时不打算用这种方法来实现信号事件,而是换一种方法。参考《UNIX 环境高级编程》sigprocmask,sigpending和sigsuspend函数errno多线程安全Linux多线程应用编写安全信号处理函数UNIX系统主要信号下面只列出主要信号:名称描述FreeBSDLinuxmacOSSolaris默认动作SIGABRTcallsabort()YYYYterminated+YYYY由coreSIGALRMalarm()SIGBUS硬件故障终止YYYY终止+coreSIGCHLD子进程状态更改YYYY忽略SIGHUP连接断开YYYY终止SIGINTCtrl+CYYYY终止SIGKILL终止;uncatchableYYYY终止SIGPIPE写入关闭的管道YYYY终止SIGQUITCtrl+\YYYY终止+coreSIGSEGV段错误YYYY终止+coreSIGSTOP停止YYYY暂停进程SIGTERMkill(1)YYYY终止SIGUSR1用户定义1YYYY终止SIGUSR2用户定义2YYYY终止SIGPOLL事件循环发生-可训练的设备。Y.Y终止SIGPWR主电源故障,电池电量不足。Y.Y终止或忽略C中如果要发送信号,可以使用kill()和raise()。后者是向当前进程发送信号,而前者可以向任意进程发送信号。kill()的pid参数可以有以下几种可能的值:pid>0:发送给指定进程pid==0:发送给与当前进程属于同一进程组的所有进程,但需要权限允许pid<0:发送给进程组ID等于(0-pid)的所有进程,但需要权限允许pid==-1:发送给所有进程,但需要权限允许信号集操作#includeintsigemptyset(sigset_t*set);intsigfillset(sigset_t*set);intsigaddset(sigset_t*set,intsignum);intsigdelset(sigset_t*set,intsignum);intsigismember(constsigset_t*set,intsignum);上面函数的语义很明确,就是在一个集合中配置多个信号。除了实际返回BOOL的sigismenber()之外,所有其他函数都返回0表示成功,-1表示失败。sigprocmask和sigpending#includeintsigprocmask(inthow,constsigset_t*set,sigset_t*oldset);intsigpending(sigset_t*set);sigprocmask()返回状态值0或-1,而sigpending()返回一个BOOL值,其中how可以有以下值:SIG_BLOCK:屏蔽信号(注意,不是“忽略”信号)SIG_UNBLOCK:取消屏蔽SIG_SETMASK:设置整个表配置进去。这适用于sigprocmask()恢复阶段。后续会解释“屏蔽”信号的含义。sigprocmask()的作用主要是屏蔽指定信号。这个“屏蔽”的含义需要弄清楚。首先我们粗略统计一下信号在内核中的处理流程(不是确切的流程,只是为了方便说明):内核等待信号中断信号产生,触发内核中断,内核保存信号,或设置信号标志。处理信号的配置。如果用户空间没有特殊配置,按照默认行为处理完成后,清除信号标志并返回1,继续等待sigprocmask()完成的“屏蔽”。其实上面的信号处理流程卡在了3和4之间,内核可以设置信号标志,但是到不了判断处理的步骤。也就是说,即使进程调用了signal()函数并设置了SIG_IGN标志,如果指定的信号被sigprocmask()阻塞了,内核也不会判断该信号是否应该被忽略,只是将信号标志放在那里,直到调用sigprocmask()执行SIG_UNBLOCK,内核才能继续执行第4步。程序文本处理信号这里所说的“文本”是指: 不在signal()或signal()指定的handler中处理信号事件sigaction(),而是在正常的程序流程中捕获信号,并处理信号。这样做有很多好处:中断处理程序有很多限制,只能调用某些系统调用,否则可能会导致上下文异常。但是文中没有这个问题,中断处理函数和文中可以看成是两个不同的线程,两者之间的同步在文中处理起来比较麻烦,可以在文中实现类似EV_SIGNAL的功能libevent-这就是作者正在研究的内容。基本的软件流程如下:使用signal()或sigaction()设置要捕获的信号为SIG_IGN使用sigprocmask()屏蔽要捕获的信号,注意保存屏蔽前设置的信号(set参数)并进行相应的操作(如epoll()),如果发现errno为EINTR,则可以通过sigpending()获取屏蔽信号集,判断要捕获的信号是否在信号集中。使用sigprocmask()执行一次SIG_UNBLOCK操作让内核清除信号设置标志并返回2.重新屏蔽信号缺陷。不过这个过程有一个bug,就是4到6之间可能会产生信号,如果是这种情况,是不会被捕获的——这个还是需要考虑怎么处理。sigactionfunction这里,顺便记住sigaction(),POSIX建议不要再使用signal()了。在简单的情况下,您只需要在structsigcation中使用sa_handler和sa_mask来代替signal()调用。#includestructsigaction{void(*sa_handler)(int);void(*sa_sigaction)(int,siginfo_t*,void*);sigset_tsa_mask;intsa_flags;void(*sa_restorer)(void);};intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);上面提到的errno的线程安全问题“如果发现errno是EINTR...”。可能有同学会问:“errno是全局变量,这样安全吗?”其实errno是线程安全的……呃,这个优点,其实只有我自己知道……看了errno的原理,觉得真的很棒!但是,在使用errno时只有一点需要注意,那就是虽然errno在程序文本中是线程安全的,但在中断处理程序中却并非如此。任何其他位置都是可选的。这里的参考是这个和这个。