最近在尝试使用 epoll
写一个相似 libevent 的库。那么,如何像 libevent 同样,在 event loop 里加入对信号事件的观测呢?
我查了一下资料,一个可行的方法,就是使用 sigprocmask()
及其相关功能来实现啦。html
可是请注意,这个方法是存在缺陷的,请看官留心。
我的在继续研究以后,暂时是不打算使用此种方法来实现信号事件,而改用另外一个方法。linux
《UNIX 环境高级编程》
sigprocmask , sigpending 和 sigsuspend函数
errno多线程安全
Linux 多线程应用中编写安全的信号处理函数git
如下就只列出主要的信号了:github
名称 | 说明 | FreeBSD | Linux | macOS | Solaris | 默认动做 |
---|---|---|---|---|---|---|
SIGABRT |
调用了abort() |
Y | Y | Y | Y | 终止 + core |
SIGALRM |
alarm() 产生的 |
Y | Y | Y | Y | 终止 |
SIGBUS |
硬件故障 | Y | Y | Y | Y | 终止 + core |
SIGCHLD |
子进程状态改变 | Y | Y | Y | Y | 忽略 |
SIGHUP |
链接断开 | Y | Y | Y | Y | 终止 |
SIGINT |
Ctrl + C | Y | Y | Y | Y | 终止 |
SIGKILL |
终止;不可捕获 | Y | Y | Y | Y | 终止 |
SIGPIPE |
向关闭的管道写 | Y | Y | Y | Y | 终止 |
SIGQUIT |
Ctrl + \ | Y | Y | Y | Y | 终止 + core |
SIGSEGV |
段错误 | Y | Y | Y | Y | 终止 + core |
SIGSTOP |
中止 | Y | Y | Y | Y | 暂停进程 |
SIGTERM |
kill(1) |
Y | Y | Y | Y | 终止 |
SIGUSR1 |
用户自定义1 | Y | Y | Y | Y | 终止 |
SIGUSR2 |
用户自定义2 | Y | Y | Y | Y | 终止 |
SIGPOLL |
可轮训的设备发生事件 | . | Y | . | Y | 终止 |
SIGPWR |
主电源失效,电池电量不足 | . | Y | . | Y | 终止或忽略 |
若是要在 C 里面发送一个信号的话,那么能够用 kill()
和 raise()
。其中后者是想当前进程发信号,而前者能够向任意进程发信号。kill()
的 pid
参数能够有如下可能值:编程
pid > 0
:发给指定进程pid == 0
:发给与当前进程属于同一进程组的全部进程,但须要权限容许pid < 0
:发给进程组 ID 等于 (0 - pid)
的全部进程,但须要权限容许pid == -1
:发给全部进程,但须要权限容许#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum);
上面的几个函数语义都很清楚了,就是在一个集合里面配置多个信号。
除了 sigismenber()
实际上返回的是 BOOL
类型以外,其余的函数均返回 0 表明成功,-1 表明失败。segmentfault
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigpending(sigset_t *set);
sigprocmask()
返回的是 0 或者 -1 的状态值,而 sigpending()
返回 BOOL
值
其中 how
能够有如下值:安全
SIG_BLOCK
:屏蔽信号(注意,不是“忽略”信号)SIG_UNBLOCK
:解屏蔽SIG_SETMASK
:将整个表配置设置进去。这适用于 sigprocmask()
恢复阶段。后续有说明sigprocmask()
的做用,主要就是屏蔽
指定的信号。这个 “屏蔽” 的含义须要说明清楚。
首先咱们大体数一下信号在内核里的处理流程吧(不是准确的流程,只是便于说明):多线程
sigprocmask()
所作的 “屏蔽”,其实就是将上述的信号处理流程,卡在了 3 和 4 之间,让内核可以将信号标志设置好,可是却到不了判断并处理的那一步。
换句话说,即使进程调用 signal()
函数,设置了 SIG_IGN
标志,但若是指定的信号被 sigprocmask()
屏蔽了的话,内核也不会去判断是否该忽略这个信号,而只是把信号标志卡在那儿,直到调用sigprocmask()
执行SIG_UNBLOCK
为止,才能让内核继续走到第 4 步。函数
这里所说的 “正文”,指的是:
不在 signal()
或 sigaction()
中指定的 handler 中处理信号事件,而是在普通的程序流程可以中捕捉信号,而且处理信号。oop
这么作有不少好处:
libevent
中 EV_SIGNAL
功能——而这也是笔者正在研究的。基本软件流程以下:
signal()
或 sigaction()
将须要捕获的信号设置为 SIG_IGN
sigprocmask()
屏蔽须要捕获的信号,同时注意将屏蔽以前的信号集保存下来(oset
参数)epoll()
)errno
为 EINTR
,那么就能够用 sigpending()
获取被屏蔽的信号集,判断须要捕获的信号是否在信号集中sigprocmask()
执行一次 SIG_UNBLOCK
操做,让内核清除信号集标志不过这个流程有一个 bug,就是信号有可能在 4 和 6 之间产生,这样的话,就捕获不到了——这还须要想一想怎么处理。
这里顺便记一下 sigaction()
吧,POSIX 是建议不要再使用 signal()
了。
简单状况下,只须要使用 struct sigcation
里的 sa_handler
和 sa_mask
就能够替代 signal()
调用了。
#include <signal.h> struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
前文说起 “若是发现 errno 为 EINTR ...”。有同窗可能会问了:“errno
是一个全局变量啊,这安全不?”
实际上,errno
是线程安全
的……呃,这个优势,其实笔者本身也是才知道……看了一下 errno
的原理,以为实在是很厉害啊!
可是,使用 errno
只有一点要注意,就是虽然在程序正文中,errno
是线程安全的,可是在中断处理函数中却并非这样。其余位置的话,随意。