1.信號(hào)是什么?
信號(hào)其實(shí)就是一個(gè)軟件中斷。
例:
輸入命令,在Shell下啟動(dòng)一個(gè)前臺(tái)進(jìn)程。
用戶按下Ctrl-C,鍵盤輸入產(chǎn)生一個(gè)硬件中斷。
如果CPU當(dāng)前正在執(zhí)行這個(gè)進(jìn)程的代碼,則該進(jìn)程的用戶空間代碼暫停執(zhí)行, CPU從用戶態(tài)切換到內(nèi)核態(tài)處理硬件中斷。
終端驅(qū)動(dòng)程序?qū)trl-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(鍵盤輸入中斷,ctrl + c ),action:term
SIGQUIT:3號(hào)信號(hào),Quit from keyboard(鍵盤輸入退出,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)存越界訪問),action:core
SIGPIPE:13號(hào)信號(hào),Broken pipe: write to pipe with no readers(管道中止: 寫入無(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)生即通過終端按鍵產(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一定不要限制coredump文件的大小,ulimit-a 磁盤空間要足夠 如何產(chǎn)生: 3.1解引用空指針,收到11號(hào)信號(hào),產(chǎn)生coredump文件 3.2內(nèi)存訪問越界,程序一旦崩潰,就會(huì)收到11號(hào)信號(hào),也就會(huì)產(chǎn)生coredump文件 3.3 double free,收到6號(hào)信號(hào),并產(chǎn)生core dump。 3.4free(NULL),不會(huì)崩潰
3.2軟件產(chǎn)生
軟件產(chǎn)生即調(diào)用系統(tǒng)函數(shù)向進(jìn)程發(fā)信號(hào)
kill函數(shù)
#include #include intkill(pid_tpid,intsig); 參數(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 voidsigncallback(intsignumber) { printf("changethesignal%d ",signumber); } intmain() { sigset_tset; sigset_toldset; sigfillset(&set);//所有比特位全置為1,則信號(hào)全部會(huì)被阻塞 sigprocmask(SIG_BLOCK,&set,&oldset); while(1) { sleep(1); } return0; }
結(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)集,通過set參數(shù)傳出。調(diào)用成功返回0,出錯(cuò)返回-1.
例:
#include #include #include voidsignalcallback(intsignumber) { printf("changsignumber%d ",signumber); } voidprintsigset(sigset_t*set) { inti=0; for(;i32;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)生過,當(dāng)它遞達(dá)時(shí)執(zhí)行默認(rèn)處理動(dòng)作。
SIGINT信號(hào)產(chǎn)生過,但正在被阻塞,所以暫時(shí)不能遞達(dá)。雖然它的處理動(dòng)作是忽略,但在沒有解除阻塞之前不能忽略這個(gè)信號(hào),因?yàn)檫M(jìn)程仍有機(jī)會(huì)改變處理動(dòng)作之后再解除阻塞。
SIGQUIT信號(hào)未產(chǎn)生過,一旦產(chǎn)生SIGQUIT信號(hào)將被阻塞,它的處理動(dòng)作是用戶自定義函數(shù)sighandler。
8.1signal函數(shù)
該函數(shù)可以更改信號(hào)的處理動(dòng)作。
typedefvoid(*sighandler_t)(int); sighandler_tsignal(intsignum,sighandler_thandler); 參數(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)作。
intsigaction(intsignum,conststructsigaction*act,structsigaction*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_tsa_mask;//保存的是當(dāng)進(jìn)程在處理信號(hào)的時(shí)候,收到的信號(hào) intsa_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 voidsigncallback(intsignumber) { printf("changesignumber%d ",signumber); } intmain() { structsigactionact;//act為入?yún)?sigemptyset(&act.sa_mask); act.sa_flags=0; act.sa_handler=signcallback; structsigactionoldact;//oldact為出參 sigaction(3,&act,&oldact); while(1) { sleep(1); } return0; }
結(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)的處理方式,通過改變其指向,可以實(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ù)使用不同的堆棧空間,它們之間不存在調(diào)用和被調(diào)用的關(guān)系,是兩個(gè)獨(dú)立的控制流程。
sighandler函數(shù)返回后自動(dòng)執(zhí)行特殊的系統(tǒng)調(diào)用sigreturn再次進(jìn)入內(nèi)核態(tài)。
如果沒有新的信號(hào)要遞達(dá),這次再返回用戶態(tài)就是恢復(fù)main函數(shù)的上下文繼續(xù)執(zhí)行了。
10.常用信號(hào)集操作函數(shù)
intsigemptyset(sigset_t*set);://將比特位圖全置為0 intsigfillset(sigset_t*set);//將比特位圖全置為1 intsigaddset(sigset_t*set,intsignum);//將該set位圖,多少號(hào)信號(hào)置為1 intsigdelset(sigset_t*set,intsignum);//將該set位圖,多少號(hào)信號(hào)置為0 intsigismember(constsigset_t*set,intsignum);//信號(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ā)送過來(lái)的SIGCHLD信號(hào)進(jìn)行了忽略處理,就會(huì)導(dǎo)致子進(jìn)程成為僵尸進(jìn)程。
可以自定義該信號(hào)的處理方式:
#include #include #include #include #include #include voidsigncallback(intsignumber) { printf("changesignal%d ",signumber); wait(NULL); } intmain() { signal(17,signcallback); pid_tpid=fork(); if(pid0) ??{ ????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
審核編輯:湯梓紅
-
cpu
+關(guān)注
關(guān)注
68文章
11051瀏覽量
216242 -
信號(hào)
+關(guān)注
關(guān)注
11文章
2846瀏覽量
77986 -
中斷
+關(guān)注
關(guān)注
5文章
904瀏覽量
42569
原文標(biāo)題:一篇文章徹底搞定信號(hào)!
文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
一文徹底掌握MOS管
徹底搞定C指針
如何搞定PCB設(shè)計(jì)的差分信號(hào)
【178頁(yè)完整版】輕松搞定C語(yǔ)言(提高篇)!!
輕松搞定C語(yǔ)言(提高篇)
徹底搞定C指針_姚云飛

幾招搞定iPhone手機(jī)WiFi信號(hào)不穩(wěn)定
難搞的工業(yè)信號(hào)調(diào)節(jié),零漂移運(yùn)算放大器是如何搞定的?

評(píng)論