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)