一起来感受下eventfd的魅力(一、eventfd使用介绍)

一、函数介绍

       #include <sys/eventfd.h>

       int eventfd(unsigned int initval, int flags);

eventfd()函数可以创建一个被用户空间应用程序作为“等待/通知”机制使用的eventfd对象,或被内核用于通知用户空间应用程序事件消息。eventfd对象包含一个uint64_t类型计数器,由内核进行维护。该计数器通过参数interval进行初始化。

eventfd()函数返回一个文件描述符,这个fd用于对eventfd对象的引用。

第二个参数在Linux 2.6.30以后的版本没有再使用了,所以再使用该函数的时候必须置0.

函数执行成功返回值为一个新的eventfd文件描述符,否则为-1;

二、函数说明

        无论什么情况下,如果仅仅是用于发出信号事件,应用程序可以使用一个eventfd文件描述符来替换管道。而且内核使用eventfd文件描述符的开销要比管道低得多,而且一个eventfd文件描述符就可以满足要求。

        当我们在kernel中使用eventfd时,一个eventfd文件描述符就是一个由内核通向用户空间的桥梁。像KAIO(Kernel AIO)一样,向文件描述符发送信号以表示某些操作的完成。

        还有一个关键点就是,eventfd文件描述符与其他文件描述符一样,可以被select、poll、epoll监听。这就意味着一个应用程序可以同时监听传统文件是否就绪,也可以监听kernel所支持的eventfd接口文件。

        当前的eventfd计数器可以通过进程的/proc/[pid]/fdinfo来查看。

2.1 C库和kernel中实现的区别

有两个基础Linux系统调用,一个是eventfd(),一个是eventfd2()。前面一个系统调用函数的没有实现flags参数,后面一个系统调用函数则有对flags进行了实现。

2.2 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。

三、使用例子

3.1 代码案例

       #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");
           }
       }

        这个例子只是官方给我们的如何使用eventfd的一个例子,并不涉及到user space和kernel space之间的事件通信。因此我们还需要继续调研如使用eventfd来实现kerne向user space发送事件消息。来感受eventfd真正的魅力。

3.2 案例编译执行

编译命令如下:

gcc eventfd_test.c -std=c99 -o eventfd_test

执行过程如下:


root@DESKTOP-TTJOEDD:~/workspace/01_code/00_demo# gcc eventfd_test.c -std=c99 -o eventfd_test
root@DESKTOP-TTJOEDD:~/workspace/01_code/00_demo# ls
eventfd_test  eventfd_test.c
root@DESKTOP-TTJOEDD:~/workspace/01_code/00_demo# ./eventfd_test 1 3 5 7 9
Child writing 1 to efd
Child writing 3 to efd
Child writing 5 to efd
Child writing 7 to efd
Child writing 9 to efd
Child completed write loop
Parent about to read
Parent read 25 (0x19) from efd

四、再来说说evenfd

        前半篇读起来或许有些生硬。实际上可以把eventfd理解为是Linux内核为用户空间应用程序提供了一种信号量机制,但相较于传统POSIX信号量的优势是,eventfd在内核中以文件形式存在,可以用于select/epoll监听以达到异步的目的,避免在没有事件时发生阻塞。

        我们再看下具体的使用方法。

4.1 read

读取计数器的值。

  • 如果计数器中的值大于0
    • 设置了EFD_SEMAPHORE标志位,则返回1,且计数器中的值减去1.
    • 没有设置EFD_SEMAPHORE标志位,则返回计数器中的值,且计数器设置为0.
  • 如果计数器中的值为0
    • 设置了EFD_NONBLOCK标志位就直接返回-1.
    • 没有设置EFD_NONBLOCK标志位就会一直阻塞直到计数器中的值大于0.

4.2 write

向计数器中写入值。

  • 如果写入的值和小于0xFFFFFFFFFFFFFFFE,则写入成功。
  • 如果写入的值和大于0xFFFFFFFFFFFFFFFE
    • 设置了EFD_NONBLOCK标志位就直接返回-1
    • 如果没有设置EFD_NONBLOCK标志位,则会一直阻塞直到read操作执行。

4.3 IO多路复用

epoll()/poll()/select(): 支持 IO 多路复用操作

4.4 close

关闭文件描述符

在下一篇博客中我们再来聊一聊,kernel如何使用eventfd向user space发送消息事件。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在 Linux 系统中,eventfd 可以通过事件循环机制实现。事件循环机制是一个通用的机制,可以用于管理多种类型的事件和文件描述符,包括 socket、pipe、timer 等。它的核心是一个等待事件发生的循环,当有事件发生时,循环将调用相应的处理函数进行处理。 eventfd 本质上是一个文件描述符,它可以被添加到事件循环中进行监控。在 Linux 系统中,事件循环机制通过 epoll 系统调用来实现。 下面是一个使用事件循环机制实现的 eventfd 示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/epoll.h> #include <sys/eventfd.h> #define MAX_EVENTS 10 int main() { int efd, nfds, i; struct epoll_event ev, events[MAX_EVENTS]; uint64_t count = 0; // 创建 eventfd 文件描述符 efd = eventfd(0, EFD_NONBLOCK); if (efd == -1) { perror("eventfd"); exit(EXIT_FAILURE); } // 创建 epoll 实例 int epfd = epoll_create1(0); if (epfd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } // 将 eventfd 添加到 epoll 监控列表中 ev.events = EPOLLIN | EPOLLET; ev.data.fd = efd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &ev) == -1) { perror("epoll_ctl: efd"); exit(EXIT_FAILURE); } // 等待事件发生 while (1) { nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } // 处理所有事件 for (i = 0; i < nfds; i++) { if (events[i].data.fd == efd) { // 读取 eventfd 中的计数器值 if (read(efd, &count, sizeof(uint64_t)) == -1) { perror("read"); exit(EXIT_FAILURE); } printf("eventfd count: %llu\n", (unsigned long long) count); } } } close(efd); return 0; } ``` 在上面的代码中,首先创建了一个 eventfd 文件描述符,并将其添加到 epoll 实例中进行监控。然后,进入一个无限循环,等待事件的发生。当有事件发生时,通过 epoll_wait 函数获取事件,然后处理所有事件。如果是 eventfd 文件描述符的事件,则读取其中的计数器值,并进行处理。 需要注意的是,在使用 eventfd 时,必须将其设置为非阻塞模式(EFD_NONBLOCK),否则可能会导致阻塞进程。此外,eventfd 的计数器值必须是一个 64 位的无符号整数,因此在读取时需要使用 uint64_t 类型进行存储。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高桐@BILL

分享快乐,快乐分享...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值