引言
在計(jì)算機(jī)科學(xué)領(lǐng)域,信號(hào)是一種重要的通信機(jī)制,用于處理各種系統(tǒng)事件和進(jìn)程間的通信。Linux作為一個(gè)開源操作系統(tǒng),以其穩(wěn)定性和高度可定制性而聞名。在Linux下,信號(hào)的處理是實(shí)現(xiàn)進(jìn)程間通信和事件處理的關(guān)鍵機(jī)制之一。
本文將繼續(xù)探討Linux下信號(hào)的相關(guān)主題,著重介紹信號(hào)的保存、阻塞以及sigprocmask函數(shù)的用法。通過深入了解這些概念和技術(shù),我們能夠更好地掌握Linux信號(hào)處理的原理和方法,提高系統(tǒng)的可靠性和穩(wěn)定性。
通過本文的學(xué)習(xí),讀者將對(duì)Linux下信號(hào)的保存、阻塞和sigprocmask函數(shù)有更深入的了解。這些知識(shí)將幫助讀者在開發(fā)和維護(hù)Linux應(yīng)用程序時(shí)更好地處理信號(hào),提高系統(tǒng)的穩(wěn)定性和性能。無論是初學(xué)者還是有經(jīng)驗(yàn)的開發(fā)人員,都將受益于本文所涵蓋的內(nèi)容。讓我們一起深入研究Linux信號(hào)處理的精髓吧!
一、阻塞信號(hào)
1. 信號(hào)相關(guān)常見概念
(1)信號(hào)遞達(dá)
信號(hào)遞達(dá)是指當(dāng)信號(hào)被發(fā)送和接收后,信號(hào)的處理過程。在Linux中,進(jìn)程可以通過系統(tǒng)調(diào)用kill()
向其他進(jìn)程發(fā)送信號(hào),同時(shí)也可以接收來自其他進(jìn)程的信號(hào)。當(dāng)信號(hào)被發(fā)送到一個(gè)進(jìn)程時(shí),需要經(jīng)過多個(gè)步驟才能被接收并處理:
-
發(fā)送信號(hào):進(jìn)程A使用
kill()
系統(tǒng)調(diào)用向進(jìn)程B發(fā)送信號(hào)。 -
信號(hào)遞送:信號(hào)從進(jìn)程A發(fā)送到進(jìn)程B,進(jìn)程B接收到信號(hào)。
-
信號(hào)處理:進(jìn)程B根據(jù)信號(hào)的類型和處理方式來進(jìn)行相應(yīng)的處理。
在Linux中,每個(gè)信號(hào)都有一個(gè)默認(rèn)的處理方式。例如,SIGKILL信號(hào)會(huì)強(qiáng)制終止進(jìn)程,而SIGINT信號(hào)會(huì)讓進(jìn)程中斷并退出。然而,進(jìn)程也可以通過signal()
系統(tǒng)調(diào)用或sigaction()
系統(tǒng)調(diào)用來改變信號(hào)的處理方式,以便實(shí)現(xiàn)更靈活的信號(hào)處理行為。
??注意:在信號(hào)遞送和信號(hào)處理的過程中,可能會(huì)發(fā)生信號(hào)丟失或者信號(hào)被阻塞的情況。當(dāng)一個(gè)進(jìn)程處于阻塞狀態(tài)時(shí),它將無法接收到任何信號(hào),直到解除了阻塞狀態(tài)。如果多個(gè)信號(hào)同時(shí)到達(dá)進(jìn)程時(shí),可能會(huì)出現(xiàn)信號(hào)排隊(duì)的情況,此時(shí)進(jìn)程需要按照一定的規(guī)則來處理這些信號(hào)。
(2)信號(hào)未決
在Linux中,信號(hào)未決(Pending Signal)指的是一個(gè)進(jìn)程接收到但尚未處理的信號(hào)。當(dāng)一個(gè)信號(hào)被發(fā)送給一個(gè)進(jìn)程時(shí),如果該進(jìn)程當(dāng)前正在執(zhí)行某個(gè)信號(hào)處理函數(shù)或者該信號(hào)已經(jīng)處于未決狀態(tài),則該信號(hào)會(huì)被放入進(jìn)程的信號(hào)未決位集(Pending Signal Mask)中,等待進(jìn)程從信號(hào)處理函數(shù)返回后進(jìn)行處理。
在信號(hào)未決位集中,每個(gè)位代表一個(gè)信號(hào),如果該位為1,則表示該位對(duì)應(yīng)的信號(hào)處于未決狀態(tài)。一個(gè)進(jìn)程可以通過sigpending()
系統(tǒng)調(diào)用來查詢自己的信號(hào)未決位集。
如果一個(gè)進(jìn)程接收到多個(gè)同類型的信號(hào)并且信號(hào)處理函數(shù)尚未返回,則這些信號(hào)將被合并成一個(gè)信號(hào),并只記錄一次信號(hào)未決。進(jìn)程可以使用sigprocmask()
系統(tǒng)調(diào)用來設(shè)置或修改信號(hào)未決掩碼,以控制哪些信號(hào)可以被接收和處理。
??注意:當(dāng)進(jìn)程解除信號(hào)阻塞狀態(tài)后,它必須處理所有未決的信號(hào),否則這些信號(hào)將繼續(xù)被保留在信號(hào)未決位集中,可能會(huì)導(dǎo)致信號(hào)丟失或者其他問題。因此,在處理信號(hào)的過程中,要注意及時(shí)處理所有未決信號(hào),避免信號(hào)積壓導(dǎo)致系統(tǒng)異常。
(3)阻塞信號(hào)
阻塞信號(hào)是指進(jìn)程可以選擇暫時(shí)延遲處理某些特定信號(hào)的傳遞和處理。在Linux中,進(jìn)程可以通過設(shè)置信號(hào)阻塞掩碼(Signal Mask)來達(dá)到這一目的。
當(dāng)一個(gè)信號(hào)被發(fā)送給一個(gè)進(jìn)程時(shí),內(nèi)核會(huì)首先檢查該信號(hào)是否在進(jìn)程的信號(hào)阻塞掩碼中。如果信號(hào)在阻塞掩碼中,則該信號(hào)將被暫時(shí)掛起,直到該信號(hào)從阻塞狀態(tài)解除后才能被處理。進(jìn)程可以使用sigprocmask()系統(tǒng)調(diào)用來修改信號(hào)阻塞掩碼。
通過設(shè)置信號(hào)阻塞掩碼,進(jìn)程可以靈活地控制哪些信號(hào)可以被接收和處理,以及在何種情況下可以延遲處理某些信號(hào)。這種機(jī)制在多線程編程和信號(hào)處理復(fù)雜的應(yīng)用中尤為重要。
??注意:當(dāng)進(jìn)程解除對(duì)某個(gè)信號(hào)的阻塞時(shí),如果有多個(gè)該類型的信號(hào)在阻塞期間到達(dá),那么這些信號(hào)將按照某種規(guī)則進(jìn)行排隊(duì),等待進(jìn)程逐個(gè)處理。另外,即使信號(hào)被阻塞,但仍然會(huì)記錄在信號(hào)未決位集中,等待進(jìn)程解除阻塞后處理。
(4)忽略信號(hào)
忽略信號(hào)是指進(jìn)程可以選擇不對(duì)某些特定信號(hào)進(jìn)行處理,即忽略該信號(hào)的傳遞和默認(rèn)處理行為。在Linux中,進(jìn)程可以通過設(shè)置信號(hào)處理函數(shù)為SIG_IGN來達(dá)到這一目的。
當(dāng)一個(gè)信號(hào)被發(fā)送給一個(gè)進(jìn)程時(shí),內(nèi)核會(huì)首先檢查該信號(hào)的處理方式。如果進(jìn)程將該信號(hào)的處理函數(shù)設(shè)置為SIG_IGN(忽略信號(hào)),則內(nèi)核將不對(duì)該信號(hào)進(jìn)行任何處理,直接丟棄該信號(hào)。
通過忽略信號(hào),進(jìn)程可以屏蔽一些不需要處理的信號(hào),從而避免其產(chǎn)生默認(rèn)的處理行為。對(duì)于某些特定的信號(hào),可能會(huì)存在一些默認(rèn)的處理行為,比如終止進(jìn)程、終止進(jìn)程并生成core文件等。通過忽略信號(hào),進(jìn)程可以防止這些默認(rèn)的處理行為發(fā)生。
??注意:并非所有的信號(hào)都可以被忽略。一些重要的信號(hào),如SIGKILL和SIGSTOP,默認(rèn)情況下是不能被忽略的,它們具有固定的處理行為。此外,一些特殊的信號(hào),如SIGCHLD,可以被設(shè)置為忽略,但是會(huì)導(dǎo)致一些系統(tǒng)資源無法正確釋放,因此需要謹(jǐn)慎使用。
2. 信號(hào)在內(nèi)核中的表示
?信號(hào)在內(nèi)核中的表示示意圖
- 每個(gè)信號(hào)都有兩個(gè)標(biāo)志位分別表示阻塞(block)和未決(pending),還有一個(gè)函數(shù)指針表示處理動(dòng)作。信號(hào)產(chǎn)生時(shí),內(nèi)核在進(jìn)程控制塊中設(shè)置該信號(hào)的未決標(biāo)志,直到信號(hào)遞達(dá)才清除該標(biāo)志。在上圖的例子中,SIGHUP信號(hào)未阻塞也未產(chǎn)生過,當(dāng)它遞達(dá)時(shí)執(zhí)行默認(rèn)處理動(dòng)作。
- SIGINT信號(hào)產(chǎn)生過,但正在被阻塞,所以暫時(shí)不能遞達(dá)。雖然它的處理動(dòng)作是忽略,但在沒有解除阻塞之前不能忽略這個(gè)信號(hào),因?yàn)檫M(jìn)程仍有機(jī)會(huì)改變處理動(dòng)作之后再解除阻塞。
- SIGQUIT信號(hào)未產(chǎn)生過,一旦產(chǎn)生SIGQUIT信號(hào)將被阻塞,它的處理動(dòng)作是用戶自定義函數(shù)sighandler。如果在進(jìn)程解除對(duì)某信號(hào)的阻塞之前這種信號(hào)產(chǎn)生過多次,將如何處理?POSIX.1允許系統(tǒng)遞送該信號(hào)一次或多次。Linux是這樣實(shí)現(xiàn)的:常規(guī)信號(hào)在遞達(dá)之前產(chǎn)生多次只計(jì)一次,而實(shí)時(shí)信號(hào)在遞達(dá)之前產(chǎn)生多次可以依次放在一個(gè)隊(duì)列里。本章不討論實(shí)時(shí)信號(hào)。
3. sigset_t (數(shù)據(jù)類型)
sigset_t
是一個(gè)數(shù)據(jù)類型,用于表示信號(hào)集。它是一個(gè)由位圖組成的數(shù)據(jù)結(jié)構(gòu),用于跟蹤和管理多個(gè)信號(hào)的狀態(tài)。在 C 語言中,sigset_t
常常以無符號(hào)整數(shù)或者數(shù)組的形式實(shí)現(xiàn)。
在 Linux 中,sigset_t
是通過使用位操作來表示信號(hào)集的。每個(gè)信號(hào)對(duì)應(yīng) sigset_t
中的一個(gè)位(bit),如果某個(gè)位被設(shè)置為 1,則表示相應(yīng)的信號(hào)在該信號(hào)集中被包含;如果某個(gè)位被設(shè)置為 0,則表示相應(yīng)的信號(hào)在該信號(hào)集中不被包含。
sigset_t
通常用于以下操作:
- 設(shè)置信號(hào)集中的某個(gè)位:可以使用宏函數(shù)
sigaddset()
或sigemptyset()
來設(shè)置信號(hào)集中的位。sigaddset()
可以將指定的信號(hào)添加到信號(hào)集中,而sigemptyset()
可以清空信號(hào)集。 - 清除信號(hào)集中的某個(gè)位:可以使用宏函數(shù)
sigdelset()
來清除信號(hào)集中的位,從而從信號(hào)集中刪除指定的信號(hào)。 - 檢查信號(hào)集中的某個(gè)位是否被設(shè)置:可以使用宏函數(shù)
sigismember()
來檢查信號(hào)集中的位是否被設(shè)置,從而判斷指定的信號(hào)是否在信號(hào)集中。
sigset_t
的使用可以幫助進(jìn)程或線程控制和管理信號(hào)的行為,如阻塞或解除阻塞某些信號(hào),判斷信號(hào)是否被阻塞等。在信號(hào)處理函數(shù)中,可以通過調(diào)用相關(guān)的系統(tǒng)調(diào)用來獲取和修改當(dāng)前進(jìn)程或線程的 sigset_t
,以實(shí)現(xiàn)對(duì)信號(hào)的處理和控制。
4. 信號(hào)集操作函數(shù)
在C語言中,可以使用以下函數(shù)來進(jìn)行信號(hào)集(sigset_t
)的操作:
函數(shù) | 功能 |
---|---|
sigemptyset(sigset_t *set) | 清空信號(hào)集,將所有信號(hào)從集合中移除。 |
sigfillset(sigset_t *set) | 將所有信號(hào)添加到信號(hào)集中,使其包含所有信號(hào)。 |
sigaddset(sigset_t *set, int signum) | 將指定的信號(hào)添加到信號(hào)集中。 |
sigdelset(sigset_t *set, int signum) | 從信號(hào)集中刪除指定的信號(hào)。 |
sigismember(const sigset_t *set, int signum) | 檢查指定的信號(hào)是否在信號(hào)集中,如果在返回1,否則返回0。 |
這些函數(shù)都返回一個(gè)整數(shù)值來表示操作的成功與否。如果函數(shù)執(zhí)行成功,返回值為0;如果函數(shù)執(zhí)行失敗,返回值為-1,并設(shè)置相應(yīng)的錯(cuò)誤號(hào)(errno
)。
這些函數(shù)通常用于與信號(hào)處理相關(guān)的操作,例如設(shè)置阻塞信號(hào)集、檢查信號(hào)是否被阻塞、解除阻塞等。通過操作信號(hào)集,可以對(duì)進(jìn)程接收和處理的信號(hào)進(jìn)行控制。
需要包含 <signal.h>
頭文件才能使用上述函數(shù)。此外,還有其他一些與信號(hào)處理相關(guān)的函數(shù),如 sigprocmask()
、sigpending()
等,它們也可用于信號(hào)集的操作和管理。
二、sigprocmask() 函數(shù)
sigprocmask()
函數(shù)用于更改或檢索進(jìn)程的信號(hào)屏蔽字(signal mask)。信號(hào)屏蔽字決定了進(jìn)程當(dāng)前阻塞的信號(hào)集,即哪些信號(hào)在被阻塞的情況下不能被進(jìn)程接收到。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask()
函數(shù)接受三個(gè)參數(shù):
-
how
:用于指定對(duì)信號(hào)屏蔽字的操作。
操作類型 | 描述 |
---|---|
SIG_BLOCK | 將 set 指向的信號(hào)集中的信號(hào)添加到當(dāng)前的信號(hào)屏蔽字中。 |
SIG_UNBLOCK | 從當(dāng)前的信號(hào)屏蔽字中移除 set 指向的信號(hào)集中的信號(hào)。 |
SIG_SETMASK | 將當(dāng)前的信號(hào)屏蔽字替換為 set 指向的信號(hào)集。 |
-
set
:一個(gè)指向sigset_t
類型的指針,指向要設(shè)置的新的信號(hào)屏蔽字。 -
oldset
:可選參數(shù),如果不為NULL
,則舊的信號(hào)屏蔽字將被存儲(chǔ)在oldset
指向的位置。
sigprocmask()
函數(shù)返回值表示操作的成功與否。如果函數(shù)執(zhí)行成功,返回值為0;如果函數(shù)執(zhí)行失敗,返回值為-1,并設(shè)置相應(yīng)的錯(cuò)誤號(hào)(errno
)。
以下示例演示了如何使用 sigprocmask()
函數(shù):
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t newset, oldset;
// 設(shè)置要阻塞的信號(hào)集
sigemptyset(&newset);
sigaddset(&newset, SIGINT);
// 阻塞 SIGINT 信號(hào)
if (sigprocmask(SIG_BLOCK, &newset, &oldset) == -1) {
perror("sigprocmask");
return 1;
}
printf("SIGINT is blocked. Press Ctrl+C to send the signal.\n");
// 掛起進(jìn)程,等待信號(hào)到達(dá)
pause();
// 恢復(fù)原來的信號(hào)屏蔽字
if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
perror("sigprocmask");
return 1;
}
printf("SIGINT is unblocked. Signal handling resumed.\n");
return 0;
}
上述示例將 SIGINT
信號(hào)添加到新的信號(hào)屏蔽字中,然后使用 sigprocmask()
函數(shù)將其阻塞。當(dāng)程序運(yùn)行時(shí),按下 Ctrl+C 將發(fā)送 SIGINT
信號(hào),但由于該信號(hào)被阻塞,進(jìn)程掛起直到信號(hào)解除阻塞后才繼續(xù)執(zhí)行。在恢復(fù)原來的信號(hào)屏蔽字后,程序可以正常處理 SIGINT
信號(hào)。
三、sigpending() 函數(shù)
sigpending()
函數(shù)用于獲取當(dāng)前進(jìn)程掛起(pending)的信號(hào)集,即已經(jīng)產(chǎn)生但尚未被進(jìn)程處理的信號(hào)。
#include <signal.h>
int sigpending(sigset_t *set);
sigpending()
函數(shù)接受一個(gè)指向 sigset_t
類型的指針作為參數(shù),用于存儲(chǔ)當(dāng)前掛起的信號(hào)集。該函數(shù)將會(huì)將當(dāng)前進(jìn)程掛起的信號(hào)填充到 set
指向的信號(hào)集中。
sigpending()
函數(shù)返回值表示操作的成功與否。如果函數(shù)執(zhí)行成功,返回值為0;如果函數(shù)執(zhí)行失敗,返回值為-1,并設(shè)置相應(yīng)的錯(cuò)誤號(hào)(errno
)。
以下示例演示了如何使用 sigpending()
函數(shù):
#include <stdio.h>
#include <signal.h>
void handler(int signum) {
printf("Received signal: %d\n", signum);
}
int main() {
sigset_t pending_set;
// 設(shè)置信號(hào)處理函數(shù)
signal(SIGINT, handler);
// 發(fā)送 SIGINT 信號(hào)
raise(SIGINT);
// 獲取掛起的信號(hào)集
if (sigpending(&pending_set) == -1) {
perror("sigpending");
return 1;
}
// 檢查 SIGINT 是否在掛起的信號(hào)集中
if (sigismember(&pending_set, SIGINT)) {
printf("SIGINT is pending.\n");
} else {
printf("SIGINT is not pending.\n");
}
return 0;
}
上述示例中,首先定義了一個(gè)信號(hào)處理函數(shù) handler
,當(dāng)接收到 SIGINT
信號(hào)時(shí),該函數(shù)將被調(diào)用。然后使用 signal()
函數(shù)將 SIGINT
信號(hào)與該處理函數(shù)關(guān)聯(lián)。
接下來,通過調(diào)用 raise(SIGINT)
發(fā)送 SIGINT
信號(hào)給當(dāng)前進(jìn)程。
然后,使用 sigpending()
函數(shù)獲取當(dāng)前掛起的信號(hào)集,并將結(jié)果存儲(chǔ)在 pending_set
中。
最后,使用 sigismember()
函數(shù)檢查 SIGINT
是否在掛起的信號(hào)集中,根據(jù)結(jié)果輸出相應(yīng)的信息。
請(qǐng)注意,由于信號(hào)的處理是異步的,在獲取掛起的信號(hào)集之前,可能已經(jīng)有其他信號(hào)被處理掉了。因此,sigpending()
只能提供當(dāng)前未被處理的掛起信號(hào)的部分信息。
溫馨提示
感謝您對(duì)博主文章的關(guān)注與支持!如果您喜歡這篇文章,可以點(diǎn)贊、評(píng)論和分享給您的同學(xué),這將對(duì)我提供巨大的鼓勵(lì)和支持。另外,我計(jì)劃在未來的更新中持續(xù)探討與本文相關(guān)的內(nèi)容。我會(huì)為您帶來更多關(guān)于Linux以及C++編程技術(shù)問題的深入解析、應(yīng)用案例和趣味玩法等。如果感興趣的話可以關(guān)注博主的更新,不要錯(cuò)過任何精彩內(nèi)容!文章來源:http://www.zghlxwxcb.cn/news/detail-752444.html
再次感謝您的支持和關(guān)注。我們期待與您建立更緊密的互動(dòng),共同探索Linux、C++、算法和編程的奧秘。祝您生活愉快,排便順暢!文章來源地址http://www.zghlxwxcb.cn/news/detail-752444.html
到了這里,關(guān)于【探索Linux】—— 強(qiáng)大的命令行工具 P.17(進(jìn)程信號(hào) —— 信號(hào)保存 | 阻塞信號(hào) | sigprocmask() | sigpending() )的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!