三十四、Linux 程序與訊號——訊號特點、訊號集和訊號遮蔽函式
阿新 • • 發佈:2018-12-30
34.1 訊號特點
- 訊號的發生是隨機的,但訊號在何種條件下發生是可預測的
- 程序槓開始啟動時,所有訊號的處理方式要麼預設,要麼忽略;忽略是 SIGUSR1 和 SIGUSR2 兩個訊號,其他都採取預設方式(大多數是終止程序)。
- 程序在呼叫 exec 函式後,原有訊號的捕捉函式失效
- 子程序的誕生總是繼承父程序的訊號處理方式
- 在系統層面上訊號的發生是可靠的
- 在Linux 中的可靠性只保證一次,程序在處理訊號期間,若發生同類型的訊號不會丟失(核心會保留),會被延遲處理
- 但同類型訊號的多次發生只會保留一次,即被處理一次。
- 若不同型別的訊號發生也會被核心保留直接會被處理,處理完後再處理原有訊號
- 使用者層面可靠性依賴於訊號而執行的使用者程式碼放置在訊號處理程式內部執行是可靠的,否則不一定可靠
- 在訊號發生時,慢系統呼叫以被中斷並在訊號處理後系統呼叫會被重啟
- 在訊號發生時,使用者函式可以被中斷,但不能被重啟,沿著中斷點繼續執行
- 在使用者函式中要保證資料一致性,即可重入性,不要去訪問全域性變數和靜態變數,堆中的變數若在函式內部分配沒有關係,否則會出現不可重入性
34.2 訊號集和訊號遮蔽函式
34.2.1 訊號集
- 訊號集為一個或多個訊號的集合,主要用在訊號遮蔽函式中
1 #include <signal.h> 2int sigemptyset(sigset_t *set);
- 函式功能:將訊號集清空,對應將所有訊號遮蔽字置 0
- 函式引數:
- set 訊號集合
#include <signal.h> int sigfillset(sigset_t *set);
- 函式功能:將所有訊號加入到訊號集中,對應將所有訊號遮蔽字置 1
#include <signal.h> int sigaddset(sigset_t *set, intsigno);
- 函式功能:將某個訊號加入到訊號集中,對應將訊號遮蔽字某位置 1
#include <signal.h> int sigdelset(sigset_t *set, int signo);
- 函式功能:將某個訊號從訊號集中刪除,對應將訊號遮蔽字某位置 0
上述函式返回,若成功返回0,出錯返回 -1
1 #include <signal.h> 2 int sigismember(sigset_t *set, int signo);
- 函式功能:測試訊號集中是否包含某個訊號,對應判斷訊號遮蔽字某位是否置 1
- 返回值:真,返回1,假,返回0;出錯返回 -1
34.2.2 訊號遮蔽函式
1 #include <signal.h> 2 int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrct oset);
- 函式功能:利用 set 去覆蓋核心中訊號遮蔽字, oset 存放原有的訊號遮蔽字
- 函式引數:
- @ how
- SIG_BLOCK:利用 set 中訊號設定訊號遮蔽字
- SIG_UNBLOCK:利用 set 中訊號不設定訊號遮蔽字
- SIG_SETMASK:利用 set 中訊號去替換核心訊號遮蔽字
- @ how
- 返回:成功,返回0;失敗,返回 -1
- 說明:
- 程序可以暫時遮蔽訊號,使得程序在執行過程中發生的相應訊號暫時被阻塞,等待程序解除訊號遮蔽後,再由核心或驅動將訊號傳遞給程序
- 訊號遮蔽可遮蔽程式執行過程中的中斷
1 #include <signal.h> 2 int sigpending(sigset_t *set);
- 函式功能:獲取訊號未決字的內容
- 返回值:成功返回0;出錯返回 -1
34.2.3 訊號遮蔽設定
- 訊號在處理過程中是被遮蔽的(置 1),處理完畢解除遮蔽(被置 0),可在函式可重入性中使用訊號遮蔽技術
- 核心中的 task_struct 中包含兩個 32 位字(記錄相關的訊號資訊),分別為訊號遮蔽字 mask 和訊號未決字 pending。
- mask:
- 共有 31 位(代表 1~31 號訊號,0 號沒有意義),每一位代表一個訊號,初始為 0,若這位上發生訊號,會被立即處理;若為 1(設定 1 則訊號被遮蔽,設定 0 則訊號不被遮蔽),則在該位上發生訊號不會被處理,會延遲處理
- pengding
- 初始為 0,若 mask 中某一位為 1,但又發生了同樣的訊號,則在 pending 同樣的位置會被置為 1,以便讓程序知道該訊號又發生過而進行延遲處理
- mask:
- 若干個訊號一起設定為 0 或 1 稱為訊號集
- 子程序繼承父程序中的訊號遮蔽字,而不繼承訊號未決字
34.2.4 獲取訊號遮蔽字
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <sys/types.h> 5 #include <unistd.h> 6 7 8 void out_set(sigset_t set) 9 { 10 int i; 11 for(i = 1; i < 31; i++){ 12 if(sigismember(&set, i)) { 13 printf("%d\n", i); 14 } 15 } 16 } 17 18 void sig_handler(int signo) 19 { 20 printf("begin process the %d\n", signo); 21 22 /** 獲得正在處理訊號時核心中的訊號遮蔽字的內容 */ 23 sigset_t oset;///< 放置核心遮蔽字的內容 24 sigemptyset(&oset); 25 if((sigprocmask(SIG_BLOCK, NULL, &oset)) < 0) { 26 perror("sigprocmask error"); 27 } 28 out_set(oset); 29 printf("finish process the %d\n", signo); 30 } 31 32 33 int main(void) 34 { 35 if(signal(SIGUSR1, sig_handler) == SIG_ERR) { 36 perror("signal sigusr1 error"); 37 } 38 39 if(signal(SIGUSR2, sig_handler) == SIG_ERR) { 40 perror("signal sigusr2 error"); 41 } 42 43 sigset_t oset;///< 放置核心遮蔽字的內容 44 printf("before signal occured mask:\n"); 45 /** 清空訊號集 oset */ 46 sigemptyset(&oset); 47 /** 在訊號發生前,獲得訊號遮蔽字的內容 */ 48 if((sigprocmask(SIG_BLOCK, NULL, &oset)) < 0) { 49 perror("sigprocmask error"); 50 } 51 out_set(oset); 52 printf("process %d wait signal...\n", getpid()); 53 pause();///< 程序暫停等待訊號 54 55 printf("after signal occured mask:\n"); 56 sigemptyset(&oset); 57 /** 在訊號發生後,獲得訊號遮蔽字的內容 */ 58 if((sigprocmask(SIG_BLOCK, NULL, &oset)) < 0) { 59 perror("sigprocmask error"); 60 } 61 out_set(oset); 62 return 0; 63 }
測試:
在訊號發生前,訊號遮蔽字都為 0,然後傳送 SIGUSR1 訊號,訊號集被設定為 10。在結束後,訊號又被重新設定回 0.
34.2.5 解決函式不可重入性
1 #include <signal.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 6 int g_v[10]; 7 int *h_v; ///< 堆中變數 8 9 void set(int val) 10 { 11 int a_v[10]; 12 13 int i = 0; 14 for(; i < 10; i++) { 15 a_v[i] = val; 16 g_v[i] = val; 17 h_v[i] = val; 18 sleep(1); 19 } 20 21 printf("g_v:"); 22 for(i = 0; i < 10; i++){ 23 if(i != 0) { 24 printf(", %d", g_v[i]); 25 } 26 else { 27 printf(", %d", g_v[i]); 28 } 29 } 30 printf("\n"); 31 32 printf("h_v:"); 33 for(i = 0; i < 10; i++){ 34 if(i != 0) { 35 printf(", %d", h_v[i]); 36 } 37 else { 38 printf(", %d", h_v[i]); 39 } 40 } 41 42 printf("\n"); 43 printf("a_v:"); 44 for(i = 0; i < 10; i++){ 45 if(i != 0) { 46 printf(", %d", a_v[i]); 47 } 48 else { 49 printf(", %d", a_v[i]); 50 } 51 } 52 } 53 54 void sig_handler(int signo) 55 { 56 if(signo == SIGTSTP){ 57 printf("SIGTSTP occured\n"); 58 set(20); 59 printf("\nend SIGTSTP\n"); 60 } 61 } 62 63 64 int main(void) 65 { 66 if(signal(SIGTSTP, sig_handler) == SIG_ERR){ 67 perror("signal sigtstp error"); 68 } 69 70 h_v = (int *)calloc(10, sizeof(int)); 71 72 printf("begin running main\n"); 73 //遮蔽訊號(1~31) 74 sigset_t sigset; 75 sigemptyset(&sigset); 76 sigfillset(&sigset); ///<要遮蔽所有的訊號 77 if(sigprocmask(SIG_SETMASK, &sigset, NULL) < 0){ 78 perror("sigprocmask error"); 79 } 80 set(10); 81 //解除訊號遮蔽 82 if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) < 0){ 83 perror("sigprocmask error"); 84 } 85 printf("\nend running main\n"); 86 return 0; 87 }
未加遮蔽之前的測試結果,開始執行後,按 CTRL+Z 進行中斷:
加入上述訊號遮蔽函式後:
可以看出,數值都可以正常輸出。
34.2.6 檢視訊號未決字的內容
在不斷髮同類型的訊號的時候,檢視訊號未決字
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <unistd.h> 5 6 void out_set(sigset_t set) 7 { 8 int i = 1; 9 for(; i < 31; i++){ 10 if(sigismember(&set, i)){ 11 printf("%d,", i); 12 } 13 } 14 15 printf("\n"); 16 } 17 18 void sig_handler(int signo) 19 { 20 printf("begin the signal handler\n"); 21 int i = 0; 22 sigset_t sigset; 23 for(; i < 20; i++) { 24 sigemptyset(&sigset); 25 if(sigpending(&sigset) < 0) { 26 perror("sigpending error"); 27 } 28 else { 29 printf("pending signal:"); 30 out_set(sigset); 31 sigemptyset(&sigset); 32 } 33 34 printf("i is %d\n", i); 35 sleep(1); 36 } 37 38 printf("end the signal handler\n"); 39 } 40 41 int main(void) 42 { 43 if(signal(SIGTSTP, sig_handler) == SIG_ERR){ 44 perror("signal sigtstp error"); 45 } 46 47 printf("process %d wait signal...\n", getpid()); 48 pause();///< 程序暫停等待訊號 49 printf("process finished\n"); 50 }
只發送一次中斷訊號的結果:
訊號未決字在傳送一個訊號的時候,再發送同類訊號的時候,訊號未決字才置1,
連續傳送兩次訊號:
可以看出訊號被處理了兩次,未決字發生了變化。
同樣可以傳送超過兩次同類訊號,會發現也只會處理兩次。