首頁 > 軟體

Linux下捕捉信號

2020-06-16 17:35:22

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

信號由三種處理方式:

  1. 忽略
  2. 執行該信號的預設處理動作
  3. 捕捉信號

如果信號的處理動作是使用者自定義函數,在信號遞達時就呼叫這個自定義函數,這稱為捕捉信號

進程收到一個信號後不會被立即處理,而是在恰當時機進行處理!即核心態返回使用者態之前 !

但是由於信號處理常式的程式碼在使用者空間,所以這增加了核心處理信號捕捉的複雜度。

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

  1. 使用者為某信號註冊一個信號處理常式sighandler。
  2. 當前正在執行主程式,這時候因為中斷、異常或系統呼叫進入核心態。
  3. 在處理完異常要返回使用者態的主程式之前,檢查到有信號未處理,並行現該信號需要按照使用者自定義的函數來處理。
  4. 核心決定返回使用者態執行sighandler函數,而不是恢復main函數的上下文繼續執行!(sighandler和main函數使用的是不同的堆疊空間,它們之間不存在呼叫和被呼叫的關係,是兩個獨立的控制流程)
  5. sighandler函數返回後,執行特殊的系統呼叫sigreturn從使用者態回到核心態
  6. 檢查是否還有其它信號需要遞達,如果沒有 則返回使用者態並恢復主程式的上下文資訊繼續執行。

 

signal

給某一個進程的某一個信號(標號為signum)註冊一個相應的處理常式,即對該信號的預設處理動作進行修改,修改為handler函數指向的方式;

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

signal函數接受兩個引數:一個整型的信號編號,以及一個指向使用者定義的信號處理常式的指標。  

此外,signal函數的返回值是一個指向呼叫使用者定義信號處理常式的指標。

sigaction

sigaction函數可以讀取和修改與指定信號相關聯的處理動作。

1
2
3
4
5
6
7
8
9
10
#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是指定信號的編號。

處理方式:

  1. 若act指標非空,則根據act結構體中的信號處理常式來修改該信號的處理動作。
  2. 若oact指標非 空,則通過oact傳出該信號原來的處理動作。
  3. 現將原來的處理動作備份到oact裡,然後根據act修改該信號的處理動作。

(註:後兩個引數都是輸入輸出型引數!)

將sa_handler三種可選方式:

  1. 賦值為常數SIG_IGN傳給sigaction表示忽略信號;
  2. 賦值為常數SIG_DFL表示執行系統預設動作;
  3. 賦值為一個函數指標表示用自定義函數捕捉信號,或者說向核心註冊一個信號處理函 數,該函數返回值為void,可以帶一個int引數,通過引數可以得知當前信號的編號,這樣就可以用同一個函數處理多種信號。

(註:這是一個回撥函數,不是被main函數呼叫,而是被系統所呼叫)

  當某個信號的處理常式被呼叫時,核心自動將當前信號加入進程的信號遮蔽字,當信號處理常式返回時自動恢復原來的信號遮蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那麼 它會被阻塞到當前處理結束為止。

 pause

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

1
2
#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信號以前的處理 動作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*************************************************************************
 > 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 passedn");
        printf("###################n");
    }
    return 0;
}

    

定義一個鬧鐘並掛起等待,收到信號後執行自定義處理動作,在沒有恢復預設處理動作前,收到SIGALRM信號都會按照其自定義處理常式來處理。恢復自定義處理動作之後收到SIGALRM信號則執行其預設處理動作即終止進程!

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-08/134304.htm


IT145.com E-mail:sddin#qq.com