[Linux 高并发服务器] 信号
[Linux 高并發(fā)服務(wù)器] 信號(hào)
文章概述
牛客網(wǎng)C++項(xiàng)目課:Linux高并發(fā)服務(wù)器的個(gè)人筆記,記錄了信號(hào)相關(guān)的知識(shí)點(diǎn)。
作者信息
NEFU 2020級(jí) zsl
ID:fishingrod/魚(yú)竿釣魚(yú)干
Email:851892190@qq.com
歡迎各位引用此博客,引用時(shí)在顯眼位置放置原文鏈接和作者基本信息
參考資料
感謝前輩們留下的優(yōu)秀資料,從中學(xué)到很多,冒昧引用,如有冒犯可以私信或者在評(píng)論區(qū)下方指出
| 牛客網(wǎng)C++項(xiàng)目課:Linux高并發(fā)服務(wù)器 | 牛客網(wǎng) | 貫穿全文,作為筆記的基礎(chǔ) |
| 硬件中斷和軟件中斷的區(qū)別 | dela_ | 用于信號(hào)概念補(bǔ)充說(shuō)明 |
| 異步和同步的區(qū)別 | 菠蘿 | 用于信號(hào)概念的補(bǔ)充 |
| linux下不產(chǎn)生core文件的原因 | Small-K | 處理不產(chǎn)生core文件的問(wèn)題 |
| 10張圖讓你徹底理解回調(diào)函數(shù) | 碼農(nóng)的荒島求生 | signal信號(hào)捕捉函數(shù),對(duì)回調(diào)函數(shù)作補(bǔ)充說(shuō)明 |
正文部分
信號(hào)的概念
信號(hào)是 Linux 進(jìn)程間通信的最古老的方式之一,是事件發(fā)生時(shí)對(duì)進(jìn)程的通知機(jī)制,有時(shí)也稱(chēng)之為軟件中斷,它是在軟件層次上對(duì)中斷機(jī)制的一種模擬,是一種異步通信的方式。信號(hào)可以導(dǎo)致一個(gè)正在運(yùn)行的進(jìn)程被另一個(gè)正在運(yùn)行的異步進(jìn)程中斷,轉(zhuǎn)而處理某一個(gè)突發(fā)事件。
補(bǔ)充:
硬中斷和軟中斷區(qū)別
異步和同步的區(qū)別
引發(fā)信號(hào)的各類(lèi)事件
發(fā)往進(jìn)程的諸多信號(hào),通常都是源于內(nèi)核。引發(fā)內(nèi)核為進(jìn)程產(chǎn)生信號(hào)的各類(lèi)事件如下:
信號(hào)的目的
信號(hào)的特點(diǎn)
查看系統(tǒng)定義的信號(hào)列表
使用kill -l命令查看系統(tǒng)定義的信號(hào)列表
archlinux上的比較少
ubuntu上的比較多(下面以此為例子)
前31個(gè)為常規(guī)信號(hào),后31個(gè)為實(shí)時(shí)信號(hào),沒(méi)錯(cuò)只有62個(gè),沒(méi)有32,33兩個(gè)信號(hào)
下面這些列表來(lái)源于牛客的課程
表格中標(biāo)紅的需要重點(diǎn)了解,同時(shí)我們通過(guò)表格也可以知道信號(hào)有四個(gè)要素:編號(hào),名稱(chēng),事件,默認(rèn)動(dòng)作
信號(hào)相關(guān)的更多信息
我們可以通過(guò)man 7 signal來(lái)了解信號(hào)的相關(guān)信息
信號(hào)的 5 種默認(rèn)處理動(dòng)作
默認(rèn)處理動(dòng)作即,信號(hào)一定會(huì)執(zhí)行下面動(dòng)作中的某幾個(gè)
1.Term 終止進(jìn)程
2. Ign 當(dāng)前進(jìn)程忽略掉這個(gè)信號(hào)
3. Core 終止進(jìn)程,并生成一個(gè)Core文件,用于記錄終止的原因
4. Stop 暫停當(dāng)前進(jìn)程
5. Cont 繼續(xù)執(zhí)行當(dāng)前被暫停的進(jìn)程
對(duì)于Core文件,我們以下面這個(gè)程序?yàn)槔?/p> #include <stdio.h> #include <string.h>int main() {char * buf;strcpy(buf, "hello");return 0; }
明顯的,程序會(huì)發(fā)生訪問(wèn)野內(nèi)存的錯(cuò)誤
我對(duì)其編譯運(yùn)行
發(fā)現(xiàn)并沒(méi)有產(chǎn)生core文件,我們使用ulimit -a進(jìn)行查看
發(fā)現(xiàn)core file size是unlimited狀態(tài),按道理是生成了core文件的,但是為什么沒(méi)有呢?
通過(guò)這篇博客第三條提示,發(fā)現(xiàn)生成的core文件貌似被腳本文件刪除了。重新配置vim /proc/sys/kernel/core_pattern設(shè)置core文件生成在程序目錄下
成功生成core文件
接下來(lái)使用gdb調(diào)試來(lái)查看core
可以發(fā)現(xiàn),程序發(fā)生了段錯(cuò)誤,信號(hào)是SIGSEGV 查詢(xún)上面的表格可以知道進(jìn)程進(jìn)行了無(wú)效的進(jìn)程訪問(wèn)
信號(hào)的幾種狀態(tài):
產(chǎn)生
未決:信號(hào)產(chǎn)生到遞達(dá)之間的狀態(tài)
遞達(dá):實(shí)際執(zhí)行信號(hào)的處理動(dòng)作
SIGKILL 和 SIGSTOP 信號(hào)不能被捕捉、阻塞或者忽略,只能執(zhí)行默認(rèn)動(dòng)作。
信號(hào)相關(guān)的函數(shù)
kill、raise、abort函數(shù)
/* #include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);- 功能:給任何的進(jìn)程或者進(jìn)程組pid, 發(fā)送任何的信號(hào) sig- 參數(shù):- pid :> 0 : 將信號(hào)發(fā)送給指定的進(jìn)程= 0 : 將信號(hào)發(fā)送給當(dāng)前的進(jìn)程組= -1 : 將信號(hào)發(fā)送給每一個(gè)有權(quán)限接收這個(gè)信號(hào)的進(jìn)程< -1 : 這個(gè)pid=某個(gè)進(jìn)程組的ID取反 (-12345)- sig : 需要發(fā)送的信號(hào)的編號(hào)或者是宏值,0表示不發(fā)送任何信號(hào)kill(getppid(), 9);kill(getpid(), 9);int raise(int sig);- 功能:給當(dāng)前進(jìn)程發(fā)送信號(hào)- 參數(shù):- sig : 要發(fā)送的信號(hào)- 返回值:- 成功 0- 失敗 非0kill(getpid(), sig); void abort(void);- 功能: 發(fā)送SIGABRT信號(hào)給當(dāng)前的進(jìn)程,殺死當(dāng)前進(jìn)程kill(getpid(), SIGABRT); */#include <stdio.h> #include <sys/types.h> #include <signal.h> #include <unistd.h>int main() {pid_t pid = fork();if(pid == 0) {// 子進(jìn)程int i = 0;for(i = 0; i < 5; i++) {printf("child process\n");sleep(1);}} else if(pid > 0) {// 父進(jìn)程printf("parent process\n");sleep(2);printf("kill child process now\n");kill(pid, SIGINT);}return 0; }tip:發(fā)送信號(hào)最好使用宏名,因?yàn)椴煌到y(tǒng)架構(gòu)的信號(hào)編號(hào)不一定相同。
alarm函數(shù)
/*#include <unistd.h>unsigned int alarm(unsigned int seconds);- 功能:設(shè)置定時(shí)器(鬧鐘)。函數(shù)調(diào)用,開(kāi)始倒計(jì)時(shí),當(dāng)?shù)褂?jì)時(shí)為0的時(shí)候,函數(shù)會(huì)給當(dāng)前的進(jìn)程發(fā)送一個(gè)信號(hào):SIGALARM- 參數(shù):seconds: 倒計(jì)時(shí)的時(shí)長(zhǎng),單位:秒。如果參數(shù)為0,定時(shí)器無(wú)效(不進(jìn)行倒計(jì)時(shí),不發(fā)信號(hào))。取消一個(gè)定時(shí)器,通過(guò)alarm(0)。- 返回值:- 之前沒(méi)有定時(shí)器,返回0- 之前有定時(shí)器,返回之前的定時(shí)器剩余的時(shí)間- SIGALARM :默認(rèn)終止當(dāng)前的進(jìn)程,每一個(gè)進(jìn)程都有且只有唯一的一個(gè)定時(shí)器。alarm(10); -> 返回0過(guò)了1秒alarm(5); -> 返回9alarm(100) -> 該函數(shù)是不阻塞的 */#include <stdio.h> #include <unistd.h>int main() {int seconds = alarm(5);printf("seconds = %d\n", seconds); // 0sleep(2);seconds = alarm(2); // 不阻塞printf("seconds = %d\n", seconds); // 3while(1) {}return 0; } // 1秒鐘電腦能數(shù)多少個(gè)數(shù)? #include <stdio.h> #include <unistd.h>/*實(shí)際的時(shí)間 = 內(nèi)核時(shí)間 + 用戶(hù)時(shí)間 + 消耗的時(shí)間(內(nèi)核用戶(hù)轉(zhuǎn)換時(shí)間)進(jìn)行文件IO操作的時(shí)候比較浪費(fèi)時(shí)間定時(shí)器,與進(jìn)程的狀態(tài)無(wú)關(guān)(自然定時(shí)法)。無(wú)論進(jìn)程處于什么狀態(tài),alarm都會(huì)計(jì)時(shí)。 */int main() { alarm(1);int i = 0;while(1) {printf("%i\n", i++);}return 0; }setitimer 定時(shí)器函數(shù)
相比alarm,setitimer可以實(shí)現(xiàn)周期性定時(shí)并且時(shí)間更精細(xì)
/*#include <sys/time.h>int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);- 功能:設(shè)置定時(shí)器(鬧鐘)。可以替代alarm函數(shù)。精度微妙us,可以實(shí)現(xiàn)周期性定時(shí)- 參數(shù):- which : 定時(shí)器以什么時(shí)間計(jì)時(shí)ITIMER_REAL: 真實(shí)時(shí)間,時(shí)間到達(dá),發(fā)送 SIGALRM 常用ITIMER_VIRTUAL: 用戶(hù)時(shí)間,時(shí)間到達(dá),發(fā)送 SIGVTALRMITIMER_PROF: 以該進(jìn)程在用戶(hù)態(tài)和內(nèi)核態(tài)下所消耗的時(shí)間來(lái)計(jì)算,時(shí)間到達(dá),發(fā)送 SIGPROF- new_value: 設(shè)置定時(shí)器的屬性struct itimerval { // 定時(shí)器的結(jié)構(gòu)體struct timeval it_interval; // 每個(gè)階段的時(shí)間,間隔時(shí)間struct timeval it_value; // 延遲多長(zhǎng)時(shí)間執(zhí)行定時(shí)器};struct timeval { // 時(shí)間的結(jié)構(gòu)體time_t tv_sec; // 秒數(shù) suseconds_t tv_usec; // 微秒 };過(guò)10秒后,每個(gè)2秒定時(shí)一次- old_value :記錄上一次的定時(shí)的時(shí)間參數(shù),一般不使用,指定NULL- 返回值:成功 0失敗 -1 并設(shè)置錯(cuò)誤號(hào) */#include <sys/time.h> #include <stdio.h> #include <stdlib.h>// 過(guò)3秒以后,每隔2秒鐘定時(shí)一次 int main() {struct itimerval new_value;// 設(shè)置間隔的時(shí)間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設(shè)置延遲的時(shí)間,3秒之后開(kāi)始第一次定時(shí)new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定時(shí)器開(kāi)始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0; }signal信號(hào)捕捉函數(shù)
/*#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);- 功能:設(shè)置某個(gè)信號(hào)的捕捉行為- 參數(shù):- signum: 要捕捉的信號(hào)- handler: 捕捉到信號(hào)要如何處理- SIG_IGN : 忽略信號(hào)- SIG_DFL : 使用信號(hào)默認(rèn)的行為- 回調(diào)函數(shù) : 這個(gè)函數(shù)是內(nèi)核調(diào)用,程序員只負(fù)責(zé)寫(xiě),捕捉到信號(hào)后如何去處理信號(hào)。回調(diào)函數(shù):- 需要程序員實(shí)現(xiàn),提前準(zhǔn)備好的,函數(shù)的類(lèi)型根據(jù)實(shí)際需求,看函數(shù)指針的定義- 不是程序員調(diào)用,而是當(dāng)信號(hào)產(chǎn)生,由內(nèi)核調(diào)用- 函數(shù)指針是實(shí)現(xiàn)回調(diào)的手段,函數(shù)實(shí)現(xiàn)之后,將函數(shù)名放到函數(shù)指針的位置就可以了。- 返回值:成功,返回上一次注冊(cè)的信號(hào)處理函數(shù)的地址。第一次調(diào)用返回NULL失敗,返回SIG_ERR,設(shè)置錯(cuò)誤號(hào)SIGKILL SIGSTOP不能被捕捉,不能被忽略。 */#include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h>void myalarm(int num) {printf("捕捉到了信號(hào)的編號(hào)是:%d\n", num);printf("xxxxxxx\n"); }// 過(guò)3秒以后,每隔2秒鐘定時(shí)一次 int main() {// 注冊(cè)信號(hào)捕捉// signal(SIGALRM, SIG_IGN);// signal(SIGALRM, SIG_DFL);// void (*sighandler_t)(int); 函數(shù)指針,int類(lèi)型的參數(shù)表示捕捉到的信號(hào)的值。signal(SIGALRM, myalarm);struct itimerval new_value;// 設(shè)置間隔的時(shí)間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設(shè)置延遲的時(shí)間,3秒之后開(kāi)始第一次定時(shí)new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定時(shí)器開(kāi)始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0; }tip:捕捉信號(hào)之前要先把信號(hào)捕捉注冊(cè)掉
關(guān)于回調(diào)函數(shù)的補(bǔ)充:
10張圖讓你徹底理解回調(diào)函數(shù)
信號(hào)集
信號(hào)集概念
參考牛客課程PPT(見(jiàn)參考資料1)
許多信號(hào)相關(guān)的系統(tǒng)調(diào)用都需要能表示一組不同的信號(hào),多個(gè)信號(hào)可使用一個(gè)稱(chēng)之為信號(hào)集的數(shù)據(jù)結(jié)構(gòu)來(lái)表示,其系統(tǒng)數(shù)據(jù)類(lèi)型為 sigset_t。
在 PCB 中有兩個(gè)非常重要的信號(hào)集**。一個(gè)稱(chēng)之為 “阻塞信號(hào)集” ,另一個(gè)稱(chēng)之為“未決信號(hào)集”** 。這兩個(gè)信號(hào)集都是內(nèi)核使用位圖機(jī)制來(lái)實(shí)現(xiàn)的。但操作系統(tǒng)不允許我們直接對(duì)這兩個(gè)信號(hào)集進(jìn)行位操作。而需自定義另外一個(gè)集合,借助信號(hào)集操作函數(shù)
來(lái)對(duì) PCB 中的這兩個(gè)信號(hào)集進(jìn)行修改。
信號(hào)的 “未決” 是一種狀態(tài),指的是從信號(hào)的產(chǎn)生到信號(hào)被處理前的這一段時(shí)間。
信號(hào)的 “阻塞” 是一個(gè)開(kāi)關(guān)動(dòng)作,指的是阻止信號(hào)被處理,但不是阻止信號(hào)產(chǎn)生。信號(hào)的阻塞就讓系統(tǒng)暫時(shí)保留信號(hào)留待以后發(fā)送。由于另外有辦法讓系統(tǒng)忽略信號(hào),所以一般情況下信號(hào)的阻塞只是暫時(shí)的,只是為了防止信號(hào)打斷敏感的操作
下圖修改自牛客PPT1.用戶(hù)通過(guò)鍵盤(pán) Ctrl + C, 產(chǎn)生2號(hào)信號(hào)SIGINT (信號(hào)被創(chuàng)建)
2.信號(hào)產(chǎn)生但是沒(méi)有被處理 (未決)
在內(nèi)核中將所有的沒(méi)有被處理的信號(hào)存儲(chǔ)在一個(gè)集合中 (未決信號(hào)集)
SIGINT信號(hào)狀態(tài)被存儲(chǔ)在第二個(gè)標(biāo)志位上
這個(gè)標(biāo)志位的值為0, 說(shuō)明信號(hào)不是未決狀態(tài)
這個(gè)標(biāo)志位的值為1, 說(shuō)明信號(hào)處于未決狀態(tài)
3.這個(gè)未決狀態(tài)的信號(hào),需要被處理,處理之前需要和另一個(gè)信號(hào)集(阻塞信號(hào)集),進(jìn)行比較
阻塞信號(hào)集默認(rèn)不阻塞任何的信號(hào)
如果想要阻塞某些信號(hào)需要用戶(hù)調(diào)用系統(tǒng)的API
4.在處理的時(shí)候和阻塞信號(hào)集中的標(biāo)志位進(jìn)行查詢(xún),看是不是對(duì)該信號(hào)設(shè)置阻塞了
如果沒(méi)有阻塞,這個(gè)信號(hào)就被處理
如果阻塞了,這個(gè)信號(hào)就繼續(xù)處于未決狀態(tài),直到阻塞解除,這個(gè)信號(hào)就被處理
信號(hào)集相關(guān)函數(shù)
我們不能直接修改內(nèi)核中的信號(hào)集,這樣太危險(xiǎn)了,系統(tǒng)已經(jīng)封裝了一些函數(shù)給我們操作。
/*以下信號(hào)集相關(guān)的函數(shù)都是對(duì)自定義的信號(hào)集進(jìn)行操作。int sigemptyset(sigset_t *set);- 功能:清空信號(hào)集中的數(shù)據(jù),將信號(hào)集中的所有的標(biāo)志位置為0- 參數(shù):set,傳出參數(shù),需要操作的信號(hào)集- 返回值:成功返回0, 失敗返回-1int sigfillset(sigset_t *set);- 功能:將信號(hào)集中的所有的標(biāo)志位置為1- 參數(shù):set,傳出參數(shù),需要操作的信號(hào)集- 返回值:成功返回0, 失敗返回-1int sigaddset(sigset_t *set, int signum);- 功能:設(shè)置信號(hào)集中的某一個(gè)信號(hào)對(duì)應(yīng)的標(biāo)志位為1,表示阻塞這個(gè)信號(hào)- 參數(shù):- set:傳出參數(shù),需要操作的信號(hào)集- signum:需要設(shè)置阻塞的那個(gè)信號(hào)- 返回值:成功返回0, 失敗返回-1int sigdelset(sigset_t *set, int signum);- 功能:設(shè)置信號(hào)集中的某一個(gè)信號(hào)對(duì)應(yīng)的標(biāo)志位為0,表示不阻塞這個(gè)信號(hào)- 參數(shù):- set:傳出參數(shù),需要操作的信號(hào)集- signum:需要設(shè)置不阻塞的那個(gè)信號(hào)- 返回值:成功返回0, 失敗返回-1int sigismember(const sigset_t *set, int signum);- 功能:判斷某個(gè)信號(hào)是否阻塞- 參數(shù):- set:需要操作的信號(hào)集- signum:需要判斷的那個(gè)信號(hào)- 返回值:1 : signum被阻塞0 : signum不阻塞-1 : 失敗*/#include <signal.h> #include <stdio.h>int main() {// 創(chuàng)建一個(gè)信號(hào)集sigset_t set;// 清空信號(hào)集的內(nèi)容,沒(méi)有初始化清空的話標(biāo)志位可能是隨機(jī)的sigemptyset(&set);// 判斷 SIGINT 是否在信號(hào)集 set 里int ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 添加幾個(gè)信號(hào)到信號(hào)集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 判斷SIGINT是否在信號(hào)集中ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 判斷SIGQUIT是否在信號(hào)集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}// 從信號(hào)集中刪除一個(gè)信號(hào)sigdelset(&set, SIGQUIT);// 判斷SIGQUIT是否在信號(hào)集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}return 0; }sigprocmask和sigpending函數(shù)
/*int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);- 功能:將自定義信號(hào)集中的數(shù)據(jù)設(shè)置到內(nèi)核中(設(shè)置阻塞,解除阻塞,替換)- 參數(shù):- how : 如何對(duì)內(nèi)核阻塞信號(hào)集進(jìn)行處理SIG_BLOCK: 將用戶(hù)設(shè)置的阻塞信號(hào)集添加到內(nèi)核中,內(nèi)核中原來(lái)的數(shù)據(jù)不變假設(shè)內(nèi)核中默認(rèn)的阻塞信號(hào)集是mask, mask | setSIG_UNBLOCK: 根據(jù)用戶(hù)設(shè)置的數(shù)據(jù),對(duì)內(nèi)核中的數(shù)據(jù)進(jìn)行解除阻塞mask &= ~setSIG_SETMASK:覆蓋內(nèi)核中原來(lái)的值- set :已經(jīng)初始化好的用戶(hù)自定義的信號(hào)集- oldset : 保存設(shè)置之前的內(nèi)核中的阻塞信號(hào)集的狀態(tài),可以是 NULL- 返回值:成功:0失敗:-1設(shè)置錯(cuò)誤號(hào):EFAULT、EINVALint sigpending(sigset_t *set);- 功能:獲取內(nèi)核中的未決信號(hào)集- 參數(shù):set,傳出參數(shù),保存的是內(nèi)核中的未決信號(hào)集中的信息。 */// 編寫(xiě)一個(gè)程序,把所有的常規(guī)信號(hào)(1-31)的未決狀態(tài)打印到屏幕 // 設(shè)置某些信號(hào)是阻塞的,通過(guò)鍵盤(pán)產(chǎn)生這些信號(hào)#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <unistd.h>int main() {// 設(shè)置2、3號(hào)信號(hào)阻塞sigset_t set;sigemptyset(&set);// 將2號(hào)和3號(hào)信號(hào)添加到信號(hào)集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 修改內(nèi)核中的阻塞信號(hào)集sigprocmask(SIG_BLOCK, &set, NULL);int num = 0;while(1) {num++;// 獲取當(dāng)前的未決信號(hào)集的數(shù)據(jù)sigset_t pendingset;sigemptyset(&pendingset);sigpending(&pendingset);// 遍歷前32位for(int i = 1; i <= 31; i++) {if(sigismember(&pendingset, i) == 1) {printf("1");}else if(sigismember(&pendingset, i) == 0) {printf("0");}else {perror("sigismember");exit(0);}}printf("\n");sleep(1);if(num == 10) {// 解除阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);}}return 0; }sigaction信號(hào)捕捉函數(shù)
sigaction與signal功能大致相同,但是我們建議使用sigaction函數(shù)來(lái)做信號(hào)捕捉,因?yàn)閟ignal是ANSI C標(biāo)準(zhǔn),不一定都適用。
/*#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);- 功能:檢查或者改變信號(hào)的處理。信號(hào)捕捉- 參數(shù):- signum : 需要捕捉的信號(hào)的編號(hào)或者宏值(信號(hào)的名稱(chēng))- act :捕捉到信號(hào)之后的處理動(dòng)作- oldact : 上一次對(duì)信號(hào)捕捉相關(guān)的設(shè)置,一般不使用,傳遞NULL- 返回值:成功 0失敗 -1struct sigaction {// 函數(shù)指針,指向的函數(shù)就是信號(hào)捕捉到之后的處理函數(shù)void (*sa_handler)(int);// 不常用void (*sa_sigaction)(int, siginfo_t *, void *);// 臨時(shí)阻塞信號(hào)集,在信號(hào)捕捉函數(shù)執(zhí)行過(guò)程中,臨時(shí)阻塞某些信號(hào)。sigset_t sa_mask;// 使用哪一個(gè)信號(hào)處理對(duì)捕捉到的信號(hào)進(jìn)行處理// 這個(gè)值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigactionint sa_flags;// 被廢棄掉了void (*sa_restorer)(void);};*/ #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h>void myalarm(int num) {printf("捕捉到了信號(hào)的編號(hào)是:%d\n", num);printf("xxxxxxx\n"); }// 過(guò)3秒以后,每隔2秒鐘定時(shí)一次 int main() {struct sigaction act;act.sa_flags = 0;act.sa_handler = myalarm;sigemptyset(&act.sa_mask); // 清空臨時(shí)阻塞信號(hào)集// 注冊(cè)信號(hào)捕捉sigaction(SIGALRM, &act, NULL);struct itimerval new_value;// 設(shè)置間隔的時(shí)間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設(shè)置延遲的時(shí)間,3秒之后開(kāi)始第一次定時(shí)new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定時(shí)器開(kāi)始了...\n");if(ret == -1) {perror("setitimer");exit(0);}// getchar();while(1);return 0; }信號(hào)捕捉的特點(diǎn)
1.在信號(hào)捕捉處理過(guò)程中,使用臨時(shí)的阻塞信號(hào)集,當(dāng)信號(hào)處理完后會(huì)回到內(nèi)核PCB的信號(hào)集
2.在執(zhí)行某個(gè)回調(diào)函數(shù)期間,相同的信號(hào)會(huì)被默認(rèn)屏蔽掉,再次發(fā)送同一個(gè)信號(hào)就沒(méi)用了,等前一次回調(diào)函數(shù)執(zhí)行好了以后采取執(zhí)行
3.阻塞的信號(hào)不能排隊(duì),因?yàn)槲礇Q信號(hào)集只有01來(lái)看狀態(tài),不能統(tǒng)計(jì)數(shù)量(后面實(shí)時(shí)信號(hào)是可以排隊(duì)的)
信號(hào)捕捉過(guò)程
觀察上圖,可以看到信號(hào)處理過(guò)程中回調(diào)函數(shù)的路徑,并且它是由內(nèi)核來(lái)自動(dòng)控制的
SIGCHLD信號(hào)
使用SIGCHLD信號(hào)解決僵尸進(jìn)程的問(wèn)題。
/*SIGCHLD信號(hào)產(chǎn)生的3個(gè)條件:1.子進(jìn)程結(jié)束2.子進(jìn)程暫停了3.子進(jìn)程繼續(xù)運(yùn)行都會(huì)給父進(jìn)程發(fā)送該信號(hào),父進(jìn)程默認(rèn)忽略該信號(hào)。使用SIGCHLD信號(hào)解決僵尸進(jìn)程的問(wèn)題。 */#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <sys/wait.h>void myFun(int num) {printf("捕捉到的信號(hào) :%d\n", num);// 回收子進(jìn)程PCB的資源// while(1) {// wait(NULL); 這樣會(huì)死循環(huán)// }while(1) {int ret = waitpid(-1, NULL, WNOHANG);if(ret > 0) {printf("child die , pid = %d\n", ret);} else if(ret == 0) {// 說(shuō)明還有子進(jìn)程或者break;} else if(ret == -1) {// 沒(méi)有子進(jìn)程break;}} }int main() {// 提前設(shè)置好阻塞信號(hào)集,阻塞SIGCHLD,因?yàn)橛锌赡茏舆M(jìn)程很快結(jié)束,父進(jìn)程還沒(méi)有注冊(cè)完信號(hào)捕捉sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 創(chuàng)建一些子進(jìn)程pid_t pid;for(int i = 0; i < 20; i++) {pid = fork();if(pid == 0) {break;}}if(pid > 0) {// 父進(jìn)程// 捕捉子進(jìn)程死亡時(shí)發(fā)送的SIGCHLD信號(hào)struct sigaction act;act.sa_flags = 0;act.sa_handler = myFun;sigemptyset(&act.sa_mask);sigaction(SIGCHLD, &act, NULL);// 注冊(cè)完信號(hào)捕捉以后,解除阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);while(1) {printf("parent process pid : %d\n", getpid());sleep(2);}} else if( pid == 0) {// 子進(jìn)程printf("child process pid : %d\n", getpid());}return 0; }總結(jié)
以上是生活随笔為你收集整理的[Linux 高并发服务器] 信号的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【发现问题】IDEA设置全局新创建文件默
- 下一篇: 闪回表操作语法+使用闪回删除