浅谈eventfd

函数定义和头文件

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

简述

eventfd()函数会创建一个“eventfd”对象,用户空间的应用程序可以用这个eventfd来实现事件的等待或通知机制,也可以用于内核通知新的事件到用户空间应用程序。 这个对象包含一个64-bit的整形计数器,内核空间维护这个计数器,创建这个计数器的时候使用第一个入参initval来初始化计数器。

函数的第二个参数通过bit mask标识,可以有以下标志位

  • EFD_CLOEXEC
  • EFD_NONBLOCK
  • EFD_SEMAPHORE: 提供类似于信号量的机制

eventfd函数返回一个新的文件描述符(fd),指向新创建的eventfd对象,可以对这个描述符进行如下操作

  1. 文件描述符的read(2)操作
    每次成功的操作都返回一个8字节的整数
  • 如果没有设置EFD_SEMAPHORE,并且eventfd计数器非0,返回计数器的值(8字节),eventfd置为0
  • 如果设置了EFD_SEMAPHORE,并且eventfd计数器非0,返回值为1(8字节),计数器的值递减1
  • 如果eventfd计数器为0,那么read(2)操作会阻塞直到计数器非0;但是如果文件描述符设置了NONBLOCK,那么会返回一个错误(EAGAIN)
  1. 文件描述符的write(2)操作
    write操作会往eventfd计数器上累加一个8字节的整数值。
  2. 文件描述符的poll(2)/select(2)操作
    返回的file descriptor支持poll(2)操作和select(2)操作。
    拿poll()操作为例,它会监控一组文件描述符,来判断是否可以对这个文件描述符进行I/O操作。这组文件描述符通过如下的结构体格式来指定.
struct pollfd {
  int fd;            //文件描述符
  short events;  // 请求的事件,例如POLLIN
  short revents;  // 返回的事件
};
  1. 文件描述符的close(2)操作
    当文件描述符不再需要时,需要close这个文件描述符,当eventfd对象相关的所有的文件描述符都被关了,这个eventfd对象会被内核释放。

NOTES

如果只是使用管道pipe来进行事件通知,那么应用程序可以用eventfd文件描述符来替换pipe。使用eventfd值需要一个文件描述符,而pipe则需要两个。
eventfd的一个关键的特点是,它可以像其他的文件描述符一样,使用select(),poll()等函数来监控。利用poll函数可以进行I/O的复用。

Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的。

这个函数会创建一个事件对象(eventfd object),返回一个文件描述符,用来实现进程或线程间的等待/通知(wait/notify)机制。内核为这个对象维护了一个无符号的64位整形计数器 counter,用第一个参数(initval)初始化这个计数器,创建时一般可将其设为0,后面有例子测试这个参数产生的效果。

具体例子

1.我们可以看个父子进程间通信的例子,如下:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <stdint.h>         /* Definition of uint64_t */

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)

int main(int argc, char **argv)
{
    int efd, i;

    uint64_t u;
    ssize_t rc;

    if(argc < 2){
        fprintf(stderr, "Usage: %s <num>...\n",argv[0]);
        exit(EXIT_FAILURE);
    }


    efd = eventfd(0,0);
    if(efd == -1)
        handle_error("eventfd");

    switch(fork()){
        case 0:
            for(i=1; i<argc; i++){
                printf("Child writing %s to efd\n",argv[i]);
                u = atoll(argv[i]);
                rc = write(efd, &u, sizeof(uint64_t));
                if(rc != sizeof(uint64_t))
                    handle_error("write");
            }
            printf("Child completed write loop\n");

            exit(EXIT_SUCCESS);
        default:
            sleep(2);

            printf("Parent about to read\n");
            rc = read(efd, &u, sizeof(uint64_t));
            if(rc != sizeof(uint64_t))
                handle_error("read");

            printf("Parent read %llu from efd\n",(unsigned long long)u);
        case -1:
            handle_error("fork");
    }
    return 0;
}

这里写图片描述

父进程 sleep(2) ,保证子进程向 eventfd 连续写入, 然后父进程从 eventfd 中读取。

2.再看个线程间唤醒的例子

#include <stdio.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <pthread.h>
#include <unistd.h>

int efd;

void *threadFunc()
{
    uint64_t buffer;
    int rc;
    while(1){
        rc = read(efd, &buffer, sizeof(buffer));

        if(rc == 8){
            printf("notify success\n");
        }

        printf("rc = %llu, buffer = %lu\n",(unsigned long long)rc, buffer);
    }//end while
}

int main()
{
    pthread_t tid;
    int rc;
    uint64_t buf = 1;

    efd = eventfd(0,0);     // blocking阻塞等待
    if(efd == -1){
        perror("eventfd");
    }

    //create thread
    if(pthread_create(&tid, NULL, threadFunc, NULL) < 0){
        perror("pthread_create");
    }

    while(1){
        rc = write(efd, &buf, sizeof(buf));

        if(rc != 8){
            perror("write");
        }
        sleep(2);
    }//end while
    close(efd);
    return 0;
}

这里写图片描述

下面我们改下 initval 参数,设为 3,即efd = eventfd(3,0),其他代码不变,运行程序结果如下:

这里写图片描述

可以看到我们改变initval 这个计数器的初始值,只会影响第一次读到的 buffer 的值,后面都还是 1。

下面我们把 main 函数的 buf 设为 0,即 counter 每次不变,initval 还是设为 3,看下。

    uint64_t buf = 0;

    efd = eventfd(3,0);     // blocking
    if(efd == -1){
        perror("eventfd");
    }

运行效果如下:

这里写图片描述

可以看到唤醒了一次,然后就一直阻塞了。

从上面可以看出来,eventfd 支持三种操作:read、write、close。

read 返回值的情况如下:

读取 8 字节值,如果当前 counter > 0,那么返回 counter 值,并重置 counter 为 0。 如果调用 read 时 counter 为 0,那么 1)阻塞直到 counter 大于 0;2)非阻塞,直接返回 -1,并设置 errno 为 EAGAIN。如果 buffer 的长度小于 8 字节,那么 read 会失败,并设置 errno 为 EINVAL。 可以看出来 eventfd 只允许一次 read,对应两种状态:0和非0。下面看下 write。

write :

写入一个 64 bit(8字节)的整数 value 到 eventfd。 返回值:counter 最大能存储的值是 0xffff ffff ffff fffe,write 尝试将 value 加到 counter 上,如果结果超过了 max,那么 write 一直阻塞直到 read 操作发生,或者返回 -1 并设置 errno 为 EAGAIN。

可多次 write,一次 read。close 就是关掉 fd。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值