linux之eventfd

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

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值