1.新内核版本为什么要增加eventfd?
首先说明的一点是eventfd是用来实现多进程或多线程的之间的事件通知的,那么我们在没接触eventfd之前用到的事件通知机制都有那些?
1.条件变量
2.管道
我们来逐一比较此俩中机制与eventfd的效果方面的好坏,首先,条件变量必须和互斥锁结合使用,使用起来麻烦,而且性能未必比eventfd好,其次条件变量不能像eventfd一样为I/O事件驱动,因此不能和服务器的I/O模式很好的融合,所以在某些时候不如eventfd好用
接着是管道,虽然管道能与I/O复用很好的融合,但很明显管道相比eventfd多用了一个文件描述符,而且管道的话内核还得给其管理的缓冲区,eventfd则不需要,所以单纯作为事件通知的话还是管道好用
2.event的主要接口
eventfd只有一个接口,形式如下
int eventfd(unsigned int initval, int flags);
//成功返回事件驱动的文件描述符
eventfd()创建一个文件描述符,这个文件描述符用户可以通过等待其可读来实现事件通知,该通知靠内核来响应用户空间的应用事件。上述接口的第一个参数是一个由内核来保持的64位计数器,这个计数器有参数initval来初始化,关于此计数器的影响我在下文中的具体实例中给大家演示,一般我们可将其设为0
第二个参数flags可以为EFD_NONBLOCK或EFD_CLOEXEC,其含义分别为阻塞文件描述符,和与普通文件的CLOEXEC标志一样的功能(这里不想详细将CLOEXEC有兴趣的我之前写文件的基本操作的博文里有它的详细解释)
3.具体实例
#include <sys/eventfd.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int fd;
uint64_t buffer;
void threadFunc(void) //线程函数
{
int t;
while(1)
{
t = read(fd,&buffer,sizeof(buffer)); //阻塞等待fd可读,及通知事件发生
if(sizeof(buffer) < 8)
{
printf("buffer错误\n");
}
printf("t = %llu buffer = %llu\n",t,buffer);
if(t == 8)
{
printf("唤醒成功\n");
}
}
}
int main(void)
{
uint64_t buf = 1;
int ret;
pthread_t tid;
if((fd = eventfd(0,0)) == -1) //创建事件驱动的文件描述符
{
printf("创建失败\n");
}
//创建线程
if(pthread_create(&tid,NULL,threadFunc,NULL) < 0)
{
printf("线程创建失败\n");
}
while(1)
{
ret = write(fd,&buf,sizeof(buf)); //通过往fd里写东西来进行事件通知
if(ret != 8)
{
printf("写错误\n");
}
sleep(2); //没2s通知一次
}
return 0;
}
上述代码中,我们创建一个线程,通过主线程往fd里写数据,来通知另一个线程。
代码执行结果如下
关于eventfd中的initval参数的作用,我的测试结果如下,前提把上述代码eventfd函数中的0改为3
可以看出这个由内核管理的计数器,我们的初始化值,只会影响程序第一次buffer的值,后续buffer中的值依然为1.