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的机制。这块就不详细写示例了。