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)