[單刷APUE系列]第一章——Unix基礎知識[1]
[單刷APUE系列]第一章——Unix基礎知識[2]
[單刷APUE系列]第二章——Unix標準及實現
[單刷APUE系列]第三章——文件I/O
[單刷APUE系列]第四章——文件和目錄[1]
[單刷APUE系列]第四章——文件和目錄[2]
[單刷APUE系列]第五章——標準I/O庫
[單刷APUE系列]第六章——系統數據文件和信息
[單刷APUE系列]第七章——進程環境
[單刷APUE系列]第八章——進程控制[1]
[單刷APUE系列]第八章——進程控制[2]
[單刷APUE系列]第九章——進程關係
[單刷APUE系列]第十章——信號[1]shell
從前面的文章和實際使用中,你們應該也對信號有一些模糊的認識了。好比,Nginx
使用信號來管理進程的啓動關閉,UNIX進程信號是經典的操做異步事件機制。在早期,Unix信號是每一個實現都不一樣的,可是隨着Unix標準化的進行,標準提出了統一的信號機制模型。除了共有的信號之外,各自的實現還提供了擴展信號,固然,這個不是重點。
在頭文件<signal.h>
中定義了全部的信號,咱們能夠經過編輯這個頭文件來查看當前Unix實現提供的信號。segmentfault
#define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt */ #define SIGQUIT 3 /* quit */ #define SIGILL 4 /* illegal instruction (not reset when caught) */ #define SIGTRAP 5 /* trace trap (not reset when caught) */ #define SIGABRT 6 /* abort() */ ...
就是這樣列出來的,從上面能夠看到,信號不存在爲0的編號,在最初的幾章裏,提到過產生信號的條件windows
終端按鍵產生信號,例如:Ctrl+C、Ctrl+\這種形式安全
硬件產生信號,好比除以0錯誤等,都是經過中斷通知內核,而後產生信號多線程
進程調用 kill 函數發送信號,在前面權限部分就提到過,想要發送信號,則全部者權限須要檢查或者說有效用戶組是root權限異步
用戶使用 kill 命令,實際上就是調用了 kill 函數socket
系統產生的軟中斷函數
信號是進程交互和異步事件的方式,因爲信號出現是不可知的,因此不能說像檢查返回值,檢查 errno 同樣判斷是否有信號,系統提供了一套完善的機制來實現,開發者只須要讓進程註冊函數來處理信號。
進程收到信號後,能夠選擇3種方式處理行爲:學習
忽略信號ui
捕捉信號
執行系統默認行爲
下面列出了一般的信號
No Name Default Action Description 1 SIGHUP terminate process terminal line hangup 2 SIGINT terminate process interrupt program 3 SIGQUIT create core image quit program 4 SIGILL create core image illegal instruction 5 SIGTRAP create core image trace trap 6 SIGABRT create core image abort program (formerly SIGIOT) 7 SIGEMT create core image emulate instruction executed 8 SIGFPE create core image floating-point exception 9 SIGKILL terminate process kill program 10 SIGBUS create core image bus error 11 SIGSEGV create core image segmentation violation 12 SIGSYS create core image non-existent system call invoked 13 SIGPIPE terminate process write on a pipe with no reader 14 SIGALRM terminate process real-time timer expired 15 SIGTERM terminate process software termination signal 16 SIGURG discard signal urgent condition present on socket 17 SIGSTOP stop process stop (cannot be caught or ignored) 18 SIGTSTP stop process stop signal generated from keyboard 19 SIGCONT discard signal continue after stop 20 SIGCHLD discard signal child status has changed 21 SIGTTIN stop process background read attempted from control terminal 22 SIGTTOU stop process background write attempted to control terminal 23 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2)) 24 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2)) 25 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2)) 26 SIGVTALRM terminate process virtual time alarm (see setitimer(2)) 27 SIGPROF terminate process profiling timer alarm (see setitimer(2)) 28 SIGWINCH discard signal Window size change 29 SIGINFO discard signal status request from keyboard 30 SIGUSR1 terminate process User defined signal 1 31 SIGUSR2 terminate process User defined signal 2
void (*signal(int sig, void (*func)(int)))(int); or in the equivalent but easier to read typedef'd version: typedef void (*sig_t) (int); sig_t signal(int sig, sig_t func);
Unix使用信號的接口就是signal
函數。在前面的章節中提到過signal函數的定義和具體意義。signal函數其實是ISO C定義的,可是因爲這種跨平臺函數具備實現的不一樣,因此windows仍是Unix各個實現,實際上都有其特殊的方式。對於函數自己來講,帶有兩個參數,一個是signo信號名,func的值是一個常亮
1 SIGHUP terminate process terminal line hangup 2 SIGINT terminate process interrupt program 3 SIGQUIT create core image quit program 4 SIGILL create core image illegal instruction 5 SIGTRAP create core image trace trap 6 SIGABRT create core image abort program (formerly SIGIOT) 7 SIGEMT create core image emulate instruction executed 8 SIGFPE create core image floating-point exception 9 SIGKILL terminate process kill program 10 SIGBUS create core image bus error 11 SIGSEGV create core image segmentation violation 12 SIGSYS create core image non-existent system call invoked 13 SIGPIPE terminate process write on a pipe with no reader 14 SIGALRM terminate process real-time timer expired 15 SIGTERM terminate process software termination signal 16 SIGURG discard signal urgent condition present on socket 17 SIGSTOP stop process stop (cannot be caught or ignored) 18 SIGTSTP stop process stop signal generated from keyboard 19 SIGCONT discard signal continue after stop 20 SIGCHLD discard signal child status has changed 21 SIGTTIN stop process background read attempted from control terminal 22 SIGTTOU stop process background write attempted to control terminal 23 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2)) 24 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2)) 25 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2)) 26 SIGVTALRM terminate process virtual time alarm (see setitimer(2)) 27 SIGPROF terminate process profiling timer alarm (see setitimer(2)) 28 SIGWINCH discard signal Window size change 29 SIGINFO discard signal status request from keyboard 30 SIGUSR1 terminate process User defined signal 1 31 SIGUSR2 terminate process User defined signal 2 If a process explicitly specifies SIG_IGN as the action for the signal SIGCHLD, the system will not create zombie processes when children of the callingprocess exit. As a consequence, the system will discard the exit status from the child processes. If the calling process subsequently issues a call to wait(2) or equivalent, it will block until all of the calling process's children terminate, and then return a value of -1 with errno set to ECHILD.
上面這些是蘋果系統列出的可以使用的信號,若是func參數能夠指定3種,分別是常量SIG_IGN、常量SIG_DFL或者一個函數指針。常量SIG_IGN表示內核忽略此信號,常量SIG_DFL表示執行內核默認動做,函數指針則是當信號發生時,調用該函數,通常叫作信號處理函數。
結合前文中對signal函數的理解,實際上signal函數要求兩個參數,只有一個返回值,signal函數第一個參數是signo信號名,第二個參數是一個函數指針,通常是信號處理函數,最後返回這個信號處理函數指針。因爲函數原型很是複雜,通常都是使用typedef將其簡化。
#define SIG_DFL (void (*)(int))0 #define SIG_IGN (void (*)(int))1 #define SIG_HOLD (void (*)(int))5 #define SIG_ERR ((void (*)(int))-1)
通常聲明就是像上面同樣。下面是一個例程
#include "include/apue.h" static void sig_usr(int); int main(int argc, char *argv[]) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR2"); for ( ; ; ) pause(); } static void sig_usr(int signo) { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo); }
而後編譯運行
> ./a.out & [1] 21588 > kill -USR1 21588 received SIGUSR1 > kill -USR2 21588 received SIGUSR2 > kill 21588 [1] + 21588 terminated ./a.out
咱們知道,全部的進程都是內核啓動的,當啓動一個進程的時候,全部的信號都會被設置爲系統默認或者忽略,在Unix環境中最典型的一個例子就是shell啓動其餘進程,爲了保證前臺和後臺對中斷信號和退出信號的捕捉,通常都這樣寫
void sig_int(int), sig_quit(int); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, sig_int); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, sig_quit);
由於全部都是被子進程繼承的,因此在exec時,父進程能夠控制子進程的忽略狀態,因此須要這樣判斷。
在上面能夠看出,signal函數除了改變當前信號的處理方式之外,還會返回以前的狀態,這就很是的繁瑣,因此在實際中不多使用了。
信號是異步調用,進程自身執行各類正常指令序列,當接受到信號的時候,內核就會通知進程處理,而此時正常的指令序列就會被中斷,很容易形成進程空間的破壞,因此如今的Unix實現都會提供在信號處理程序中保證安全調用的函數,通俗的說,就是異步信號安全。至於具體列表,則須要查看具體Unix實現。
在前面學習errno錯誤信息的時候講到過,每一個進程都維護了一個errno變量,當須要得到的時候就經過extern int errno
的形式獲取。固然實際不是這樣的,由於有多線程的模型存在,因此實際狀況更加複雜。當進程執行正常代碼,而後調用的系統函數修改了errno值,若是一個信號被髮送給進程,進程的信號處理函數調用了另外一個系統函數修改errno值,結果最終返回的時候就是信號處理程序內調用獲得的errno,因此在實際開發中,若是須要調用系統函數,須要先保存errno值,而後調用後恢復errno