使用 pipe 在程序正文中捕獲和處理信號

個人上一篇文章研究了一下如何在程序的正文(而不是信號處理函數)中捕獲和處理信號。當時用的方案是 sigprocmask()。但那個方法理論上是可能漏掉一些信號的。html

真正安全的作法,是使用進程 / 線程間通訊手段,在信號處理函數中向外發送信號,而後在程序正文中監聽(epoll, select 等等)這些數據。git

這其中是須要使用全局變量的,我目前尚未不使用全局變量的方案。github

本文地址:http://www.javashuo.com/article/p-elsiypvy-en.html編程

Reference & Related

《UNIX 環境高級編程》
libevent 源碼深度剖析
使用 sigprocmask 和 sigpending 在程序正文中捕獲和處理信號segmentfault

基本原理

在設置捕獲信號以前(signal()),首先建立一個通訊通道。在中斷處理函數中,將捕獲到的信號數往這個通道內寫入。而在程序正文中,則對這個通道進行讀取,這樣就能夠實如今程序正文中捕獲信號了。安全

這實際上是參考了 「libevent 源碼深度剖析」 裏面所說的 libevent 實現 evsignal的方案。你們都知道,在信號處理函數中,咱們通常不要調用 printf 等 stdio 庫裏的函數。緣由請參照《UNIX 環境高級編程》中 「信號」 章的 「可重入函數」 小節。架構

而實現這個功能中最重要的 read / write 函數,是能夠在信號處理函數中調用的!這就是本方案的原理基礎。異步

優勢

  1. 因爲通訊通道基本上是 FIFO 的,因此若是信號屢次產生,程序正文也能夠收到排隊發來的數據,避免了錯過多個的信號。
  2. 信號處理函數很是很是短,只須要調用一個 write() 而且寫入極少的數據便可。
  3. 大量的數據處理放在程序正文中,獲取信號的方法很簡單,只是 read()。而且每次獲取數據的長度是固定的:signum 的類型是 int,獲取 sizeof(int) 字節的數據便可。
  4. pipe 能夠直接使用 read / write API 操做,能夠方便地對接異步 I/O 庫。

選擇 pipe 的緣由

libevent 源碼深度剖析」 中提到 libevent 使用的是 UNIX域socket(AF_UNIX)。這裏我不使用這個方案,而用了 pipe,緣由以下:socket

  1. pipe 初始化和建立簡單
  2. pipe 的建立是非命名的,生存週期僅在進程內部,也不會出現多個進程使用相同架構,發生命名衝突的問題
  3. AF_UNIX 是命名的,並且協議棧較爲複雜,系統開銷稍有些大

通常而言 pipe 是用在父子進程間通訊用的,甚至在《UNIX 環境高級編程》中還原文提到 「單個進程中的管道幾乎沒有任何用處」 。我就哈哈大笑啦——在本文的應用場景下,實際上就是 pipe 爲數很少的在單個進程以內的使用。函數

代碼實現

我正在本身設計一個基於 epoll 的異步 I/O 庫(GitHub 連接),目前已經實現了相似於 libevent 的普通 event 和 evsignal。若是對這個實現感興趣的話,能夠直接到個人工程裏看代碼。本文內容主要是 epEventSignal.c 文件裏的實現。

主要的實際上也就是 epEventSignal_AddToBase() 函數啦。在這個函數的操做流程以下:

  1. 調用 pipe() 建立管道
  2. 設置 nonblock、closeonexec 選項
  3. pipe[0] 賦值到全局變量中
  4. 使用 sigaction() 函數捕獲信號。信號處理函數中,將信號值寫入 pipe[0]
  5. pipe[1] 註冊入 epoll 中,捕獲讀事件

我在本身的簡單測試程序 test_server.c 中捕獲了兩個信號,分別是 SIGQUIT(忽略,僅輸出)和 SIGINT(觸發 event loop 安全退出)。讀者能夠 checkout 出來試試看。

有什麼問題,歡迎告訴我~~~~

相關文章
相關標籤/搜索