Linux下捕捉信號

關於 信號signal的知識鋪墊 點這裏html

信號由三種處理方式:bash

  1. 忽略
  2. 執行該信號的默認處理動做
  3. 捕捉信號

若是信號的處理動做是用戶自定義函數,在信號遞達時就調用這個自定義函數,這稱爲捕捉信號併發

進程收到一個信號後不會被當即處理,而是在恰當時機進行處理!即內核態返回用戶態以前 !函數

可是因爲信號處理函數的代碼在用戶空間,因此這增長了內核處理信號捕捉的複雜度。spa

內核實現信號捕捉的步驟:

  1. 用戶爲某信號註冊一個信號處理函數sighandler。
  2. 當前正在執行主程序,這時候由於中斷、異常或系統調用進入內核態。
  3. 在處理完異常要返回用戶態的主程序以前,檢查到有信號未處理,並發現該信號須要按照用戶自定義的函數來處理。
  4. 內核決定返回用戶態執行sighandler函數,而不是恢復main函數的上下文繼續執行!(sighandler和main函數使用的是不一樣的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程)
  5. sighandler函數返回後,執行特殊的系統調用sigreturn從用戶態回到內核態
  6. 檢查是否還有其它信號須要遞達,若是沒有 則返回用戶態並恢復主程序的上下文信息繼續執行。

 

signal

給某一個進程的某一個信號(標號爲signum)註冊一個相應的處理函數,即對該信號的默認處理動做進行修改,修改成handler函數指向的方式;.net

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//即:
void (*signal(int, void(*)(int)))(int);

signal函數接受兩個參數:一個整型的信號編號,以及一個指向用戶定義的信號處理函數的指針。  3d

此外,signal函數的返回值是一個指向調用用戶定義信號處理函數的指針。指針

sigaction

sigaction函數能夠讀取和修改與指定信號相關聯的處理動做。rest

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction
{
               void     (*sa_handler)(int);          //信號處理方式
               void     (*sa_sigaction)(int, siginfo_t *, void *);  //實時信號的處理方式  暫不討論
               sigset_t   sa_mask;   //額外屏蔽的信號
               int        sa_flags;
               void     (*sa_restorer)(void);     
};

signum是指定信號的編號。htm

處理方式:

  1. 若act指針非空,則根據act結構體中的信號處理函數來修改該信號的處理動做。
  2. 若oact指針非 空,則經過oact傳出該信號原來的處理動做。
  3. 現將原來的處理動做備份到oact裏,而後根據act修改該信號的處理動做。

(注:後兩個參數都是輸入輸出型參數!)

將sa_handler三種可選方式:

  1. 賦值爲常數SIG_IGN傳給sigaction表示忽略信號;
  2. 賦值爲常數SIG_DFL表示執行系統默認動做;
  3. 賦值爲一個函數指針表示用自定義函數捕捉信號,或者說向內核註冊一個信號處理函 數,該函數返回值爲void,能夠帶一個int參數,經過參數能夠得知當前信號的編號,這樣就能夠用同一個函數處理多種信號。

(注:這是一個回調函數,不是被main函數調用,而是被系統所調用)

  當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,若是這種信號再次產生,那麼 它會被阻塞到當前處理結束爲止。

 pause

pause函數使調用進程掛起直到有信號遞達!

#include <unistd.h>
int pause(void);

處理方式: 

  • 若是信號的處理動做是終止進程,則進程終止,pause函數沒有機會返回;
  • 若是信號的處理動做是忽略,則進程繼續處於掛起狀態,pause不返回;
  • 若是信號的處理動做是捕捉,則調用了信號處理函數以後pause返回-1,errno設置爲EINTR。

因此pause只有出錯的返回值(相似exec函數家族)。錯誤碼EINTR表示「被信號中斷」。

 舉個栗子

  1. 定義一個鬧鐘,約定times秒後,內核向該進程發送一個SIGALRM信號;
  2. 調用pause函數將進程掛起,內核切換到別的進程運行;
  3. times秒後,內核向該進程發送SIGALRM信號,發現其處理動做是一個自定義函數,因而切回用戶態執行該自定義處理函數;
  4. 進入sig_alrm函數時SIGALRM信號被自動屏蔽,從sig_alrm函數返回時SIGALRM信號自動解除屏蔽。而後自動執行特殊的系統調用sigreturn再次進入內核,以後再返回用戶態繼續執行進程的主控制流程(main函數調用的mytest函數)。

  5. pause函數返回-1,而後調用alarm(0)取消鬧鐘,調用sigaction恢復SIGALRM信號之前的處理 動做。

/*************************************************************************
 > File Name: Pause.c
 > Author:Lynn-Zhang 
 > Mail: iynu17@yeah.net
 > Created Time: Sun 14 Aug 2016 12:27:03 PM CST
 ************************************************************************/

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_alarm(int signum)
{
    printf("I am a custom handler!\n");
}
void mysleep(unsigned int times)
{
    //註冊兩個信號處理動做
    struct sigaction new,old;
    new.sa_handler=sig_alarm; //信號處理函數
    sigemptyset(&new.sa_mask);//不屏蔽任何信號屏蔽字
    new.sa_flags=0;
    
    //對SIGALRM 信號的默認處理動做修改成自定義處理動做
    sigaction(SIGALRM,&new,&old);
    alarm(times);
    pause(); //掛起等待
    alarm(1); 
    sleep(2);
    alarm(0); //取消鬧鐘 
    //恢復SIGALRM 信號到默認處理動做
    sigaction(SIGALRM,&old,NULL);
    alarm(1);
    sleep(2);
}
int main()
{
    while(1)
    {
        mysleep(2);
        printf("many seconds passed\n");
        printf("###################\n");
    }
    return 0;
}

    

定義一個鬧鐘並掛起等待,收到信號後執行自定義處理動做,在沒有恢復默認處理動做前,收到SIGALRM信號都會按照其自定義處理函數來處理。恢復自定義處理動做以後收到SIGALRM信號則執行其默認處理動做即終止進程!

相關文章
相關標籤/搜索