Unix 信号(如 SIGINT,
SIGTERM)是操作系统与进程通信的一种古老机制。传统的信号处理函数(Signal
Handler)是在中断上下文中执行的,这带来了很多限制(只能调用
Async-Signal-Safe 函数,不能使用锁,不能 malloc)。
Libevent 通过 统一事件源 (Unified Event Source) 的设计,将信号转换为普通的 I/O 事件,使得我们可以在安全的 Event Loop 上下文中处理信号。
1. 基本用法
static void signal_cb(evutil_socket_t sig, short events, void *user_data) {
struct event_base *base = user_data;
printf("Caught an interrupt signal; exiting cleanly.\n");
event_base_loopexit(base, NULL);
}
// 创建信号事件
struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, base);
// 添加信号事件 (无需传入 fd,因为 fd 隐含在信号编号中)
evsignal_add(signal_event, NULL);2. 实现原理
Libevent 如何将异步信号转化为同步事件?主要有三种策略。
2.1. Socket Pair (Self-Pipe Trick)
这是最通用、兼容性最好的方法(适用于 select, poll, epoll)。
- 初始化: Libevent 创建一对 socket
(
socketpair) 或管道 (pipe)。一端读 (read_fd),一端写 (write_fd)。 - 注册: 将
read_fd注册到 epoll 中,监听可读事件。 - 触发: 当 OS 触发信号时,执行一个极简的
C 语言信号处理函数。该函数只做一件事:往
write_fd写入一个字节(通常是信号编号)。 - 回调:
write_fd的写入导致read_fd变为可读。epoll 返回,Event Loop 捕获到读事件,进而调用用户注册的signal_cb。
2.2. signalfd (Linux 特有)
Linux 2.6.22 引入了 signalfd
系统调用,允许创建一个文件描述符来接收信号。 * Libevent
直接将这个 fd 注册到 epoll。 * 当信号发生时,fd 可读。 *
优势:
避免了用户态的信号处理函数,完全由内核接管,性能更高,且不会打断系统调用。
2.3. EVFILT_SIGNAL (kqueue 特有)
BSD 和 macOS 的 kqueue 原生支持信号过滤器。 * 直接调用
kevent 注册 EVFILT_SIGNAL。 *
这是最高效的实现方式。
3. 注意事项
虽然 Libevent 帮我们处理了大部分脏活,但仍需注意: *
不要在多线程中共享信号处理:
信号通常发送给进程,哪个线程收到是不确定的。最好在主线程初始化所有信号事件。
* exec 后失效: exec()
会重置信号处理函数,子进程需要重新注册。
4. 总结
Libevent
的信号处理机制是“将异步转同步”的典范。它让我们能够像处理网络包一样处理
Ctrl+C,极大地简化了服务端的代码逻辑。
上一篇: 03-events/timer.md - 定时器管理 下一篇: 03-events/files-pipes.md - 文件与管道处理