理論部分
簡單來說
信號是用來通知進(jìn)程發(fā)生了異步事件
比如标捺,在終端運(yùn)行程序氛堕,按下ctrl+c就產(chǎn)生一個(gè)中端信號顾瞪,大概過程是這樣的:
- 用戶從shell下啟動(dòng)一個(gè)進(jìn)程掷邦;
- 用戶按下ctrl+c汪诉,產(chǎn)生一個(gè)硬件中端信號废恋;
- cpu從用戶態(tài)切換到進(jìn)程態(tài),處理中端扒寄;(這個(gè)過程當(dāng)年讀書的時(shí)候在《計(jì)算機(jī)組成原理》看過)
- 終端驅(qū)動(dòng)程序?qū)trl+c解釋成一個(gè)SIGINT信號鱼鼓,記在該進(jìn)程的PCB中(也可以說發(fā)送了一個(gè)SIGINT信號給該進(jìn)程)。
- 當(dāng)某個(gè)時(shí)刻要從內(nèi)核返回到該進(jìn)程的用戶空間代碼繼續(xù)執(zhí)行之前,首先處理PCB中記錄的信號该编,發(fā)現(xiàn)有一個(gè)SIGINT信號待處理迄本,而這個(gè)信號的默認(rèn)處理動(dòng)作是終止進(jìn)程,所以直接終止進(jìn)程而不再返回它的用戶空間代碼執(zhí)行课竣。
前臺(tái)進(jìn)程在運(yùn)行過程中用戶隨時(shí)可能按下ctrl+c而產(chǎn)生一個(gè)信號嘉赎,也就是說該進(jìn)程的用戶空間代碼執(zhí)行到任何地方都有可能收到SIGINT信號而終止,所以信號相對于進(jìn)程的控制流程來說是異步的于樟。
kill -l 可以查看系統(tǒng)定義的信號公条,這些定義是在signal.h中。
也可以通過man 7 signal來查看迂曲。
其中Action代表默認(rèn)處理動(dòng)作靶橱,
Term : 終止當(dāng)前進(jìn)程
Core : 終止并且Core Dump
Ing : 忽略
Stop : 停止當(dāng)前進(jìn)程
Cont: 繼續(xù)執(zhí)行先前停止的進(jìn)程
注:當(dāng)一個(gè)進(jìn)程要異常終止時(shí),可以選擇把進(jìn)程的用戶空間內(nèi)存數(shù)據(jù)全部保存到磁盤上路捧,文件名通常是core关霸,這叫做Core Dump。
產(chǎn)生信號的條件主要是:
- 終端的按鍵鬓长,如ctrl+c產(chǎn)生SIGINT信號谒拴,ctrl+\產(chǎn)生SIGQUIT信號,ctrl+z產(chǎn)生SIGTSTP信號涉波。
- 硬件異常產(chǎn)生信號英上。
- 一個(gè)進(jìn)程調(diào)用kill (man 2 kill) 可以發(fā)送信號給另一個(gè)進(jìn)程炭序。
- 可以調(diào)用kill (man 1 kill)。
- 當(dāng)內(nèi)核檢測到某種軟件條件發(fā)生時(shí)也可以通過信號通知進(jìn)程苍日,例如鬧鐘超時(shí)產(chǎn)生SIGALRM信號惭聂,向讀端已關(guān)閉的管道寫數(shù)據(jù)時(shí)產(chǎn)生SIGPIPE信號。
用戶程序可以調(diào)用sigaction(man 2 sigaction)函數(shù)告訴內(nèi)核如何處理某種信號相恃,可選的操作有
- 忽略
- 執(zhí)行默認(rèn)動(dòng)作
- 提供一個(gè)信號處理函數(shù)辜纲,要求內(nèi)核在處理該信號時(shí)切換到用戶態(tài)執(zhí)行這個(gè)處理函數(shù),這種方式稱為捕捉一個(gè)信號拦耐。
發(fā)送信號可以用:
#include <signal.h>
int kill(pid_t pid, int signo);//向其他進(jìn)程發(fā)信號
int raise(int signo);//向自己發(fā)信號
#include <stdlib.h>
void abort(void);//使當(dāng)前進(jìn)程接收到SIGABRT信號而異常終止
#include <unistd.h>
unsigned int alarm(unsigned int seconds);//內(nèi)核在seconds秒之后給當(dāng)前進(jìn)程發(fā)SIGALRM信號
信號從產(chǎn)生到遞達(dá)之間的狀態(tài)耕腾,稱為信號未決。
每個(gè)信號都有兩個(gè)標(biāo)志位分別表示阻塞和未決杀糯,還有一個(gè)函數(shù)指針表示處理動(dòng)作
信號集操作函數(shù)
sigset_t類型對于每種信號用一個(gè)bit表示“有效”或“無效”狀態(tài)扫俺,至于這個(gè)類型內(nèi)部如何存儲(chǔ)這些bit則依賴于系統(tǒng)實(shí)現(xiàn),從使用者的角度是不必關(guān)心的固翰,使用者只能調(diào)用以下函數(shù)來操作sigset_t變量狼纬,而不應(yīng)該對它的內(nèi)部數(shù)據(jù)做任何解釋。
#include<signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
函數(shù)sigemptyset初始化set所指向的信號集骂际,使其中所有信號的對應(yīng)bit清零疗琉,表示該信號集不包含任何有效信號。函數(shù)sigfillset初始化set所指向的信號集歉铝,使其中所有信號的對應(yīng)bit置位盈简,表示該信號集的有效信號包括系統(tǒng)支持的所有信號。注意太示,在使用sigset_t類型的變量之前送火,一定要調(diào)用sigemptyset或sigfillset做初始化,使信號集處于確定的狀態(tài)先匪。初始化sigset_t變量之后就可以在調(diào)用sigaddset和sigdelset在該信號集中添加或刪除某種有效信號种吸。這四個(gè)函數(shù)都是成功返回0,出錯(cuò)返回-1呀非。sigismember是一個(gè)布爾函數(shù)坚俗,用于判斷一個(gè)信號集的有效信號中是否包含某種信號,若包含則返回1岸裙,不包含則返回0猖败,出錯(cuò)返回-1。
#include<signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);//讀取或更改進(jìn)程的信號屏蔽字
#include<signal.h>
int sigpending(sigset_t *set);//讀取當(dāng)前進(jìn)程的未決信號集降允,通過set參數(shù)傳出
信號補(bǔ)捉過程
#include<signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);//讀取和修改與指定信號相關(guān)聯(lián)的處理動(dòng)作
act和oact指向sigaction結(jié)構(gòu)體
struct sigaction {
void (*sa_handler)(int); /* addr of signal handler, */
/* or SIG_IGN, or SIG_DFL */
sigset_t sa_mask; /* additional signals to block */
int sa_flags; /* signal options, Figure 10.16 */
/* alternate handler */
void (*sa_sigaction)(int, siginfo_t *, void *);
};
將sa_handler賦值為常數(shù)SIG_IGN傳給sigaction表示忽略信號恩闻,賦值為常數(shù)SIG_DFL表示執(zhí)行系統(tǒng)默認(rèn)動(dòng)作,賦值為一個(gè)函數(shù)指針表示用自定義函數(shù)捕捉信號剧董,或者說向內(nèi)核注冊了一個(gè)信號處理函數(shù)幢尚,該函數(shù)返回值為void破停,可以帶一個(gè)int參數(shù),通過參數(shù)可以得知當(dāng)前信號的編號尉剩,這樣就可以用同一個(gè)函數(shù)處理多種信號真慢。顯然,這也是一個(gè)回調(diào)函數(shù)理茎,不是被main函數(shù)調(diào)用黑界,而是被系統(tǒng)所調(diào)用。
當(dāng)某個(gè)信號的處理函數(shù)被調(diào)用時(shí)皂林,內(nèi)核自動(dòng)將當(dāng)前信號加入進(jìn)程的信號屏蔽字朗鸠,當(dāng)信號處理函數(shù)返回時(shí)自動(dòng)恢復(fù)原來的信號屏蔽字,這樣就保證了在處理某個(gè)信號時(shí)础倍,如果這種信號再次產(chǎn)生童社,那么它會(huì)被阻塞到當(dāng)前處理結(jié)束為止。如果在調(diào)用信號處理函數(shù)時(shí)著隆,除了當(dāng)前信號被自動(dòng)屏蔽之外,還希望自動(dòng)屏蔽另外一些信號呀癣,則用sa_mask字段說明這些需要額外屏蔽的信號美浦,當(dāng)信號處理函數(shù)返回時(shí)自動(dòng)恢復(fù)原來的信號屏蔽字。
#include<unistd.h>
int pause(void);//使調(diào)用進(jìn)程掛起直到有信號遞達(dá)
當(dāng)捕捉到信號時(shí)项栏,不論進(jìn)程的主控制流程當(dāng)前執(zhí)行到哪兒浦辨,都會(huì)先跳到信號處理函數(shù)中執(zhí)行,從信號處理函數(shù)返回后再繼續(xù)執(zhí)行主控制流程沼沈。信號處理函數(shù)是一個(gè)單獨(dú)的控制流程流酬,因?yàn)樗椭骺刂屏鞒淌钱惒降模卟淮嬖谡{(diào)用和被調(diào)用的關(guān)系列另,并且使用不同的堆椦刻冢空間。引入了信號處理函數(shù)使得一個(gè)進(jìn)程具有多個(gè)控制流程页衙,如果這些控制流程訪問相同的全局資源(全局變量摊滔、硬件資源等),就有可能出現(xiàn)沖突店乐。
理解部分
關(guān)于信號艰躺,之前接觸的比較多的是kill這個(gè)命令,現(xiàn)在看來眨八,發(fā)送信號給進(jìn)程是一種交互方式腺兴。當(dāng)進(jìn)程中實(shí)現(xiàn)了補(bǔ)捉信號以及處理的函數(shù)時(shí),就可以與用戶進(jìn)行簡單的交互廉侧,之所以簡單是因?yàn)樾盘枖?shù)量很少页响。所以一般用做進(jìn)程的啟動(dòng)篓足,重啟,停止拘泞,錯(cuò)誤處理等等纷纫。若使用其他的信息傳遞方式我想也是可以實(shí)現(xiàn)的,就是麻煩了一些陪腌。
下面來實(shí)現(xiàn)一個(gè)簡單的例子辱魁,后臺(tái)跑一個(gè)進(jìn)程,打印出發(fā)送給該進(jìn)程的信號诗鸭。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void show_handler(int sig)
{
printf("I got signal %d\n", sig);
}
int main(void)
{
int i = 0;
struct sigaction act, oldact;
act.sa_handler = show_handler;
sigaddset(&act.sa_mask, SIGQUIT);
act.sa_flags = 0;
sigaction(SIGINT, &act, &oldact);
sigaction(SIGCONT, &act, &oldact);
while(1) {
sleep(1);
i++;
}
}
貼上運(yùn)行圖
注:sigaction 是不能接收SIGKILL 和 SIGSTOP的信號