当信号处理碰到IO复用模型,有许多需要考虑的问题。
比如我们的程序,需要CTRL+C的时候,优雅的退出进程。我们在信号处理函数设置一个标记,然后在主循环里面,检测到此标记的时候退出循环。
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/select.h> static int g_sig; void handler(int sig) { g_sig = sig; } void main() { int ret; unsigned long i = 0; signal(SIGINT, handler); while (1) { if (g_sig) return; // do something while (i < 10000L * 100000) i++; select(0, NULL, NULL, NULL, NULL); } }
这个程序有个bug。当信号发生在do something时,虽然g_sig标记了,但是select将阻塞,直到下一次事件才会处理。
直观体验,就是程序运行立即按CTRL+C,程序不会退出,等something运行完后,会卡在select。此时再按CTRL+C才会退出。
[yuan@fedora test]$ ./a.out ^C ^C[yuan@fedora test]$
解决办法,就是signal mask,配合特定的io multiplexing接口,如pselect。
根据手册,一个信号可以是blocked,此时收到一个信号,不会立即执行,而是处理挂起状态。直到后续信号被重新设置为unblocked。
A signal may be blocked, which means that it will not be delivered until it is later unblocked. Between the time when it is generated and when it is delivered a signal is said to be pending.
int pselect(int nfds, fd_set *_Nullable restrict readfds,
fd_set *_Nullable restrict writefds,
fd_set *_Nullable restrict exceptfds,
const struct timespec *_Nullable restrict timeout,
const sigset_t *_Nullable restrict sigmask);
if sigmask is not NULL, then pselect() first replaces the current signal mask by the one pointed to by sigmask, then does the "select" function, and then restores the original signal mask.
pselect会将signal mask设置为sigmask参数,然后select,然后再恢复之前的signal mask,这整个是个原子操作。
因此修改后的代码如下:
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/select.h> static int g_sig; static sigset_t oldmask; void handler(int sig) { g_sig = sig; dprintf(STDOUT_FILENO, "\nhandle signal %d\n", sig); } void block_intr() { sigset_t sigmask; sigemptyset(&sigmask); sigaddset(&sigmask, SIGINT); sigprocmask(SIG_BLOCK, &sigmask, &oldmask); } void main() { int ret; unsigned long i = 0; signal(SIGINT, handler); block_intr(); while (1) { if (g_sig) break; // do something while (i < 10000L * 100000) i++; printf("select\n"); ret = pselect(0, NULL, NULL, NULL, NULL, &oldmask); if (ret < 0) fprintf(stderr, "select failed: %m\n"); } }
先block SIGINT信号。然后在pselect里面,再检查SIGINT信号。这就避免了信号发生在select之前,select一直卡住的问题。