Linux Signal 示例
訊號是系統響應某些條件而產生的一個事件,接收到該信的程序做出相應的處理。通常信是由錯誤產生的,如段錯誤(SIGSEGV
)。 但信還可以作為程序間通訊的一種方式,由一個程序傳送給另一個程序。
訊號定義在signal.h
檔案中,以SIG
作為開頭,可用kill -l
命令檢視,詳細資訊參見ofollow,noindex">man 7 signal
。
訊號處理
訊號可以通過signal
和sigaction
函式來註冊處理,signal
函式是struct sigaction
中sa_handler
的一種便捷實現。
signal
函式
原型:
void (*signal(int sig, void (*func)(int)))(int);
其中sig
是需要捕獲的signal number
, 後一個是捕獲到訊號後的處理函式指標,所以處理函式的原型必須是void func(int)
,簡單的程式碼示例如下:
#include <stdio.h> #include <string.h> #include <signal.h> static void handler(int sig) { printf("Recieved signal: %d\n", sig); } int main(int argc, char *argv[]) { signal(SIGINT, handler); printf("Caught SIGINT, input 'quit' to exit...\n"); // wait signal caught char buf[1024] = {0}; while (1) { printf("Please input: "); scanf("%s", buf); if (strcmp(buf, "quit") == 0) { break; } } printf("Exit...\n"); return 0; }
另外api
中也提供了下面 2 個特殊的handler
:
-
SIG_IGN
忽略此訊號
-
SIG_DFL
恢復此訊號的預設行為
sigaction
函式
原型:
int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
其中sig
為signal number
,act
指定訊號的處理行為,oact
如果不為NULL
則返回訊號之前的處理行為。
struct sigaction
的主要成員如下:
型別 | 名稱 | 描述 |
---|---|---|
void(*) (int) | sa_handler |
處理函式指標,同 signal 函式中的func
引數 |
sigset_t | sa_mask | 訊號遮蔽字,是指當前被阻塞的一組訊號,不能被當前程序收到 |
int | sa_flags | 處理行為修改器,指明哪種處理函式生效,詳見下文 |
void(*) (int, siginfo_t *, void *) | sa_sigaction | 處理函式指標,僅 sa_flags == SA_SIGINFO 時有效 |
其中sa_flags
主要可以設定為以下值:
-
SA_NOCLDSTOP
子程序停止時不產生
SIGCHLD
訊號 -
SA_RESETHAND
將訊號的處理函式在處理函式的入口重置為
SIG_DFL
-
SA_RESTART
重啟可中斷的函式而不是給出
EINTR
錯誤 -
SA_SIGINFO
使用
sa_sigaction
做為訊號的處理函式 -
SA_NODEFER
捕獲到訊號時不將它新增到訊號遮蔽字中
簡單的程式碼示例如下:
#include <stdio.h> #include <string.h> #include <signal.h> #define SIG SIGINT static void sig_handler(int sig, siginfo_t *si, void *data) { printf("Caught signal: %d\n", sig); printf("Sender pid: %d\n", si->si_pid); printf("Sender uid: %d\n", si->si_uid); } static int sig_caught(int sig) { printf("Start caught signal: %d\n", sig); struct sigaction sa; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = sig_handler; sigemptyset(&sa.sa_mask); int ret = sigaction(sig, &sa, NULL); if (ret == -1) { printf("Failed to caught signal: %d\n", sig); return -1; } return 0; } int main(int argc, char *argv[]) { if (sig_caught(SIG) == -1) { return -1; } printf("Caught signal(%d), input 'quit' to exit...\n", SIG); char buf[1024] = {0}; while(1) { printf("Please input: "); scanf("%s", buf); if (strcmp(buf, "quit") == 0) { break; } } printf("Exit...\n"); return 0; }
訊號遮蔽字
考慮一下這種情況:在signal()/sigaction()
返回之前程序就已經收到了需要處理的訊號,此時程序會以預設行為來處理,這顯然不符合我們的期望。 這時就需要用到訊號遮蔽字了,在程序啟動時就將需要處理的訊號加入的遮蔽字中,等signal()/sigaction()
返回後再解除遮蔽,解除遮蔽後至少會將收到的待處理訊號傳送一個給程序。
遮蔽字用到一下函式:
int sigemptyset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
sigprocmask
中set
為需要設定的遮蔽字集,oset
為之前的遮蔽字集,how
控制著set
如何生效,可設定為以下值:
-
SIG_BLOCK
該程序的遮蔽字集將為當期遮蔽字集與
set
的並集,set
中包含了需要遮蔽的訊號集 -
SIG_UNBLOCK
該程序的遮蔽字集將為當期遮蔽字集與
set
的補集的交集,set
中包含了需要解除遮蔽的訊號集 -
SIG_SETMASK
該程序的遮蔽字集將設定為
set
的值
簡單的設定流程如下:
int sig_block(int sig, int how) { sigset_t mask; sigemptyset(&mask) sigaddset(&mask, sig); sigprocmask(how, &mask, NULL); }
訊號傳送
訊號可以通過kill
函式傳送給指定程序,也可以通過raise
或者alarm
函式傳送給當前執行的執行緒或程序,下面來分別說說這幾個函式。
kill
原型:
int kill(pid_t pid, int sig);
kill
函式向指定程序傳送指定的訊號,如果訊號為 0 將執行錯誤檢查,訊號並不會傳送,可以用來檢查pid
的有效性。
pid
大於 0 時訊號將傳送給此程序,pid
小於等於 0 時,如下:
-
等於 0
訊號將傳送給傳送者所在組裡的所有程序
-
等於 -1
訊號將傳送給所有程序
-
小於 -1
訊號將傳送給程序組為
pid
絕對值的所有組內程序
alarm
原型:
unsigned alarm(unsigned seconds);
alarm
函式將在指定的seconds
之後傳送一個SIGALRM
訊號,如果seconds
為 0, 則取消之前的定時器請求。如果不為 0 則取消之前的請求,重新設定為seconds
。 如果在等待結束之前有其他的事件產生,那定時器請求也將被取消。
簡單的程式碼示例如下:
#include <stdio.h> #include <unistd.h> #include <signal.h> static void handler(int sig) { printf("alarm arrived: %d\n", sig); } int main(int argc, char *argv[]) { signal(SIGALRM, handler); alarm(2); sleep(2); printf("alarm 5s over\n"); alarm(10); sleep(1); unsigned int remaining = alarm(3); printf("alarm 10s remain: %u, reset to 3\n", remaining); sleep(3); printf("alarm 3s over\n"); alarm(20); sleep(3); remaining = alarm(0); printf("cancel alarm 20s, remian: %u, exit...\n", remaining); }
raise
原型:
int raise(int sig);
raise
函式將給當前執行的執行緒或程序傳送訊號,如果訊號處理函式已經被呼叫,raise
函式將等待訊號處理函式呼叫結束才返回。
結語
訊號處理函式是會被重複呼叫的,所以必要儲存其是可重入的,注意處理邏輯。
另外本文中的程式碼都在signal
中,這個repo
也有其它的示例,有興趣的可以看看。
附錄
訊號表
/* ISO C99 signals.*/ #define SIGINT2/* Interactive attention signal.*/ #define SIGILL4/* Illegal instruction.*/ #define SIGABRT6/* Abnormal termination.*/ #define SIGFPE8/* Erroneous arithmetic operation.*/ #define SIGSEGV11/* Invalid access to storage.*/ #define SIGTERM15/* Termination request.*/ /* Historical signals specified by POSIX. */ #define SIGHUP1/* Hangup.*/ #define SIGQUIT3/* Quit.*/ #define SIGTRAP5/* Trace/breakpoint trap.*/ #define SIGKILL9/* Killed.*/ #define SIGBUS10/* Bus error.*/ #define SIGSYS12/* Bad system call.*/ #define SIGPIPE13/* Broken pipe.*/ #define SIGALRM14/* Alarm clock.*/ /* New(er) POSIX signals (1003.1-2008, 1003.1-2013).*/ #define SIGURG16/* Urgent data is available at a socket.*/ #define SIGSTOP17/* Stop, unblockable.*/ #define SIGTSTP18/* Keyboard stop.*/ #define SIGCONT19/* Continue.*/ #define SIGCHLD20/* Child terminated or stopped.*/ #define SIGTTIN21/* Background read from control terminal.*/ #define SIGTTOU22/* Background write to control terminal.*/ #define SIGPOLL23/* Pollable event occurred (System V).*/ #define SIGXCPU24/* CPU time limit exceeded.*/ #define SIGXFSZ25/* File size limit exceeded.*/ #define SIGVTALRM26/* Virtual timer expired.*/ #define SIGPROF27/* Profiling timer expired.*/ #define SIGUSR130/* User-defined signal 1.*/ #define SIGUSR231/* User-defined signal 2.*/ /* Nonstandard signals found in all modern POSIX systems (including both BSD and Linux).*/ #define SIGWINCH28/* Window size change (4.3 BSD, Sun).*/ /* Archaic names for compatibility.*/ #define SIGIOSIGPOLL /* I/O now possible (4.2 BSD).*/ #define SIGIOTSIGABRT /* IOT instruction, abort() on a PDP-11.*/ #define SIGCLDSIGCHLD /* Old System V name */ /* Not all systems support real-time signals.bits/signum.h indicates that they are supported by overriding __SIGRTMAX to a value greater than __SIGRTMIN.These constants give the kernel-level hard limits, but some real-time signals may be used internally by glibc.Do not use these constants in application code; use SIGRTMIN and SIGRTMAX (defined in signal.h) instead.*/ #define __SIGRTMIN32 #define __SIGRTMAX__SIGRTMIN /* Biggest signal number + 1 (including real-time signals).*/ #define _NSIG(__SIGRTMAX + 1)