使用 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 出来试试看。

有什么问题,欢迎告诉我~~~~

相关文章
相关标签/搜索