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ū)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(鍵盤(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)
#include <sys/types.h> #include <signal.h> 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 <stdio.h> #include <signal.h> #include <unistd.h> void signcallback (int signumber) { printf ('change the signal %d\n' ,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 <stdio.h> #include <unistd.h> #include <signal.h> void signalcallback (int signumber) { printf ('chang signumber %d\n' ,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 <stdio.h> #include <unistd.h> #include <signal.h> void signcallback (int signumber) { printf ('change signumber %d\n' ,signumber); }int main () { struct sigaction act ;//act為入?yún)?/span> 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 <stdio.h> #include <unistd.h> #include <signal.h> #include <string.h> #include <sys/wait.h> #include <stdlib.h> void signcallback (int signumber) { printf ('change signal %d\n' ,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\n' ); sleep(1 ); exit (12 ); } else { while (1 ) { sleep(1 ); } } return 0 ; }
指令查看后臺(tái):ps aux | grep ./fork