欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Linux指南:详解eventfd(2)功能的中文翻译与介绍

最编程 2024-07-24 11:09:46
...

\color{#A00000}{NAME}
eventfd - 为event notification创建文件描述符

\color{#A00000}{SYNOPSIS}

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

\color{#A00000}{DESCRIPTION}

eventfd() 创建了一个“eventfd 对象”,它可以被用户空间应用程序用作事件等待/通知机制,内核可以将事件通知用户空间应用程序。 该对象包含一个由内核维护的无符号 64 位整数 (uint64_t) 计数器。 此计数器使用参数 initval 中指定的值进行初始化。
eventfd() 将返回一个文件描述符,指向“eventfd 对象”。
参数 flag 可以是以下值按位或运算:

  • EFD_CLOEXEC
    对应于open的 O_CLOEXEC 参数,即在执行execve是关闭此fd
  • EFD_NONBLOCK
    对应于open的 O_NONBLOCK 参数,即将其设置非阻塞的fd,如果fd处于不可读写的状态,读写非阻塞的fd将不会阻塞线程,而是返回 EAGAIN
  • EFD_SEMAPHORE
    赋予eventfd类似于信号量的语义。 见下文。

在 Linux 2.6.26 之前的版本中,flags 参数未使用,必须指定为零。
可以对 eventfd() 返回的文件描述符执行以下操作:

read(2)

成功读取会返回一个 8 字节整数。 如果提供的缓冲区的大小小于 8 字节,则 read(2) 失败并显示错误 EINVAL。
read(2) 返回的值是主机字节顺序,即主机上整数的本机字节顺序。换句话说,返回的值可以直接转化为整数。
read(2) 的语义取决于 eventfd 计数器当前是否具有非零值以及在创建 eventfd 文件描述符时是否指定了 EFD_SEMAPHORE 标志:

  • 如果未指定 EFD_SEMAPHORE 并且 eventfd 计数器具有非零值,则 read(2) 返回 eventfd 计数器的值,同时将计数器置零。
  • 如果指定了 EFD_SEMAPHORE 并且 eventfd 计数器具有非零值,则 read(2) 返回1,同时将计数器的值减 1。
  • 如果在调用 read(2) 时 eventfd 计数器为零则:
    • 未配置EFD_NONBLOCK flags,则调用阻塞,直到计数器变为非零(此时,read(2) 如上所述返回结果)
    • 若配置了EFD_NONBLOCK flags,则调用失败并返回错误 EAGAIN

write(2)

write(2) 调用将其缓冲区中提供的 8 字节整数值添加到计数器(计数器的值加上write提供的值)。计数器的最大值为2^64-1(即0xffffffffffffffffe),如果相加的结果超过最大值,那么将阻塞write(),直到执行read(),当然,如果设置了EFD_NONBLOCK flags,则调用失败并返回错误 EAGAIN

poll(2), select(2),epoll(7)

  • 如果计数器的值大于 0,则文件描述符是可读的(epoll(2) POLLIN 标志)。
  • 如果可以在不阻塞的情况下写入至少为“1”的值,则文件描述符是可写的(epoll(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) 中保留。

\color{#A00000}{RETURN VALUE}
成功时, eventfd() 返回一个新的 eventfd 文件描述符。 出错时,返回 -1 并设置 errno 以指示错误。

\color{#A00000}{ERRORS}

  • EINVAL
    FLAGS中指定了不支持的值。

  • EMFILE
    达到了每个进程可打开的文件描述符数目的上限

  • EMFILE
    达到了系统可打开的文件描述符数目的上限

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

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

\color{#A00000}{VERSIONS}
eventfd() is available on Linux since kernel 2.6.22. Working support is provided in glibc since version 2.8. The eventfd2() system call (see NOTES) is available on Linux since kernel 2.6.27. Since version 2.9, the glibc eventfd() wrapper will employ the eventfd2() system call, if it is supported by the kernel.

\color{#A00000}{ATTRIBUTES}

Interface Attribute Value
eventfd() Thread safety MT-Safe

\color{#A00000}{CONFORMING TO}
eventfd() and eventfd2() are Linux-specific.

\color{#A00000}{NOTES}
在管道仅用于发送事件信号的所有情况下,应用程序都可以使用 eventfd 文件描述符而不是管道(请参阅 pipe(2))。 eventfd 文件描述符的内核开销远低于管道,并且只需要一个文件描述符(而管道需要两个)。
当在内核中使用时,eventfd 文件描述符可以提供从内核到用户空间的桥梁,例如,允许诸如 KAIO(内核 AIO)之类的功能向文件描述符发出信号,表明某些操作已完成。
eventfd 文件描述符的一个关键点是它可以像任何其他文件描述符一样使用 select(2)、poll(2) 或 epoll(7) 进行监视。这意味着应用程序可以同时监视“传统”文件的准备情况和支持 eventfd 接口的其他内核机制的准备情况。 (没有 eventfd() 接口,这些机制无法通过 select(2)、poll(2) 或 epoll(7) 进行多路复用。)
在进程的 /proc/[pid]/fdinfo 目录中,可以通过相应文件描述符的条目查看 eventfd 计数器的当前值。有关更多详细信息,请参阅 proc(5)。
C library/kernel differences
有两个底层的 Linux 系统调用:eventfd() 和最近的 eventfd2()。 前一个系统调用没有实现 flags 参数。 后一个系统调用实现了上述标志值。 glibc 包装器函数将在可用的地方使用 eventfd2()。
Additional glibc features
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。
\color{#A00000}{EXAMPLES}
下面的程序创建一个 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 <inttypes.h>           /* Definition of PRIu64 & PRIx64 */
#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;
    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 (int 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 %"PRIu64" (%#"PRIx64") from efd\n", u, u);
        exit(EXIT_SUCCESS);

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

推荐阅读