eventfd(2) 结合 select(2) 源码分析
eventfd(2) 结合 select(2) 源码分析
本文代码选自内核 4.17
eventfd(2) - 创建一个文件描述符用于事件通知。
#include <sys/eventfd.h> int eventfd(unsigned int initval, int flags); int eventfd2(unsigned int initval, int flags); 参数 - \initval 为初始值(关联内部结构的 count) - \flags 内核 2.6.26 之前的版本这个参数无效且必须指定为 0 flags 有意义的参数为 - EFD_CLOEXEC, 等效于 O_CLOEXEC - EFD_NONBLOCK, 等效于 O_NONBLOCK - EFD_SEMAPHORE, 信号量选项,影响 read(2) 的取值 返回 - 成功返回一个新的文件描述符,失败返回 -1 并设置 errno
eventfd 作为一个非常简单的抽象文件,每个文件描述符都对应一个在内核空间维护的 __u64 count
, 一个无符号64位整形的计数器,而eventfd对应的文件操作都与这个计数器相关。
提供的文件操作
- read(2), 读取 count 减少的值,若flags设置 EFD_SEMAPHORE 则
count -= 1
, 否则count -= count
; 函数成功返回 8 - write(2), 写入一个 cnt,
count += cnt
,函数成功返回 8 - poll(2), poll 操作,事件通知的核心,详见下
- close(2), eventfd 结构对象引用计数减一,若未0,则释放所占用的内存
使用
eventfd(2) 核心就是其 poll 操作,最常见的用法是配合 select(2)/poll(2)/epoll(2) 使用达到不同线程间通信的作用。
#include <poll.h> #include <unistd.h> #include <stdio.h> #include <sys/eventfd.h> #include <pthread.h> int efd; void *run_eventfd_write(void *arg) { uint64_t count = 1; while (1) { printf("write count: %zu\n", count); write(efd, &count, sizeof(count)); count++; sleep(2); } } int main() { struct pollfd fds; pthread_t pid; unsigned int initval = 1000; // 观察将 1000 改为 0 后打印的顺序 int flags = 0; int timeout = 1000; // flags |= EFD_SEMAPHORE; // 观察将该注释取消打印的结果 efd = eventfd(initval, flags); fds.fd = efd; fds.events |= POLLIN; pthread_create(&pid, NULL, run_eventfd_write, NULL); while (1) { int ret = poll(&fds, 1, timeout); if (ret > 0) { uint64_t count; read(efd, &count, sizeof(count)); printf("read count: %zu\n", count); } } } read count: 1000 write count: 1 read count: 1 write count: 2 read count: 2 write count: 3 read count: 3 write count: 4 read count: 4
这里使用了一个非常简单的示例,程序不严谨但是很好的展示了如何在两个线程进行通信,在子线程中,通过一个无限循环每隔一秒向 eventfd 中写入一个逐渐增大的无符号长整形数字,在主线程中通过 poll(2) 接收到有就绪事件产生,并且使用 read 函数读取内核空间的计数器减少的值。
read write 系统调用的参数都是以下的形式
int read(int, void *, size_t);
而 eventfd 内部是维护的计数器,所以在使用的时候,保持第二个参数和第三个参数分别为 uint64_t *
和 sizeof(uint64_t)