在阅读fdbus源码的过程中涉及到了eventfd,这块不是很熟悉,特此记录一下。
注意:eventfd只有在linux下才有意义,在windows下不存在。
在linux下一切皆文件,每个文件都都对应一个fd(file descriptor文件描述符),要理解eventfd,就需要对fd的类型有一个认识,fd也是有类型的,我们都知道socket fd,也知道pipe fd,timer fd,同样也有eventfd这样一种类型。
Linux 2.6.22
eventfd是专门用于事件通知的文件描述符( fd )。它创建一个eventfd对象,eventfd对象不仅可以用于进程间的通信,还能用于用户态和内核态的通信。eventfd对象在内核中包含了一个计数器,该计数器是64位的无符号整数(uint64_t),计数不为零是有可读事件发生,read 之后计数会清零,write 则会递增计数器。
用于用户进程之间通信,也可以用于用户态和内核态之间的通信。
eventfd,顾名思义就是用来通信的,该fd对应一个文件,通过读取该文件中的值来传递是否有值,但是需要注意的是eventfd不能传递具体的内容,只能传递一个状态,即如果两个进程间需要传递具体数据,那么考虑使用eventfd是不合适的;若是类似生产者和消费者模型,即一个进程或者线程写入了向队列写入了数据,要通知消费者读取,那么就可以考虑使用eventfd。因为eventfd对应的文件仅能写入一个数值,无法传递复杂的数据信息。
一句话:eventfd仅用于事件通知,不能用于传递具体的消息内容。
/* 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写入数据
/*
* Copyright (C) 2015 Jeremy Chen [email protected]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include
#include
#include
#include
namespace ipc {
namespace fdbus {
CEventFd::CEventFd()
: mEventFd(-1)
{
}
CEventFd::~CEventFd()
{
}
bool CEventFd::create(int &fd)
{
bool ret = false;
mEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (mEventFd > 0)
{
ret = true;
fd = mEventFd;
}
return ret;
}
bool CEventFd::pickEvent()
{
eventfd_t val;
int err;
do
{
err = eventfd_read(mEventFd, &val);
}
while ((err < 0) && (errno == EINTR));
return (err >= 0) || (errno == EAGAIN);
}
bool CEventFd::triggerEvent()
{
int err;
do
{
err = eventfd_write(mEventFd, 1);
}
while ((err < 0) && (errno == EINTR));
return err >= 0;
}
}
}
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
#include
#include
#include
#include
#include
#include
#include
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");
}
}
return;
}
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,0);
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 0;
}
iRet = pthread_create(&stWthread, NULL, eventfd_child_Task, NULL);
if (0 != iRet)
{
close(g_iEvtfd);
close(iEpfd);
return;
}
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 %llu (0x%llx) from iEvtfd\n", uiRead, uiRead);
}
}
close(g_iEvtfd);
close(iEpfd);
return 0;
}
read eventfd 的时候,如果计数器的值为 0,就会阻塞(这种就等同于没“文件”内容)。
这种可以设置 fd 的属性为非阻塞类型,这样读的时候,如果计数器为 0 ,返回 EAGAIN 即可,这样就不会阻塞整个系统。代码如下所示:
mEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
默认情况下eventfd是阻塞的,如果要非阻塞,需要显式的调用EFD_NONBLOCK设置非阻塞。
eventfd介绍