溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Linux的Signal機(jī)制是什么

發(fā)布時(shí)間:2022-02-18 09:43:41 來(lái)源:億速云 閱讀:163 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹了Linux的Signal機(jī)制是什么的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Linux的Signal機(jī)制是什么文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

Signal機(jī)制在Linux中是一個(gè)非常常用的進(jìn)程間通信機(jī)制,很多人在使用的時(shí)候不會(huì)考慮該機(jī)制是具體如何實(shí)現(xiàn)的。signal機(jī)制可以被理解成進(jìn)程的軟中斷,因此,在實(shí)時(shí)性方面還是相對(duì)比較高的。

Linux的Signal機(jī)制是什么
信號(hào)概述
  1. 信號(hào)的名字和編號(hào): 每個(gè)信號(hào)都有一個(gè)名字和編號(hào),這些名字都以“SIG”開(kāi)頭,例如“SIGIO ”、“SIGCHLD”等等。 信號(hào)定義在signal.h頭文件中,信號(hào)名都定義為正整數(shù)。 具體的信號(hào)名稱可以使用kill -l來(lái)查看信號(hào)的名字以及序號(hào),信號(hào)是從1開(kāi)始編號(hào)的,不存在0號(hào)信號(hào)。kill對(duì)于信號(hào)0又特殊的應(yīng)用。

    Linux的Signal機(jī)制是什么

    信號(hào)的名稱

  2. 信號(hào)的處理: 信號(hào)的處理有三種方法,分別是:忽略、捕捉和默認(rèn)動(dòng)作

  • 忽略信號(hào),大多數(shù)信號(hào)可以使用這個(gè)方式來(lái)處理,但是有兩種信號(hào)不能被忽略(分別是 SIGKILLSIGSTOP)。因?yàn)樗麄兿騼?nèi)核和超級(jí)用戶提供了進(jìn)程終止和停止的可靠方法,如果忽略了,那么這個(gè)進(jìn)程就變成了沒(méi)人能管理的的進(jìn)程,顯然是內(nèi)核設(shè)計(jì)者不希望看到的場(chǎng)景
  • 捕捉信號(hào),需要告訴內(nèi)核,用戶希望如何處理某一種信號(hào),說(shuō)白了就是寫一個(gè)信號(hào)處理函數(shù),然后將這個(gè)函數(shù)告訴內(nèi)核。當(dāng)該信號(hào)產(chǎn)生時(shí),由內(nèi)核來(lái)調(diào)用用戶自定義的函數(shù),以此來(lái)實(shí)現(xiàn)某種信號(hào)的處理。
  • 系統(tǒng)默認(rèn)動(dòng)作,對(duì)于每個(gè)信號(hào)來(lái)說(shuō),系統(tǒng)都對(duì)應(yīng)由默認(rèn)的處理動(dòng)作,當(dāng)發(fā)生了該信號(hào),系統(tǒng)會(huì)自動(dòng)執(zhí)行。不過(guò),對(duì)系統(tǒng)來(lái)說(shuō),大部分的處理方式都比較粗暴,就是直接殺死該進(jìn)程。 具體的信號(hào)默認(rèn)動(dòng)作可以使用man 7 signal來(lái)查看系統(tǒng)的具體定義。在此,我就不詳細(xì)展開(kāi)了,需要查看的,可以自行查看。也可以參考 《UNIX 環(huán)境高級(jí)編程(第三部)》的 P251——P256中間對(duì)于每個(gè)信號(hào)有詳細(xì)的說(shuō)明。

了解了信號(hào)的概述,那么,信號(hào)是如何來(lái)使用呢?

?

其實(shí)對(duì)于常用的 kill 命令就是一個(gè)發(fā)送信號(hào)的工具,kill 9 PID來(lái)殺死進(jìn)程。比如,我在后臺(tái)運(yùn)行了一個(gè) top 工具,通過(guò) ps 命令可以查看他的 PID,通過(guò) kill 9 來(lái)發(fā)送了一個(gè)終止進(jìn)程的信號(hào)來(lái)結(jié)束了 top 進(jìn)程。如果查看信號(hào)編號(hào)和名稱,可以發(fā)現(xiàn)9對(duì)應(yīng)的是 9) SIGKILL,正是殺死該進(jìn)程的信號(hào)。而以下的執(zhí)行過(guò)程實(shí)際也就是執(zhí)行了9號(hào)信號(hào)的默認(rèn)動(dòng)作——殺死進(jìn)程。

Linux的Signal機(jī)制是什么

kill 殺死進(jìn)程

對(duì)于信號(hào)來(lái)說(shuō),最大的意義不是為了殺死信號(hào),而是實(shí)現(xiàn)一些異步通訊的手段,那么如何來(lái)自定義信號(hào)的處理函數(shù)呢?

信號(hào)處理函數(shù)的注冊(cè)

信號(hào)處理函數(shù)的注冊(cè)不只一種方法,分為入門版和高級(jí)版

  1. 入門版:函數(shù)signal
  2. 高級(jí)版:函數(shù)sigaction
信號(hào)處理發(fā)送函數(shù)

信號(hào)發(fā)送函數(shù)也不止一個(gè),同樣分為入門版和高級(jí)版 1.入門版:kill 2.高級(jí)版:sigqueue

信號(hào)注冊(cè)函數(shù)——入門版

在正式開(kāi)始了解這兩個(gè)函數(shù)之前,可以先來(lái)思考一下,處理中斷都需要處理什么問(wèn)題。 按照我們之前思路來(lái)看,可以發(fā)送的信號(hào)類型是多種多樣的,每種信號(hào)的處理可能不一定相同,那么,我們肯定需要知道到底發(fā)生了什么信號(hào)。 另外,雖然我們知道了系統(tǒng)發(fā)出來(lái)的是哪種信號(hào),但是還有一點(diǎn)也很重要,就是系統(tǒng)產(chǎn)生了一個(gè)信號(hào),是由誰(shuí)來(lái)響應(yīng)? 如果系統(tǒng)通過(guò) ctrl+c 產(chǎn)生了一個(gè) SIGINT(中斷信號(hào)),顯然不是所有程序同時(shí)結(jié)束,那么,信號(hào)一定需要有一個(gè)接收者。對(duì)于處理信號(hào)的程序來(lái)說(shuō),接收者就是自己。

開(kāi)始的時(shí)候,先來(lái)看看入門版本的信號(hào)注冊(cè)函數(shù),他的函數(shù)原型如下: signal 的函數(shù)原型

#include typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

根據(jù)函數(shù)原型可以看出由兩部分組成,一個(gè)是真實(shí)處理信號(hào)的函數(shù),另一個(gè)是注冊(cè)函數(shù)了。 對(duì)于sighandler_t signal(int signum, sighandler_t handler);函數(shù)來(lái)說(shuō),signum 顯然是信號(hào)的編號(hào),handler 是中斷函數(shù)的指針。 同樣,typedef void (*sighandler_t)(int);中斷函數(shù)的原型中,有一個(gè)參數(shù)是 int 類型,顯然也是信號(hào)產(chǎn)生的類型,方便使用一個(gè)函數(shù)來(lái)處理多個(gè)信號(hào)。我們先來(lái)看看簡(jiǎn)單一個(gè)信號(hào)注冊(cè)的代碼示例吧。

#include#include#include //typedef void (*sighandler_t)(int);
void
handler(int signum)
{
   if(signum == SIGIO)
       printf("SIGIO   signal: %d\n", signum);
   else if(signum == SIGUSR1)
       printf("SIGUSR1   signal: %d\n", signum);
   else       printf("error\n");
}

int
main(void)
{
   //sighandler_t signal(int signum, sighandler_t handler);
   signal(SIGIO, handler);
   signal(SIGUSR1, handler);
   printf("%d  %d\n", SIGIO, SIGUSR1);
   for(;;)
   {
       sleep(10000);
   }
   return 0;
}

我們先使用 kill 命令發(fā)送信號(hào)給之前所寫的程序,關(guān)于這個(gè)命令,我們后面再談。

Linux的Signal機(jī)制是什么

通過(guò) kill 命令發(fā)送信號(hào)

Linux的Signal機(jī)制是什么

程序接收到的信號(hào)的處理結(jié)果

簡(jiǎn)單的總結(jié)一下,我們通過(guò) signal 函數(shù)注冊(cè)一個(gè)信號(hào)處理函數(shù),分別注冊(cè)了兩個(gè)信號(hào)(SIGIO 和 SIGUSER1);隨后主程序就一直“長(zhǎng)眠”了。 通過(guò) kill 命令發(fā)送信號(hào)之前,我們需要先查看到接收者,通過(guò) ps 命令查看了之前所寫的程序的 PID,通過(guò) kill 函數(shù)來(lái)發(fā)送。 對(duì)于已注冊(cè)的信號(hào),使用 kill 發(fā)送都可以正常接收到,但是如果發(fā)送了未注冊(cè)的信號(hào),則會(huì)使得應(yīng)用程序終止進(jìn)程。

那么,已經(jīng)可以設(shè)置信號(hào)處理函數(shù)了,信號(hào)的處理還有兩種狀態(tài),分別是默認(rèn)處理和忽略,這兩種設(shè)置很簡(jiǎn)單,只需要將 handler 設(shè)置為 SIG_IGN(忽略信號(hào))或 SIG_DFL(默認(rèn)動(dòng)作)即可。

在此還有兩個(gè)問(wèn)題需要說(shuō)明一下:

  1. 當(dāng)執(zhí)行一個(gè)程序時(shí),所有信號(hào)的狀態(tài)都是系統(tǒng)默認(rèn)或者忽略狀態(tài)的。除非是 調(diào)用exec進(jìn)程忽略了某些信號(hào)。exec 函數(shù)將原先設(shè)置為要捕捉的信號(hào)都更改為默認(rèn)動(dòng)作,其他信號(hào)的狀態(tài)則不會(huì)改變 。 2.當(dāng)一個(gè)進(jìn)程調(diào)動(dòng)了 fork 函數(shù),那么子進(jìn)程會(huì)繼承父進(jìn)程的信號(hào)處理方式。

入門版的信號(hào)注冊(cè)還是比較簡(jiǎn)單的,只需要一句注冊(cè)和一個(gè)處理函數(shù)即可,那么,接下來(lái)看看,如何發(fā)送信號(hào)吧。

信號(hào)發(fā)送函數(shù)——入門版

kill 的函數(shù)原型

#include #include int kill(pid_t pid, int sig);

正如我之前所說(shuō)的,信號(hào)的處理需要有接受者,顯然發(fā)送者必須要知道發(fā)給誰(shuí),根據(jù) kill 函數(shù)的遠(yuǎn)行可以看到,pid 就是接受者的 pid,sig 則是發(fā)送的信號(hào)的類型。從原型來(lái)看,發(fā)送信號(hào)要比接受信號(hào)還要簡(jiǎn)單些,那么我們直接上代碼吧~~!Show me the code!!!

#include #include #include#include int main(int argc, char** argv)
{
   if(3 != argc)
   {
       printf("[Arguments ERROR!]\n");
       printf("\tUsage:\n");
       printf("\t\t%s  \n", argv[0]);
       return -1;
   }
   int pid = atoi(argv[1]);
   int sig = atoi(argv[2]);
   //int kill(pid_t pid, int sig);
   if(pid > 0 && sig > 0)
   {
       kill(pid, sig);
   }
   else   {
       printf("Target_PID or Signal_Number MUST bigger than 0!\n");
   }
   
   return 0;
}
Linux的Signal機(jī)制是什么
img

發(fā)送信號(hào)

Linux的Signal機(jī)制是什么

接收信號(hào)的結(jié)果

總結(jié)一下: 根據(jù)以上的結(jié)果可看到,基本可以實(shí)現(xiàn)了信號(hào)的發(fā)送,雖然不能直接發(fā)送信號(hào)名稱,但是通過(guò)信號(hào)的編號(hào),可以正常的給程序發(fā)送信號(hào)了,也是初步實(shí)現(xiàn)了信號(hào)的發(fā)送流程。

關(guān)于 kill 函數(shù),還有一點(diǎn)需要額外說(shuō)明,上面的程序限定了 pid 必須為大于0的正整數(shù),其實(shí) kill 函數(shù)傳入的 pid 可以是小于等于0的整數(shù)。 pid > 0:將發(fā)送個(gè)該 pid 的進(jìn)程 pid == 0:將會(huì)把信號(hào)發(fā)送給與發(fā)送進(jìn)程屬于同一進(jìn)程組的所有進(jìn)程,并且發(fā)送進(jìn)程具有權(quán)限想這些進(jìn)程發(fā)送信號(hào)。 pid

關(guān)于信號(hào),還有更多的話題,比如,信號(hào)是否都能夠準(zhǔn)確的送達(dá)到目標(biāo)進(jìn)程呢?答案其實(shí)是不一定,那么這就有了可靠信號(hào)和不可靠信號(hào)

可靠信號(hào)和不可靠信號(hào)

不可靠信號(hào):信號(hào)可能會(huì)丟失,一旦信號(hào)丟失了,進(jìn)程并不能知道信號(hào)丟失 可靠信號(hào):也是阻塞信號(hào),當(dāng)發(fā)送了一個(gè)阻塞信號(hào),并且該信號(hào)的動(dòng)作時(shí)系統(tǒng)默認(rèn)動(dòng)作或捕捉該信號(hào),如果信號(hào)從發(fā)出以后會(huì)一直保持未決的狀態(tài),直到該進(jìn)程對(duì)此信號(hào)解除了阻塞,或?qū)?duì)此信號(hào)的動(dòng)作更改為忽略。 對(duì)于信號(hào)來(lái)說(shuō),信號(hào)編號(hào)小于等于31的信號(hào)都是不可靠信號(hào),之后的信號(hào)為可卡信號(hào),系統(tǒng)會(huì)根據(jù)有信號(hào)隊(duì)列,將信號(hào)在遞達(dá)之前進(jìn)行阻塞。

信號(hào)的阻塞和未決是通過(guò)信號(hào)的狀態(tài)字來(lái)管理的,該狀態(tài)字是按位來(lái)管理信號(hào)的狀態(tài)。每個(gè)信號(hào)都有獨(dú)立的阻塞字,規(guī)定了當(dāng)前要阻塞地達(dá)到該進(jìn)程的信號(hào)集。

信號(hào)阻塞狀態(tài)字(block),1代表阻塞、0代表不阻塞;信號(hào)未決狀態(tài)字(pending)的1代表未決,0代表信號(hào)可以抵達(dá)了;它們都是每一個(gè)bit代表一個(gè)信號(hào)

  • 阻塞和未決是如何工作的? 比如向進(jìn)程發(fā)送SIGINT信號(hào),內(nèi)核首先會(huì)判斷該進(jìn)程的信號(hào)阻塞狀態(tài)字是否阻塞狀態(tài),如果該信號(hào)被設(shè)置為阻塞的狀態(tài),也就是阻塞狀態(tài)字對(duì)應(yīng)位為1,那么信號(hào)未決狀態(tài)字(pending)相應(yīng)位會(huì)被內(nèi)核設(shè)置為1;如果該信號(hào)阻塞解除了,也就是阻塞狀態(tài)字設(shè)置為了0,那么信號(hào)未決狀態(tài)字(pending)相應(yīng)位會(huì)被內(nèi)核設(shè)置為0,表示信號(hào)此時(shí)可以抵達(dá)了,也就是可以接收該信號(hào)了。 阻塞狀態(tài)字用戶可以讀寫,未決狀態(tài)字用戶只能讀,是由內(nèi)核來(lái)設(shè)置表示信號(hào)遞達(dá)狀態(tài)的。 PS:這里額外說(shuō)明以下,只有支持了 POSIX.1實(shí)時(shí)擴(kuò)展的系統(tǒng)才支持排隊(duì)的功能(也就阻塞狀態(tài)下多次同一信號(hào)發(fā)送給某一進(jìn)程可以得到多次,而不是一次)。
  • 關(guān)于進(jìn)程關(guān)于信號(hào)的阻塞狀態(tài)字的設(shè)置 可以通過(guò)int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);函數(shù)來(lái)獲取或者設(shè)置。

該函數(shù)管理信號(hào),是通過(guò)信號(hào)集的數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行管理的,信號(hào)集可以通過(guò)以下的函數(shù)進(jìn)行管理。 信號(hào)集操作函數(shù)(狀態(tài)字表示)

#include       int sigemptyset(sigset_t *set);  //初始化 set 中傳入的信號(hào)集,清空其中所有信號(hào)
      int sigfillset(sigset_t *set);  //把信號(hào)集填1,讓 set 包含所有的信號(hào)
      int sigaddset(sigset_t *set, int signum);//把信號(hào)集對(duì)應(yīng)位置為1
      int sigdelset(sigset_t *set, int signum);//吧信號(hào)集對(duì)應(yīng)位置為0
      int sigismember(const sigset_t *set, int signum);//判斷signal是否在信號(hào)集

對(duì)于信號(hào)集分配好內(nèi)存空間,需要使用初始化函數(shù)來(lái)初始化。初始化完成后,可以在該集合中添加、刪除特定的信號(hào)。 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 其中 how 變量決定了是如何操作該狀態(tài)字。 SIG_BLOCK:set包含了我們希望添加到當(dāng)前信號(hào)阻塞字的信號(hào),相當(dāng)于mask=mask|set SIG_UNBLOCK:set包含了我們希望從當(dāng)前信號(hào)阻塞字中解除阻塞的信號(hào),相當(dāng)于mask=mask&~set SIG_SETMASK:設(shè)置當(dāng)前信號(hào)阻塞字為set所指的值,相當(dāng)于mask=set

pending是由內(nèi)核來(lái)根據(jù)block設(shè)置的,只可以讀取其中的數(shù)據(jù),來(lái)段判斷信號(hào)是否會(huì)遞達(dá)。通過(guò)設(shè)置block可以將希望阻塞的信號(hào)進(jìn)行阻塞,對(duì)應(yīng)的pending會(huì)由內(nèi)核來(lái)設(shè)置

設(shè)置信號(hào)阻塞、未達(dá)的步驟:

  1. 分配內(nèi)存空間sigset sigset bset;
  2. 置空sigemptyset(&bset);
  3. 添加信號(hào)sigaddset(&bset, SIGINT);
  4. 添加其他需要管理的信號(hào)….
  5. 設(shè)置信號(hào)集中的信號(hào)處理方案(此處為解除阻塞)sigprocmask(SIG_UNBLOCK, &bset, NULL);
  • 簡(jiǎn)化版設(shè)置阻塞狀態(tài)字
#include int sigpending(sigset_t *set);

這個(gè)函數(shù)使用很簡(jiǎn)單,對(duì)于調(diào)用他的進(jìn)程來(lái)說(shuō),其中信號(hào)集中的信號(hào)是阻塞不能遞送的,那么,也就一定會(huì)是當(dāng)前未決的。

  • 原子操作的信號(hào)阻塞字的恢復(fù)并進(jìn)入休眠狀態(tài)
#include int sigsuspend(const sigset_t *mask);

為何會(huì)出現(xiàn)原子性的解除阻塞的函數(shù)呢? 因?yàn)?,?dāng)信號(hào)被阻塞的時(shí)候,產(chǎn)生了信號(hào),那么該信號(hào)的遞送就要推遲到這個(gè)信號(hào)被解除了阻塞為止。如果此時(shí),應(yīng)用程序正好處在,解除 SIGINT 的阻塞和 pause 之間,那么此時(shí),會(huì)產(chǎn)生問(wèn)題,可能永遠(yuǎn) pause 不能夠等到SIGINT 信號(hào)來(lái)打斷他,造成程序永久阻塞在 pause 處。 為了解決這個(gè)問(wèn)題,,需要在一個(gè)原子性的操作來(lái)恢復(fù)信號(hào)的屏蔽字,然后才能讓進(jìn)程進(jìn)入休眠狀態(tài),以保證不會(huì)出現(xiàn)上述的問(wèn)題。

進(jìn)程的信號(hào)屏蔽字設(shè)置為

信號(hào)注冊(cè)函數(shù)——高級(jí)版

我們已經(jīng)成功完成了信號(hào)的收發(fā),那么為什么會(huì)有高級(jí)版出現(xiàn)呢?其實(shí)之前的信號(hào)存在一個(gè)問(wèn)題就是,雖然發(fā)送和接收到了信號(hào),可是總感覺(jué)少些什么,既然都已經(jīng)把信號(hào)發(fā)送過(guò)去了,為何不能再攜帶一些數(shù)據(jù)呢? 正是如此,我們需要另外的函數(shù)來(lái)通過(guò)信號(hào)傳遞的過(guò)程中,攜帶一些數(shù)據(jù)。咱么先來(lái)看看發(fā)送的函數(shù)吧。

sigaction 的函數(shù)原型

#include int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
  void       (*sa_handler)(int); //信號(hào)處理程序,不接受額外數(shù)據(jù),SIG_IGN 為忽略,SIG_DFL 為默認(rèn)動(dòng)作
  void       (*sa_sigaction)(int, siginfo_t *, void *); //信號(hào)處理程序,能夠接受額外數(shù)據(jù)和sigqueue配合使用
  sigset_t   sa_mask;//阻塞關(guān)鍵字的信號(hào)集,可以再調(diào)用捕捉函數(shù)之前,把信號(hào)添加到信號(hào)阻塞字,信號(hào)捕捉函數(shù)返回之前恢復(fù)為原先的值。
  int        sa_flags;//影響信號(hào)的行為SA_SIGINFO表示能夠接受數(shù)據(jù)
};
//回調(diào)函數(shù)句柄sa_handler、sa_sigaction只能任選其一

這個(gè)函數(shù)的原版幫助信息,可以通過(guò)man sigaction來(lái)查看。

sigaction 是一個(gè)系統(tǒng)調(diào)用,根據(jù)這個(gè)函數(shù)原型,我們不難看出,在函數(shù)原型中,第一個(gè)參數(shù)signum應(yīng)該就是注冊(cè)的信號(hào)的編號(hào);第二個(gè)參數(shù)act如果不為空說(shuō)明需要對(duì)該信號(hào)有新的配置;第三個(gè)參數(shù)oldact如果不為空,那么可以對(duì)之前的信號(hào)配置進(jìn)行備份,以方便之后進(jìn)行恢復(fù)。

在這里額外說(shuō)一下struct sigaction結(jié)構(gòu)體中的 sa_mask 成員,設(shè)置在其的信號(hào)集中的信號(hào),會(huì)在捕捉函數(shù)調(diào)用前設(shè)置為阻塞,并在捕捉函數(shù)返回時(shí)恢復(fù)默認(rèn)原有設(shè)置。這樣的目的是,在調(diào)用信號(hào)處理函數(shù)時(shí),就可以阻塞默寫信號(hào)了。在信號(hào)處理函數(shù)被調(diào)用時(shí),操作系統(tǒng)會(huì)建立新的信號(hào)阻塞字,包括正在被遞送的信號(hào)。因此,可以保證在處理一個(gè)給定信號(hào)時(shí),如果這個(gè)種信號(hào)再次發(fā)生,那么他會(huì)被阻塞到對(duì)之前一個(gè)信號(hào)的處理結(jié)束為止。

sigaction 的時(shí)效性:當(dāng)對(duì)某一個(gè)信號(hào)設(shè)置了指定的動(dòng)作的時(shí)候,那么,直到再次顯式調(diào)用 sigaction并改變動(dòng)作之前都會(huì)一直有效。

關(guān)于結(jié)構(gòu)體中的 flag 屬性的詳細(xì)配置,在此不做詳細(xì)的說(shuō)明了,只說(shuō)明其中一點(diǎn)。如果設(shè)置為 SA_SIGINFO 屬性時(shí),說(shuō)明了信號(hào)處理程序帶有附加信息,也就是會(huì)調(diào)用 sa_sigaction 這個(gè)函數(shù)指針?biāo)赶虻男盘?hào)處理函數(shù)。否則,系統(tǒng)會(huì)默認(rèn)使用 sa_handler 所指向的信號(hào)處理函數(shù)。在此,還要特別說(shuō)明一下,sa_sigaction 和 sa_handler 使用的是同一塊內(nèi)存空間,相當(dāng)于 union,所以只能設(shè)置其中的一個(gè),不能兩個(gè)都同時(shí)設(shè)置。

關(guān)于void (*sa_sigaction)(int, siginfo_t *, void *);處理函數(shù)來(lái)說(shuō)還需要有一些說(shuō)明。void* 是接收到信號(hào)所攜帶的額外數(shù)據(jù);而struct siginfo這個(gè)結(jié)構(gòu)體主要適用于記錄接收信號(hào)的一些相關(guān)信息。

siginfo_t {
              int      si_signo;    /* Signal number */
              int      si_errno;    /* An errno value */
              int      si_code;     /* Signal code */
              int      si_trapno;   /* Trap number that caused
                                       hardware-generated signal
                                       (unused on most architectures) */
              pid_t    si_pid;      /* Sending process ID */
              uid_t    si_uid;      /* Real user ID of sending process */
              int      si_status;   /* Exit value or signal */
              clock_t  si_utime;    /* User time consumed */
              clock_t  si_stime;    /* System time consumed */
              sigval_t si_value;    /* Signal value */
              int      si_int;      /* POSIX.1b signal */
              void    *si_ptr;      /* POSIX.1b signal */
              int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
              int      si_timerid;  /* Timer ID; POSIX.1b timers */
              void    *si_addr;     /* Memory location which caused fault */
              int      si_band;     /* Band event */
              int      si_fd;       /* File descriptor */
}

其中的成員很多,si_signo 和 si_code 是必須實(shí)現(xiàn)的兩個(gè)成員??梢酝ㄟ^(guò)這個(gè)結(jié)構(gòu)體獲取到信號(hào)的相關(guān)信息。 關(guān)于發(fā)送過(guò)來(lái)的數(shù)據(jù)是存在兩個(gè)地方的,sigval_t si_value這個(gè)成員中有保存了發(fā)送過(guò)來(lái)的信息;同時(shí),在si_int或者si_ptr成員中也保存了對(duì)應(yīng)的數(shù)據(jù)。

那么,kill 函數(shù)發(fā)送的信號(hào)是無(wú)法攜帶數(shù)據(jù)的,我們現(xiàn)在還無(wú)法驗(yàn)證發(fā)送收的部分,那么,我們先來(lái)看看發(fā)送信號(hào)的高級(jí)用法后,我們?cè)賮?lái)看看如何通過(guò)信號(hào)來(lái)攜帶數(shù)據(jù)吧。

信號(hào)發(fā)送函數(shù)——高級(jí)版
#include int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
  int   sival_int;
  void *sival_ptr;
};

使用這個(gè)函數(shù)之前,必須要有幾個(gè)操作需要完成

  1. 使用 sigaction 函數(shù)安裝信號(hào)處理程序時(shí),制定了 SA_SIGINFO 的標(biāo)志。
  2. sigaction 結(jié)構(gòu)體中的 sa_sigaction 成員提供了信號(hào)捕捉函數(shù)。如果實(shí)現(xiàn)的時(shí) sa_handler 成員,那么將無(wú)法獲取額外攜帶的數(shù)據(jù)。

sigqueue 函數(shù)只能把信號(hào)發(fā)送給單個(gè)進(jìn)程,可以使用 value 參數(shù)向信號(hào)處理程序傳遞整數(shù)值或者指針值。

sigqueue 函數(shù)不但可以發(fā)送額外的數(shù)據(jù),還可以讓信號(hào)進(jìn)行排隊(duì)(操作系統(tǒng)必須實(shí)現(xiàn)了 POSIX.1的實(shí)時(shí)擴(kuò)展),對(duì)于設(shè)置了阻塞的信號(hào),使用 sigqueue 發(fā)送多個(gè)同一信號(hào),在解除阻塞時(shí),接受者會(huì)接收到發(fā)送的信號(hào)隊(duì)列中的信號(hào),而不是直接收到一次。

但是,信號(hào)不能無(wú)限的排隊(duì),信號(hào)排隊(duì)的最大值受到SIGQUEUE_MAX的限制,達(dá)到最大限制后,sigqueue 會(huì)失敗,errno 會(huì)被設(shè)置為 EAGAIN。

那么我們來(lái)嘗試一下,發(fā)送一個(gè)攜帶有額外數(shù)據(jù)的信號(hào)吧。 Show me the code??! 接收端

#include#include#include //void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t * info, void * context)
{
   if(signum == SIGIO)
       printf("SIGIO   signal: %d\n", signum);
   else if(signum == SIGUSR1)
       printf("SIGUSR1   signal: %d\n", signum);
   else       printf("error\n");
   
   if(context)
   {
       printf("content: %d\n", info->si_int);
       printf("content: %d\n", info->si_value.sival_int);
   }
}

int main(void)
{
   //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
   struct sigaction act;
   
   /*
    struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    };
    */
   act.sa_sigaction = handler;
   act.sa_flags = SA_SIGINFO;
   
   sigaction(SIGIO, &act, NULL);
   sigaction(SIGUSR1, &act, NULL);
   for(;;)
   {
       sleep(10000);
   }
   return 0;
}

發(fā)送端

#include #include #include#include int main(int argc, char** argv)
{
   if(4 != argc)
   {
       printf("[Arguments ERROR!]\n");
       printf("\tUsage:\n");
       printf("\t\t%s   \n", argv[0]);
       return -1;
   }
   int pid = atoi(argv[1]);
   int sig = atoi(argv[2]);

   if(pid > 0 && sig > 0)
   {
       //int sigqueue(pid_t pid, int sig, const union sigval value);
       union sigval val;
       val.sival_int = atoi(argv[3]);
       printf("send: %d\n", atoi(argv[3]));
       sigqueue(pid, sig, val);
   }
   else   {
       printf("Target_PID or Signal_Number MUST bigger than 0!\n");
   }
   
   return 0;
}
Linux的Signal機(jī)制是什么

接收到的信號(hào)和信息

Linux的Signal機(jī)制是什么

發(fā)送的信號(hào)和數(shù)據(jù)

關(guān)于“Linux的Signal機(jī)制是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Linux的Signal機(jī)制是什么”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI