?
1.信號(hào)是什么?
信號(hào)其實(shí)就是一個(gè)軟件中斷。
例:
輸入命令,在 Shell 下啟動(dòng)一個(gè)前臺(tái)進(jìn)程。
用戶按下 Ctrl-C,鍵盤(pán)輸入產(chǎn)生一個(gè)硬件中斷。
如果 CPU 當(dāng)前正在執(zhí)行這個(gè)進(jìn)程的代碼,則該進(jìn)程的用戶空間代碼暫停執(zhí)行, CPU 從用戶態(tài)切換到內(nèi)核態(tài)處理硬件中斷。
終端驅(qū)動(dòng)程序?qū)?Ctrl-C 解釋成一個(gè) SIGINT 信號(hào),記在該進(jìn)程的 PCB 中(也可以說(shuō)發(fā)送了一個(gè) SIGINT 信號(hào)給該進(jìn)程)。
當(dāng)某個(gè)時(shí)刻要從內(nèi)核返回到該進(jìn)程的用戶空間代碼繼續(xù)執(zhí)行之前,首先處理 PCB 中記錄的信號(hào),發(fā)現(xiàn)有一個(gè) SIGINT 信號(hào)待處理,而這個(gè)信號(hào)的默認(rèn)處理動(dòng)作是終止進(jìn)程,所以直接終止進(jìn)程而不再返回它的用戶空間代碼執(zhí)行。
在這個(gè)例子中,由 ctrl+c 產(chǎn)生的硬件中斷就是一個(gè)信號(hào)。Ctrl+C 產(chǎn)生的信號(hào)只能發(fā)送給前臺(tái)進(jìn)程,命令后加 & 就可放到后臺(tái)運(yùn)行。Shell 可同時(shí)運(yùn)行一個(gè)前臺(tái)進(jìn)程和任意多個(gè)后臺(tái)進(jìn)程,只有前臺(tái)進(jìn)程才能接受到像 CTRL+C 這種控制鍵產(chǎn)生的信號(hào)。
2.信號(hào)的種類
使用命令查看:
?
kill?-l
?
非可靠信號(hào):1~31 號(hào)信號(hào),信號(hào)可能會(huì)丟失 可靠信號(hào):34~64 號(hào)信號(hào),信號(hào)不可能丟失
SIGHUP:1 號(hào)信號(hào),Hangup detected on controlling terminal or death of controlling process(在控制終端上掛起信號(hào),或讓進(jìn)程結(jié)束),ation:term
SIGINT:2 號(hào)信號(hào),Interrupt from keyboard(鍵盤(pán)輸入中斷,「ctrl + c」 ),action:term
SIGQUIT:3 號(hào)信號(hào),Quit from keyboard(鍵盤(pán)輸入退出「ctrl+ |」 ),action:core,產(chǎn)生 core dump 文件
SIGABRT:6 號(hào)信號(hào),Abort signal from abort(3)(非正常終止,「double free」),action:core
SIGKILL:9 號(hào)信號(hào),Kill signal(殺死進(jìn)程信號(hào)),action:term,該信號(hào)不能被阻塞、忽略、自定義處理
SIGSEGV:11 號(hào)信號(hào),Invalid memory reference(無(wú)效的內(nèi)存引用,解引用空指針、內(nèi)存越界訪問(wèn)),action:core
SIGPIPE:13 號(hào)信號(hào),Broken pipe: write to pipe with no readers(管道中止: 寫(xiě)入無(wú)人讀取的管道,會(huì)導(dǎo)致管道破裂),action:term
SIGCHLD:17 號(hào)信號(hào),Child stopped or terminated(子進(jìn)程發(fā)送給父進(jìn)程的信號(hào),但該信號(hào)為忽略處理的)
SIGSTOP:19 號(hào)信號(hào),Stop process(停止進(jìn)程),action:stop
SIGTSTP:20 號(hào)信號(hào),Stop typed at terminal(終端上發(fā)出的停止信號(hào),「ctrl + z」),action:stop
具體的信號(hào)采取的動(dòng)作和詳細(xì)信息可查看:「man 7 signal」
3.信號(hào)的產(chǎn)生
3.1 硬件產(chǎn)生
硬件產(chǎn)生即通過(guò)終端按鍵產(chǎn)生的信號(hào):
ctrl + c:SIGINT(2),發(fā)送給前臺(tái)進(jìn)程,& 進(jìn)程放到后臺(tái)運(yùn)行,fg 把剛剛放到后臺(tái)的進(jìn)程,再放到前臺(tái)來(lái)運(yùn)行
ctrl + z:SIGTSTP(20),一般不用,除非有特定場(chǎng)景
ctrl + | :SIGQUIT(3),產(chǎn)生 core dump 文件
產(chǎn)生 core dump 文件的條件:
?
當(dāng)前OS一定不要限制core?dump文件的大小,ulimit?-a 磁盤(pán)空間要足夠 如何產(chǎn)生: 3.1?解引用空指針,收到11號(hào)信號(hào),產(chǎn)生core?dump文件 3.2?內(nèi)存訪問(wèn)越界,程序一旦崩潰,就會(huì)收到11號(hào)信號(hào),也就會(huì)產(chǎn)生core?dump文件 3.3 double free,收到6號(hào)信號(hào),并產(chǎn)生core dump。 3.4?free(NULL),不會(huì)崩潰
?
3.2 軟件產(chǎn)生
軟件產(chǎn)生即調(diào)用系統(tǒng)函數(shù)向進(jìn)程發(fā)信號(hào)
kill 函數(shù)
?
#include?#include? int?kill(pid_t?pid,?int?sig); 參數(shù)解釋: pid:進(jìn)程號(hào) sig:要發(fā)送的信號(hào)值 返回值:成功返回0,失敗返回-1,并設(shè)置錯(cuò)誤
?
kill 命令:kill -[信號(hào)] pid,
abort:void abort(void);,收到 6 號(hào)信號(hào),誰(shuí)調(diào)用該函數(shù),誰(shuí)就收到信號(hào)
alarm:unsigned int alarm(unsigned int seconds);,收到 14 號(hào)信號(hào),告訴內(nèi)核在 seconds 秒后給進(jìn)程發(fā)送 SIGALRM 信號(hào),該信號(hào)默認(rèn)處理動(dòng)作為終止當(dāng)前進(jìn)程。
4.信號(hào)的注冊(cè)
信號(hào)注冊(cè)又分為可靠信號(hào)的注冊(cè)和非可靠信號(hào)的注冊(cè)。信號(hào)注冊(cè)實(shí)際上是一個(gè)位圖和一個(gè) sigqueue 隊(duì)列。
4.1 非可靠信號(hào)的注冊(cè)
當(dāng)進(jìn)程收到非可靠信號(hào)時(shí):
將非可靠信號(hào)對(duì)應(yīng)的比特位置為 1
添加 sigqueue 節(jié)點(diǎn)到 sigqueue 隊(duì)列當(dāng)中,但是,在添加 sigqueue 節(jié)點(diǎn)的時(shí)候,隊(duì)列當(dāng)中已然有了該信號(hào)的 sigqueue 節(jié)點(diǎn),則不添加
4.2 可靠信號(hào)的注冊(cè)
當(dāng)進(jìn)程所受到可靠信號(hào)時(shí):
在 sig 位圖中更改信號(hào)對(duì)應(yīng)的比特位為 1 不論之前 sigqueue 隊(duì)列中是否存在該信號(hào)的 sigqueue 節(jié)點(diǎn),都再次添加 sigqueue 節(jié)點(diǎn)到 sigqueue 隊(duì)列當(dāng)中去
5.信號(hào)的注銷
5.1 非可靠信號(hào)的注銷
信號(hào)對(duì)應(yīng)的比特位從 1 置為 0 將該信號(hào)的 sigqueue 節(jié)點(diǎn)從 sigqueue 隊(duì)列當(dāng)中進(jìn)行出隊(duì)操作
5.2 可靠信號(hào)的注銷
將該信號(hào)的 sigqueue 節(jié)點(diǎn)從 sigqueue 隊(duì)列當(dāng)中進(jìn)行出隊(duì)操作 需要判斷 sigqueue 隊(duì)列當(dāng)中是否還有相同的 sigqueue 節(jié)點(diǎn):①?zèng)]有了:信號(hào)比特位從 1 置為 0 ②還有:不會(huì)更改 sig 位圖中的比特位
6.信號(hào)阻塞
6.1 信號(hào)是怎樣阻塞的?
?
信號(hào)的阻塞,并不會(huì)干擾信號(hào)的注冊(cè)。信號(hào)能注冊(cè),但不能被立即處理, 將 block 位圖中對(duì)應(yīng)的信號(hào)比特位置為 1,表示阻塞該信號(hào) 進(jìn)程收到該信號(hào),還是一如既往的注冊(cè) 當(dāng)進(jìn)程進(jìn)入到內(nèi)核空間,準(zhǔn)備返回用戶空間的時(shí)候,調(diào)用 do_signal 函數(shù),就不會(huì)立即去處理該信號(hào)了 當(dāng)該信號(hào)不被阻塞后,就可以進(jìn)行處理了
?
6.2sigprocmask
函數(shù)原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 參數(shù)解釋:
?
how,該做什么樣的操作 SIG_BLOCK:設(shè)置信號(hào)為阻塞 SIG_UNBLOCK:解除信號(hào)阻塞 SIG_SETMASK:替換阻塞位圖 set:用來(lái)設(shè)置阻塞位圖 SIG_BLOCK:設(shè)置某個(gè)信號(hào)為阻塞,block(new)?= block(old)?|?set SIG_UNBLOCK:解除某個(gè)信號(hào)阻塞,block(new)= block(old)?&?(~set) SIG_SETMASK:替換阻塞位圖,block(new)=?set oldset:原來(lái)的阻塞位圖
?
例:下述例子,信號(hào)全部被阻塞,采用 kill -9,將該進(jìn)程結(jié)束掉
?
#include?#include? #include? void?signcallback(int?signumber) { ??printf("change?the?signal?%d ",signumber); } int?main() { ??sigset_t?set; ??sigset_t?oldset; ??sigfillset(&set);//所有比特位全置為1,則信號(hào)全部會(huì)被阻塞 ??sigprocmask(SIG_BLOCK,&set,&oldset); ??while(1) ??{ ????sleep(1); ??} ??return?0; }
?
結(jié)果:此時(shí)發(fā)送信號(hào)是不會(huì)有作用的,采用 kill -9 強(qiáng)殺掉
7.信號(hào)未決
7.1 未決概念
實(shí)際執(zhí)行信號(hào)的處理動(dòng)作稱為信號(hào)遞達(dá)(Delivery),信號(hào)從產(chǎn)生到遞達(dá)之間的狀態(tài),稱為信號(hào)未決(Pending)。進(jìn)程可以選擇阻塞(Block)某個(gè)信號(hào)。被阻塞的信號(hào)產(chǎn)生時(shí)將保持在未決狀態(tài),直到進(jìn)程解除對(duì)此信號(hào)的阻塞,才執(zhí)行遞達(dá)的動(dòng)作。注意,阻塞和忽略是不同的,只要信號(hào)被阻塞就不會(huì)遞達(dá),而忽略是、在遞達(dá)之后可選的一種處理動(dòng)作。
7.2 sigpending
函數(shù)原型:int sigpending(sigset_t *set); 讀取當(dāng)前進(jìn)程的未決信號(hào)集,通過(guò) set 參數(shù)傳出。調(diào)用成功返回 0,出錯(cuò)返回 - 1.
例:
?
#include?#include? #include? void?signalcallback(int?signumber) { ??printf("chang?signumber?%d ",signumber); } void?printsigset(sigset_t?*set) { ??int?i?=?0; ??for(;i?32;i++) ??{ ????if(sigismember(set,i)) ????{ ??????putchar('1'); ????} ????else{ ??????putchar('0'); ????} ??} } int?main() { ??signal(2,signalcallback); ??signal(10,signalcallback); ??sigset_t?set; ??sigset_t?oldset; ??sigset_t?pending; ??sigfillset(&set);//所有比特位全部置為1,則信號(hào)會(huì)全部被阻塞 ??sigprocmask(SIG_BLOCK,&set,&oldset); ??while(1) ??{ ????sigpending(&pending); ????printsigset(&pending); ????sleep(1); ??} ??return?0; }
?
結(jié)果:
8.信號(hào)的處理方式
每個(gè)信號(hào)都有兩個(gè)標(biāo)志位分別表示阻塞和未決,還有一個(gè)函數(shù)指針表示處理動(dòng)作。
?
在上述例子中:
SIGHUP 信號(hào)未阻塞也未產(chǎn)生過(guò),當(dāng)它遞達(dá)時(shí)執(zhí)行默認(rèn)處理動(dòng)作。
SIGINT 信號(hào)產(chǎn)生過(guò),但正在被阻塞,所以暫時(shí)不能遞達(dá)。雖然它的處理動(dòng)作是忽略,但在沒(méi)有解除阻塞之前不能忽略這個(gè)信號(hào),因?yàn)檫M(jìn)程仍有機(jī)會(huì)改變處理動(dòng)作之后再解除阻塞。
SIGQUIT 信號(hào)未產(chǎn)生過(guò),一旦產(chǎn)生 SIGQUIT 信號(hào)將被阻塞,它的處理動(dòng)作是用戶自定義函數(shù) sighandler。
8.1signal 函數(shù)
該函數(shù)可以更改信號(hào)的處理動(dòng)作。
?
typedef?void?(*sighandler_t)(int); sighandler_t?signal(int?signum,?sighandler_t?handler); 參數(shù)解釋: signum:更改的信號(hào)值 handler:函數(shù)指針,要更改的動(dòng)作是什么
?
實(shí)際上,該函數(shù)內(nèi)部也調(diào)用了 sigaction 函數(shù)。
8.2sigaction 函數(shù)
讀取和修改與指定信號(hào)相關(guān)聯(lián)的處理動(dòng)作。
?
int?sigaction(int?signum,?const?struct?sigaction?*act,?struct?sigaction?*oldact);
?
參數(shù)解釋:
?
signum:待更改的信號(hào)值
?
struct sigaction 結(jié)構(gòu)體:
?
void?????(*sa_handler)(int);//函數(shù)指針,保存了內(nèi)核對(duì)信號(hào)的處理方式 void?????(*sa_sigaction)(int,?siginfo_t?*,?void?*);// sigset_t???sa_mask;//保存的是當(dāng)進(jìn)程在處理信號(hào)的時(shí)候,收到的信號(hào) int????????sa_flags;//SA_SIGINFO,OS在處理信號(hào)的時(shí)候,調(diào)用的就是sa_sigaction函數(shù)指針當(dāng)中 //保存的值0,在處理信號(hào)的時(shí)候,調(diào)用sa_handler保存的函數(shù) void?????(*sa_restorer)(void);
?
例:
?
#include?#include? #include? void?signcallback(int?signumber) { ??printf("change?signumber?%d ",signumber); } int?main() { ??struct?sigaction?act;//act為入?yún)???sigemptyset(&act.sa_mask); ??act.sa_flags?=?0; ??act.sa_handler?=?signcallback; ??struct?sigaction?oldact;//oldact為出參 ??sigaction(3,&act,&oldact); ??while(1) ??{ ????sleep(1); ??} ??return?0; }
?
結(jié)果:
8.3 自定義信號(hào)處理的流程
「task_struct」 結(jié)構(gòu)體中有一個(gè)「struct sighand_struct」 結(jié)構(gòu)體。
「struct sighand_struct」 結(jié)構(gòu)體有一個(gè) 「struct k_sigaction action[_NSIG]」 結(jié)構(gòu)體數(shù)組。
該數(shù)組中,其中的 「_sighandler_t sa_handler」 保存的是信號(hào)的處理方式,通過(guò)改變其指向,可以實(shí)現(xiàn)我們對(duì)自定義信號(hào)的處理。
9.信號(hào)的捕捉
9.1 信號(hào)捕捉的條件
如果信號(hào)的處理動(dòng)作是用戶自定義函數(shù),在信號(hào)遞達(dá)時(shí)就調(diào)用這個(gè)函數(shù),這就稱為信號(hào)捕捉。
9.2 信號(hào)捕捉流程
內(nèi)核態(tài)返回用戶態(tài)會(huì)調(diào)用 do_signal 函數(shù),兩種情況:
無(wú)信號(hào):sys_return 函數(shù),返回用戶態(tài)
有信號(hào):先處理信號(hào),信號(hào)返回,再調(diào)用 do_signal 函數(shù) 例:
程序注冊(cè)了 SIGQUIT 信號(hào)的處理函數(shù) sighandler。
當(dāng)前正在執(zhí)行 main 函數(shù),這時(shí)發(fā)生中斷或異常切換到內(nèi)核態(tài)。
在中斷處理完畢后要返回用戶態(tài)的 main 函數(shù)之前檢查到有信號(hào) SIGQUIT 遞達(dá)。
內(nèi)核決定返回用戶態(tài)后不是恢復(fù) main 函數(shù)的上下文繼續(xù)執(zhí)行,而是執(zhí)行 sighandler 函數(shù), sighandler 和 main 函數(shù)使用不同的堆??臻g,它們之間不存在調(diào)用和被調(diào)用的關(guān)系,是兩個(gè)獨(dú)立的控制流程。
sighandler 函數(shù)返回后自動(dòng)執(zhí)行特殊的系統(tǒng)調(diào)用 sigreturn 再次進(jìn)入內(nèi)核態(tài)。
如果沒(méi)有新的信號(hào)要遞達(dá),這次再返回用戶態(tài)就是恢復(fù) main 函數(shù)的上下文繼續(xù)執(zhí)行了。
10.常用信號(hào)集操作函數(shù)
?
int?sigemptyset(sigset_t?*set);://將比特位圖全置為0 int?sigfillset(sigset_t?*set);//將比特位圖全置為1 int?sigaddset(sigset_t?*set,?int?signum);//將該set位圖,多少號(hào)信號(hào)置為1 int?sigdelset(sigset_t?*set,?int?signum);//將該set位圖,多少號(hào)信號(hào)置為0 int?sigismember(const?sigset_t?*set,?int?signum);//信號(hào)signum是否是set位圖中的信號(hào)
?
11.SIGCHLD 信號(hào)
該信號(hào)是子進(jìn)程在結(jié)束是發(fā)送給父進(jìn)程的信號(hào),但是該信號(hào)的處理方式是默認(rèn)處理的。父進(jìn)程對(duì)子進(jìn)程發(fā)送過(guò)來(lái)的 SIGCHLD 信號(hào)進(jìn)行了忽略處理,就會(huì)導(dǎo)致子進(jìn)程成為僵尸進(jìn)程。
可以自定義該信號(hào)的處理方式:
?
#include?#include? #include? #include? #include? #include? void?signcallback(int?signumber) { ??printf("change?signal?%d ",signumber); ??wait(NULL); } int?main() { ??signal(17,signcallback); ??pid_t?pid?=?fork(); ??if(pid?0) ??{ ????perror("fork"); ????return?-1; ??} ??else?if(pid?==?0) ??{ ????printf("I?am?child "); ????sleep(1); ????exit(12); ??} ??else{ ????while(1) ????{ ??????sleep(1); ????} ??} ??return?0; }
?
指令查看后臺(tái):「ps aux | grep ./fork」
評(píng)論