linux 訊號與處理

澆築菜鳥發表於2021-10-15

一、linux訊號是什麼

  1. 基本概念
    訊號是事件發生時對程式的通知機制,也就是所謂的軟體中斷。訊號和硬體的中斷類似,是軟體層對中斷機制的模擬,在多數情況下是無法預測訊號產生的時間,所以軟體層提供了一種處理非同步事件的方法。

二、 訊號來源

訊號的來源分為硬體來源和軟體來源。

  1. 硬體來源:
  • 硬體發生異常,即硬體檢測到錯誤條件並通知核心,隨即再由核心傳送相應的訊號給相關程式,如除數為0、無效的記憶體引用等。
  • 使用者按終端鍵,引起終端產生的訊號(比如Ctrl + C鍵產生SIGINT)。
  1. 軟體來源:
  • 使用者通過指令殺死,如kill指令。
  • 發生軟體事件, 如程式執行raise, alarm、setitimer、sigqueue等函式。

三、 訊號處理

訊號通常是傳送給對應的程式,當訊號到達後,該程式需要做出相應的處理措施,通常程式會視具體訊號執行相應的操作,有三種操作方式。

  1. 忽略訊號:
      訊號到達後、直接忽略,就好像是沒有出該訊號,訊號對該程式不會產生任何影響。事實上,大多數訊號都可以使用這種方式進行處理,但有兩種訊號卻決不能被忽略,分別是SIGKILL 和 SIGSTOP。
  2. 捕獲訊號:
      當訊號到達程式後,執行signal()繫結好的訊號處理函式。
  3. 執行系統預設操作:
      程式不對該訊號事件作出處理,而是交由系統進行處理,每一種訊號都會有其對應的系統預設的處理方式。

四、常見訊號

在linux系統中通過kill -l命令可以檢視到相應的訊號。訊號編號是從 1 開始,不存在編號為 0 的訊號,事實上 kill()函式對訊號編號 0 有著特殊的應用。

注意:括號" ) "前面的數字對應該訊號的編號,編號 1~31 所對應的是不可靠訊號,編號 34~64 對應的是可靠訊號,從圖中可知,可靠訊號並沒有一個具體對應的名字,而是使用了 SIGRTMIN+N 或 SIGRTMAXN 的方式來表示。其中32和33空缺。
不可靠訊號表

名稱 解釋 預設動作
1 SIGHUP 掛起
2 SIGINT 中斷
3 SIGQUIT 退出
4 SIGILL 非法指令
5 SIGTRAP 斷點或陷阱指令
6 SIGABRT abort發出的訊號
7 SIGBUS 非法記憶體訪問
8 SIGFPE 浮點異常
9 SIGKILL kill訊號 不能被忽略、處理和阻塞
10 SIGUSR1 使用者訊號1
11 SIGSEGV 無效記憶體訪問
12 SIGUSR2 使用者訊號2
13 SIGPIPE 管道破損,沒有讀端的管道寫資料
14 SIGALRM alarm發出的訊號
15 SIGTERM 終止訊號
16 SIGSTKFLT 棧溢位
17 SIGCHLD 子程式退出 預設忽略
18 SIGCONT 程式繼續
19 SIGSTOP 程式停止 不能被忽略、處理和阻塞
20 SIGTSTP 程式停止
21 SIGTTIN 程式停止,後臺程式從終端讀資料時
22 SIGTTOU 程式停止,後臺程式想終端寫資料時
23 SIGURG I/O有緊急資料到達當前程式 預設忽略
24 SIGXCPU 程式的CPU時間片到期
25 SIGXFSZ 檔案大小的超出上限
26 SIGVTALRM 虛擬時鐘超時
27 SIGPROF profile時鐘超時
28 SIGWINCH 視窗大小改變 預設忽略
29 SIGIO I/O相關
30 SIGPWR 關機 預設忽略
31 SIGSYS 系統呼叫異常

五、訊號處理

  1. signal()
    "signal.h"訊號處理庫提供了signal函式,用來捕獲突發事件。以下是 signal() 函式的語法ads。
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • signum:可使用訊號名(巨集)或訊號的數字編號,建議使用訊號名。
  • handler:引數 handler 既可以設定為使用者自定義的函式,也可以設定為 SIG_IGN 或 SIG_DFL,SIG_IGN 表示此程式需要忽略該訊號,SIG_DFL 則表示設定為系統預設操作。
  1. raise()
    有時程式需要向自身傳送訊號,raise()函式可用於實現這一要求.
int raise(int sig);
  • sig:需要傳送的訊號。
  1. sigaction()
    除了signal()之外,sigaction()系統呼叫是設定訊號處理方式的另一選擇,雖然 signal()函式簡單好用,而 sigaction()更為複雜,但作為回報,sigaction()也更具靈活性以及移植性。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • signum:需要設定的訊號,除了 SIGKILL 訊號和 SIGSTOP 訊號之外的任何訊號。
  • act:引數 act 不為 NULL,則表示需要為訊號設定新的處理方式;如果引數 act 為 NULL,則表示無需改變訊號當前的處理方式
  • oldact:引數oldact 不為 NULL,則會將訊號之前的處理方式等資訊通過引數 oldact 返回出來;如果無意獲取此類資訊,那麼可將該引數設定為 NULL。
  • 返回值:成功返回 0;失敗將返回-1,並設定 errno。
    struct sigaction 結構體
struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
  • sa_handler:指定訊號處理函式,與 signal()函式的 handler 引數相同。
  • sa_sigaction:也用於指定訊號處理函式,這是一個替代的訊號處理函式。
  • sa_mask:引數 sa_mask 定義了一組訊號。
  • sa_restorer:該成員已過時,不要再使用了。
  • sa_flags:引數 sa_flags 指定了一組標誌,這些標誌用於控制訊號的處理過程。
  1. kill()
    kill()系統呼叫可將訊號傳送給指定的程式或程式組中的每一個程式。
int kill(pid_t pid, int sig);
  • pid:引數 pid 為正數的情況下,用於指定接收此訊號的程式 pid。
  • sig:引數 sig 指定需要傳送的訊號,也可設定為 0,如果引數 sig 設定為 0 則表示不傳送訊號,但任執行錯誤檢查,這通常可用於檢查引數 pid 指定的程式是否存在。
  • 返回值:成功返回 0;失敗將返回-1,並設定 errno。
  1. alarm()
    使用 alarm()函式可以設定一個定時器(鬧鐘),當定時器定時時間到時,核心會向程式傳送 SIGALRM訊號。
unsigned int alarm(unsigned int seconds);
  • seconds:設定定時時間,以秒為單位;如果引數 seconds 等於 0,則表示取消之前設定的 alarm 鬧鐘。
  • 返回值:如果在呼叫 alarm()時,之前已經為該程式設定了 alarm 鬧鐘還沒有超時,則該鬧鐘的剩餘值作為本次 alarm()函式呼叫的返回值,之前設定的鬧鐘則被新的替代;否則返回 0。
  1. pause()
    pause()系統呼叫可以使得程式暫停執行、進入休眠狀態,直到程式捕獲到一個訊號為止,只有執行了訊號處理函式並從其返回時,pause()才返回,在這種情況下,pause()返回-1,並且將 errno 設定為EINTR。
int pause(void);
  1. 使用案例
    demo1
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void signal_handler(int signum)
{
    printf("Interrupt signal (%d) received. \n", signum);
    switch(signum){
        case SIGINT:
            printf("ctrl + c \n");
            exit(signum);
            break;
        case SIGQUIT:
            printf("ctrl + \\ \n");
            exit(signum);

    }
}

int main(int argc, char *argv[]) {
    int i = 0;
    
    //註冊訊號與訊號處理程式
    signal(SIGINT, signal_handler);
    signal(SIGQUIT, signal_handler);

    while(1)
    {
        printf("Going to sleep....\n");
        if (i > 5){
            raise( SIGINT);
        }
        sleep(1);
        i++;
    }

    return 0;
}

參考文獻

Linux訊號(signal)機制:http://gityuan.com/2015/12/20/signal/
linux 訊號及處理過程詳解:https://blog.csdn.net/u010765526/article/details/80085895
linux kill訊號詳解:https://www.cnblogs.com/gcb-1991/p/6922694.html
《正點原子應用程式設計指南》

相關文章