使用場景:1、為了并發(fā)蹬叭,中斷處理其它事件藕咏,1、進程間通信
1秽五、中斷
中止(注意不是終止)當(dāng)前正在執(zhí)行的程序孽查,
轉(zhuǎn)而執(zhí)行其它任務(wù)。
硬件中斷:來自硬件設(shè)備的中斷坦喘。
軟件中斷:來自其它程序的中斷盲再。
2、信號是一種軟件中斷
信號提供了一種以異步方式執(zhí)行任務(wù)的機制瓣铣。
3答朋、常見的信號
SIGHUP(1):連接斷開信號
如果終端接口檢測一個連接斷開,則將此信號發(fā)送給與該終端相關(guān)的控制進程棠笑。
SIGINT :Ctrl+C
SIGQUIT: Ctrl+
SIGKILL:kill -9
SIGTERM: kill
SIGCHLD:
4梦碗、不可靠信號(又叫非實時信號)
1、那些建立在早期機制傷的信號被稱為“不可靠信號”。
小于SIGRTMIN(34)的信號都是不可靠信號洪规。
2印屁、不支持排隊,可能會丟失斩例。同一個信號產(chǎn)生多次库车,
進程可能只收到一次該信號。
3樱拴、進程每次處理完這些信號后柠衍,對相應(yīng)信號的響應(yīng)被自動恢復(fù)為默認動作,
除非顯式地通過signal函數(shù)重新設(shè)置一次信號處理機制晶乔。
5珍坊、可靠信號(實時信號,排隊信號)
2正罢、支持排隊阵漏,不會丟失
3、無論可靠信號還是不可靠信號
都可以通過sigqueue/qigaction函數(shù)發(fā)送/安裝
以獲得比其早期版本kill/signal函數(shù)更可靠的使用效果
6翻具、信號的來源
1履怯、硬件異常:除0、無效的內(nèi)存訪問等裆泳。
這些異常通常被硬件(驅(qū)動)檢測到叹洲,并通知系統(tǒng)內(nèi)核。
系統(tǒng)內(nèi)核再向引發(fā)這些異常的進程遞送相應(yīng)的信號工禾。
2运提、軟件異常:通過kill/raise/alarm/setitimer/sigqueue函數(shù)產(chǎn)生的信號。
7闻葵、信號處理
1民泵、忽略
2、終止進程槽畔。
3栈妆、終止進程同時產(chǎn)生core文件
4、捕獲并處理厢钧。當(dāng)信號發(fā)生時鳞尔,內(nèi)核會調(diào)用一個事先注冊好的用戶函數(shù)(信號處理函數(shù))
二、signal
|#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal (int signum,
sighandler_t handler);
signum - 信號碼坏快,也可以使用系統(tǒng)預(yù)定義的常量宏铅檩,如SIGINT等。
handler - 信號處理函數(shù)指針或以下常量莽鸿;
SIG_IGN:忽略該信號;
SIG_DFL:默認處理;
成功返回原來的信號處理函數(shù)指針或SIG_IGN/SIG_DFL常量祥得,失敗返回SIG_ERR兔沃。
1、在某些unix系統(tǒng)上级及,
通過signal函數(shù)注冊的信號處理函數(shù)只一次有效乒疏,
即內(nèi)核每次調(diào)用該信號處理函數(shù)前,
會將對該信號的處理自動恢復(fù)為默認方式饮焦。
為了獲得更持久有效的信號處理怕吴,可以在信號處理函數(shù)中再次調(diào)用signal函數(shù),
重新注冊一次县踢。
例如:
void sigint (int signum){
...
signal(SIGINT, sigint);
}
int main(void){
...
signal(SIGINT, sigint);
}
三转绷、子進程的信號處理
1、子進程會繼承父進程的信號處理方式硼啤,只到子進程調(diào)用exec函數(shù)议经。
2、子進程調(diào)用exec函數(shù)后
exec函數(shù)將被父進程設(shè)置為捕獲的信號恢復(fù)至默認處理谴返,其余保持不變煞肾。
四、發(fā)送信號
1嗓袱、鍵盤
Ctrl+C SIGINT 終端中斷
Ctrl+\ SIGQUIT 終端退出
Ctrl+Z SIGTSTP 終端暫停
2籍救、錯誤
除0 SIGFPE 算術(shù)異常
非法內(nèi)存訪問 SIGSEGV 段錯誤
硬件故障 SIGBUS 總線錯誤
3、命令
kill -信號 進程號
4渠抹、函數(shù)
|#include <signal.h>
int kill(pid_t pid, int sig) //不等待信號處理完就返回钧忽,異步處理
成功返回0 ,失敗返回-1
pid >0 -向pid進程發(fā)送sig信號
pid=0 - 向同進程組的所有進程發(fā)送信號
pid = -1 -向所有進程發(fā)送信號逼肯,前提是有權(quán)限
pid < -1 -向絕對值等于pid的進程組發(fā)送信號
0信號為空信號耸黑。
若sig取0,則kill函數(shù)仍會執(zhí)行錯誤檢查篮幢,但并不實際發(fā)送信號大刊。常被用來確定一個
進程是否存在。
向一個不存在的進程發(fā)送信號三椿,會返回-1缺菌,且errno為ESRCH。
int raise (int sig)
向調(diào)用進程自身發(fā)送sig信號搜锰。成功返回0伴郁,失敗返回-1。kill函數(shù)也可以實現(xiàn)此功能
|#include <signal.h>
|#include <stdio.h>
|#include <stdlib.h>
|#include <unistd.h>
void sigint (int signum){
printf("%u進程蛋叼,永別了焊傅!\n", getpid());
exit(0);
//raise(signum); 這里不是遞歸調(diào)用剂陡,raise返回,sigint也返回狐胎,函數(shù)棧并不一致增長鸭栖,會有波動,所以程序不會爆掉握巢。
}
int main(void){
if(signal(SIGINT, sigint) == SIG_ERR){
perror("signal");
return -1;
}
printf("%u進程晕鹊,我要自殺。暴浦。溅话。\n", getpid());
if(raise(SIGINT) == -1){
perror("raise");
return -1;
};
}
五、pause 暫停函數(shù)
int pause(void);
1歌焦、使調(diào)用進程進入睡眠狀態(tài)飞几,不再占有cpu,直到有信號終止該進程或被捕獲同规。
2循狰、只有調(diào)用了信號處理函數(shù)并從中返回以后,該函數(shù)才會返回-1券勺;
3绪钥、該函數(shù)要么不返回(未捕獲到信號),要么返回-1(被信號中斷)关炼,errno為EINTR程腹。
4、相當(dāng)于沒有時間限制的sleep函數(shù)儒拂。
|#include <stdio.h>
|#include <signal.h>
|#include <unistd.h>
|#include <sys/wait.h>
void sigint (int signum){
printf("%u進程寸潦,收到SIGINT信號!\n", getpid());
}
int main(void){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if (pid == 0)
{
if (signal(SIGINT, sigint) == -1){ //沒信號不阻塞
perror("signal");
return -1;
}
printf("%u進程:我是子進程 , 大睡ing....\n",getpid());
pause();
printf("%u進程:我是子進程 , 我被喚醒了......\n",getpid());
return 0 ;
}
sleep(1);
printf("%u進程:我是父進程 , 向%u發(fā)送信號......\n",getpid(), pid);
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
if((pid = wait(0) == -1){ //回收僵尸社痛,子進程退出见转,wait返回
perror("wait");
return -1;
}
printf("%u進程:%u進程已退出。\n", getpid(), pid);
return 0;
}
六蒜哀、sleep 跟pause函數(shù)功能差不多
|#include <unistd.h>
unsigned int sleep(unsigned int seconds);
1斩箫、使調(diào)用進程睡眠seconds秒,除非有信號終止該進程或被捕獲撵儿。
2乘客、只有睡夠seconds秒,或調(diào)用了信號處理函數(shù)并從中返回以后淀歇,該函數(shù)才會返回易核。
3、該函數(shù)要么返回0(睡夠)浪默,
要么返回剩余秒數(shù)(被信號中斷)牡直。
4缀匕、相當(dāng)于有時間限制的pause函數(shù)。
|#include <stdio.h>
|#include <signal.h>
|#include <unistd.h>
|#include <sys/wait.h>
void sigint (int signum){
printf("%u進程井氢,收到SIGINT信號弦追!\n", getpid());
}
int main(void){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if (pid == 0)
{
if (signal(SIGINT, sigint) == -1){ //沒信號不阻塞
perror("signal");
return -1;
}
printf("%u進程:我是子進程 , 小睡10秒....\n",getpid());
unsigned int left = sleep(10);
printf("%u進程:我是子進程 , 還剩%u秒沒睡岳链,即將退出......\n",getpid()花竞,left);
return 0 ;
}
sleep(3);
printf("%u進程:我是父進程 , 向%u發(fā)送信號......\n",getpid(), pid);
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
if((pid = wait(0) == -1){ //回收僵尸,子進程退出掸哑,wait返回
perror("wait");
return -1;
}
printf("%u進程:%u進程已退出约急。\n", getpid(), pid);
return 0;
}
七、usleep 以微秒為單位
int usleep (useconds_t usec);
使調(diào)用進程睡眠usec微秒苗分,除非有信號終止該進程或被捕獲厌蔽。
成功返回0,失敗返回-1.
七摔癣、alarm 等于設(shè)置一個定時器
|#include <unistd.h>
unsigned int alarm(unsigned int seconds);
1奴饮、使內(nèi)核在seconds秒之后,向調(diào)用進程發(fā)送SIGALRM(14)鬧鐘信號择浊。
2戴卜、SIGALRM信號的默認處理是終止進程。
3琢岩、若之前已設(shè)過定時且尚未超時投剥,則調(diào)用該函數(shù)會重新設(shè)置定時,并返回之前定時的剩余時間担孔。
4江锨、seconds取0表示取消之前設(shè)過且尚未超時的定時。
|#include <stdio.h>
|#include <time.h>
|#incude <signal.h>
void sigalrm(int signum){
time_t t = time(NULL);
struct tm* lt = localtime(&t);
printf("\r%02d:%02d:%02d", lt->tm_hour, lt->tm_min, lt->tm_sec);
//'\r' 回車糕篇,回到當(dāng)前行的行首啄育,而不會換到下一行,如果接著輸出的話拌消,本行以前的內(nèi)容會被逐一覆蓋挑豌;
alarm(1);
}
int main(void){
setbuf(stdout, 0);
if(signal(SIGALRM,sigalrm) == -1){
perror("signal");
return 0;
}
sigalrm(SIGALRM);
FOR (;;){
}
return 0;
}
結(jié)果:每秒每秒更新時間
unsigned int remain = alarm(10);
sleep(2);
remain = alarm(5); //此時remain = 8
remain = alarm(0); //取消定時器
八、信號集與信號阻塞
1拼坎、信號集
1)多個信號的捷類型:
sigset_t浮毯,128個二進制位(實際預(yù)編譯查看是128字節(jié)),每個位代表一個信號泰鸡。
2)相關(guān)函數(shù)
|#include <signal.h>
//將信號集中的全部信號位置1
int sigfillset(sigset_t* set);
//將信號集中的全部信號位清0
int sigemptyset(sigset_t* set);
//將信號集set中的signum信號對應(yīng)位置1
int sigaddset(sigset_t* set, int signum);
//將信號集set中的signum信號對應(yīng)位清0
int sigdelset(sigset_t* set, int signum);
成功返回0债蓝,失敗返回-1
//判斷信號集set中與signum對應(yīng)的位是否為1,是1則返回1盛龄,否則返回0
int sigismember(const sigset_t* set, int signum);
|#include <stdio.h>
|#include <signal.h>
int main(viod){
sigset_t set; //預(yù)編譯后看到是一個unsigned long int的32個元素數(shù)組128個字節(jié)
print("%u\n", sizeof(set));
sigfillset(&set);
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigdelset(&set, SIGINT);
if(sigismember(&set, SIGINT))
printf("有\(zhòng)n");
else
printf("沒有\(zhòng)n");
return 0;
}
2饰迹、信號屏蔽(信號阻塞)
1)芳誓、當(dāng)信號產(chǎn)生時,系統(tǒng)內(nèi)核會在其所維護的進程表中啊鸭,為特定的進程設(shè)置一個
與該信號相對應(yīng)的標(biāo)志位锹淌,這個過程稱為遞送。
2)赠制、信號從產(chǎn)生到完成遞送之間存在一定的時間間隔赂摆。
處于這段時間間隔中的信號狀態(tài),稱為未決钟些。
3)烟号、每一個進程都有一個信號掩碼(signal mask)
它實際上是一個信號集,
其中包括了所有需要被屏蔽的信號政恍。 所以這些信號就變成了未決狀態(tài)的信號了汪拥。
4)、可以通過sigprocmask函數(shù)篙耗,
檢測和修改調(diào)用進程的信號掩碼迫筑。
也可以通過sigpending函數(shù),
獲取調(diào)用進程當(dāng)前處于未決狀態(tài)的信號集宗弯。
5)脯燃、當(dāng)進程執(zhí)行諸如更新數(shù)據(jù)庫等敏感任務(wù)時,
可能不希望被某些信號中斷罕伯。
這時可以暫時屏蔽(注意不是忽略)這些信號曲伊,
使其停留在未決狀態(tài),
待任務(wù)完成以后追他,再回過頭來處理這些信號坟募。
6)、在信號處理函數(shù)的執(zhí)行過程中邑狸,
這個正在被處理的信號總是處于信號掩碼中懈糯。
這就避免了同樣的信號同時再調(diào)用同一個函數(shù)。
|#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
成功返回0 单雾,失敗返回-1
how - 修改信號掩碼的方式赚哗,可取以下值
SIG_BLOCK: 新掩碼是當(dāng)前掩碼和set的并集(將set加入信號掩碼)
SIG_UNBLOCK: 新掩碼是當(dāng)前掩碼和set的補集的交集(將set從信號掩碼移除)
SIG_SETMASK: 新掩碼是set(將信號掩碼設(shè)為set)
set: NULL則忽略
oldset:備份以前的信號掩碼, NULL則不備份
int sigpending(sigset_t set);
set - 輸出硅堆,調(diào)用進程當(dāng)前處于未決狀態(tài)的信號集屿储。
成功返回0 ,失敗返回-1
注意:對于不可靠信號渐逃,
通過sigprocmask函數(shù)設(shè)置信號掩碼以后够掠,
相同的被屏蔽信號只會屏蔽第一個,
并在恢復(fù)信號掩碼后被遞送茄菊,其余的則直接忽略掉疯潭。
而對于可靠信號赊堪,
則會在信號屏蔽時按其產(chǎn)生的先后順序排隊,
一旦恢復(fù)信號掩碼竖哩,這些信號會依次被信號處理函數(shù)處理哭廉。
|#include <stdio.h>
|#include <unistd.h>
|#include <signal.h>
|#include <stdlib.h>
void sighand(int signum){
printf("%u進程:收到%d信號!\n", getpid(), signum);
}
void updatedb (void){
int i;
for(i=0;i<5;++i){
printf("%u進程:更新第%d記錄相叁!\n", getpid(), i+1);
sleep(1);
}
}
int main(void){
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
pid_t pid = getppid();
int i ;
for(i=0; i<5; ++i){
printf("%u進程:向%u進程發(fā)送%d信號... \n", getpid(), pid, 50);
kill(pid, 50);
}
return 0;
}
updatedb();
return 0;
}
更新數(shù)據(jù)庫操作頻繁被打斷,如下圖遵绰。
做一個修正。
|#include <stdio.h>
|#include <unistd.h>
|#include <signal.h>
|#include <stdlib.h>
void sighand(int signum){
printf("%u進程:收到%d信號钝荡!\n", getpid(), signum);
}
void updatedb (void){
int i;
for(i=0;i<5;++i){
printf("%u進程:更新第%d記錄街立!\n", getpid(), i+1);
sleep(1);
}
}
int main(void){
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
printf("%u進程:屏蔽%d信號和%d信號... \n", getpid(), SIGINT, 50);
sigset_t new;
if(sigemptyset(&new) == -1){
perror("sigemptyset");
return -1;
}
if(sigaddset(&new, SIGINT) == -1){
perror("sigaddset");
return -1;
}
if(sigaddset(&new, 50) == -1){
perror("sigaddset");
return -1;
}
sigset_t old;
if(sigprocmask(SIG_SETMASK, &new舶衬, &old) == -1){
perror("sigprocmask");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
pid_t pid = getppid();
int i ;
for(i=0; i<5; ++i){
printf("%u進程:向%u進程發(fā)送%d信號... \n", getpid(), pid, 50);
kill(pid, 50);
}
return 0;
}
updatedb();
//將信號集從掩碼集中移除埠通,恢復(fù)處理之前未決的信號
sigset_t pend;
if(sigpending(&pend) == -1){
perror("sigpending");
return -1;
}
if(sigismember(&pend, SIGINT)) //執(zhí)行過程中按Ctrl+C
printf("%u進程:發(fā)現(xiàn)%d信號未決... \n", getpid(), SIGINT);
if(sigismember(&pend, 50))
printf("%u進程:發(fā)現(xiàn)%d信號未決... \n", getpid(), 50);
//取消SIGINT ,50 的屏蔽
if(sigprocmask(SIG_SETMASK, &old, NULL) == -1){
perror("sigprocmask");
return -1;
}
return 0;
}
-
此時不會再次打斷進程逛犹。上傳結(jié)果圖片端辱,此時最后可以收到5個50信號,1個2(不可靠)信號:
圖片發(fā)自簡書App