進(jìn)程信號
1.進(jìn)程信號的概念和介紹
??在Linux中,進(jìn)程信號是一種異步的事件通知機(jī)制,用于通知進(jìn)程某個事件已經(jīng)發(fā)生。它是進(jìn)程間通信的一種方式,可以用來控制進(jìn)程的行為。
??當(dāng)一個進(jìn)程收到信號時,操作系統(tǒng)會中斷該進(jìn)程的正??刂屏鞒?,并執(zhí)行相應(yīng)的處理函數(shù)。進(jìn)程收到信號后有三種處理方式:
??忽略信號:進(jìn)程可以選擇忽略收到的信號,不進(jìn)行任何操作。
??執(zhí)行默認(rèn)處理函數(shù):操作系統(tǒng)為每種信號都提供了默認(rèn)的處理函數(shù),進(jìn)程收到信號后會自動執(zhí)行默認(rèn)處理函數(shù)。
??自定義處理函數(shù):進(jìn)程可以注冊自定義的處理函數(shù)來處理特定的信號,當(dāng)收到該信號時,執(zhí)行自定義的處理函數(shù)。
??在Linux中,可以使用kill命令向進(jìn)程發(fā)送信號。例如,使用kill -9命令可以向進(jìn)程發(fā)送SIGKILL信號,強(qiáng)制結(jié)束進(jìn)程。此外,還有其他一些信號可以用來控制進(jìn)程的行為,例如SIGINT信號用于中斷進(jìn)程,SIGTERM信號用于請求進(jìn)程正常退出等。
??綜上:信號是進(jìn)程之間事件異步通知的一種方式,屬于軟中斷。
????????????
2.產(chǎn)生信號
2.1通過終端按鍵產(chǎn)生信號
??在Linux中,可以通過終端按鍵產(chǎn)生信號。具體來說,用戶在終端按下某些鍵時,終端驅(qū)動程序會發(fā)送信號給前臺進(jìn)程。常見的通過終端按鍵產(chǎn)生的信號包括:
??SIGINT的默認(rèn)處理動作是終止進(jìn)程,SIGQUIT的默認(rèn)處理動作是終止進(jìn)程并且Core Dump。
??SIGINT:當(dāng)用戶按下Ctrl+C組合鍵時, 終端驅(qū)動程序會向前臺進(jìn)程發(fā)送SIGINT信號。默認(rèn)情況下,SIGINT信號的處理動作是終止進(jìn)程。
??SIGQUIT:當(dāng)用戶按下Ctrl+\組合鍵時, 終端驅(qū)動程序會向前臺進(jìn)程發(fā)送SIGQUIT信號。默認(rèn)情況下,SIGQUIT信號的處理動作是終止進(jìn)程并且產(chǎn)生核心轉(zhuǎn)儲(Core Dump)。核心轉(zhuǎn)儲是將進(jìn)程的用戶空間內(nèi)存數(shù)據(jù)全部保存到磁盤上,以供后續(xù)的調(diào)試和分析使用。
??SIGTSTP:當(dāng)用戶按下Ctrl+Z組合鍵時, 終端驅(qū)動程序會向前臺進(jìn)程發(fā)送SIGTSTP信號。默認(rèn)情況下,SIGTSTP信號的處理動作是停止進(jìn)程的執(zhí)行。
??
Core Dump
首先解釋什么是Core Dump。當(dāng)一個進(jìn)程要異常終止時,可以選擇把進(jìn)程的用戶空間內(nèi)存數(shù)據(jù)全部保存到磁盤上,文件名通常是core,這叫做CoreDump。進(jìn)程異常終止通常是因?yàn)橛蠦ug,比如非法內(nèi)存訪問導(dǎo)致段錯誤,事后可以用調(diào)試器檢查core文件以查清錯誤原因,這叫做Post-mortem Debug(事后調(diào)試)。一個進(jìn)程允許產(chǎn)生多大的core文件取決于進(jìn)程的Resource Limit(這個信息保存在PCB中)。默認(rèn)是不允許產(chǎn)生core文件的,因?yàn)閏ore文件中可能包含用戶密碼等敏感信息,不安全。在開發(fā)調(diào)試階段可以用ulimit命令改變這個限制,允許產(chǎn)生core文件。 首先用ulimit命令改變Shell進(jìn)程的Resource Limit,允許core文件最大為1024K:$ulimit -c 1024
??
2.2 調(diào)用系統(tǒng)函數(shù)向進(jìn)程發(fā)信號
??在Linux中,可以使用系統(tǒng)函數(shù)向進(jìn)程發(fā)送信號。常用的系統(tǒng)函數(shù)包括:
??kill命令是調(diào)用kill函數(shù)實(shí)現(xiàn)的。kill函數(shù)可以給一個指定的進(jìn)程發(fā)送指定的信號。raise函數(shù)可以給當(dāng)前進(jìn)程發(fā)送指定的信號(自己給自己發(fā)信號)。
??kill():該函數(shù)用于向指定的進(jìn)程發(fā)送信號。需要提供目標(biāo)進(jìn)程的進(jìn)程ID(PID)和要發(fā)送的信號。如果進(jìn)程ID為0,則表示向當(dāng)前進(jìn)程發(fā)送信號。
??示例代碼:
#include <signal.h>
#include <stdio.h>
int main() {
int pid = 12345; // 目標(biāo)進(jìn)程的PID
int signal = SIGINT; // 要發(fā)送的信號
if (kill(pid, signal) == -1) {
perror("kill");
return 1;
}
printf("Signal sent to process %d\n", pid);
return 0;
}
??raise():該函數(shù)用于向當(dāng)前進(jìn)程發(fā)送信號。需要提供要發(fā)送的信號。
??示例代碼:
#include <signal.h>
#include <stdio.h>
int main() {
int signal = SIGINT; // 要發(fā)送的信號
if (raise(signal) == -1) {
perror("raise");
return 1;
}
printf("Signal sent to current process\n");
return 0;
}
??
2.3 由軟件條件產(chǎn)生信號
????SIGPIPE是一種由軟件條件產(chǎn)生的信號,在“管道”中已經(jīng)介紹過了。alarm函數(shù) 和SIGALRM信號也是由軟件條件產(chǎn)生信號。
??SIGPIPE是一種由軟件條件產(chǎn)生的信號。 當(dāng)進(jìn)程在使用管道進(jìn)行通信時,如果讀端進(jìn)程關(guān)閉了管道的讀端,而寫端進(jìn)程還在向管道寫入數(shù)據(jù),那么寫端進(jìn)程就會收到SIGPIPE信號。收到SIGPIPE信號的進(jìn)程可以選擇忽略該信號,或者執(zhí)行默認(rèn)的終止操作。
??SIGPIPE信號的產(chǎn)生通常是由于程序邏輯錯誤導(dǎo)致的,例如,在使用管道通信時沒有正確地處理讀端進(jìn)程的關(guān)閉操作。為了避免SIGPIPE信號的產(chǎn)生,開發(fā)者需要確保在進(jìn)程關(guān)閉讀端之前,寫端進(jìn)程已經(jīng)完成了數(shù)據(jù)的寫入操作,或者采取其他措施來處理管道通信中的錯誤情況。
??
2.4硬件異常產(chǎn)生信號
??硬件異常產(chǎn)生信號是由硬件檢測到異常情況并通知給操作系統(tǒng)的過程。 當(dāng)程序執(zhí)行了非法的操作,如除以0、訪問無效的內(nèi)存地址等,硬件會檢測到這些異常并產(chǎn)生相應(yīng)的信號。
??在Linux中,硬件異常產(chǎn)生的信號主要包括:當(dāng)前進(jìn)程執(zhí)行了除以0的指令,CPU的運(yùn)算單元會產(chǎn)生異常,內(nèi)核將這個異常解釋 為SIGFPE信號發(fā)送給進(jìn)程。再比如當(dāng)前進(jìn)程訪問了非法內(nèi)存地址,MMU會產(chǎn)生異常,內(nèi)核將這個異常解釋為SIGSEGV信號發(fā)送給進(jìn)程。
??SIGFPE:浮點(diǎn)異常,當(dāng)程序執(zhí)行了浮點(diǎn)數(shù)異常操作時產(chǎn)生。例如,除以0或溢出。
??SIGSEGV:段錯誤,當(dāng)程序訪問無效的內(nèi)存地址時產(chǎn)生。例如,訪問不屬于自己進(jìn)程的內(nèi)存空間或未初始化的指針。
??SIGBUS:總線錯誤,當(dāng)程序訪問總線錯誤時產(chǎn)生。這通常是由于硬件故障或內(nèi)存問題引起的。
??SIGILL:非法指令,當(dāng)程序執(zhí)行了非法的指令時產(chǎn)生。例如,嘗試執(zhí)行系統(tǒng)保留的特殊指令或未定義的指令。
??綜上:硬件異常被硬件以某種方式被硬件檢測到并通知內(nèi)核,然后內(nèi)核向當(dāng)前進(jìn)程發(fā)送適當(dāng)?shù)男盘枴?/font>
????????????
3.阻塞信號
3.1信號在內(nèi)核中的表示
??每個信號都有兩個標(biāo)志位分別表示阻塞(block)和未決(pending),還有一個函數(shù)指針表示處理動作。信號產(chǎn)生時,內(nèi)核在進(jìn)程控制塊中設(shè)置該信號的未決標(biāo)志,直到信號遞達(dá)才清除該標(biāo)志。在上圖的例子中,SIGHUP信號未阻塞也未產(chǎn)生過,當(dāng)它遞達(dá)時執(zhí)行默認(rèn)處理動作。
??SIGINT信號產(chǎn)生過,但正在被阻塞,所以暫時不能遞達(dá)。 雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因?yàn)檫M(jìn)程仍有機(jī)會改變處理動作之后再解除阻塞。
??SIGQUIT信號未產(chǎn)生過,一旦產(chǎn)生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數(shù)sighandler。 如果在進(jìn)程解除對某信號的阻塞之前這種信號產(chǎn)生過多次,將如何處理?POSIX.1允許系統(tǒng)遞送該信號一次或多次。Linux是這樣實(shí)現(xiàn)的:常規(guī)信號在遞達(dá)之前產(chǎn)生多次只計(jì)一次,而實(shí)時信號在遞達(dá)之前產(chǎn)生多次可以依次放在一個隊(duì)列里。
sigset_t
從上圖來看,每個信號只有一個bit的未決標(biāo)志,非0即1,不記錄該信號產(chǎn)生了多少次,阻塞標(biāo)志也是這樣表示的。因此,未決和阻塞標(biāo)志可以用相同的數(shù)據(jù)類型sigset_t來存儲,sigset_t稱為信號集,這個類型可以表示每個信號的“有效”或“無效”狀態(tài),在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處于未決狀態(tài)。下一節(jié)將詳細(xì)介紹信號集的各種操作。 阻塞信號集也叫做當(dāng)前進(jìn)程的信號屏蔽字(Signal Mask),這里的“屏蔽”應(yīng)該理解為阻塞而不是忽略。
??
3.2信號集操作函數(shù)
??sigset_t類型對于每種信號用一個bit表示“有效”或“無效”狀態(tài),至于這個類型內(nèi)部如何存儲這些bit則依賴于系統(tǒng)實(shí)現(xiàn),從使用者的角度是不必關(guān)心的,使用者只能調(diào)用以下函數(shù)來操作sigset_ t變量,而不應(yīng)該對它的內(nèi)部數(shù)據(jù)做任何解釋,比如用printf直接打印sigset_t變量是沒有意義的。
#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在該信號集中添加或刪除某種有效信號。
??這四個函數(shù)都是成功返回0,出錯返回-1。sigismember是一個布爾函數(shù),用于判斷一個信號集的有效信號中是否包含某種 信號,若包含則返回1,不包含則返回0,出錯返回-1。
??
3.3sigprocmask
??調(diào)用函數(shù)sigprocmask可以讀取或更改進(jìn)程的信號屏蔽字(阻塞信號集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
//返回值:若成功則為0,若出錯則為-1
??如果oset是非空指針,則讀取進(jìn)程的當(dāng)前信號屏蔽字通過oset參數(shù)傳出。如果set是非空指針,則 更改進(jìn)程的信號屏蔽字,參數(shù)how指示如何更改。 如果oset和set都是非空指針,則先將原來的信號 屏蔽字備份到oset里,然后根據(jù)set和how參數(shù)更改信號屏蔽字。假設(shè)當(dāng)前的信號屏蔽字為mask,下表說明了how參數(shù)的可選值。如果調(diào)用sigprocmask解除了對當(dāng)前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達(dá)。
????????????
4.捕捉信號
4.1內(nèi)核如何實(shí)現(xiàn)信號的捕捉
??
??如果信號的處理動作是用戶自定義函數(shù),在信號遞達(dá)時就調(diào)用這個函數(shù),這稱為捕捉信號。 由于信號處理函數(shù)的代碼是在用戶空間的,處理過程比較復(fù)雜。
??舉例如下:用戶程序注冊了SIGQUIT信號的處理函數(shù)sighandler。 當(dāng)前正在執(zhí)行main函數(shù),這時發(fā)生中斷或異常切換到內(nèi)核態(tài)。 在中斷處理完畢后要返回用戶態(tài)的main函數(shù)之前檢查到有信號SIGQUIT遞達(dá)。 內(nèi)核決定返回用戶態(tài)后不是恢復(fù)main函數(shù)的上下文繼續(xù)執(zhí)行,而是執(zhí)行sighandler函數(shù),sighandler和main函數(shù)使用不同的堆??臻g,它們之間不存在調(diào)用和被調(diào)用的關(guān)系,是兩個獨(dú)立的控制流程。 sighandler函數(shù)返回后自動執(zhí)行特殊的系統(tǒng)調(diào)用sigreturn再次進(jìn)入內(nèi)核態(tài)。 如果沒有新的信號要遞達(dá),這次再返回用戶態(tài)就是恢復(fù)main函數(shù)的上下文繼續(xù)執(zhí)行了。
??
4.2 sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
??sigaction函數(shù)可以讀取和修改與指定信號相關(guān)聯(lián)的處理動作。調(diào)用成功則返回0,出錯則返回-1。signo是指定信號的編號。若act指針非空,則根據(jù)act修改該信號的處理動作。若oact指針非空,則通過oact傳出該信號原來的處理動作。act和oact指向sigaction結(jié)構(gòu)體:
??將sa_handler賦值為常數(shù)SIG_IGN傳給sigaction表示忽略信號,賦值為常數(shù)SIG_DFL表示執(zhí)行系統(tǒng)默認(rèn)動作,賦值為一個函數(shù)指針表示用自定義函數(shù)捕捉信號,或者說向內(nèi)核注冊了一個信號處理函數(shù),該函數(shù)返回值為void,可以帶一個int參數(shù),通過參數(shù)可以得知當(dāng)前信號的編號,這樣就可以用同一個函數(shù)處理多種信號。顯然,這也是一個回調(diào)函數(shù),不是被main函數(shù)調(diào)用,而是被系統(tǒng)所調(diào)用。文章來源:http://www.zghlxwxcb.cn/news/detail-786834.html
??當(dāng)某個信號的處理函數(shù)被調(diào)用時,內(nèi)核自動將當(dāng)前信號加入進(jìn)程的信號屏蔽字,當(dāng)信號處理函數(shù)返回時自動恢復(fù)原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產(chǎn)生,那么它會被阻塞到當(dāng)前處理結(jié)束為止。 如果在調(diào)用信號處理函數(shù)時,除了當(dāng)前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當(dāng)信號處理函數(shù)返回時自動恢復(fù)原來的信號屏蔽字。sa_flags字段包含一些選項(xiàng),這里都把sa_flags設(shè)為0,sa_sigaction是實(shí)時信號的處理函數(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-786834.html
到了這里,關(guān)于【Linux】進(jìn)程信號——進(jìn)程信號的概念和介紹、產(chǎn)生信號、四種產(chǎn)生信號方式、阻塞信號、捕捉信號、阻塞和捕捉信號的函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!