eventfd 传递事件

文章介绍了Linux中的eventfd机制,它是一种用于进程间以及用户态与内核态通信的文件描述符。eventfd以计数器为基础,write增加计数,read清零计数。它常用于多生产者单消费者场景,并且可以与epoll等IO多路复用机制配合使用。文章还提到了epoll的适用场景和poll的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、是什么

Linux 一切皆文件,但这个文件 fd 也是有类型的,文件 fd,socket fd, pipe fd,epollfd,timerfd ,还有一种叫做 eventfd 的事件 fd 类型,Linux 2.6.22版本引入 ,代码 fs/eventfd.c, 头文件 #include <sys/eventfd.h>

eventfd 不仅可以用于进程间的通信,还能用于用户态和内核态的通信。

eventfd 是一个计数相关的fd。计数不为零是有可读事件发生,read 之后计数会清零,write 则会递增计数器。

eventfd 对应的文件内容是一个 uint64_t, 8 字节的数字,这个数字是 read/write 操作维护的计数。

int eventfd(unsigned int initval, int flags);// initval :初始化计数,flags:EFD_CLOEXEC:表示返回的eventfd文件描述符在fork后exec其他程序时会自动关闭这个文件描述符;EFD_NONBLOCK:设置返回的eventfd非阻塞;EFD_SEMAPHORE表示将eventfd作为一个信号量来使用

static const struct file_operations eventfd_fops = {
         .release = eventfd_release,
         .poll = eventfd_poll,
         .read = eventfd_read,
         .write = eventfd_write,
         .llseek = noop_llseek }

write 的时候,累加计数(写多少累加多少),read 的时候读取计数,并且清零。

二、怎么用

API :

  • read(): 读取 count 值后置 0。如果设置 EFD_SEMAPHORE,读到的值为 1,同时 count 值递减 1。
  • write(): 其实是执行 add 操作,累加 count 值。
  • epoll()/poll()/select(): 支持 IO 多路复用操作。
  • close(): 关闭文件描述符,eventfd 对象引用计数减 1,若减为 0,则释放 eventfd 对象资源。
uint64_t u = 0;

//int efd = eventfd(0, 0);

int efd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

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

size_t n = write(efd, &u, sizeof(uint64_t));

size_t n = read(efd, &u, sizeof(uint64_t));

// 写 3 次
write(efd, &u /* u = 1 */ , 8)
write(efd, &u /* u = 2 */ , 8)
write(efd, &u /* u = 3 */ , 8)

read(efd, &x, 8); //x=1+2+3=6

read(efd, &x, 8); //x=0, 读完清0

使用场景:

一个消费者和多个生产者,这种就可以借助 eventfd 优雅的完成事件通知。

在 pipe 仅用于发出事件信号的所有情况下,都可以使用 eventfd 取而代之。

三、poll

不是所有的 fd 类型都可用 epoll 池来监听事件的,只有实现了 file_operation->poll 的调用的“文件” fd 才能被 epoll 管理。

epoll 池则是专门用来管理事件的池子,eventfd 是专门用来传递事件的 fd。

socket fd:可以写入发送数据,触发可写事件。数据来了,可以读,触发可读事件;

文件 fd:文件 fd 的可读可写事件就更有意思了,因为文件一直是可写的,所以一直都触发可写事件,文件里的数据也一直是可读的,所以一直触发可读事件。这个也是为什么类似 ext4 这种文件系统不实现 poll 接口的原因。因为文件 fd 一直是可读可写的,poll 监听没有任何意义

eventfd 实现的是计数的功能,所以 eventfd 计数不为 0 ,那么 fd 是可读的,如果计数器的值为 0,read的时候就会阻塞。可以设置 fd 的属性为非阻塞类型,这样读的时候,如果计数器为 0 ,返回 EAGAIN 即可。

eventfd 如果用 epoll 监听事件,那么都是监听读事件,因为监听写事件无意义,因为 eventfd 一直可写(可以一直累计计数),一直有可写事件。

int CanRead( int timeout) {

    auto fd = eventfd(1,EFD_NONBLOCK);

    FD_SET fds;
    FD_ZERO(&fds);
    TIMEVAL timeval;
    timeval.tv_sec = timeout / 1000;
    timeval.tv_usec = (timeout % 1000) * 1000;

    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    int rc = select(0, &fds, NULL, NULL, timeout >= 0 ? &timeval : NULL);
     
    if (rc > 0 && FD_ISSET(fd, &fds) > 0) return 0;    

    if (rc == 0) return OS_SOCK_TIMEOUT;】

    return OS_SOCK_GENERAL_ERROR;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tangcpp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值