土法炼钢兴趣小组的算法知识备份

信号处理 (Signal)

目录

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)。

  1. 初始化: Libevent 创建一对 socket (socketpair) 或管道 (pipe)。一端读 (read_fd),一端写 (write_fd)。
  2. 注册: 将 read_fd 注册到 epoll 中,监听可读事件。
  3. 触发: 当 OS 触发信号时,执行一个极简的 C 语言信号处理函数。该函数只做一件事:往 write_fd 写入一个字节(通常是信号编号)。
  4. 回调: 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 - 文件与管道处理

返回 Libevent 专题索引


By .