1. 程式人生 > 實用技巧 >[ APUE ] 第十章 訊號

[ APUE ] 第十章 訊號

2. 訊號概念

linux 可支援31種訊號。不存在編號為0的訊號。

不存在編號為0的訊號,kill函式對訊號編號0有特殊應用,這個訊號被稱為空訊號。

產生訊號的條件:

  1. Ctrl+C 產生中斷訊號(SIGINT)。可以停止程式執行。
  2. 硬體異常產生訊號:除數為0,無效的記憶體引用等。
  3. kill函式可以將任意訊號傳送給另一個程序或程序組。但有兩個限制:接收程序和傳送程序的所有者必須相同,或者傳送程序的所有者是超級使用者。
  4. 檢測到某種軟體條件發生,並應將其通知有關程序時也產生訊號。

某個訊號出現時,可以告訴核心按照以下3種方式之一進行處理。

  1. 忽略。 大多數訊號都可以忽略,但是SIGKILL和SIGSTOP訊號不可忽略。這兩種訊號不可忽略的原因是:它們向核心和超級使用者提供了使程序終止或停止的可靠方法。如果忽略了某些由硬體產生的異常訊號,則程序產生的行為使未定義的。

  2. 捕捉。表示 通知核心 在某種訊號發生時,呼叫一個使用者函式。

    如鍵盤產生SIGINT訊號,同時希望接收到中斷訊號時程序退出,則呼叫使用者函式終止當前程序。

    如捕捉到SIGCHLD訊號,則表示子程序已終止,呼叫waitpid以取得程序終止狀態。

    如程序建立了臨時檔案,則可能要為SIGTERM編寫一個訊號捕捉函式來清除臨時檔案。(SIGTERM是kill命令的系統預設訊號)。

    注意不能捕捉SIGKILL和SIGSTOP。

  3. 執行系統預設動作。

    對於大多數訊號,系統的預設動作是終止該程序。

    終止+core :程序在當前工作目錄的core檔案中複製了該程序的記憶體映像,大多數UNIX系統除錯程式都使用core檔案檢查程序終止時的狀態。

3. signal函式

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
						返回值:成功返回以前的訊號處理函式指標,出錯則返回SIG_ERR

handler的值是常量SIG_IGN、SIG_DFL或者訊號處理函式。

SIG_IGN表示忽略該訊號。SIG_DFL表示接到此訊號後的動作是預設動作。

  1. 程式啟動

    當exec執行程式時,程序原先設定的訊號捕捉函式失效,所有的訊號都被設定為預設動作。(因為一個程序原先要捕捉的訊號,當其執行exec函式替換程序映像時,原來的訊號處理函式地址很可能就被覆蓋,無效了)。

  2. 程序建立

    當程序呼叫fork時,其子程序繼承父程序的訊號處理方式。因為子程序在開始時複製了父程序記憶體映像,所以訊號捕捉函式的地址在子程序中是有意義的。

5.中斷的系統呼叫

6. 可重入函式

程序正常執行的指令序列被訊號處理程式臨時中斷,訊號處理程式不能判斷程序原來的指令執行至何處。這就會帶來一系列問題。

譬如,當malloc執行時中斷;當執行修改靜態儲存單元的函式時中斷;

當malloc維護堆記憶體塊連結串列時,中斷可能會破壞剛剛分配的記憶體。

存入靜態儲存單元的資訊可能在訊號處理函式中被覆蓋(破壞)。

SUS說明了可以在訊號處理程式中保證呼叫安全的函式。這些函式是可重入的,並被稱為是非同步訊號安全的。這些函式除了可重入,它還會阻止任何引起不一致的訊號傳送。

(注:可重入函式,即可以被中斷的函式,任何時刻都可被中斷,返回控制時不會引起什麼錯誤。)

沒有列入表中的函式大多數不可重入,它們有以下特徵:

  1. 使用了靜態資料結構(如果注意了關中斷和互斥手段其實也沒啥)
  2. 呼叫malloc或者free
  3. 它們是標準IO函式(標準IO庫的很多實現都是通過不可重入的方式使用全域性資料結構

即便是對於上表中的函式,每個程序只有一個errno變數,所以訊號處理函式可能會修改原來的值。譬如main函式中設定了errno,之後呼叫上表中函式,之後發生中斷訊號,訊號處理函式中呼叫了read等函式,它可能更改errno。

因此,我們在呼叫上表中函式之前要先儲存errno,之後恢復errno

(尤其是 SIGCHLD這種常被捕捉的訊號,訊號處理程式中通常要呼叫wait,而wait函式通常會修改errno)。

注意longjmp 和 siglongjmp 也是不可重入的。如果在更新資料結構時產生訊號,且訊號處理程式中呼叫siglongjmp,那麼這次更新可能就半途而廢。所以在更新全域性資料結構,同時某些訊號的訊號處理程式可能會執行siglongjmp的時候,要阻塞這些訊號。

7.SIGCLD語義

linux上等同於SIGCLD

注意SIGCLD的訊號處理函式sig_cld如果先設定signal(SIGCLD,sig_cld); 後wait,則會無限遞迴呼叫sig_cld(在某些系統上,linux似乎解決了這個問題)

8. 可靠訊號術語和語義

訊號的產生和遞送之間的時間間隔內,我們稱訊號是未決的。

程序產生了一個阻塞訊號,且對該訊號的動作為系統預設動作或者捕捉,則該程序將此訊號保持為未決狀態,直到該程序對這個訊號解除了阻塞,或者對此訊號的動作更改為忽略。

* 訊號遞達(Delivery):實際執行訊號的處理動作
* 訊號未決(Pending):訊號從產生到遞達之間的狀態
* 訊號阻塞(Block):程序可以選擇阻塞某個訊號,被阻塞的訊號產生時將保持在未決狀態,直到程序解除對此訊號的阻塞,才執行遞達的動作

*** 阻塞和忽略是不同的,只要訊號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作**

核心在遞送一個被阻塞的訊號給程序時,才決定他的處理方式(而非在產生該訊號時)。於是訊號在這期間仍然可以改變對這個訊號的動作。程序呼叫 sigpending 函式來判斷哪些訊號設定為阻塞並處於未決狀態。

特殊情況:程序解除對某個訊號的阻塞之前,這個訊號發生了多次。

Linux下的解決方式:常規訊號在遞達之前產生多次只計一次,而實時訊號在遞達之前產生多次可以依次放在一個佇列裡。

9. kill 和 raise函式

kill函式將訊號傳送給程序或程序組,raise函式允許程序向自身傳送訊號。

kill返回前該訊號就被傳送給該程序

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
   												
#include <signal.h>

int raise(int sig);
   												成功返回0,失敗返回-1

呼叫 raise (signo), 等價於呼叫kill(getpid(), signo)

kill 的 pid引數有以下4種不同的情況

  • pid > 0, 將訊號傳送給程序ID為PID的程序
  • pid == 0, 將訊號傳送給與傳送程序同屬同一程序組的所有程序(不包括系統程序集)
  • pid < 0, 將該訊號傳送給其程序組ID等於pid絕對值的所有程序,而且傳送程序具有向這些程序傳送訊號的許可權