Linux下的信號詳解及捕捉信號
信號的基本概念
每個信號都有一個編號和一個宏定義名稱 ,這些宏定義可以在 signal.h
中找到。
使用kill -l
命令查看系統(tǒng)中定義的信號列表: 1-31是普通信號; 34-64是實時信號
所有的信號都由操作系統(tǒng)來發(fā)!
對信號的三種處理方式
1、忽略此信號:大多數(shù)信號都可使用這種方式進行處理,但有兩種信號卻決不能被忽略。它們是:SIGKILL
和SIGSTOP
。這兩種信號不能被忽略的,原因是:它們向超級用戶提供一種使進程終止或停止的可靠方法。另外,如果忽略某些由硬件異常產生的信號(例如非法存儲訪問或除以0),則進程的行為是示定義的。
2、直接執(zhí)行進程對于該信號的默認動作 :對大多數(shù)信號的系統(tǒng)默認動作是終止該進程。
3、捕捉信號:執(zhí)行自定義動作(使用signal
函數(shù)),為了做到這一點要通知內核在某種信號發(fā)生時,調用一個用戶函數(shù)handler
。在用戶函數(shù)中,可執(zhí)行用戶希望對這種事件進行的處理。注意,不能捕捉SIGKILL
和SIGSTOP
信號。
#include <signal.h> typedef void( *sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
signal函數(shù)的作用:給某一個進程的某一個特定信號(標號為signum)注冊一個相應的處理函數(shù),即對該信號的默認處理動作進行修改,修改為handler
函數(shù)所指向的方式。
1、第一個參數(shù)是信號的標號
2、第二個參數(shù),sighandler_t
是一個typedef
來的,原型是void (*)(int)
函數(shù)指針,int
的參數(shù)會被設置成signum
舉個代碼例子:
#include<stdio.h> #include<signal.h> void handler(int sig) { printf("get a sig,num is %d\n",sig); } int main() { signal(2,handler); while(1) { sleep(1); printf("hello\n"); } return 0; }
修改了2號信號(Ctrl-c)的默認處理動作為handler
函數(shù)的內容,則當該程序在前臺運行時,鍵入Ctrl-c后不會執(zhí)行它的默認處理動作(終止該進程)
信號的處理過程:
進程收到一個信號后不會被立即處理,而是在恰當 時機進行處理!什么是適當?shù)臅r候呢?比如說中斷返回的時候,或者內核態(tài)返回用戶態(tài)的時候(這個情況出現(xiàn)的比較多)。
信號不一定會被立即處理,操作系統(tǒng)不會為了處理一個信號而把當前正在運行的進程掛起(切換進程),掛起(進程切換)的話消耗太大了,如果不是緊急信號,是不會立即處理的。操作系統(tǒng)多選擇在內核態(tài)切換回用戶態(tài)的時候處理信號,這樣就利用兩者的切換來處理了(不用單獨進行進程切換以免浪費時間)。
總歸是不能避免的,因為很有可能在睡眠的進程就接收到信號,操作系統(tǒng)肯定不愿意切換當前正在運行的進程,于是就得把信號儲存在進程唯一的PCB(task_struct)當中。
產生信號的條件
1.用戶在終端按下某些鍵時,終端驅動程序會發(fā)送信號給前臺程序。
例如:Ctrl-c產生SIGINT信號,Ctrl-\產生SIGQUIT信號,Ctrl-z產生SIGTSTP信號
2.硬件異常產生信號。
這類信號由硬件檢測到并通知內核,然后內核向當前進程發(fā)送適當?shù)男盘枴?br />
例如:當前進程執(zhí)行除以0的指令,CPU的運算單元會產生異常,內核將這個進程解釋為SIGFPE信號發(fā)送給當前進程。
當前進程訪問了非法內存地址,MMU會產生異常,內核將這個異常解釋為SIGSEGV信號發(fā)送給進程。
3.一個進程調用kill(2)
函數(shù)可以發(fā)送信號給另一個進程。
可以用kill(1)
命令發(fā)送信號給某個進程,kill(1)
命令也是調用kill(2)
函數(shù)實現(xiàn)的,如果不明確指定信號則發(fā)送SIGTERM信號,該信號的默認處理動作是終止進程。
信號的產生
1.通過終端按鍵產生信號
舉個栗子:寫一個死循環(huán),前臺運行這個程序,然后在終端鍵入Ctrl-c
當CPU正在執(zhí)行這個進程的代碼 , 終端驅動程序發(fā)送了一 個 SIGINT
信號給該進程,記錄在該進程的 PCB中,則該進程的用戶空間代碼暫停執(zhí)行 ,CPU從用戶態(tài) 切換到內核態(tài)處理硬件中斷。
從內核態(tài)回到用戶態(tài)之前, 會先處理 PCB中記錄的信號 ,發(fā)現(xiàn)有一個 SIGINT
信號待處理, 而這個信號的默認處理動作是終止進程,所以直接終止進程而不再返回它的用戶空間代碼執(zhí)行。
2.調用系統(tǒng)函數(shù)向進程發(fā)信號
/************************************************************************* > File Name: test.c > Author:Lynn-Zhang > Mail: iynu17@yeah.net > Created Time: Fri 15 Jul 2016 03:03:57 PM CST ************************************************************************/ #include<stdio.h> int main() { printf("get pid :%d circle ...\n",getpid()); while(1); return 0; }
寫一個上面的程序在后臺執(zhí)行死循環(huán),并獲取該進程的id,然后用kill命令給它發(fā)送SIGSEGV
信號,可以使進程終止。也可以使用kill -11 5796,11是信號SIGSEGV
的編號。
打開終端1,運行程序:
利用終端2,給進程發(fā)送信號
終端1 顯示進程被core了:
kill命令是調用kill函數(shù)實現(xiàn)的。kill函數(shù)可以給一個指定的進程發(fā)送指定信號
。
raise函數(shù)可 以給當前進程發(fā)送指定的信號 (自己給自己發(fā)信號 )
#include<signal.h> int kill(pid_t pid,int signo); int raise(int signo);
這兩個函數(shù)都是成功返回0,錯誤返回-1.
除此之外,abort
函數(shù)使當前進程接收到SIGABRT
信號而異常終止。
#include<stdlib.h> void abort(void);
就像 exit
函數(shù)一樣 ,abort
函數(shù)總是會成功的 ,所以沒有返回值。
3.由軟件條件產生信號
/************************************************************************* > File Name: alarm.c > Author:Lynn-Zhang > Mail: iynu17@yeah.net > Created Time: Fri 15 Jul 2016 08:52:02 PM CST ************************************************************************/ #include<stdio.h> int main() { int count=0; alarm(1); while(1) { printf("%d\n",count); count++; } return 0; }
通過實現(xiàn)以上代碼,調用alarm
函數(shù)可以設定一個鬧鐘,告訴內核在seconds
秒之后給當前進程發(fā)SIGALRM
信號, 該信號的默認處理動作是終止當前進程。
該程序會在1秒鐘之內不停地數(shù)數(shù),并打印計數(shù)器,1秒鐘到了就被SIGALRM
信號終止。由于電腦配置等的不同,每臺電腦一秒鐘之內計數(shù)值是不同的一般是不同的。
#include <unistd.h> unsigned int alarm(unsigned int seconds);
alarm
函數(shù)的返回值是0或上次設置鬧鐘剩余的時間。
阻塞信號
1.信號在內核中的表示:
信號遞達delivery:實際執(zhí)行信號處理信號的動作
信號未決pending:信號從產生到抵達之間的狀態(tài),信號產生了但是未處理
忽略:抵達之后的一種 動作
阻塞block:收到信號不立即處理 被阻塞的信號將保持未決狀態(tài),直到進程解除對此信號的阻塞,才執(zhí)行抵達動作
信號產生和阻塞沒有直接關系 抵達和解除阻塞沒有直接關系!
進程收到一個信號后,不會立即處理,它會在恰當?shù)臅r機被處理。
每個信號都由兩個標志位分別表示阻塞和未決,以及一個函數(shù)指針表示信號的處理動作。
在上圖的例子中,
1. SIGHUP
信號未阻塞也未產生過,當它遞達時執(zhí)行默認處理動作。
2. SIGINT
信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒 有解除阻塞之前不能忽略這個信號,因為進程仍有機會改變處理動作之后再解除阻塞。
3. SIGQUIT
信號未產生過,一旦產生SIGQUIT
信號將被阻塞,它的處理動作是用戶自定義函數(shù)sighandler
。阻塞信號集也叫作信號屏蔽字。
信號產生但是不立即處理,前提條件是要把它保存在pending
表中,表明信號已經產生。
2.信號集操作函數(shù)
#include <signal.h> int sigemptyset(sigset_t *set); //初始化set所指向的信號集,使所有信號的對應位清0 int sigfillset(sigset_t *set); //初始化set所指向的信號集,表示該信號集的有效信號包括系統(tǒng)支持的所有信號 int sigaddset(sigset_t *set, int signo); //在該信號集中添加有效信號 int sigdelset(sigset_t *set, int signo); //在該信號集中刪除有效信號 int sigismember(const sigset_t *set, int signo); //用于判斷一個信號集的有效信號中是否包含某種信號
參數(shù)解析:
sigset_t
結構體的參數(shù)表示信號集,信號操作的時候都是以信號集合的方式進行操作,需要事先創(chuàng)建一個該結構體的對象,然后把想要操作的信號添加到信號集合對象當中去
signo就是信號的標號了
3.調用函數(shù)sigprocmask可以讀取或更改進程的信號屏蔽字(阻塞信號集)。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
一個進程的信號屏蔽字規(guī)定了當前阻塞而不能遞送給該進程的信號集。調用函數(shù)sigprocmask
可以檢測或更改(或兩者)進程的信號屏蔽字。如果調用sigprocmask
解除了對當前若干個未決信號的阻塞,則在sigprocmask
返回前,至少將其中 一個信號遞達。
參數(shù)解析:
how,有三個宏
SIG_BLOCK 添加到block表當中去
SIG_UNBLOCK 從block表中刪除
SIG_SETMASK 設置block表 設置當前信號屏蔽字為set所指向的值
set表示新設置的信號屏蔽字,oset表示當前信號屏蔽字
處理方式:
set 非空, oset 為NULL :按照how指示的方法更改set指向信號集的信號屏蔽字。
set 為NULL,oset 非空:讀取oset指向信號集的信號屏蔽字,通過oset參數(shù)傳出。
set 和 oset 都非空 :現(xiàn)將原來的信號屏蔽字備份到oset里,然后根據(jù)set和how參數(shù)更改信號屏蔽字。
4. sigpending讀取當前進程的未決信號集,通過set參數(shù)傳出
#include <signal.h> int sigpending(sigset_t *set);
這是一個輸出型參數(shù),會把當前進程的pending
表打印到傳入的set集中。
實例驗證上面幾個函數(shù):
一開始沒有任何信號,所以pending
表中全是0,我通過Ctrl+C傳入2號信號,看到pending表中有2號被置位了,經過10秒取消阻塞,2號信號被處理(經過我自定義的函數(shù))
Linux下捕捉信號
信號由三種處理方式:
忽略
執(zhí)行該信號的默認處理動作
捕捉信號
如果信號的處理動作是用戶自定義函數(shù),在信號遞達時就調用這個自定義函數(shù),這稱為捕捉信號。
進程收到一個信號后不會被立即處理,而是在恰當時機進行處理!即內核態(tài)返回用戶態(tài)之前 !
但是由于信號處理函數(shù)的代碼在用戶空間,所以這增加了內核處理信號捕捉的復雜度。
內核實現(xiàn)信號捕捉的步驟:
1、用戶為某信號注冊一個信號處理函數(shù)sighandler
。
2、當前正在執(zhí)行主程序,這時候因為中斷、異?;蛳到y(tǒng)調用進入內核態(tài)。
3、在處理完異常要返回用戶態(tài)的主程序之前,檢查到有信號未處理,并發(fā)現(xiàn)該信號需要按照用戶自定義的函數(shù)來處理。
4、內核決定返回用戶態(tài)執(zhí)行sighandler
函數(shù),而不是恢復main
函數(shù)的上下文繼續(xù)執(zhí)行?。?code>sighandler和main
函數(shù)使用的是不同的堆棧空間,它們之間不存在調用和被調用的關系,是兩個獨立的控制流程)
5、sighandler
函數(shù)返回后,執(zhí)行特殊的系統(tǒng)調用sigreturn
從用戶態(tài)回到內核態(tài)
6、檢查是否還有其它信號需要遞達,如果沒有 則返回用戶態(tài)并恢復主程序的上下文信息繼續(xù)執(zhí)行。
signal
給某一個進程的某一個信號(標號為signum)注冊一個相應的處理函數(shù),即對該信號的默認處理動作進行修改,修改為handler
函數(shù)指向的方式;
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);<br>//即:<br>void (*signal(int, void(*)(int)))(int);
signal函數(shù)接受兩個參數(shù):一個整型的信號編號,以及一個指向用戶定義的信號處理函數(shù)的指針?! ?/p>
此外,signal函數(shù)的返回值是一個指向調用用戶定義信號處理函數(shù)的指針。
sigaction
sigaction函數(shù)可以讀取和修改與指定信號相關聯(lián)的處理動作。
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction { void (*sa_handler)(int); //信號處理方式 void (*sa_sigaction)(int, siginfo_t *, void *); //實時信號的處理方式 暫不討論 sigset_t sa_mask; //額外屏蔽的信號 int sa_flags; void (*sa_restorer)(void); };
signum
是指定信號的編號。
處理方式:
1、若act指針非空,則根據(jù)act結構體中的信號處理函數(shù)來修改該信號的處理動作。
2、若oact指針非 空,則通過oact傳出該信號原來的處理動作。
3、現(xiàn)將原來的處理動作備份到oact里,然后根據(jù)act修改該信號的處理動作。
(注:后兩個參數(shù)都是輸入輸出型參數(shù)?。?/p>
將sa_handler三種可選方式:
1、賦值為常數(shù)SIG_IGN
傳給sigaction
表示忽略信號;
2、賦值為常數(shù)SIG_DFL
表示執(zhí)行系統(tǒng)默認動作;
3、賦值為一個函數(shù)指針表示用自定義函數(shù)捕捉信號,或者說向內核注冊一個信號處理函 數(shù),該函數(shù)返回值為void,可以帶一個int參數(shù),通過參數(shù)可以得知當前信號的編號,這樣就可以用同一個函數(shù)處理多種信號。
(注:這是一個回調函數(shù),不是被main函數(shù)調用,而是被系統(tǒng)所調用)
當某個信號的處理函數(shù)被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數(shù)返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那么 它會被阻塞到當前處理結束為止。
pause
pause函數(shù)使調用進程掛起直到有信號遞達!
#include <unistd.h> int pause(void);
處理方式:
如果信號的處理動作是終止進程,則進程終止,pause
函數(shù)沒有機會返回;
如果信號的處理動作是忽略,則進程繼續(xù)處于掛起狀態(tài),pause
不返回;
如果信號的處理動作是捕捉,則調用了信號處理函數(shù)之后pause
返回-1,errno設置為EINTR。
所以pause
只有出錯的返回值(類似exec函數(shù)家族)。錯誤碼EINTR表示“被信號中斷”。
舉個栗子
1、定義一個鬧鐘,約定times秒后,內核向該進程發(fā)送一個SIGALRM
信號;
2、調用pause
函數(shù)將進程掛起,內核切換到別的進程運行;
3、times秒后,內核向該進程發(fā)送SIGALRM信號,發(fā)現(xiàn)其處理動作是一個自定義函數(shù),于是切回用戶態(tài)執(zhí)行該自定義處理函數(shù);
4、進入sig_alrm
函數(shù)時SIGALRM
信號被自動屏蔽,從sig_alrm
函數(shù)返回時SIGALRM
信號自動解除屏蔽。然后自動執(zhí)行特殊的系統(tǒng)調用sigreturn
再次進入內核,之后再返回用戶態(tài)繼續(xù)執(zhí)行進程的主控制流程(main
函數(shù)調用的mytest
函數(shù))。
5、pause
函數(shù)返回-1,然后調用alarm(0)
取消鬧鐘,調用sigaction
恢復SIGALRM
信號以前的處理 動作。
/************************************************************************* > File Name: Pause.c > Author:Lynn-Zhang > Mail: iynu17@yeah.net > Created Time: Sun 14 Aug 2016 12:27:03 PM CST ************************************************************************/ #include<stdio.h> #include<signal.h> #include<unistd.h> void sig_alarm(int signum) { printf("I am a custom handler!\n"); } void mysleep(unsigned int times) { //注冊兩個信號處理動作 struct sigaction new,old; new.sa_handler=sig_alarm; //信號處理函數(shù) sigemptyset(&new.sa_mask);//不屏蔽任何信號屏蔽字 new.sa_flags=0; //對SIGALRM 信號的默認處理動作修改為自定義處理動作 sigaction(SIGALRM,&new,&old); alarm(times); pause(); //掛起等待 alarm(1); sleep(2); alarm(0); //取消鬧鐘 //恢復SIGALRM 信號到默認處理動作 sigaction(SIGALRM,&old,NULL); alarm(1); sleep(2); } int main() { while(1) { mysleep(2); printf("many seconds passed\n"); printf("###################\n"); } return 0; }
定義一個鬧鐘并掛起等待,收到信號后執(zhí)行自定義處理動作,在沒有恢復默認處理動作前,收到SIGALRM
信號都會按照其自定義處理函數(shù)來處理。恢復自定義處理動作之后收到SIGALRM
信號則執(zhí)行其默認處理動作即終止進程!
總結
以上就是關于Linux下信號與捕捉信號的全部內容,希望本文的內容對大家學習Linux信號能有所幫助,如果有疑問歡迎留言討論。
相關文章
完美解決Linux操作系統(tǒng)下aes解密失敗的問題
以下是針對在Linux操作系統(tǒng)下關于AES解密失敗的問題進行了詳細的分析介紹,需要的朋友可以過來參考下2013-08-08Ubuntu系統(tǒng)安裝使用搜狗輸入法的方法(超簡單)
下面小編就為大家分享一篇Ubuntu系統(tǒng)安裝使用搜狗輸入法的方法(超簡單),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12FreeBSD5.4Release X Windows 安裝筆記 (Freebsd5.4R+Gnome2.10.0)
FreeBSD5.4Release X Windows 安裝筆記 (Freebsd5.4R+Gnome2.10.0)...2007-05-05ipfilter+ipnat包過濾、轉發(fā)和DHCP服務器架構筆記
ipfilter+ipnat包過濾、轉發(fā)和DHCP服務器架構筆記...2007-05-05使用壓縮的方式將Windows下的zip壓縮包上傳到Linux系統(tǒng)的方法解析
這篇文章給大家介紹了如何使用壓縮的方式將Windows下的zip壓縮包上傳到Linux系統(tǒng),本文分步驟給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友參考下吧2019-05-05解決Ubuntu 16.04.6 + Win10 雙系統(tǒng)時間錯誤且不一致問題
這篇文章主要介紹了Ubuntu 16.04.6 + Win10 雙系統(tǒng)時間錯誤且不一致問題的解決方法,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-05-05