信號是系統響應某些條件而產生的一個事件,接收到該信的進程作出相應的處理。一般信是由錯誤產生的,如段錯誤(SIGSEGV
)。 但信還能夠做爲進程間通訊的一種方式,由一個進程發送給另外一個進程。html
信號定義在 signal.h
文件中,以 SIG
做爲開頭,可用 kill -l
命令查看,詳細信息參見 man 7 signal。linux
信號能夠經過 signal
和 sigaction
函數來註冊處理, signal
函數是 struct sigaction
中 sa_handler
的一種便捷實現。git
signal
函數原型:github
void (*signal(int sig, void (*func)(int)))(int);
其中 sig
是須要捕獲的 signal number
, 後一個是捕獲到信號後的處理函數指針,因此處理函數的原型必須是 void func(int)
,簡單的代碼示例以下:api
#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
:app
SIG_IGN
忽略此信號socket
SIG_DFL
恢復此信號的默認行爲函數
sigaction
函數原型:ui
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
主要能夠設置爲如下值:
子進程中止時不產生 SIGCHLD
信號
將信號的處理函數在處理函數的入口重置爲 SIG_DFL
重啓可中斷的函數而不是給出 EINTR
錯誤
使用 sa_sigaction
作爲信號的處理函數
捕獲到信號時不將它添加到信號屏蔽字中
簡單的代碼示例以下:
#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
如何生效,可設置爲如下值:
該進程的屏蔽字集將爲當期屏蔽字集與 set
的並集, set
中包含了須要屏蔽的信號集
該進程的屏蔽字集將爲當期屏蔽字集與 set
的補集的交集, set
中包含了須要解除屏蔽的信號集
該進程的屏蔽字集將設置爲 set
的值
簡單的設置流程以下:
int sig_block(int sig, int how) { sigset_t mask; sigemptyset(&mask) sigaddset(&mask, sig); sigprocmask(how, &mask, NULL); }
信號能夠經過 kill
函數發送給指定進程,也能夠經過 raise
或者 alarm
函數發送給當前執行的線程或進程,下面來分別說說這幾個函數。
原型:
int kill(pid_t pid, int sig);
kill
函數向指定進程發送指定的信號,若是信號爲 0 將執行錯誤檢查,信號並不會發送,能夠用來檢查 pid
的有效性。
pid
大於 0 時信號將發送給此進程, pid
小於等於 0 時,以下:
信號將發送給發送者所在組裏的全部進程
信號將發送給全部進程
信號將發送給進程組爲 pid
絕對值的全部組內進程
原型:
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); }
原型:
int raise(int sig);
raise
函數將給當前執行的線程或進程發送信號,若是信號處理函數已經被調用, raise
函數將等待信號處理函數調用結束才返回。
信號處理函數是會被重複調用的,因此必要保存其是可重入的,注意處理邏輯。
另外本文中的代碼都在 signal 中,這個 repo
也有其它的示例,有興趣的能夠看看。
/* ISO C99 signals. */ #define SIGINT 2 /* Interactive attention signal. */ #define SIGILL 4 /* Illegal instruction. */ #define SIGABRT 6 /* Abnormal termination. */ #define SIGFPE 8 /* Erroneous arithmetic operation. */ #define SIGSEGV 11 /* Invalid access to storage. */ #define SIGTERM 15 /* Termination request. */ /* Historical signals specified by POSIX. */ #define SIGHUP 1 /* Hangup. */ #define SIGQUIT 3 /* Quit. */ #define SIGTRAP 5 /* Trace/breakpoint trap. */ #define SIGKILL 9 /* Killed. */ #define SIGBUS 10 /* Bus error. */ #define SIGSYS 12 /* Bad system call. */ #define SIGPIPE 13 /* Broken pipe. */ #define SIGALRM 14 /* Alarm clock. */ /* New(er) POSIX signals (1003.1-2008, 1003.1-2013). */ #define SIGURG 16 /* Urgent data is available at a socket. */ #define SIGSTOP 17 /* Stop, unblockable. */ #define SIGTSTP 18 /* Keyboard stop. */ #define SIGCONT 19 /* Continue. */ #define SIGCHLD 20 /* Child terminated or stopped. */ #define SIGTTIN 21 /* Background read from control terminal. */ #define SIGTTOU 22 /* Background write to control terminal. */ #define SIGPOLL 23 /* Pollable event occurred (System V). */ #define SIGXCPU 24 /* CPU time limit exceeded. */ #define SIGXFSZ 25 /* File size limit exceeded. */ #define SIGVTALRM 26 /* Virtual timer expired. */ #define SIGPROF 27 /* Profiling timer expired. */ #define SIGUSR1 30 /* User-defined signal 1. */ #define SIGUSR2 31 /* User-defined signal 2. */ /* Nonstandard signals found in all modern POSIX systems (including both BSD and Linux). */ #define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ /* Archaic names for compatibility. */ #define SIGIO SIGPOLL /* I/O now possible (4.2 BSD). */ #define SIGIOT SIGABRT /* IOT instruction, abort() on a PDP-11. */ #define SIGCLD SIGCHLD /* 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 __SIGRTMIN 32 #define __SIGRTMAX __SIGRTMIN /* Biggest signal number + 1 (including real-time signals). */ #define _NSIG (__SIGRTMAX + 1)