Linux - 信號

中斷【純概念,可是很重要】

在學習信號以前,首先須要理解一下什麼是中斷,由於信號與中斷有不少的類似之處,中斷,顧名思義就是中途打斷:linux

那什麼是異步事件呢?它是沒有必定時序關係,隨機發生的事件,在中斷技術出現以前,計算機對異步事件處理能力是有限的,經過是經過查詢的方式來處理的,舉一個現實生活中的例子: 好比張三正在看書,這時廚房裏又正在燒開水,這時,張三看書時並不知道水是否燒開了,他就須要跑到廚房當中"查詢"一下水是否燒開了,而後再回來看書,這時又不放心廚房水是否燒開了,因而又跑進廚房查詢,如此循環,直到水燒開以後,他纔可以靜下心來看本身的書,這就是典型的查詢技術。 有了中斷技術後,又是如何的呢?仍是以上面這個例子: 張三在看書的同時,設置一個鬧鐘,好比說是10分鐘的鬧鐘,當水燒開的時候,會響鈴,其中響鈴能夠看做是一箇中斷信號,鬧鐘能夠當作是一箇中斷源,當鬧鐘響以後,張三就會去處理這一次的中斷事件,也就是跑到廚房將煤氣給關閉,將開水倒進熱水瓶當中,這叫中斷執行程序,實際上張三在作這件事以前,須要保護現場,記住當前看到了第幾頁,當執行完中斷執行程序以後,則恢復現場,繼續從以前看到的頁數開始看書,這就是中斷執行的整個流程,總結一下:   中斷源 -> 中斷屏蔽 -> 保護現場 -> 中斷處理程序 -> 恢復現場 那中斷處理程序是保存在哪的呢?實際上,它的入口地址是保存在中斷向量表當中的,因爲計算機中的中斷個數是固定的,通常在操做系統啓動的時候,會初始化一箇中斷向量表,它會保存固定個數的中斷處理程序入口的地址,這樣的話,CPU就能夠根據中斷號,從中斷向量表當中找到對應中斷的中斷處理程序的入口地址,從而調用處理程序。 對於鬧鐘這個中斷源,產生了一箇中斷信號,不一樣的中斷源會產生不一樣的中斷信號,再回到這個例子,張三在看書的時候,可能鬧鐘響的同時,會聽到外面有人敲門的中斷信號到來,還有多是電話響起來產生另一箇中斷信號,可是對於同時到來的中斷,張三能夠決定哪一個中斷先處理,這也就是中斷的優先級,他以爲開水燒開的中斷優化級最高。另外張三也有可能屏蔽一些沒必要要的中斷,好比說電話響鈴了,在看書之時,他以爲這個中斷是能夠屏蔽的,也就是中斷屏蔽.shell

中斷分類

好比鍵盤產生的中斷、鼠標產生的中斷、打印機產生的中斷等,這些都是屬於硬件中斷。安全

好比說除0中斷、單步執行、對於X86平臺來講執行了一個INT指令,從用戶空間到內核空間bash

信號【核心】

信號與中斷總結

信號名稱

這些信號都有它們不一樣的涵義:異步

對於這些信號的默認處理行爲(也就是咱們能夠自定義本身的行爲),能夠在man手冊上查詢到:函數

進程對信號的三種響應

當對於到來的信號,能夠有三種不一樣的響應學習

爲何呢?SIGKILL是殺死進程的信號,它是9號進程:測試

在shell命令中,咱們能夠用kill -9 pid來對一個進程進行殺死,若是進程可以忽略9號信號的話,意味着當管理員向進程發送9號信號時,若是進程能夠屏蔽不處理的話,那就沒法殺死一個非法的進程了,而同理,SIGSTOP是中止一個進程信號,優化

也就是咱們看到的信號對應的action,上面有介紹過:ui

signal

下面來學一下安裝信號函數:

下面以代碼來進行說明:

編譯運行:

按下ctrl+c:

能夠信號是一種異步事件的響應,當響應完以後,會還原現場,又回到了for死循環代碼上了:

這時按下ctrl+\退出:

實際上,ctrl+\會產生一個退出信號:

因爲咱們程序沒有註冊該信號,因此由系統默認處理,既程序結束。

對於signal函數,它的返回值爲它註冊的上一個信號的處理程序,這樣說有點空洞,下面以程序來講明一下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
//typedef void (*__sighandler_t) (int); //Mac 上須要自定義__sighandler_t函數指針
int main(int argc, char *argv[])
{
    __sighandler_t oldhandler;
    oldhandler = signal(SIGINT, handler);//這時返回的處理程序是註冊handler以前的,也就是系統默認的處理程序
    if (oldhandler == SIG_ERR)
        ERR_EXIT("signal error");

    while (getchar() != '\n')//死循環是爲了測試,當按了ctrl+c以後,會不斷死循環,直到按了回車鍵
        ;
    if (signal(SIGINT, oldhandler) == SIG_ERR)//這時,再次註冊信號,可是此次是註冊成了默認處理程序,而ctrl+c的默認處理就是終止程序
        ERR_EXIT("signal error");
    for (;;) ;
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}
複製代碼

編譯運行:

按回車鍵:

這時,再按ctrl+c:

實際上,恢復默認的處理行爲,還能夠用它來代替:

編譯運行,輸出效果同樣:

信號分類

可靠信號與不可靠信號

不可靠信號

咱們用上節課的例子來解釋上面的這斷話:

運行來看下:

而第二次再按ctrl+c,仍是會輸出咱們的處理程序:

因此與之對比,上面文字中提到的不可靠信號的默認動做就能理解了,就是早期的unix當咱們註冊完了信號,並處理信號時,則會恢復到默認動做,表現形式也就是這樣(模擬):

"用戶若是不但願這樣的操做,那麼就要在信號處理函數結尾再一次調用signal(),從新安裝該信號。",也就是這樣作:

從上面這段程序能夠看出,當再次註冊信號時,若是新的SIGINT信號過來了,可是信號還沒註冊完,那麼仍是會響應ctrl+c的默認動做,也就是程序退出了,這就說明是作出了一個錯誤的反應,另外,關於信號可能會丟失說的是哪方面呢?是指當來了多個SIGINT信號時,不可靠信號是不會排隊的,只會保留一個,其它的都被丟棄掉。

也就是說,現在的linux的不可靠信號在處理完以後,是不會被恢復成默認動做的,而不可靠信號一樣還有這樣的特徵:"不可靠信號是不會排隊的,只會保留一個,其它的都被丟棄掉",也就是存在信號的丟失。

總結一下:早期unix的不可靠信號在執行完以後會被恢復成默認動做,也就是會作出錯誤的反應,而且信號不會排隊,存在信號丟失問題;而現在的linux的不可靠信號在執行完以後是不會被恢復的,也就是不會作出錯誤的反應,可是仍是存在信號丟失的問題,因此基於這個緣由,就出現了下面要介紹的可靠信號了。

可靠信號

那新增的可靠信號是哪些呢?

在以前介紹不可靠信號時,其中說到「linux信號安裝函數(signal)是在可靠機制上的實現」,也就是說signal和sigaction是同樣的,都是可靠信號的安裝,實際上它們都調用了更加底層的dosigaction內核函數,只能內核才能調用到,作一個瞭解。

實時信號

其中後32個信號是沒有具體含義的,可供應用程序進行使用, 另外SIGRTMIN不必定都是從34開始,先查看一下目前它是從哪開始的:

能夠從signal的幫助文檔中能夠閱讀到:

信號發送

關於kill函數,咱們常常會用到:

實際上更準確的說法是向pid進程發送9號信號,因爲9號信號不能被忽略,也不能被捕獲的,而它默認動做就是將進程給殺掉,因此咱們常常用這個命令來殺死進程。

實際上kill命令的實現是靠kill系統函數,能夠man查看一下:

下面對該函數的描述進行認識,以後會用到:

當pid=-1時,信號sig將發送給調用者進程有權限發送的每個進程,除了1號進程和自身以外。

舉一個子進行程父進程發送信號的例子:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
    if (signal(SIGUSR1, handler) == SIG_ERR)//註冊了一個可靠信號
        ERR_EXIT("signal error");
    pid_t pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

    if (pid == 0)
    {
        kill(getppid(), SIGUSR1);//向父進程發送一個信號
        exit(EXIT_SUCCESS);
    }

    sleep(5);//父進程睡眠五秒
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}
複製代碼

編譯運行:

緣由是因爲sleep函數會被信號所打斷而返回,在打斷返回以前會先處理信號,因此就沒有出現了咱們的預期,那若是要實現真正睡眠5秒怎麼作呢?能夠查看sleep函數的man幫助:

這時再看效果:

可見經過這種取巧的辦法就解決了咱們所遇到的問題。 下面來看一下這個狀況,也就是給進程組發送信號:

首先查看一下怎麼獲得進程組ID:

下面看下具體代碼:

運行效果:

解釋一下程序,之因此打印出兩條語句,是因爲註冊信號是在fork()以前,因此子進程會繼承父進程的信號所安裝的程序,也就是子進程中也安裝了這個信號,而子進程向進程組發送了一個信號,則每一個進程都會收到信號,當子進程收到時,會打印一條語句,而後立馬退出了,而父進程一樣也會收到,可是它會sleep五秒後才退出,因此纔出現瞭如上效果。

另外向父進程發送信號還有另一種等價的使用方法:

查看一下幫助:

pause

而比較好的方式是採用咱們要學的這個pause函數來讓進程掛起,直到一個信號被捕獲了,代碼調整以下:

看下效果:

能夠很清楚的看到,當收到信號時,則pause函數就被返回了,這樣的作法就會比較好,在信號沒發送以前讓進程掛起,信號處理完,則就返回了。

更多信號發送函數

alarm:只能發送SIGALRM信號

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
    if (signal(SIGALRM, handler) == SIG_ERR)//註冊了一個alrm信號
        ERR_EXIT("signal error");

    alarm(1);//發送一個alarm信號
    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}
複製代碼

關於alarm函數的說明能夠查看man:

運行效果:

可見,是隔了一秒才發送出alarm信號的,實際上,咱們能夠找到該進程,用shell命令中人爲的發送該信號:

經過kill命令來發送信號,爲了看到效果,新開一個命令終端,效果以下:

能夠看到手動發送信號也是能夠正常收到的,另外,咱們在發送信號時,既能夠用數字,也能夠用它對應的名稱,以下:

實際上,對於進程的ID,能夠經過動態方式來獲取,按以下步驟(瞭解一下):

一、先過濾掉其它行

二、而後再只獲得進程ID列,過濾掉其它列

因此,手動發送信號時,就能夠用動態的方式來發送了,以下:

咱們發現,alarm函數不能每隔一秒發送一次信號,那若是要作到這點該怎麼辦呢?

setitimer:發送SIGALRM、SIGVTALRM、SIGPROF信號

abort:只能發送SIGABRT信號

可重入函數與不可重入函數

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


typedef struct
{
    int a;
    int b;
} TEST;

TEST g_data;//定義了一個全局變量,是爲了說明不可重入函數的問題

void handler(int sig);
int main(int argc, char *argv[])
{
    TEST zeros = {0, 0};
    TEST ones = {1, 1};
    if (signal(SIGALRM, handler) == SIG_ERR)
        ERR_EXIT("signal error");

    g_data = zeros;//默認賦值
    alarm(1);
    for (;;)
    {
        g_data = zeros;
        g_data = ones;//不斷對其進行賦值,正常狀況應該要不是zeros,要不就是ones
    }
    return 0;
}

void handler(int sig)
{
    printf("%d %d\n", g_data.a, g_data.b);//打印出值,觀察其輸出能夠看到不可重錄函數的缺點
    alarm(1);
}
複製代碼

編譯運行:

這是爲何呢?

這是因爲有一個全局變量g_data,並且在for循環中不斷進行賦值,因爲賦值不是一個原子操做,拿g_data=zeros這個賦值操做來講,由兩部組成:

①g_data.a = zeros.a;

②g_data.b = zeros.b

若是g_data以前的值爲ones,當執行到第①步賦值操做時,信號來了終止了第②步賦值操做,那處理handler打印時,則會打印出0,1,由於第二個賦值操做中止了,形成了只賦值了一部分,因此上面程序的打印結果就能夠解釋了,將handler中的打印語句提取到一個新的函數中:

致使不可重錄函數的緣由,是因爲: 中斷以前的處理程序跟中斷程序訪問了一些共享數據g_data,

【說明】:若是此處不訪問g_data的話,也就不會產生不可重錄的問題。

因此致使不可重錄只要有如下幾個方面:

那一些函數纔算是安全能夠在信號處理函數中使用呢?查看man幫助:

除了這些函數是安全的以外,其它的都是不安全的,因此說使用信號是很容易出錯的,如今的內核也正在考慮有沒有一個機制來替換信號,實際上正在考慮可否用文件描述符來替換信號,這正是下個內核要實現的功能,將信號融入到文件描述上進行處理。

信號在內核中的表示

下面用圖來進一步描述這種信號從產生到遞達之間的狀態(信號阻塞與未訣):

那是怎麼來決定的呢?下面慢慢來舉例分解:

信號集操做函數

其中解釋一下sigset_t,百度百科解釋爲:

而這個函數的意義就是將這64位清0

這個函數的意義是將這屏蔽字的64位都變爲1

將這個信號所對應的位置爲1

將這個信號所對應的位置爲0

檢測這一個信號所對應的位當前是0仍是1

以上是操做信號集的五個相關的函數,可是注意:這五個函數僅僅是改變這個信號集變量,如set,並不是真正改變進程信號當中的屏蔽字,因此接下來介紹的函數就是改變信號當中的屏蔽字的

sigprocmask

一個信號從產生到遞達的一個狀態轉換過程:

首先,咱們打印出系統的未訣信號,目的是爲了觀察以後的狀態:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
void printsigset(sigset_t *set)//打印出信號集的狀態,其中參數set爲未訣狀態的信號集
{
    int i;
    for (i=1; i<NSIG; ++i)//NSIG表示信號的最大值,也就是等於64
    {
        if (sigismember(set, i))//說明是未訣狀態的信號
            putchar('1');
        else
            putchar('0');//說明不是未訣狀態的信號
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    sigset_t pset;

    for (;;)
    {
        sigpending(&pset);//該函數是獲取進程當中未訣狀態的信號集 ,保存在pset當中
        printsigset(&pset);//打印信號集的狀態,看有沒有未訣狀態的信號產生
        sleep(1);
    }
    return 0;
}
複製代碼

【說明】:sigpending是用來獲取進程中全部的未訣信號集:

這時看一下運行效果:

能夠發現,當前狀態沒有未訣的信號,由於尚未被阻塞的信號過,信號也沒有產生過,因此不可能有未訣的狀態。

這時,咱們來安裝一個SIGINT信號:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
void printsigset(sigset_t *set)
{
    int i;
    for (i=1; i<NSIG; ++i)
    {
        if (sigismember(set, i))
            putchar('1');
        else
            putchar('0');
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    sigset_t pset;
    if (signal(SIGINT, handler) == SIG_ERR)//安裝一個SIGINT信號
        ERR_EXIT("signal error");

    for (;;)
    {
        sigpending(&pset);
        printsigset(&pset);
        sleep(1);
    }
    return 0;
}

void handler(int sig)
{
        printf("recv a sig=%d\n", sig);
}
複製代碼

這時再看下效果:

從結果來看,信號被直接遞達了,因此此次也沒有看到有1的未訣狀態的信號,由於信號必須被阻塞纔會出現未訣狀態,因此接下來將SIGINT信號利用上面介紹到的函數來將其阻塞掉:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
void printsigset(sigset_t *set)
{
    int i;
    for (i=1; i<NSIG; ++i)
    {
        if (sigismember(set, i))
            putchar('1');
        else
            putchar('0');
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    sigset_t pset;
    
    sigset_t bset;
    sigemptyset(&bset);//將信號集清0
    sigaddset(&bset, SIGINT);//將SIGINT所對應的位置1

    if (signal(SIGINT, handler) == SIG_ERR)
        ERR_EXIT("signal error");

    sigprocmask(SIG_BLOCK, &bset, NULL);//更改進程中的信號屏蔽字,其中第三個參數傳NULL,由於不關心它原來的信號屏蔽字
    for (;;)
    {
        sigpending(&pset);
        printsigset(&pset);
        sleep(1);
    }
    return 0;
}

void handler(int sig)
{
        printf("recv a sig=%d\n", sig);
}
複製代碼

編譯運行:

從結果來看,將SIGINT信號來了,因爲添加到了信號屏蔽字爲1,因此會被阻塞掉,而且能夠看到SIGINT對應的位也打印爲1了。

【說明】:SIGINT對應的位是指:

下面,咱們作一件事情,就是當咱們按下ctrl+\解除阻塞,這樣處於未訣狀態的信號就會被遞達,則對應的未訣狀態位也會還原成0,具體代碼以下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
void printsigset(sigset_t *set)
{
    int i;
    for (i=1; i<NSIG; ++i)
    {
        if (sigismember(set, i))
            putchar('1');
        else
            putchar('0');
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    sigset_t pset;
    sigset_t bset;
    sigemptyset(&bset);
    sigaddset(&bset, SIGINT);
    if (signal(SIGINT, handler) == SIG_ERR)
        ERR_EXIT("signal error");
    if (signal(SIGQUIT, handler) == SIG_ERR)//註冊一個ctrl+c信號
        ERR_EXIT("signal error");

    sigprocmask(SIG_BLOCK, &bset, NULL);
    for (;;)
    {
        sigpending(&pset);
        printsigset(&pset);
        sleep(1);
    }
    return 0;
}

void handler(int sig)
{
    if (sig == SIGINT)
        printf("recv a sig=%d\n", sig);
    else if (sig == SIGQUIT)
    {
        sigset_t uset;//當按下ctrl+\時,則對SIGINT信號解除阻塞
        sigemptyset(&uset);
        sigaddset(&uset, SIGINT);
        sigprocmask(SIG_UNBLOCK, &uset, NULL);
    }
}
複製代碼

編譯運行:

從中能夠看到,當咱們按下ctrl+\時,並無退出,而是解除了阻塞,因此對應的SIGINT位也變爲0了。

另外,看下這種狀況:

屢次按了ctrl+c,可在按ctrl+\解除阻塞時,只響應了一次信號處理函數,這也因爲SIGINT是不可靠信號,不支持排隊。

另外,因爲咱們捕獲了ctrl+\信號,因此沒辦法退出這個進程了,那怎麼辦呢,能夠利用shell命令將其強制殺掉以下:

sigaction函數

安裝信號以前咱們已經學過一個函數:signal,它最先是在unix上出現的,它是對不可靠信號進行安裝的,以後出現了可靠信號和實時信號,因此新的安裝函數sigaction函數就出現了,它的原形以下:

sigaction結構體

經過man手冊來查看一下它的說明:

sigaction

實現signal相同的功能來安裝一個SIGINT信號:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);

int main(int argc, char *argv[])
{
    struct sigaction act;//安裝信號時須要傳參
    //act.__sigaction_u.__sa_handler = handler;//for mac
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);//先將sa_mask清空,關於這個屬性的用法以後實驗再說明
    act.sa_flags = 0;//一樣將sa_flags設爲0,這個實現不須要關心,以後會說明

    if (sigaction(SIGINT, &act, NULL) < 0)//安裝信號
        ERR_EXIT("sigaction error\n");

    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}
複製代碼

編譯運行:

實際上,對於signal這個安裝函數是在可靠的機制之上進行的,也就是說能夠認爲它是經過sigaction來實現的,因此接下來,用sigaction來模擬signal函數的行爲:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
__sighandler_t my_signal(int sig, __sighandler_t handler);//這跟signal的信號安裝函數聲明同樣,實現本身的signal

int main(int argc, char *argv[])
{
/*
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error\n");
*/
    my_signal(SIGINT, handler);
    for (;;)
        pause();
    return 0;
}

__sighandler_t my_signal(int sig, __sighandler_t handler)
{
    struct sigaction act;
    struct sigaction oldact;//保存最初的行爲
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(sig, &act, &oldact) < 0)
        return SIG_ERR;

    return oldact.sa_handler;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}
複製代碼

效果也是同樣的,從以上代碼能夠看出,sigaction功能比signal要強大說了,其中有兩個參數須要說明一下:

其中sa_handler只適合不可信號的安裝,也就是說不可信號的安裝不能用sa_sigaction,這個須要注意。

下面來講明一下sa_mask這個屬性是什麼效果,而後再回頭來看下文字說明:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
    sleep(5);//故意睡眠5秒是爲了看在執行期間按了ctrl+\就能立馬響應退出信號
}
複製代碼

編譯運行:

能夠看到,在執行信號處理函數期間,按ctrl+c時,並無等待sleep5秒完以後,再執行退出動做,而是立馬執行了,那能不能改變這種默認,也就是必須得等sleep5秒後再執行退出動做,答案是固然能夠的,sa_mask屬性就派上用場了:

結果以下:

從實驗結果來看,在執行信號處理時,屢次按了ctrl+c退出信號,並未立馬執行退出動做,而是等執行完了才退出的,這也就是sa_mask的做用,實際上也就是上節學習的信號屏蔽字。

思考一個問題:sa_mask的做用跟以前學的進程中的信號屏蔽字能夠對其信號進行阻塞有什麼區別呢?

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    //sigaddset(&act.sa_mask, SIGQUIT);//將其sa_mask註冊去掉,也就是清零了
    act.sa_flags = 0;

    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGINT);
    sigprocmask(SIG_BLOCK, &s, NULL);//這就是以前學的,將SIGINT加入到進程的屏蔽字中

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
    sleep(5);
}
複製代碼

編譯運行:

這時能夠看到,信號被阻塞而壓縮就不會執行處理函數了,這也是以前學過期的現象,從實驗能夠總結出:

sa_mask中指定的掩碼也能夠阻塞信號,它阻塞的信號是指函數在執行的過程中,若是發生了在掩碼級中指定的信號,信號將被阻塞,直到handler返回,這些信號才能被遞達;

sigprocmask它所阻塞的信號表示將這些集合中的信號添加到進程信號屏蔽字當中,這些信號就不能被遞達了,既使它發生了。

sigqueue函數

sigval聯合體

實際上sigval參數是用來進程間通訊用的,實際上信號是一個很古老的進程間通訊的一種手段,經過這個參數,能夠從一個進程發送到另一個進程,而且能夠附帶參數。下面以實際代碼來講明sigqueue是如何傳遞數據的:

這裏須要編寫一個進程發送信號和一個進程接收信號來講明sigqueue:

接收信號:

要想接收信號數據,則須要在sigaction上加入flag,也就是上節當中遺漏的一個知識點,查看man幫助:

另外,還得注意,若是要想接收信號,須要用到sigaction的另一個處理函數:

具體接收代碼以下sigaction_recv.c:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int, siginfo_t *, void *);//須要用帶多個參數的handler

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_sigaction = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;//設置該flags能夠接收其它進程傳過來的信號

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (;;)
        pause();
    return 0;
}

void handler(int sig, siginfo_t *info, void *ctx)
{
    printf("recv a sig=%d data=%d\n", sig, info->si_value.sival_int);//它實際上就是sigqueue的sigval聯合體,並打印出傳過來的整型值
}
複製代碼

發送信號: 編寫一個經過sigqueue向某進程發送信號並攜帶數據來達到進程間通信的目的: sigaction_send.c:

編譯運行: 先運行接收信號的程序:

再運行進程發送程序,向該接收進程發送信號,先找到該接收進程號:

而後進行咱們寫的信號發送程序:

另外,數據的接收還能夠用這種方式來打印:

輸出以下:

爲啥能夠這樣寫呢?查看sigaction的幫助:

因此經過這種方式就能達到進程間通信的目的。

接下來再演示一個不可靠與可靠信號的一個問題:可靠信號支持排隊,不可靠信號是不支持排隊的。

驗證方法:

① 寫一個接收程序,裏面註冊一個可靠信號,一個不可靠信號,並將其加入屏蔽字中進行阻塞,而後再註冊一個解除這兩個信號阻塞的信號,程序以下:

sigaction_recv.c:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGINT);
    sigaddset(&s, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &s, NULL);//將這兩個信號都加入屏蔽字當中進行阻塞
    if (sigaction(SIGINT, &act, NULL) < 0)//註冊一個不可靠信號
        ERR_EXIT("sigaction error");

    if (sigaction(SIGRTMIN, &act, NULL) < 0)//註冊一個可靠信號
        ERR_EXIT("sigaction error");

    if (sigaction(SIGUSR1, &act, NULL) < 0)//註冊一個解除阻塞信號的信號
        ERR_EXIT("sigaction error");
    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    if (sig == SIGINT || sig == SIGRTMIN)
        printf("recv a sig=%d\n", sig);
    else if (sig == SIGUSR1)
    {//當接收到SIGUSR1信號時,則解除阻塞,這時被阻塞的SIGINT,SIGRTMIN信號就變爲遞達狀態了,這時就能夠觀察二者的區別了
        sigset_t s;
        sigemptyset(&s);
        sigaddset(&s, SIGINT);
        sigaddset(&s, SIGRTMIN);
        sigprocmask(SIG_UNBLOCK, &s, NULL);
    }
}
複製代碼

② 編寫一個發送信號的程序,裏面發送多個SIGINT可靠信號,多個SIGTMIN不可靠信號,而且延時幾秒以後,再將發送SIGUSR1信號解除綁定,最終觀察看SIGINT收到了幾個,SIGTMIN收到了幾個,具體代碼以下:

sigaction_send.c:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage %s pid\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t pid = atoi(argv[1]);
    union sigval v;
    v.sival_int = 100;
    sigqueue(pid, SIGINT, v);
    sigqueue(pid, SIGINT, v);
    sigqueue(pid, SIGINT, v);//發送三個SIGINT不可靠信號

    sigqueue(pid, SIGRTMIN, v);
    sigqueue(pid, SIGRTMIN, v);
    sigqueue(pid, SIGRTMIN, v);//發送三個SIGTMIN可靠信號
    
    sleep(3);//休眠3秒,目的是讓其多個信號進行阻塞
    
    kill(pid, SIGUSR1);//發送一個SIGUSR1信號進行解除阻塞,改用kill來發送,固然也可用sigqueue啦
    return 0;
}
複製代碼

編譯運行:

從運行結果來看,通過3秒以後,34號信號,也就是SIGRTMIN信號接收了三次,而2號信號SIGINT信號只接收了一次,因此也論證了可靠信號是支持排隊的,而不可靠信號是不支持排隊的。

三種不一樣精度的睡眠

查看man幫助:

以微秒爲單位,那微秒跟秒是什麼關係呢? 1秒=10的3次方毫秒=10的6次方微妙

以納秒爲單位的休眠

三種時間結構

setitimer

查看一下man幫助,其中ittimerval結構體的內容以下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>//另外須要包含頭文件


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}

int main(int argc, char *argv[])
{
    if (signal(SIGALRM, handler) == SIG_ERR)//註冊一個SIGALRM信號
        ERR_EXIT("signal error");

    struct timeval tv_interval = {1, 0};
    struct timeval tv_value = {5, 0};//那這兩個值有什麼意義呢?
    struct itimerval it;
    it.it_interval = tv_interval;
    it.it_value = tv_value;
    setitimer(ITIMER_REAL, &it, NULL);//這個函數會間接性地產生SIGALRM信號,需傳遞ITIMER_REAL參數

    for (;;)
        pause();
    return 0;
}
複製代碼

編譯運行:

從運行結果能夠知道,等待五秒鐘以後纔打印出來,之後每過一秒打印,那再回過頭來理解下代碼:

struct timeval tv_interval = {1, 0};//以後產生信號的間隔時間

struct timeval tv_value = {5, 0};//第一次產生信號的時間

另外,setitimer的第三個參數ovalue是幹嗎用的呢?下面用實驗也來講明下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(int argc, char *argv[])
{
    struct timeval tv_interval = {1, 0};
    struct timeval tv_value = {1, 0};//第一次產生信號須要等待1秒
    struct itimerval it;
    it.it_interval = tv_interval;
    it.it_value = tv_value;
    setitimer(ITIMER_REAL, &it, NULL);

    int i;
    for (i=0; i<10000; i++);//故意產生10000次的循環,運行時間確定不足一秒,因此目的也就是爲了看到第三個參數的效果
        ;

    struct itimerval oit;
    setitimer(ITIMER_REAL, &it, &oit);//在第一次信號尚未產生之時,又從新設置一個定時,這時oit會存放上一次設置的時鐘它還剩餘的時間
    printf("%d %d %d %d\n", (int)oit.it_interval.tv_sec, (int)oit.it_interval.tv_usec, (int)oit.it_value.tv_sec, (int)oit.it_value.tv_usec);

    return 0;
}
複製代碼

編譯運行:

【注意】:這個效果跟下面要說明的getitimer效果同樣,可是有一些區別,getitimer獲得剩餘時間是不從新設置時鐘。

另外還有一個相關函數:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(int argc, char *argv[])
{
    struct timeval tv_interval = {1, 0};
    struct timeval tv_value = {1, 0};
    struct itimerval it;
    it.it_interval = tv_interval;
    it.it_value = tv_value;
    setitimer(ITIMER_REAL, &it, NULL);

    int i;
    for (i=0; i<10000; i++);
        ;

    getitimer(ITIMER_REAL, &it);//得到產生首次信號的時間,並無從
    printf("%d %d %d %d\n", (int)it.it_interval.tv_sec, (int)it.it_interval.tv_usec, (int)it.it_value.tv_sec, (int)it.it_value.tv_usec);

    return 0;
}
複製代碼
相關文章
相關標籤/搜索