在linux下一切皆文件,每个文件都都对应一个fd(file descriptor文件描述符),要理解eventfd,就需要对fd的类型有一个认识,fd也是有类型的,我们都知道socket fd,也知道pipe fd,timer fd,同样也有eventfd这样一种类型。
eventfd基本概念
eventfd是专门用于事件通知的文件描述符( fd )。它创建一个eventfd对象,eventfd对象不仅可以用于进程间的通信,还能用于用户态和内核态的通信。eventfd对象在内核中包含了一个计数器,该计数器是64位的无符号整数(uint64_t),计数不为零是有可读事件发生,read 之后计数会清零,write 则会递增计数器。
eventfd作用
用于用户进程之间通信,也可以用于用户态和内核态之间的通信。
eventfd,顾名思义就是用来通信的,该fd对应一个文件,通过读取该文件中的值来传递是否有值,但是需要注意的是eventfd不能传递具体的内容,只能传递一个状态,即如果两个进程间需要传递具体数据,那么考虑使用eventfd是不合适的;若是类似生产者和消费者模型,即一个进程或者线程写入了向队列写入了数据,要通知消费者读取,那么就可以考虑使用eventfd。因为eventfd对应的文件仅能写入一个数值,无法传递复杂的数据信息。
一句话:eventfd仅用于事件通知,不能用于传递具体的消息内容。
划重点:eventfd 是一个计数相关的fd。计数不为零是有可读事件发生,read
之后计数会清零,write
则会递增计数器。
这个怎么理解?
eventfd 实现了 read/write 的调用,在调用里面实现了一套计数器的逻辑。write 仅仅是加计数,read 是读计数,并且清零。
长什么样子呢?笔者找了个进程来观摩下。
root@ubuntu:~# ll /proc/666/fd
lrwx------ 1 root root 64 Dec 12 22:12 3 -> anon_inode:[eventfd]
简单看下 eventfd 的读写究竟做了什么?
eventfd 对应的文件内容是一个 8 字节的数字,这个数字是 read/write 操作维护的计数。
首先,write 的时候,累加计数,read 的时候读取计数,并且清零。
uint64_t u;
ssize_t n;
// 写 eventfd,内部 buffer 必须是 8 字节大小;
n = write(efd, &u, sizeof(uint64_t));
// 读 eventfd
n = read(efd, &u, sizeof(uint64_t));
读写也就是 read/write,读写这个 fd 很容易理解,但是请注意了,只能 8 个字节。这个读写的内容其实是计数。
举个栗子:如下,我们连续写 3 次
// 写 3 次
write(efd, &u /* u = 1 */ , 8)
write(efd, &u /* u = 2 */ , 8)
write(efd, &u /* u = 3 */ , 8)
你猜猜读的时候,是多少?
read(ebd, &x, 8)
读到的值是 6(因为 1+2+3),理解了吧。
eventfd API接口
/* Return file descriptor for generic event channel. Set initial
value to COUNT. */
extern int eventfd (unsigned int __count, int __flags) __THROW; //创建eventfd api
/* Read event counter and possibly wait for events. */
extern int eventfd_read (int __fd, eventfd_t *__value); //读取eventfd
/* Increment event counter. */
extern int eventfd_write (int __fd, eventfd_t __value); //向eventfd写入数据
eventfd与IO多路复用的关系
IO多路复用一般有poll select和epoll。这里以epoll为例,并不是所有的fd都可以利用epoll进行监听,只有实现了file_operation->poll的调用的文件fd才能被epoll管理。
我们前面降到linux下fd有多种类型,例如socket fd, timerfd, pipefd,普通文件fd,eventfd等类型,并不是所有的文件类型都实现了poll接口,这里eventfd实现了该接口,如下所示:
static const struct file_operations eventfd_fops = {
#ifdef CONFIG_PROC_FS
.show_fdinfo = eventfd_show_fdinfo,
#endif
.release = eventfd_release,
.poll = eventfd_poll,
.read = eventfd_read,
.write = eventfd_write,
.llseek = noop_llseek,
};
故eventfd是接收epoll管理的,一般情况下我们只监听eventfd的可读事件。因为eventfd一直可写,监听它的可写事件没有意义。
如何利用epoll监听eventfd呢?demo如下所示:
#include <sys/eventfd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/epoll.h>
#include <string.h>
#include <pthread.h>
int g_iEvtfd = -1;
void *eventfd_child_Task(void *pArg)
{
//生产者调用write写一个64bit的整数value到eventfd即可
uint64_t uiWrite = 1;
while(1)
{
sleep(2);
if (0 != eventfd_write(g_iEvtfd, uiWrite))
{
printf("child write iEvtfd failed\n");
}
}
}
int main(int argc, char**argv[])
{
int iEvtfd, j;
uint64_t uiWrite = 1;
uint64_t uiRead;
ssize_t s;
int iEpfd;
struct epoll_event stEvent;
int iRet = 0;
struct epoll_event stEpEvent;
pthread_t stWthread;
iEpfd = epoll_create(1);
if (-1 == iEpfd)
{
printf("Create epoll failed.\n");
return 0;
}
iEvtfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (-1 == iEvtfd)
{
printf("failed to create eventfd\n");
return 0;
}
g_iEvtfd = iEvtfd;
memset(&stEvent, 0, sizeof(struct epoll_event));
stEvent.events = (unsigned long) EPOLLIN;
stEvent.data.fd = iEvtfd;
iRet = epoll_ctl(iEpfd, EPOLL_CTL_ADD, g_iEvtfd, &stEvent);
if (0 != iRet)
{
printf("failed to add iEvtfd to epoll\n");
close(g_iEvtfd);
close(iEpfd);
return -1;
}
iRet = pthread_create(&stWthread, NULL, eventfd_child_Task, NULL);
if (0 != iRet)
{
close(g_iEvtfd);
close(iEpfd);
return -1;
}
for(;;)
{
//1 -1 表示 epoll 数量,无限等待
iRet = epoll_wait(iEpfd, &stEpEvent, 1, -1);
if (iRet > 0)
{
s = eventfd_read(iEvtfd, &uiRead);
if (s != 0)
{
printf("read iEvtfd failed\n");
break;
}
printf("Read %lu (0x%lx) from iEvtfd\n", uiRead, uiRead);
}
}
close(g_iEvtfd);
close(iEpfd);
return 0;
}
编译运行:
gcc test_eventfd.c -lpthread -o test_eventfd
运行结果:
#$ ./test_eventfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd
Read 1 (0x1) from iEvtfd