Linux eventfd简介

linux eventfd简介

什么是eventfd

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

eventfd接口

创建eventfd

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

参数含义:
initval:创建eventfd时它所对应的64位计数器的初始值;
flags:eventfd文件描述符的标志,可由三种选项组成:EFD_CLOEXEC、EFD_NONBLOCK和EFD_SEMAPHORE。
EFD_CLOEXEC:表示返回的eventfd文件描述符在fork后exec其他程序时会自动关闭这个文件描述符;
EFD_NONBLOCK:设置返回的eventfd非阻塞;
EFD_SEMAPHORE表:表示将eventfd作为一个信号量来使用。

写eventfd

int eventfd_write(int fd, eventfd_t value); //向eventfd写入一个值
write(2)
       A write(2) call adds the 8-byte integer value supplied in its buffer to the counter.  The maximum value that may
       be stored in the counter is the largest unsigned 64-bit value minus 1 (i.e., 0xfffffffffffffffe).  If the  addi‐
       tion  would  cause the counter's value to exceed the maximum, then the write(2) either blocks until a read(2) is
       performed on the file descriptor, or fails with the error EAGAIN if the file descriptor has been made  nonblock‐
       ing.

       A write(2) fails with the error EINVAL if the size of the supplied buffer is less than 8 bytes, or if an attempt
       is made to write the value 0xffffffffffffffff.

poll(2), select(2) (and similar)
       The returned file descriptor supports poll(2) (and analogously epoll(7)) and select(2), as follows:

       *  The file descriptor is readable (the select(2) readfds argument; the poll(2) POLLIN flag) if the counter  has
          a value greater than 0.

       *  The file descriptor is writable (the select(2) writefds argument; the poll(2) POLLOUT flag) if it is possible
          to write a value of at least "1" without blocking.

       *  If an overflow of the counter value was detected, then select(2) indicates the file descriptor as being  both
          readable  and writable, and poll(2) returns a POLLERR event.  As noted above, write(2) can never overflow the
          counter.  However an overflow can occur if 2^64 eventfd "signal posts" were performed by the  KAIO  subsystem
          (theoretically  possible,  but  practically unlikely).  If an overflow has occurred, then read(2) will return
          that maximum uint64_t value (i.e., 0xffffffffffffffff).

       The eventfd file descriptor also supports the other file-descriptor multiplexing APIs: pselect(2) and ppoll(2).

从man eventfd上看write的描述,可以总结以下几点:

  • 在没有设置EFD_SEMAPHORE的情况下,write函数会将发送buf中的数据写入到eventfd对应的计数器中,最大只能写入0xffffffffffffffff,否则返回EINVAL错误;
  • 在设置了EFD_SEMAPHORE的情况下,write函数相当于是向计数器中进行“添加”,比如说计数器中的值原本是2,如果write了一个3,那么计数器中的值就变成了5。如果某一次write后,计数器中的值超过了0xfffffffffffffffe(64位最大值-1),那么write就会阻塞直到另一个进程/线程从eventfd中进行了read(如果write没有设置EFD_NONBLOCK),或者返回EAGAIN错误(如果write设置了EFD_NONBLOCK)。

读eventfd

int eventfd_read(int fd, eventfd_t *value); //从eventfd中读取一个值
read(2)
       Each successful read(2) returns an 8-byte integer.  A read(2) fails with the error EINVAL if  the  size  of  the
       supplied buffer is less than 8 bytes.

       The  value returned by read(2) is in host byte order—that is, the native byte order for integers on the host ma‐
       chine.

       The semantics of read(2) depend on whether the eventfd counter currently has a nonzero  value  and  whether  the
       EFD_SEMAPHORE flag was specified when creating the eventfd file descriptor:

       *  If  EFD_SEMAPHORE  was  not  specified  and the eventfd counter has a nonzero value, then a read(2) returns 8
          bytes containing that value, and the counter's value is reset to zero.

       *  If EFD_SEMAPHORE was specified and the eventfd counter has a nonzero value, then a read(2)  returns  8  bytes
          containing the value 1, and the counter's value is decremented by 1.

       *  If  the  eventfd  counter  is  zero at the time of the call to read(2), then the call either blocks until the
          counter becomes nonzero (at which time, the read(2) proceeds as described above) or fails with the error  EA‐
          GAIN if the file descriptor has been made nonblocking.
  • read函数会从eventfd对应的64位计数器中读取一个8字节的整型变量;
  • read函数设置的接收buf的大小不能低于8个字节,否则read函数会出错,errno为EINVAL;
  • read函数返回的值是按小端字节序的;
  • 如果eventfd设置了EFD_SEMAPHORE,那么每次read就会返回1,并且让eventfd对应的计数器减一;如果eventfd没有设置EFD_SEMAPHORE,那么每次read就会直接返回计数器中的数值,read之后计数器就会置0。不管是哪一种,当计数器为0时,如果继续read,那么read就会阻塞(如果eventfd没有设置EFD_NONBLOCK)或者返回EAGAIN错误(如果eventfd设置了EFD_NONBLOCK)。

eventfd使用

进程内通信

#include <iostream>
#include <sys/eventfd.h>

int main() {
    int efd = eventfd(0, 0);;
    if(-1 == efd){
        std::cout << "eventfd err" << std::endl;
    }

    uint64_t data = 11;
    eventfd_write(efd, data);
    std::cout << "write success" << std::endl;

    eventfd_read(efd, &data);
    std::cout << "read success data:" << data << std::endl;

    return 0;
}

运行结果:

write success
read success data:11

父子进程间通信

#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <stdio.h>
 
int main() {
	int efd = eventfd(10, 0);
	uint64_t wdata = 0;
	uint64_t rdata = 0;
 
	if (eventfd_read(efd, &rdata) == -1) {
		std::cout << "read err" << std::endl;
		return -1;
	}
	std::cout << "read: " << rdata << std::endl;
	
	wdata = 20;
	if (eventfd_write(efd, wdata) == -1) {
		std::cout << "write err" << std::endl;
		return -1;
	}
	std::cout<<"parent write: " << wdata << std::endl;
	
	if (fork() == 0) {
		wdata = 30;
		if (eventfd_read(efd, &rdata) == -1) {
            std::cout << "child read err" << std::endl;
            return -1;
		}
		std::cout << "child read: " << rdata << std::endl;

		if (eventfd_write(efd, wdata) == -1) {
            std::cout << "child write err" << std::endl;
            return -1;
		}
		std::cout << "child write: " << wdata << std::endl;
		exit(0);		
	}

	wait(nullptr);

	if(eventfd_read(efd, &rdata) == -1) {
        std::cout << "parent read err" << std::endl;
        return -1;
	}
	std::cout << "parent read:" << rdata << std::endl;
 
	return 0;
}

运行结果:

read: 10
parent write: 20
child read: 20
child write: 30
parent read:30

和poll/epoll搭配使用,一个消费者多生产者场景

实际这种使用场景会多一点,生产者是多个线程,可以通过eventfd_write唤醒消费者。消费者是单个线程,后台 loop 处理。使用 epoll 监听 eventfd 的可读事件,这样能做到一旦有请求入队,消费者就立马唤醒处理。所以这里可以总结出eventfd在实际场景中可以结合业务,做一个事件通知的通信机制,非常巧妙,而不用轮询这种耗时耗cpu的机制。这块就不详细写示例了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值