一、是什么
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;
}