eventfd 介绍
Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的。
接口
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);
这个函数会创建一个事件对象(eventfd object),返回一个文件描述符,用来实现进程或线程间的等待/通知(wait/notify)机制。内核为这个对象维护了一个无符号的64位整形计数器 counter,用第一个参数(initval)初始化这个计数器,创建时一般可将其设为0,后面有例子测试这个参数产生的效果。
flags 可以使用三个宏:
- EFD_CLOEXEC:给这个新的文件描述符设置 FD_CLOEXEC 标志,即 close-on-exec,这样在调用 exec 后会自动关闭文件描述符。因为通常执行另一个程序后,会用全新的程序替换子进程的正文,数据,堆和栈等,原来的文件描述符变量也不存在了,这样就没法关闭没用的文件描述符了。
- EFD_NONBLOCK:设置文件描述符为非阻塞的,设置了这个标志后,如果没有数据可读,就返回一个 EAGAIN 错误,不会一直阻塞。
- EFD_SEMAPHORE:不了解。
具体例子
1.我们可以看个父子进程间通信的例子,如下:
/*
* @filename: eventfd.c
* @author: Tanswer
* @date: 2018年01月08日 22:04:46
* @description:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.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, i;
uint64_t u;
ssize_t rc;
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(i=1; i<argc; i++){
printf("Child writing %s to efd\n",argv[i]);
u = atoll(argv[i]);
rc = write(efd, &u, sizeof(uint64_t));
if(rc != sizeof(uint64_t))
handle_error("write");
}
printf("Child completed write loop\n");
exit(EXIT_SUCCESS);
default:
sleep(2);
printf("Parent about to read\n");
rc = read(efd, &u, sizeof(uint64_t));
if(rc != sizeof(uint64_t))
handle_error("read");
printf("Parent read %llu from efd\n",(unsigned long long)u);
case -1:
handle_error("fork");
}
return 0;
}
父进程 sleep(2) ,保证子进程向 eventfd 连续写入, 然后父进程从 eventfd 中读取。
2.再看个线程间唤醒的例子
/*
* @filename: eventfd_pthread.c
* @author: Tanswer
* @date: 2018年01月08日 22:46:38
* @description:
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <pthread.h>
#include <unistd.h>
int efd;
void *threadFunc()
{
uint64_t buffer;
int rc;
while(1){
rc = read(efd, &buffer, sizeof(buffer));
if(rc == 8){
printf("notify success\n");
}
printf("rc = %llu, buffer = %lu\n",(unsigned long long)rc, buffer);
}//end while
}
int main()
{
pthread_t tid;
int rc;
uint64_t buf = 1;
efd = eventfd(0,0); // blocking
if(efd == -1){
perror("eventfd");
}
//create thread
if(pthread_create(&tid, NULL, threadFunc, NULL) < 0){
perror("pthread_create");
}
while(1){
rc = write(efd, &buf, sizeof(buf));
if(rc != 8){
perror("write");
}
sleep(2);
}//end while
close(efd);
return 0;
}
下面我们改下 initval 参数,设为 3,即efd = eventfd(3,0)
,其他代码不变,运行程序结果如下:
可以看到我们改变initval 这个计数器的初始值,只会影响第一次读到的 buffer 的值,后面都还是 1。
下面我们把 main 函数的 buf 设为 0,即 counter 每次不变,initval 还是设为 3,看下。
uint64_t buf = 0;
efd = eventfd(3,0); // blocking
if(efd == -1){
perror("eventfd");
}
运行效果如下:
可以看到唤醒了一次,然后就一直阻塞了。
从上面可以看出来,eventfd 支持三种操作:read、write、close。
read 返回值的情况如下:
- 读取 8 字节值,如果当前 counter > 0,那么返回 counter 值,并重置 counter 为 0。
- 如果调用 read 时 counter 为 0,那么 1)阻塞直到 counter 大于 0;2)非阻塞,直接返回 -1,并设置 errno 为 EAGAIN。如果 buffer 的长度小于 8 字节,那么 read 会失败,并设置 errno 为 EINVAL。
- 可以看出来 eventfd 只允许一次 read,对应两种状态:0和非0。下面看下 write。
write :
- 写入一个 64 bit(8字节)的整数 value 到 eventfd。
- 返回值:counter 最大能存储的值是 0xffff ffff ffff fffe,write 尝试将 value 加到 counter 上,如果结果超过了 max,那么 write 一直阻塞直到 read 操作发生,或者返回 -1 并设置 errno 为 EAGAIN。
可多次 write,一次 read。close 就是关掉 fd。
以上大概就是我了解的 eventfd,它相比于 pipe来说,少用了一个文件描述符,而且不必管理缓冲区,单纯的事件通知的话,方便很多(它的名字就叫做 eventfd),它可以和事件通知机制很好的融合。