本章簡單描述信號。信號是Linux系統中,內核和進程通訊的一種方式。若是內核但願打斷進程的順序執行,那麼內核會在進程的PCB中記錄信號。而當這個進程被分配到CPU,進入執行狀態時,首先會檢查是否有信號,若是有信號,那麼進程會先嚐試執行信號處理函數。bash
內核須要打斷進程運行的時機:session
進程沒法繼續了函數
按下ctrl+c
ctrl+c實際上是bash向前臺進程組發送SIGINTspa
運行該程序後,再按Ctrl+c,結果是四個進程所有退出rest
有了signal的處理以後,ctrl+c發送的SIGINT不會致使進程退出。code
經過kill -l
命令能夠看到系統定義的信號類型,信號值小於32的是傳統的信號,稱之爲非實時信號,而大於32的稱之爲實時信號。這裏只討論非實時信號。blog
能夠經過signal函數,註冊信號處理函數。若是沒有註冊信號處理函數,那麼按照默認方式處理。
也能夠經過signal設置忽略信號。進程
信號 | 默認處理動做 | 發出信號的緣由 |
---|---|---|
SIGHUP | A | 進程session leader退出時,同session的其餘進程會收到這個信號 |
SIGINT | A | Ctrl+C |
SIGQUIT | C | Ctrl+D |
SIGILL | C | 非法指令 |
SIGABRT | C | 調用abort函數產生的信號 |
SIGFPE | C | 浮點異常 |
SIGKILL | AEF | Kill信號 |
SIGSEGV | C | 無效的內存引用 |
SIGPIPE | A | 管道破裂: 寫一個沒有讀端口的管道 |
SIGALRM | A | 由alarm(2)發出的信號 |
SIGTERM | A | 終止信號 |
SIGUSR1 | A | 用戶自定義信號1 |
SIGUSR2 | A | 用戶自定義信號2 |
SIGCHLD | B | 子進程狀態變化會給父進程發送SIGCHLD信號 |
SIGCONT | 進程繼續(曾被中止的進程) | |
SIGSTOP | DEF | 暫停進程 |
SIGTSTP | D | 控制終端(tty)上按下中止鍵 |
SIGTTIN | D | 後臺進程企圖從控制終端讀 |
SIGTTOU | D | 後臺進程企圖從控制終端寫 |
A 缺省的動做是終止進程
B 缺省的動做是忽略此信號
C 缺省的動做是終止進程並進行內核映像轉儲(dump core)
D 缺省的動做是中止進程
E 信號不能被捕獲
F 信號不能被忽略內存
#include <stdio.h> #include <signal.h> #include <stdlib.h> void signal_handle(int a) { if(a == SIGINT) printf("signal_handle\n"); else if(a == SIGABRT) printf("abrt\n"); else if(a == SIGALRM) printf("alarm\n"); else if(a == SIGCHLD) printf("child\n"); else if(a == SIGUSR1) printf("usr1 signal\n"); } int main() { // SIGINT 2 signal(SIGINT, signal_handle); signal(SIGABRT, signal_handle); signal(SIGALRM, signal_handle); signal(SIGCHLD, signal_handle); signal(SIGUSR1, signal_handle); pid_t pid = fork(); if(pid == 0) return 0; // 給本身發送一個abrt信號 //abort(); alarm(1); while(1) { sleep(1); } }
信號值小於32的都是不可靠信號,假如進程收到一個信號來不及處理,這時候又收到一個一樣的信號,那麼這兩個信號會合併成一個信號,這個緣由是由於進程保存該信號的值只有1位。rem
假如一個進程調用了某系統調用致使該進行處於掛起狀態,而此時該進程接收到一個信號,那麼該系統調用被喚醒。一般該系統調用會返回-1,錯誤碼是EINTR
。
也有些系統調用,能夠設置打斷後重啓,這樣就不會被信號打斷,具體參考man 7 signal
若是使用signal函數註冊信號處理函數,默認被中斷的系統調用是自動重啓的。
#include <signal.h> #include <stdio.h> #include <errno.h> void handle(int v){ printf("ctrl+c\n"); } int main() { signal(SIGINT, handle); char buf; int ret = read(0, buf, sizeof(buf)); // read被中斷打斷了 printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR }
信號會致使可重入問題,好比一個全局鏈表。
以上代碼在必定狀況下會崩潰,在main函數中不停調用push_back,若是在push_back執行一半時,被中斷打斷,而後去執行中斷處理函數時,那麼該中斷處理函數的push_back會崩潰。
有些系統調用自己帶有局部靜態變量,所以那些函數不能在信號處理函數中調用,好比strtok
,readdir
等,對應的可重入的函數是strtok_r
,readdir_r
。
能夠經過kill函數發送信號。
kill也能夠進程組發送信號
#include <sys/types.h> #include <signal.h> int main() { kill(27054, SIGUSR1); }
//掩蓋不可靠信號 #include <signal.h> #include <stdio.h> #include <stdlib.h> // 掩蓋SIGINT // 掩蓋一個可靠信號 void handle(int v) { printf("sigint \n"); } int main() { signal(SIGINT, handle); sigset_t set; // 將set集合設置爲空 sigemptyset(&set); // 將SIGINT信號加入集合 sigaddset(&set, SIGINT); // 把這個集合表明的信號,加入信號掩碼 sigprocmask(SIG_BLOCK, &set, NULL); // 今後,該進程收到SIGINT,不會被處理 sleep(5); printf("remove SIGINT from mask\n"); // 去掉SIGINT的掩碼 sigprocmask(SIG_UNBLOCK, &set, NULL); while(1) { sleep(1); } }
#include <signal.h> #include <stdio.h> #include <stdlib.h> // 掩蓋34 // 掩蓋一個可靠信號 void handle(int v) { printf("hahahaha \n"); } int main() { signal(34, handle); sigset_t set; // 將set集合設置爲空 sigemptyset(&set); // 將34信號加入集合 sigaddset(&set, 34); // 把這個集合表明的信號,加入信號掩碼 sigprocmask(SIG_BLOCK, &set, NULL); // 今後,該進程收到34,不會被處理 kill(getpid(), 34); kill(getpid(), 34); kill(getpid(), 34); kill(getpid(), 34); kill(getpid(), 34); sleep(5); printf("remove 34 from mask\n"); // 去掉34的掩碼 sigprocmask(SIG_UNBLOCK, &set, NULL); while(1) { sleep(1); } }
#include <signal.h> void handle(int v) { } int main() { // 表示忽略SIGINT // 忽略信號和掩蓋信號是有區別: // // 未決的信號:已經發出可是沒有被處理的信號叫未決的信號 signal(SIGINT, SIG_IGN); // 從這裏開始就不忽略 signal(SIGINT, handle); // 設置一個處理函數 signal(SIGINT, SIG_DFL); // 恢復成默認處理 while(1) { sleep(1); } }
以上例子,忽略SIGPIPE信號,那麼進程收到SIGPIPE後,不會有任何反應。
屏蔽和忽略不一樣,忽略意味着在忽略期間,接收的信號就被忽略了。而屏蔽的意思,是暫時屏蔽,屏蔽期間收到的信號依舊在,若是某一時刻該信號再也不忽略時,該信號的處理程序會被調用。
設置屏蔽集合,使用sigprocmask
SIGCHLD信號產生於子進程退出和狀態變化,父進程一般在該信號的處理函數中,調用wait來回收子進程的PCB,這樣能夠避免阻塞。
#include <signal.h> void chld_handle(int v) { // 有子進程退出了 // wait(NULL); // while(1) // 使用while(1)是避免有多個子進程同時退出,因爲SIGCHLD是不可靠信號,函數只會調用一次 { int ret = waitpid(-1, NULL, WNOHANG); // 每次回收都應該用非阻塞方式去回收 if(ret == -1) // 沒有殭屍進程了,以前殭屍進程已經被回收了 break; } } int main() { // 等待子進程的結束,問題是:ddddd // 它阻塞主進程的執行,影響效率 // wait(NULL); // signal(SIGCHLD, chld_handle); pid_t pid = fork(); if(pid == 0) return 0; while(1) { sleep(1); } }
sigaction和signal同樣用來註冊信號處理函數,siqqueue和kill同樣,用來發送信號,可是sigaction比signal功能強大,signal比較簡單。
強大:
能夠傳遞參數
能夠得到發送信號的進程信息
能夠設置SA_RESTART
#include <signal.h> #include <stdio.h> #include <errno.h> //void handle(int v){} // // 新的信號處理函數 void handle(int v, siginfo_t* info, void* p) { printf("ctrl+c\n"); } int main() { // 默認的signal已經有SA_RESTART這個flag了 //signal(SIGINT, handle); struct sigaction sig; sig.sa_handler = NULL; sig.sa_sigaction = handle; sigemptyset(&sig.sa_mask); // sig.sa_flags = 0; sig.sa_flags = SA_RESTART; // 讓阻塞的系統調用,被這個信號打斷以後,要重啓 sig.sa_restorer = NULL; // 在Linux下沒用,直接填NULL就能夠了 sigaction(SIGINT, &sig, NULL); char buf; int ret = read(0, buf, sizeof(buf)); // read被中斷打斷了 printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR }
#include <stdio.h> #include <stdlib.h> #include <signal.h> char buf[1024]; void handle_data() { printf("user input is %s\n", buf); } void handle_chld1(int v) { while(1) { int ret = waitpid(-1, NULL, WNOHANG); if(ret < 0) break; } } void handle_chld(int v, siginfo_t* info, void* p) { handle_chld1(v); } int main() { // signal(SIGCHLD, handle_chld); struct sigaction act; act.sa_handler = NULL; act.sa_sigaction = handle_chld; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_restorer = NULL; sigaction(SIGCHLD, &act, NULL); while(1) { // char* ret = fgets(buf, sizeof(buf), stdin); // if(ret == NULL) break; int ret = read(0, buf, sizeof(buf)); if(ret < 0) { // 說明read出錯,不是真正的出錯,而是被中斷打擾了 // 那此時,應該從新調用read函數,去獲取信息 if(errno == EINTR) { continue; } // 若是是其餘錯誤緣由,就break break; } pid_t pid = fork(); if(pid == 0) { handle_data(); // 建立一個子進程去處理數據 exit(0); } } }
#include <signal.h> #include <stdio.h> void handle(int v, siginfo_t* info, void* p) { printf("recv SIGINT, arg=%d\n", info->si_value.sival_int); } int main() { struct sigaction act; act.sa_handler = NULL; act.sa_sigaction = handle; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO|SA_RESTART; act.sa_restorer = NULL; // 註冊信號處理函數 sigaction(SIGINT, &act, NULL); union sigval v; v.sival_int = 99; // 至關於kill,可是它能夠傳遞一個參數 sigqueue(getpid(), SIGINT, v); getchar(); }
#include <signal.h> #include <stdio.h> int main() { char* p = malloc(100); union sigval v; // v.sival_int = 98; v.sival_ptr = p; // 至關於kill,可是它能夠傳遞一個參數 sigqueue(28360, SIGINT, v); getchar(); }
signal:註冊信號處理函數
kill:發送信號
sigprocmask:設置信號掩碼
sigemptyset:清空信號集
sigfillset:設滿信號集
sigaddset:往信號集增長一個信號
sigdelset:從信號集刪除一個信號
sigismember:判斷信號不然在信號集
sigaction:註冊更增強大的處理函數
sigqueue:發送信號
abortalarmpause