Signal

簡介

人生總是充滿意外,PROCESS 有時候也需要接獲不預期與不定期的事件通知,其中一種方式就是透過 KERNEL 發送 SIGNAL 。

SIGNAL 的種類

  • 不可靠/傳統/標準/非即時:編號 1~31 的 SIGNAL,UNIX 遺留下來的,有可能傳丟。
  • 可靠/即時:後來又提供一組,在資源限制下,確保不會丟失。編號 SIGRTMIN~SIGRTMAX。

SIGNAL 傳遞的流程

事件引發 > KERNEL > PROCESS > HANDLER

事件發生了,訊號來到 KERNEL 成為 pending signal,等到 PROCESS 在 CPU 上執行,KERNEL 會依照一定的規則將 signal 轉發給 PROCESS,然後由 PROCESS 的 HANDLER 進行處理程序。

事件引發

主要來源有三:

  • 硬體異常
  • 終端機輸入:
    • Ctrl+CCtrl+ZCtrl+\ … 等。
    • $ sudo kill -9 <pid>
  • 軟體事件
    • 調整終端機視窗大小。
    • 計時器到期。
    • 進程超過 CPU 限制。
    • 子進程終止。
    • … 等。

傳送

KERNEL 從 pending signal 挑出,發送至 PROCESS。特別是挑選的順序。要知道,如果 PROCESS 在忙碌中,KERNEL 可能累積了很多的 SIGNAL,這時候要怎麼挑選?

  • 如果是 multi-thread,先從 thread 私有的 pending signal queue 挑。
  • 6 個同步信號,值小到大。
  • 非即時信號,值小到大。
  • 即時信號,值小到大。

接收與等待

PROCESS 收取 KERNEL 發過來的 SIGNAL。然後可以使用異步的 HANDLER,也可以使用同步的等待,來處理 SIGNAL。

系統預設訊號處理

可以分為五類預設行為:

  • ignore
  • terminate
  • coredump
  • stop(pause)
  • continue(resume)

自訂訊號處理

由 PROCESS 註冊特定 SIGNAL 的處理函式,有三種選擇:

  • 指定使用預設處理方式: SIG_DFL,將 SIGNAL 的處理方式,改回上述的預設行為。
  • 指定使用忽略訊號: SIG_IGN,將處理函式改成什麼都不要做。
  • 自訂處理方式: 自行定義函式(HANDLER)來處理。

註冊用的函式有:

  • signal()
    • 特性分類
      • ONE SHOT:每次都會回歸 SIG_DFL。
      • MASK SELF:沒有遮罩,就可能丟失 SIGNAL。
      • RESTART:中斷的系統呼叫,是否重啟。
    • 四種 signal()
      • syscall
      • signal()
      • sysv_signal()
      • bsd_signal()
  • sigaction()

自訂訊號處理是異步的,在 PROCESS 主執行流處理到一半,收到 SIGNAL 會中斷,另外開一個執行流來處理 SIGNAL。因為是兩個執行流,就容易有時間差的漏洞,特別要小心不安全的函式,可以查閱 $ man 7 signal #Async-signal-safe

異步信號安全原則如下:

  • 輕量 handler: 最好只是修改 flag,然後剩下的主作業由主執行流處理。
  • 化異步為同步: 在主執行流作等待。

等待

就是在主執行流等待 SIGNAL 到來,然後處理也在主執行流處理,就可以避免雙執行流可能造成的不安全行為。

  • pause()
  • sigsuspend():「解除信號阻塞」和「掛起進程等待信號」兩個動作原子化
  • sigwait*():化繁為簡
    • sigwait
    • sigwaitinfo
    • sigtimedwait
    • 會和正常的信號處理流程形成競爭,所以要先做遮罩,不要讓信號進正常的信號處理流程。
  • signalfd
    • 使用 FILE 的操作介面
    • 可通過 select、poll、poll 來監控這些 fd。