最近在嘗試使用 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
是線程安全的,可是在中斷處理函數中卻並非這樣。其餘位置的話,隨意。