Linux 信號(hào)(signal)

對(duì)于 Linux來(lái)說(shuō),實(shí)際信號(hào)是軟中斷,許多重要的程序都需要處理信號(hào)。信號(hào),為 Linux 提供了一種處理異步事件的方法。比如,終端用戶輸入了 ctrl+c 來(lái)中斷程序,會(huì)通過(guò)信號(hào)機(jī)制停止一個(gè)程序。

信號(hào)概述

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

    信號(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)程就變成了沒人能管理的的進(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ì)展開了,需要查看的,可以自行查看。也可以參考 《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)作——?dú)⑺肋M(jìn)程。

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ù)——入門版

在正式開始了解這兩個(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ō),接收者就是自己。

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

#include <signal.h>
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<signal.h>
#include<stdio.h>
#include <unistd.h>

//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è)命令,我們后面再談。


通過(guò) kill 命令發(fā)送信號(hào)
程序接收到的信號(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 <sys/types.h>
#include <signal.h>
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 <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>


int main(int argc, char** argv)
{
    if(3 != argc)
    {
        printf("[Arguments ERROR!]\n");
        printf("\tUsage:\n");
        printf("\t\t%s <Target_PID> <Signal_Number>\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;
}
發(fā)送信號(hào)
接收信號(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 < 0:將信號(hào)發(fā)送給進(jìn)程組ID 為 pid 的絕對(duì)值得,并且發(fā)送進(jìn)程具有權(quán)限向其發(fā)送信號(hào)的所有進(jìn)程
pid == -1:將該信號(hào)發(fā)送給發(fā)送進(jìn)程的有權(quán)限向他發(fā)送信號(hào)的所有進(jìn)程。(不包括系統(tǒng)進(jìn)程集中的進(jìn)程)

關(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 <signal.h>
       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 <signal.h>
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 <signal.h>
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),可是總感覺少些什么,既然都已經(jīng)把信號(hào)發(fā)送過(guò)去了,為何不能再攜帶一些數(shù)據(jù)呢?
正是如此,我們需要另外的函數(shù)來(lái)通過(guò)信號(hào)傳遞的過(guò)程中,攜帶一些數(shù)據(jù)。咱么先來(lái)看看發(fā)送的函數(shù)吧。

sigaction 的函數(shù)原型

#include <signal.h>
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 <signal.h>
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<signal.h>
#include<stdio.h>
#include <unistd.h>

//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 <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>


int main(int argc, char** argv)
{
    if(4 != argc)
    {
        printf("[Arguments ERROR!]\n");
        printf("\tUsage:\n");
        printf("\t\t%s <Target_PID> <Signal_Number> <content>\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;
}

接收到的信號(hào)和信息
發(fā)送的信號(hào)和數(shù)據(jù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 又來(lái)到了一個(gè)老生常談的問(wèn)題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問(wèn)題開始,來(lái)談?wù)劜?..
    tangsl閱讀 4,332評(píng)論 0 23
  • 一、進(jìn)程的創(chuàng)建和調(diào)度 相關(guān)概念: 最基礎(chǔ)的計(jì)算機(jī)動(dòng)作被稱為指令(instruction)。 程序(program)...
    穹藍(lán)奧義閱讀 5,203評(píng)論 0 6
  • Linux 進(jìn)程管理與程序開發(fā) 進(jìn)程是Linux事務(wù)管理的基本單元,所有的進(jìn)程均擁有自己獨(dú)立的處理環(huán)境和系統(tǒng)資源,...
    JamesPeng閱讀 2,611評(píng)論 1 14
  • 讀書后一個(gè)人留在了這個(gè)城市, 這座城市風(fēng)很大, 孤獨(dú)的人總是晚回家, 每天忙忙碌碌, 奔波于上班、加班、吃飯、睡覺...
    阿民漫時(shí)光閱讀 317評(píng)論 0 0
  • 此段內(nèi)容簡(jiǎn)要來(lái)自自強(qiáng)學(xué)堂的教程詳情請(qǐng)查詢自強(qiáng)學(xué)堂 一、 后臺(tái)的運(yùn)作流程 接收request請(qǐng)求 處理數(shù)據(jù) 獲取請(qǐng)求...
    coder_ben閱讀 5,351評(píng)論 6 56

友情鏈接更多精彩內(nèi)容