eventfd(2) - Linux 手册页

名称

eventfd - 创建用于事件通知的文件描述符

概要

#include <sys/eventfd.h>

int eventfd(unsigned int initval, int flags);

描述

eventfd() 创建一个“eventfd 对象”,该对象可用作用户空间应用程序之间的事件等待/通知机制,也可由内核用于向用户空间应用程序通知事件。该对象包含一个由内核维护的无符号 64 位整数(uint64_t)计数器。此计数器使用参数 initval 指定的值进行初始化。

以下值可以通过按位或运算组合到 flags 中,以改变 eventfd() 的行为:

EFD_CLOEXEC (自 Linux 2.6.27 起)
在新的文件描述符上设置执行时关闭(FD_CLOEXEC)标志。有关此标志为何有用的原因,请参阅 open(2) 中关于 O_CLOEXEC 标志的说明。
EFD_NONBLOCK (自 Linux 2.6.27 起)
在新的打开文件描述上设置 O_NONBLOCK 文件状态标志。使用此标志可以省去额外的 fcntl(2) 调用以达到同样的效果。
EFD_SEMAPHORE (自 Linux 2.6.30 起)
为从新文件描述符读取提供类似信号量的语义。详见下文。
在 Linux 2.6.26 及之前版本中,flags 参数未被使用,且必须指定为零。

eventfd() 返回一个新的文件描述符作为其返回值,该描述符可用于引用 eventfd 对象。可以在该文件描述符上执行以下操作:

read(2)
每次成功的 read(2) 调用都会返回一个 8 字节的整数。如果提供的缓冲区大小小于 8 字节,read(2) 将失败并返回错误 EINVAL
read(2) 返回的值采用主机字节序,即宿主机上整数的本机字节序。

read(2) 的语义取决于 eventfd 计数器当前是否具有非零值,以及在创建 eventfd 文件描述符时是否指定了 EFD_SEMAPHORE 标志。

*

如果未指定 EFD_SEMAPHORE 且 eventfd 计数器具有非零值,则 read(2) 返回包含该值的 8 字节,并将计数器的值重置为零。

*

如果指定了 EFD_SEMAPHORE 且 eventfd 计数器具有非零值,则 read(2) 返回包含值 1 的 8 字节,并将计数器的值减 1。

*

如果调用 read(2) 时 eventfd 计数器为零,则调用将阻塞,直到计数器变为非零(此时 read(2) 按上述方式执行),或者如果文件描述符已被设为非阻塞模式,则调用将失败并返回错误 EAGAIN

write(2)
write(2) 调用将缓冲区中提供的 8 字节整数值加到计数器上。计数器中可存储的最大值为最大无符号 64 位值减 1(即 0xfffffffffffffffe)。如果加法会导致计数器的值超过该最大值,则 write(2) 将阻塞,直到在文件描述符上执行了 read(2),或者如果文件描述符已被设为非阻塞模式,则失败并返回错误 EAGAIN
如果提供的缓冲区大小小于 8 字节,或者尝试写入值 0xffffffffffffffff,则 write(2) 将失败并返回错误 EINVAL
poll(2), select(2)(及类似函数)
返回的文件描述符支持 poll(2)(以及类似的 epoll(7))和 select(2),具体如下:
*

如果计数器的值大于 0,则文件描述符是可读的(select(2) 的 readfds 参数;poll(2) 的 POLLIN 标志)。

*

如果可以写入至少“1”的值而不阻塞,则文件描述符是可写的(select(2) 的 writefds 参数;poll(2) 的 POLLOUT 标志)。

*

如果检测到计数器溢出,则 select(2) 将该文件描述符指示为既可读又可写,并且 poll(2) 返回一个 POLLERR 事件。如上所述,write(2) 永远不会导致计数器溢出。但是,如果 KAIO 子系统执行了 2^64 次 eventfd“信号发送”(理论上可能,但实际几乎不可能),则可能会发生溢出。如果发生了溢出,那么 read(2) 将返回该最大 uint64_t 值(即 0xffffffffffffffff)。

eventfd 文件描述符还支持其他文件描述符多路复用 API:pselect(2) 和 ppoll(2)。
close(2)
当不再需要文件描述符时,应将其关闭。当所有与同一个 eventfd 对象关联的文件描述符都已关闭时,内核将释放该对象的资源。
eventfd() 创建的文件描述符的副本由 fork(2) 产生的子进程继承。重复的文件描述符关联到同一个 eventfd 对象。除非已设置 close-on-exec 标志,否则由 eventfd() 创建的文件描述符在 execve(2) 调用后得以保留。

返回值

成功时,eventfd() 返回一个新的 eventfd 文件描述符。出错时,返回 -1,并设置 errno 以指示错误。

错误

EINVAL

flags 中指定了不支持的值。

EMFILE

已达到进程对打开文件描述符的限制。

ENFILE

已达到系统范围打开文件总数的限制。

ENODEV

无法挂载(内部)匿名 inode 设备。

ENOMEM

内存不足,无法创建新的 eventfd 文件描述符。

版本

eventfd() 自 Linux 内核 2.6.22 起可用。glibc 自 2.8 版本起提供有效支持。eventfd2() 系统调用(参见 NOTES)自 Linux 内核 2.6.27 起可用。自 2.9 版本起,如果内核支持,glibc 的 eventfd() 包装器将使用 eventfd2() 系统调用。

符合

eventfd() 和 eventfd2() 是 Linux 特有的。

说明

在所有仅用于信号通知的场合,应用程序都可以使用 eventfd 文件描述符代替管道(参见 pipe(2))。eventfd 文件描述符的内核开销远低于管道,且只需要一个文件描述符(而管道需要两个)。

当在内核中使用时,eventfd 文件描述符可以提供从内核到用户空间的桥梁,例如,允许像 KAIO(内核 AIO)这样的功能向文件描述符发出信号,表示某些操作已完成。

eventfd 文件描述符的一个关键点在于,它可以像任何其他文件描述符一样,使用 select(2)、poll(2) 或 epoll(7) 进行监视。这意味着应用程序可以同时监视“传统”文件的就绪状态以及其他支持 eventfd 接口的内核机制的就绪状态。(如果没有 eventfd() 接口,这些机制无法通过 select(2)、poll(2) 或 epoll(7) 进行多路复用。)

底层 Linux 系统调用

存在两个底层 Linux 系统调用:eventfd() 和较新的 eventfd2()。前者系统调用不实现 flags 参数。后者系统调用实现了上述 flags 值。glibc 包装函数将在可用时使用 eventfd2()。

附加的 glibc 特性

GNU C 库定义了一个额外的类型和两个函数,试图抽象出对 eventfd 文件描述符进行读写的一些细节。
typedef uint64_t eventfd_t;

int eventfd_read(int fd, eventfd_t *value);
int eventfd_write(int fd, eventfd_t value);
这些函数在 eventfd 文件描述符上执行读写操作,如果传输了正确字节数则返回 0,否则返回 -1。

示例

以下程序创建一个 eventfd 文件描述符,然后 fork 以创建一个子进程。当父进程短暂睡眠时,子进程将程序命令行参数中提供的每个整数写入 eventfd 文件描述符。当父进程结束睡眠后,它会从 eventfd 文件描述符中读取内容。

以下 shell 会话展示了该程序的一个示例运行:

$ ./a.out 1 2 4 7 14
Child writing 1 to efd
Child writing 2 to efd
Child writing 4 to efd
Child writing 7 to efd
Child writing 14 to efd
Child completed write loop
Parent about to read
Parent read 28 (0x1c) from efd

程序源码

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>             /* Definition of uint64_t */

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
    int efd, j;
    uint64_t u;
    ssize_t s;

   if (argc < 2) {
        fprintf(stderr, "Usage: %s <num>...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

   efd = eventfd(0, 0);
    if (efd == -1)
        handle_error("eventfd");

   switch (fork()) {
    case 0:
        for (j = 1; j < argc; j++) {
            printf("Child writing %s to efd\n", argv[j]);
            u = strtoull(argv[j], NULL, 0);
                    /* strtoull() allows various bases */
            s = write(efd, &u, sizeof(uint64_t));
            if (s != sizeof(uint64_t))
                handle_error("write");
        }
        printf("Child completed write loop\n");

       exit(EXIT_SUCCESS);

   default:
        sleep(2);

       printf("Parent about to read\n");
        s = read(efd, &u, sizeof(uint64_t));
        if (s != sizeof(uint64_t))
            handle_error("read");
        printf("Parent read %llu (0x%llx) from efd\n",
                (unsigned long long) u, (unsigned long long) u);
        exit(EXIT_SUCCESS);

   case -1:
        handle_error("fork");
    }
}

参见

futex(2), pipe(2), poll(2), read(2), select(2), signalfd(2), timerfd_create(2), write(2), epoll(7), sem_overview(7)

引用自

explain(1), explain(3), explain_eventfd(3), explain_eventfd_or_die(3), iv_event_raw(3)