1 eventfd 是什么

eventfd 是linux内核一个计数器,主要用于进程间或者线程间,高效的事件通知。

它也是一个系统调用,在内核空间进行计数,用于事件通知 (since linux 2.6.22)。
eventfd 也是一个fd,同样可以使用read/write进行读写操作,本质上是eventfd实现了read/write接口(里氏替换)。

更详细的可以查看 man eventfd

1
2
3
#include <sys/eventfd.h>

int eventfd(unsigned int initval, int flags);

eventfd() 创建一个 eventfd 对象,可以由用户空间应用程序实现事件等待/通知机制, 或由内核发送事件通知, 到用户空间的应用程序。

该对象包含了一个无符号64位整型计数器,计数器由内核维护。 此计数器,使用参数 initval 进行初始化。

eventfd_ctx 内部数据结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct eventfd_ctx {
    struct kref kref;
    wait_queue_head_t wqh;
    /*
     * Every time that a write(2) is performed on an eventfd, the
     * value of the __u64 being written is added to "count" and a
     * wakeup is performed on "wqh". A read(2) will return the "count"
     * value to userspace, and will reset "count" to zero. The kernel
     * side eventfd_signal() also, adds to the "count" counter and
     * issue a wakeup.
     */
    __u64 count;
    unsigned int flags;
};

翻译一下:
每次对eventfd执行write(2)时,将要写入的__u64的值添加到"count",并在"wqh"上执行唤醒。
read(2)将"count" 值返回到用户空间,并将"count"重置为零。 内核端eventfd_signal()也添加到"count"计数器中并发出唤醒。

2 使用方法

一切皆为文件是 Linux 内核设计的一种高度抽象,eventfd 的实现也不例外,我们可以使用操作文件的方法操作 eventfd。

  • read(): 读取 count 值后置 0。(如果设置 EFD_SEMAPHORE,读到的值为 1,同时 count 值递减 1。)
  • write(): 其实是执行 add 操作,累加 count 值。
  • epoll()/poll()/select(): 支持 IO 多路复用操作。
  • close(): 关闭文件描述符,eventfd 对象引用计数减 1,若减为 0,则释放 eventfd 对象资源。

3 应用场景

(1) 比如 libuv 线程间通知,优先采用 eventfd, 如果平台不支持,则采用pipe.

当 pipe 仅用于发出事件信号的所有情况下,都可以使用 eventfd 取而代之。

(2) 作为桥梁,使得epoll可以集成libaio,aio的事件采用eventfd进行通知,而epoll只需要wait这个eventfd即可。

Reference

man eventfd

其他

flags 可以是以下值的 OR 运算结果,用以改变 eventfd 的行为。

  • EFD_CLOEXEC (since Linux 2.6.27)
    文件被设置成 O_CLOEXEC,创建子进程 (fork) 时不继承父进程的文件描述符。
  • EFD_NONBLOCK (since Linux 2.6.27)
    文件被设置成 O_NONBLOCK,执行 read / write 操作时,不会阻塞。
  • EFD_SEMAPHORE (since Linux 2.6.30)
    提供类似信号量语义的 read 操作,简单说就是计数值 count 递减 1。

在 Linux 2.6.26 版本之前,没有使用参数 flags,必须指定为 0。