kill() 不能直接用于跨进程可靠通知,因其异步、无状态、不可靠:未注册处理器时信号被忽略,非实时信号会合并,且无法携带数据;而 eventfd 通过共享内核计数器提供原子、可控、可等待的事件通知机制。

为什么不能直接用 kill() 发送信号到跨进程对象
Linux 的 kill() 确实能发信号(比如 SIGUSR1),但问题在于:信号是异步、无状态、不可靠的。如果接收方进程还没注册 signal() 或 sigaction() 处理器,或者信号在注册前就到达,它会被直接忽略;多个连续信号可能被合并(尤其对非实时信号);且无法携带任何数据。所以单纯封装 kill() 不构成“通知类”,只是裸系统调用转发。
用 eventfd() + fork() 模拟信号语义更可靠
eventfd() 是 Linux 特有的内核对象,支持原子写入整数、可被 epoll/poll 等待,天然适合跨进程事件通知。它比管道更轻量(无缓冲区拷贝)、比信号更可控。关键点:
- 父进程创建
eventfd(0)后fork(),子进程继承 fd,双方共享同一内核 eventfd 对象 - 通知方调用
write(eventfd_fd, &val, sizeof(val))(val通常为 1),接收方用read()或epoll_wait()等待 - 必须用
uint64_t类型写入,哪怕只通知一次——这是eventfd的硬性要求 - 注意关闭不需要的 fd 副本,避免资源泄漏;子进程若不读,父进程持续写会阻塞(除非创建时加
EFD_NONBLOCK)
// 示例:简单跨进程通知(父子)
#include <sys/eventfd.h>
#include <unistd.h>
#include <cstdint>
int main() {
int efd = eventfd(0, EFD_CLOEXEC); // 初始化计数为 0
pid_t pid = fork();
if (pid == 0) { // child: wait
uint64_t val;
read(efd, &val, sizeof(val)); // 阻塞直到通知
close(efd);
} else { // parent: notify
uint64_t val = 1;
write(efd, &val, sizeof(val));
close(efd);
wait(nullptr);
}
}
如何让非父子进程也能通信:用 memfd_create() 或临时文件绑定 eventfd
父子继承 fd 很方便,但任意进程间需要共享 fd。Linux 5.15+ 支持 memfd_create() 创建匿名内存文件,配合 sendmsg()/recvmsg() 传递 fd;更通用的做法是:用命名信号量(sem_open())或 Unix domain socket 传递一个预先创建好的 eventfd fd。但最简路径是——用 shm_open() + mmap() 共享一个 int 标志位,再配一个 pthread_cond_t(需放在共享内存中并正确初始化)。不过这已脱离“信号”语义,进入同步原语范畴。
更现实的选择:放弃“任意进程”,限定为“有共同祖先或约定路径的进程”。例如:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 接收方启动时创建
eventfd,把 fd 编号写入/tmp/myapp-notify-fd - 通知方读该文件,用
dup2()复制 fd(需确保权限和命名空间一致) - 或改用
AF_UNIXsocket 发送 fd(通过SCM_RIGHTS控制消息)——这才是生产环境推荐方式
别忽略 SIGCHLD 和 signalfd() 的替代价值
如果你真正想解决的是“子进程退出后主进程感知”,那根本不需要自建通知类——直接 sigaction(SIGCHLD, &sa, nullptr) 注册处理器,或更现代地用 signalfd() 把信号转成可 read() 的 fd。后者能和 epoll 统一事件循环,避免信号中断系统调用的麻烦。
而如果你需要的是“多进程协同启停”,eventfd 是好选择;但若只是单次触发、低频、不关心丢失,kill(getpid(), SIGUSR1) 加全局 volatile flag 也够用——只是别把它包装成“可靠 Signal 类”。
真正的难点不在发送,而在接收方如何安全地从信号上下文跳转到常规执行流,以及如何避免竞态。这些细节一旦漏掉,类就只是个幻觉。

















