evendfd

 · 2020-2-20 · 次阅读


Linux进程间通信-eventfd

eventfd是linux 2.6.22后系统提供的一个轻量级的进程间通信的系统调用。eventfd类似于管道的概念,可以实现线程间的事件通知,类似于pipe。而eventfd 是一个比 pipe 更高效的线程间事件通知机制,一方面它比 pipe 少用一个 file descriper,节省了资源;另一方面,eventfd 的缓冲区管理也简单得多,全部“buffer”一共只有8字节,不像pipe那样可能有不定长的真正buffer。
eventfd的缓冲区大小是sizeof(uint64_t)也就是8字节,它是一个64位的计数器,写入递增计数器,读取将得到计数器的值,并且清零。eventfd通过一个进程间共享的64位计数器完成进程间通信,这个计数器由在linux内核空间维护,用户可以通过调用write方法向内核空间写入一个64位的值,也可以调用read方法读取这个值。

涉及API

#include <sys/eventfd.h>
//创建一个eventfd文件描述符
int eventfd(unsigned int initval, int flags);
//向eventfd中写入一个值
int eventfd_read(int fd, eventfd_t *value); 
//从eventfd中读出一个值
int eventfd_write(int fd, eventfd_t value); 

这个函数会创建一个事件对象 (eventfd object), 用来实现进程(线程)间的等待/通知(wait/notify) 机制. 内核会为这个对象维护一个64位的计数器(uint64_t)。
并且使用第一个参数(initval)初始化这个计数器。调用这个函数就会返回一个新的文件描述符(event object)。2.6.27版本开始可以按位设置第二个参数(flags)。
flags 有如下的一些宏可以使用:

EFD_NONBLOCK:功能同open(2) 的O_NONBLOCK,设置对象为非阻塞状态,如果没有设置这个状态的话,read(2)读eventfd,并且计数器的值为0 就一直堵塞在read调用当中,要是设置了这个标志, 就会返回一个 EAGAIN 错误(errno = EAGAIN)。效果也如同 额外调用select(2)达到的效果。

EFD_CLOEXEC:顾名思义是在执行 exec() 调用时关闭文件描述符,防止文件描述符泄漏给子进程。

如果是2.6.26或之前版本的内核,flags 必须设置为0。

操作方法

创建这个对象后,可以对其做如下操作。

write: 向计数器中写入值

  • 如果写入值的和小于0xFFFFFFFFFFFFFFFE,则写入成功
  • 如果写入值的和大于0xFFFFFFFFFFFFFFFE
    • 设置了EFD_NONBLOCK标志位就直接返回-1。
    • 如果没有设置EFD_NONBLOCK标志位,则会一直阻塞知道read操作执行

write 将缓冲区写入的8字节整形值加到内核计数器上。

read: 读取计数器中的值

  • 如果计数器中的值大于0
    • 设置了EFD_SEMAPHORE标志位,则返回1,且计数器中的值也减去1。
    • 没有设置EFD_SEMAPHORE标志位,则返回计数器中的值,且计数器置0。
  • 如果计数器中的值为0
    • 设置了EFD_NONBLOCK标志位就直接返回-1。
    • 没有设置EFD_NONBLOCK标志位就会一直阻塞直到计数器中的值大于0。

read 读取8字节值, 并把计数器重设为0. 如果调用read的时候计数器为0, 要是eventfd是阻塞的, read就一直阻塞在这里,否则就得到 一个EAGAIN错误。如果buffer的长度小于8那么read会失败, 错误代码被设置成 EINVAL。

close: 关闭文件描述符

close 当不需要eventfd的时候可以调用close关闭, 当这个对象的所有句柄都被关闭的时候,内核会释放资源。 为什么不是close就直接释放呢, 如果调用fork 创建进程的时候会复制这个句柄到新的进程,并继承所有的状态。

(ps:也就是说,在write之后没有read,但是又write新的数据,那么读取的是这两次的8个字节的和,在read之后再write,可以完成read和write之间的交互)

一个小Demo

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

int main() {
int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
eventfd_write(efd, 2);
eventfd_t count;
eventfd_read(efd, &count);
std::cout << count << std::endl;
close(efd);
}

上面的程序中我们创建了一个eventfd,并将它的文件描述符保存在efd中,然后调用eventfd_write向计数器中写入数字2,然后调用eventfd_read读取计数器中的值并打印处理,最后关闭eventfd。 运行结果:

count=2

稍微复杂一点的例子,使用epoll和eventfd:

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

#define EPOLL_MAX_NUM 10

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

int efd = -1;

void *read_thread(void *arg)
{
4int ret = 0;
4uint64_t count = 0;
4int ep_fd = -1;
4struct epoll_event events[EPOLL_MAX_NUM];

4if (efd < 0)
4{
44printf("efd not inited.\n");
44return;
4}

4ep_fd = epoll_create(1024);
4if (ep_fd < 0)
4{
44handle_error("epoll_create fail: ");
4}

4{
44struct epoll_event read_event;

44read_event.events = EPOLLIN;
44read_event.data.fd = efd; //add eventfd to epoll

44ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, efd, &read_event);
44if (ret < 0)
44{
444handle_error("epoll ctl failed:");
44}
4}

4while (1)
4{
44ret = epoll_wait(ep_fd, &events[0], EPOLL_MAX_NUM, -1);
44if (ret > 0)
44{
444int i = 0;
444for (; i < ret; i++)
444{ /*
if (events[i].events & EPOLLHUP)
{
printf("epoll eventfd has epoll hup.\n");
}
else if (events[i].events & EPOLLERR)
{
printf("epoll eventfd has epoll error.\n");
}
else */
4444 if (events[i].events & EPOLLIN)
4444{
44444int event_fd = events[i].data.fd;
44444ret = read(event_fd, &count, sizeof(count));
44444if (ret < 0)
44444{
444444handle_error("read fail:");
44444}
44444else
44444{
444444struct timeval tv;

444444gettimeofday(&tv, NULL);
444444printf("success read from efd, read %d bytes(%llu) at %lds %ldus\n",
4444444 ret, count, tv.tv_sec, tv.tv_usec);
44444}
4444}
444}
44}
44else if (ret == 0)
44{
444/* time out */
444printf("epoll wait timed out.\n");
444break;
44}
44else
44{
444handle_error("epoll wait error:");
44}
4}

}

int main(int argc, char *argv[])
{
4pthread_t pid = 0;
4uint64_t count = 0;
4int ret,i;

4efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
4if (efd < 0)
4{
44handle_error("eventfd failed.");
4}

4ret = pthread_create(&pid, NULL, read_thread, NULL);
4if (ret < 0)
4{
44handle_error("pthread create:");
4}

4for (i = 0; i < 5; i++)
4{
44count = 4;
44ret = write(efd, &count, sizeof(count));
44if (ret < 0)
44{
444handle_error("write event fd fail:");
44}

44sleep(1);
4}
4
4printf("write_end\n");
4
4pthread_join(pid, NULL);

4close(efd);
4return 0;
}

参考:

https://blog.csdn.net/majianfei1023/article/details/51199702

https://juejin.im/post/5ad60fef5188255cb32ea496