1、信號安全
信號用來通知進程發生了異步事件。內核也能夠由於內部事件而給進程發送信號,通知進程發生了某個事件。注意,信號只是用來通知某進程發生了什麼事件,並不給該進程傳遞任何數據。
異步
**用kill-l 命令查看系統定義的信號列表
ide
2、信號的產生方式函數
① 經過鍵盤組合鍵向前臺發送信號,(一個命令後面加 & 能夠發到後臺運行)
spa
a.信號的默認動做是終止進程,SIGQUIT的默認處理動做是終止進程而且Core Dump,(core dump是進程異常終止時,能夠選擇把進程的用戶空間內存數據所有保存到磁盤上,文件名一般是core,這就叫作 core dump,一般就是程序有了BUG,能夠用調試器檢查core文件以查清錯誤緣由,)默認是不容許產生core文件的,由於core文件中可能包含用戶密碼,不安全,在開發調試時能夠用 ulimit命令改變這個限制,容許產生core文件。3d
用命令 ulimit -c 1024
指針
ulimit命令改變了Shell進程的Resource Limit,test進程的PCB由Shell進程複製而來,因此也具備和Shell進程相同的Resource Limit值,這樣就能夠產生Core Dump了調試
② 經過調用系統函數想進程發送信號
對象
a.kill命令是調用kill函數實現的。kill函數能夠給一個指定的進程發送指定的信號.raise函數能夠給當前進程發送指定的信號(本身給本身發信號). blog
int kill(pid_t pid, int signo);
int raise(int signo);
都是成功返回0,錯誤返回-1;
abort函數使當前進程接收到SIGABRT信號而異常終止。
void abort(void);
像exit函數同樣,abort函數總會成功的,因此沒有返回值。
③ 由軟件條件產生的信號
a.alarm函數 和SIGALRM信號
unsigned int alarm(unsigned int seconds);調用alarm函數能夠設定一個鬧鐘,就是告訴內核在senconds秒後給當前進程發SIGALRM信號,默認動做是終止當前進程,函數的返回值是0或者是之前設定的鬧鐘時間還餘下的秒數。
3、處理信號的方式
① 忽略此信號
② 執行信號的默認處理動做,通常是終止進程
③ 捕捉信號
4、信號的遞達和阻塞
①、阻塞
實際執行信號的處理動做稱爲信號遞達(Delivery),信號從產生到遞達之間的狀態,稱爲信號未決(Pending)。進程能夠選擇阻塞(Block )某個信號。被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動做。阻塞和忽略是不同的只要信號被阻塞就不會遞達,而忽略是在遞達以後可選的一種處理動做。
信號在內核中的表示
每一個信號都有兩個標誌位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動做。信號產生時,內核在進程控制塊中設置該信號的未決標誌,直到信號遞達才清除該標誌
若是在進程解除對某信號的阻塞以前這種信號產生過屢次,在Linux中常規信號在遞達以前產生屢次只計一次,而實時信號在遞達以前產生屢次能夠依次放在一個隊列裏。每一個信號只有一 個bit的未決標誌,非0即1,不記錄該信號產生了多少次,阻塞標誌也是這樣表示的。(blank是一種狀態,pending表示的是有無)未決和阻塞標誌能夠用相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,阻塞信號集也叫作當前進程的信號屏蔽字(Signal Mask),這裏的「屏蔽」應該理解爲阻塞而不是忽略。
②、信號集操做函數
#include <signal.h>
int sigemptyset(sigset_t *set);//初始化對應的信號集bit位爲0
int sigfillset(sigset_t *set);//初始化對象的信號集bit位爲1
int sigaddset(sigset_t *set, int signo);//添加有效信號
int sigdelset(sigset_t *set, int signo);//刪除有效信號
int sigismember(const sigset_t *set, int signo);//判斷一個信號集的有效信號中是否包含某種信號,包含返回1,不包含返回0。
③、sigprocmask
調用函數sigprocmask能夠讀取或更改進程的信號屏蔽字(阻塞信號集)。
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);成功則爲0,出錯則爲-1;
若是調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達.
how參數的含義
SIG_BLOCK set包含了咱們但願添加到當前信號屏蔽字的信號,
SIG_UNBLOCK set包含了咱們但願從當前信號屏蔽在字中解除阻塞的信號,
SIG_SETMASK 設置當前信號屏蔽字爲set所指向的值,
④、sigpending
int sigpending(sigset_t *set);
sigpending讀取當前進程的未決信號集,經過set參數傳出。調用成功則返回0,出錯則返回-1。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
用程序說明
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<signal.h> 4 void printsigset(sigset_t* sig) 5 { 6 int i=0; 7 for(;i<31;i++) 8 { 9 if(sigismember(sig,i))//判斷指定信號是否在目標集合中 10 { 11 printf("1"); 12 } 13 else 14 { 15 printf("0"); 16 } 17 } 18 printf("\n"); 19 } 20 int main() 21 { 22 sigset_t s,p;//定義信號集 23 sigemptyset(&s);//初始化 24 sigemptyset(&p); 25 sigaddset(&s,SIGINT);//設置信號ctrl+C 26 sigprocmask(SIG_BLOCK,&s,NULL);//設置阻塞信號集,阻塞 SIGINT信號 27 while(1) 28 { 29 sigpending(&p);//獲取未決信號集 30 printsigset(&p); 31 sleep(1); 32 } 33 return 0; 34 }
結果分析:
程序運行時,每秒鐘把各信號的未決狀態打印一遍,直到按Ctrl-C將會使SIGINT信號處於未決狀態,
5、捕捉信號
a.內核如何實現信號的捕捉
若是信號的處理動做是用戶自定義函數,在信號遞達時就調用這個函數,這稱爲捕捉信號,處理過程以下,舉例來講明
1. 用戶程序註冊了SIGQUIT信號的處理函數sighandler。
2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。
3. 在中斷處理完畢後要返回用戶態的main函數以前檢查到有信號SIGQUIT遞達。
4. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函 數,sighandler和main函數使用不一樣的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程。
5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態。
6. 若是沒有新的信號要遞達,此次再返回用戶態就是恢復main函數的上下文繼續執行了
**(先從用戶態—>內核態->返回用戶態以前檢查有信號遞達,返回用戶態處理信號->處理完成後再進入內核態->若是沒有新的信號遞達,返回用戶態恢復上下文繼續執行)
b.sigaction
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函數能夠讀取和修改與指定信號相關聯的處理動做。調用成功則返回0,出錯則返回- 1
將sa_handler賦值爲常數SIG_IGN傳給sigaction表示忽略信號,賦值爲常數SIG_DFL表示執行系統默認動做,賦值爲一個函數指針表示用自定義函數捕捉信號,或者說向內核註冊了一個信號處理函 數,該函數返回值爲void,能夠帶一個int參數,經過參數能夠得知當前信號的編號,這樣就能夠用同一個函數處理多種信號。顯然,這也是一個回調函數,不是被main函數調用,而是被系統所調用。
當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,若是這種信號再次產生,那麼它會被阻塞到當前處理結束爲止。
若是在調用信號處理函數時,除了當前信號被自動屏蔽以外,還但願自動屏蔽另一些信號,則用sa_mask字段說明這些須要額外屏蔽的信號,當信號處理函數返回時自動恢復原來的信號屏蔽字。
c.pause
int pause(void);
pause函數使調用進程掛起直到有信號遞達。若是信號的處理動做是終止進程,則進程終止,pause函數沒有機會返回;若是信號的處理動做是忽略,則進程繼續處於掛起狀態,pause不返回;若是信號的處理動做是捕捉,則調用了信號處理函數以後pause返回-1,
用alarm和pause實現sleep(3)的函數
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<signal.h> 4 void sig_alarm(int signo) 5 { 6 //do nothing 7 } 8 unsigned int my_sleep(unsigned int times) 9 { 10 struct sigaction new ,old; 11 unsigned int unslept=0; 12 new.sa_handler=sig_alarm; 13 sigemptyset(&new.sa_mask); 14 sigemptyset(&old.sa_mask); 15 new.sa_flags=0; 16 sigaction(SIGALRM,&new,&old);//註冊信號處理函數 17 alarm(times); //設置鬧鐘 18 pause(); 19 unslept=alarm(0);//取消鬧鐘 20 sigaction(SIGALRM,&old,NULL);//恢復默認信號處理動做 21 return unslept; 22 } 23 int main() 24 { 25 while(1) 26 { 27 my_sleep(5); 28 printf("5 senconds pass\n"); 29 } 30 return 0; 31 }
6、可重入函數
當捕捉到信號時,不論進程的主控制流程當前執行到哪兒,都會先跳到信號處理函數中執行,從信號處理函數返回後再繼續執行主控制流程。信號處理函數是一個單獨的控制流程,由於它和主控制流程是異步的,兩者不存在調用和被調用的關係,而且使用不一樣的堆棧空間。引入了信號處理函數使得一個進程具備多個控制流程,若是這些控制流程訪問相同的全局資源(全局變量、硬件資源等),就有可能出現衝突。