在阅读fdbus源码的过程中涉及到了eventfd,这块不是很熟悉,特此记录一下。

注意:eventfd只有在linux下才有意义,在windows下不存在。

在linux下一切皆文件,每个文件都都对应一个fd(file descriptor文件描述符),要理解eventfd,就需要对fd的类型有一个认识,fd也是有类型的,我们都知道socket fd,也知道pipe fd,timer fd,同样也有eventfd这样一种类型。

linux引入eventfd时间

Linux 2.6.22

eventfd基本概念

eventfd是专门用于事件通知的文件描述符( fd )。它创建一个eventfd对象,eventfd对象不仅可以用于进程间的通信,还能用于用户态和内核态的通信。eventfd对象在内核中包含了一个计数器,该计数器是64位的无符号整数(uint64_t)计数不为零是有可读事件发生,read 之后计数会清零,write 则会递增计数器

eventfd作用

用于用户进程之间通信,也可以用于用户态和内核态之间的通信。

eventfd,顾名思义就是用来通信的,该fd对应一个文件,通过读取该文件中的值来传递是否有值,但是需要注意的是eventfd不能传递具体的内容,只能传递一个状态,即如果两个进程间需要传递具体数据,那么考虑使用eventfd是不合适的;若是类似生产者和消费者模型,即一个进程或者线程写入了向队列写入了数据,要通知消费者读取,那么就可以考虑使用eventfd。因为eventfd对应的文件仅能写入一个数值,无法传递复杂的数据信息。

一句话:eventfd仅用于事件通知,不能用于传递具体的消息内容。

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使用demo

/*
 * Copyright (C) 2015   Jeremy Chen jeremy_cz@yahoo.com
 *
 * 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 <errno.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <fdbus/CEventFd.h>

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;
}
}
}

eventfd与IO多路复用的关系

IO多路复用一般有poll select和epoll。这里以epoll为例,并不是所有的fd都可以利用epoll进行监听,只有实现了file_operation->poll的调用的文件fd才能被epoll管理

我们前面降到linux下fd有多种类型,例如socket fd, timerfd, pipefd,普通文件fd,eventfd等类型,并不是所有的文件类型都实现了poll接口,这里eventfd实现了该接口,如下所示:

故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");
		}
	}

	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介绍

10-09 15:58