让事件飞 ——Linux eventfd 原理与实践
原文作者:杨阳
![](https://ask.qcloudimg.com/http-save/yehe-1408376/3zox727j5s.jpeg?imageView2/2/w/1620)
1. eventfd/timerfd 简介
目前越来越多的应用程序采用事件驱动的方式实现功能,如何高效地利用系统资源实现通知的管理和送达就愈发变得重要起来。在Linux系统中,eventfd是一个用来通知事件的文件描述符,timerfd是的定时器事件的文件描述符。二者都是内核向用户空间的应用发送通知的机制,可以有效地被用来实现用户空间的事件/通知驱动的应用程序。
简而言之,就是eventfd用来触发事件通知,timerfd用来触发将来的事件通知。
开发者使用eventfd相关的系统调用,需要包含头文件;对于timerfd,则是。
系统调用eventfd/timerfd自linux 2.6.22版本加入内核,由Davide Libenzi最初实现和维护。
2. 接口及参数介绍
eventfd
对于eventfd,只有一个系统调用接口
1int eventfd(unsigned int initval, int flags);
创建一个eventfd对象,或者说打开一个eventfd的文件,类似普通文件的open操作。
该对象是一个内核维护的无符号的64位整型计数器。初始化为initval的值。
flags可以以下三个标志位的OR结果:
- EFD_CLOEXEC:FD_CLOEXEC,简单说就是fork子进程时不继承,对于多线程的程序设上这个值不会有错的。
- EFD_NONBLOCK:文件会被设置成O_NONBLOCK,一般要设置。
- EFD_SEMAPHORE:(2.6.30以后支持)支持semophore语义的read,简单说就值递减1。
这个新建的fd的操作很简单:
read(): 读操作就是将counter值置0,如果是semophore就减1。
write(): 设置counter的值。
注意,还支持epoll/poll/select操作,当然,以及每种fd都都必须实现的close。
timerfd
对于timerfd,有三个涉及的系统调用接口
1int timerfd_create(int clockid, int flags);int timerfd_settime(int fd, int flags,
2 const struct itimerspec *new_value,
3 struct itimerspec *old_value);int timerfd_gettime(int fd, struct itimerspec *curr_value);
timerfd_create就是用来创建新的timerfd对象,clockid可以指定时钟的种类,比较常用的有两种:CLOCK_REALTIME(实时时钟)或 CLOCK_MONOTONIC(单调递增时钟)。实时时钟是指系统的时钟,它可以被手工修改。而后者单调递增时钟则是不会被系统时钟的人为设置的不连续所影响的。通常选择后者。而flags的选择,TFD_CLOEXEC和TFD_NONBLOCK的意义就比较直接了。
timerfd_settime函数用来设置定时器的过期时间expiration。itmerspec结构定义如下:
1struct timespec {
2 time_t tv_sec; /* Seconds */
3 long tv_nsec; /* Nanoseconds */};struct itimerspec {
4 struct timespec it_interval; /* Interval for periodic timer */
5 struct timespec it_value; /* Initial expiration */};
该结构包含两个时间间隔:it_value是指第一次过期时间,it_interval是指第一次到期之后的周期性触发到期的间隔时间,(设为0的话就是到期第一次)。
old_value如果不为NULL,将会用调用时间来更新old_value所指的itimerspec结构对象。
timerfd_gettime():返回当前timerfd对象的设置值到curr_value指针所指的对象。
read():读操作的语义是:如果定时器到期了,返回到期的次数,结果存在一个8字节的整数(uint64_6);如果没有到期,则阻塞至到期,或返回EAGAIN(取决于是否设置了NONBLOCK)。
另外,支持epoll,同eventfd。
3. 使用实例 - 实现高性能消费者线程池
生产者-消费者设计模式是常见的后台架构模式。本实例将实现多个生产者和多个消费者的事件通知框架,用以阐释eventfd/timerfd在线程通信中作为通知实现的典型场景。
本实例采用以下设计:生产者创建eventfd/timerfd并在事件循环中注册事件;消费者线程池中的线程共用一个epoll对象,每个消费者线程并行地进行针对eventfd或timerfd触发的事件循环的轮询(epoll_wait)。
eventfd对应实现
1typedef struct thread_info {
2 pthread_t thread_id;
3 int rank;
4 int epfd;} thread_info_t;static void *consumer_routine(void *data) {
5 struct thread_info *c = (struct thread_info *)data;
6 struct epoll_event *events;
7 int epfd = c->epfd;
8 int nfds = -1;
9 int i = -1;
10 uint64_t result;
11
12 log("Greetings from [consumer-%d]", c->rank);
13 events = calloc(MAX_EVENTS_SIZE, sizeof(struct epoll_event));
14 if (events == NULL) handle_error("calloc epoll events\n");
15
16 for (;;) {
17 nfds = epoll_wait(epfd, events, MAX_EVENTS_SIZE, 1000); // poll every second
18 for (i = 0; i < nfds; i++) {
19 if (events[i].events & EPOLLIN) {
20 log("[consumer-%d] got event from fd-%d", c->rank, events[i].data.fd);
21 // consume events (reset eventfd)
22 read(events[i].data.fd, &result, sizeof(uint64_t));
23 close(events[i].data.fd); // NOTE: need to close here
24 }
25 }
26 }}static void *producer_routine(void *data) {
27 struct thread_info *p = (struct thread_info *)data;
28 struct epoll_event event;
29 int epfd = p->epfd;
30 int efd = -1;
31 int ret = -1;
32
33 log("Greetings from [producer-%d]", p->rank);
34 while (1) {
35 sleep(1);
36 // create eventfd (no reuse, create new every time)
37 efd = eventfd(1, EFD_CLOEXEC|EFD_NONBLOCK);
38 if (efd == -1) handle_error("eventfd create: %s", strerror(errno));
39 // register to poller
40 event.data.fd = efd