一起来感受下eventfd的魅力(一eventfd使用介绍)
Posted 高桐@BILL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起来感受下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发送消息事件。
以上是关于一起来感受下eventfd的魅力(一eventfd使用介绍)的主要内容,如果未能解决你的问题,请参考以下文章