我的上一篇文章研究了如何捕获和处理程序文字中的信号(与信号处理函数相对)。当时使用的方案是sigprocmask()。但该方法理论上可能会漏掉一些信号。真正安全的方式是使用进程间/线程间通信,在信号处理函数中发送信号,然后在程序文本中监听(epoll、select等)这些数据。其中,需要用到全局变量。我目前没有不使用全局变量的计划。本文地址:https://segmentfault.com/a/1190000009280819Reference&Related《UNIX 环境高级编程》libevent源码深度分析使用sigprocmask和sigpending捕获并处理程序文本中的信号通信通道。在中断处理函数中,将捕获到的信号编号写入该通道。在程序文本中,读取这个通道,这样就可以在程序文本中捕获信号。这个其实参考了《深入解析libevent源码》中提到的libevent实现evsignal的解决方案。大家都知道,在信号处理函数中,我们一般不会调用printf等stdio库中的函数。原因请参考《UNIX 环境高级编程》中“信号”一章的“可重入函数”小节。而实现这个功能最重要的读/写函数可以在信号处理函数中调用!这是该方案的原理基础。优点由于通信通道基本上是FIFO,如果多次产生信号,程序文本也可以接收排队的数据,避免错过多次信号。信号处理程序非常非常短,只需调用write()并写入很少的数据。大量的数据处理放在程序文本中,获取信号的方法很简单,read()即可。并且每次获取的数据长度是固定的:signum的类型是int,只获取sizeof(int)字节的数据。pipe可以直接使用read/writeAPI操作,可以方便的连接异步I/O库。选择pipe的原因在《深入解析libevent源码》中提到libevent使用的是UNIX域套接字(AF_UNIX)。这里我不使用这个方案,而是使用pipe,原因如下:pipe初始化和创建的创建是无命名的,生命周期只在进程内部,不会出现多个进程使用造成的命名冲突同样的架构命名为AF_UNIX,协议栈比较复杂,系统开销有点高。一般来说,管道用于父子进程之间的通信。甚至在《UNIX 环境高级编程》中提到“单个进程中的管道几乎没有用”。我只是笑了——在本文的应用场景中,它实际上是管道在单个进程中的少数使用之一。代码实现我正在设计一个基于epoll的异步I/O库(GitHub链接),我实现了类似libevent的普通事件和evsignals。如果对这个实现感兴趣,可以直接去我的项目看代码。本文内容主要是epEventSignal.c文件中的实现。主要的实际上是epEventSignal_AddToBase()函数。该函数的操作流程如下:调用pipe()创建管道,设置nonblock,closeonexec选项,将pipe[0]赋值给全局变量,使用sigaction()函数捕获信号。在信号处理函数中,将信号值写入pipe[0],将pipe[1]注册到epoll中,捕获read事件。我在我的简单测试程序test_server.c中捕获了两个信号,它们是SIGQUIT(忽略,仅输出)和SIGINT(触发事件循环以安全退出)。读者可以查看并试用。如果有任何问题,请告诉我~~~~
