eventfd是在linux kernel 2.6.22之后提供的,通过创建一个文件描述符来进行事件通知。可以用于应用程序之间的事件通知,也可用于kernel通知应用程序。
在linux多线程应用开发中,线程之间的通信,经常都是通过消息队列来完成的,当一个线程向队列中写入消息,然后通过pthread_cond_t条件变量来通知那些等待中的其他线程。有了eventfd object,我们就可以通过向eventfd object写入数据,这样就可以通知到那些等待中的线程。最重要的是,eventfd 可以很好的与 select/epoll结合,这样子可以大大提高线程的效率,不至于线程只能 wait unitl contiditon is ready. 在以前没有eventfd时,其实是可以通过管道来解决的,但是管道需要占用两个fd(一个读,一个写), eventfd object只占用一个fd。
1. 创建一个eventfd object: create a file descriptor for event notification
#include <sys/eventfd.h> int eventfd(unsigned int initval, int flags);
每一个eventfd object,包含有一个无符号的64位整数计数器,并且这个计数器的初始值为initval。
flags的取值有:
1) EFD_NONBLOCK: 设置为非阻塞
2) EFD_CLOEXEC: close on exec
3) EFD_SEMAPHORE: 在2.6.30中提供,如果设置了这个标志,则在读取上会与多线程中的信号量有些类似。
通过返回的文件描述符fd, 我们可以读写此eventfd object,直接通过read, write即可完成读写操作。
2. 读取eventfd
如果eventfd object 的计数器不为0, 则read() 返回 当前的计数值,并将计数器置为0。如果计数器已经为0,此时读取的话,则会被阻塞,直至计数器不为0时,若设置了EFD_NONBLOCK,则会立即返回,并置errno=EAGAIN。
若设置了EFD_SEMAPHORE, 如果eventfd object的计数器不为0,则read()返回1,同时计数器减1.
3. 写eventfd
写eventfd object,会将其计数器加上当前写入值。如果写入后的值超过了64位整数的最大值,则会阻塞,直至被读取,若设置了EFD_NONBLOCK,则立即返回,并置errno=EAGAIN。
4. 当我们不再使用eventfd object时,调用close将其关闭释放。
Note:
在读写eventfd object时,一定要用uint64_t,也就是大小必须是8字节的,否则会返回错误。
example: 采用EFD_NONBLOCK, 写线程每隔1秒写eventfd, main线程通过epoll监听eventfd是否可读,若可读,则读取eventfd的值,并将其打印。
static void *_write_thread(void *arg) { int efd = (int)arg; int i; int ret; uint64_t count; for(i = 0; i < 100; ++i){ count = i + 1; ret = write(efd, &count, sizeof(count)); if(ret <= 0){ ERR("write eventfd fail"); break; } struct timeval tv; gettimeofday(&tv, NULL); printf("write %d bytes(%llu) at %lds %ldus\n", ret, count, tv.tv_sec, tv.tv_usec); sleep(1); } return NULL; } int eventfd_test(void) { printf("** eventfd test:\n"); int efd = eventfd(0, EFD_NONBLOCK); assert(efd >= 0); epoll_fd_t epfd; assert(epoll_fd_create(&epfd, 1024) == 0); task_t *write_task = task_create("write_task", _write_thread, (void *)efd); assert(write_task); uint64_t count = 0; assert(epoll_fd_add(&epfd, efd, EPOLLIN, NULL) == 0); while(1){ int ret = epoll_fd_wait(&epfd, -1); if(ret <= 0){ ERR("epoll_fd_wait fail"); break; } if((ret = read(efd, &count, sizeof(count))) < 0){ ERR("read eventfd fail"); break; } struct timeval tv; gettimeofday(&tv, NULL); printf("read from efd success, read %d bytes(%llu) at %lds %ldus\n", ret, count, tv.tv_sec, tv.tv_usec); } task_destroy(&write_task, NULL, 1); epoll_fd_release(&epfd); close(efd); printf("** eventfd test success\n"); return 0; }
程序运行输出:
** eventfd test: read from efd success, read 8 bytes(1) at 1445153954s 567052us write 8 bytes(1) at 1445153954s 567052us read from efd success, read 8 bytes(2) at 1445153955s 567552us write 8 bytes(2) at 1445153955s 567552us read from efd success, read 8 bytes(3) at 1445153956s 568052us write 8 bytes(3) at 1445153956s 568052us read from efd success, read 8 bytes(4) at 1445153957s 568552us write 8 bytes(4) at 1445153957s 568552us read from efd success, read 8 bytes(5) at 1445153958s 569052us write 8 bytes(5) at 1445153958s 569052us read from efd success, read 8 bytes(6) at 1445153959s 569552us write 8 bytes(6) at 1445153959s 569552us read from efd success, read 8 bytes(7) at 1445153960s 570052us write 8 bytes(7) at 1445153960s 570052us read from efd success, read 8 bytes(8) at 1445153961s 570552us write 8 bytes(8) at 1445153961s 570552us read from efd success, read 8 bytes(9) at 1445153962s 571052us write 8 bytes(9) at 1445153962s 571052us read from efd success, read 8 bytes(10) at 1445153963s 571552us write 8 bytes(10) at 1445153963s 571552us read from efd success, read 8 bytes(11) at 1445153964s 572052us write 8 bytes(11) at 1445153964s 572052us read from efd success, read 8 bytes(12) at 1445153965s 572552us write 8 bytes(12) at 1445153965s 572552us read from efd success, read 8 bytes(13) at 1445153966s 573052us write 8 bytes(13) at 1445153966s 573052us .......
如果eventfd_create()时设置了EFD_SEMAPHORE时,程序输出结果如下:
** eventfd test: read from efd success, read 8 bytes(1) at 1445154897s 318192us write 8 bytes(1) at 1445154897s 322194us write 8 bytes(2) at 1445154898s 322694us read from efd success, read 8 bytes(1) at 1445154898s 322694us read from efd success, read 8 bytes(1) at 1445154898s 322694us read from efd success, read 8 bytes(1) at 1445154899s 327196us read from efd success, read 8 bytes(1) at 1445154899s 327196us read from efd success, read 8 bytes(1) at 1445154899s 327196us write 8 bytes(3) at 1445154899s 339202us write 8 bytes(4) at 1445154900s 339702us read from efd success, read 8 bytes(1) at 1445154900s 339702us read from efd success, read 8 bytes(1) at 1445154900s 339702us read from efd success, read 8 bytes(1) at 1445154900s 339702us read from efd success, read 8 bytes(1) at 1445154900s 339702us write 8 bytes(5) at 1445154901s 340202us read from efd success, read 8 bytes(1) at 1445154901s 340202us read from efd success, read 8 bytes(1) at 1445154901s 340202us read from efd success, read 8 bytes(1) at 1445154901s 340202us read from efd success, read 8 bytes(1) at 1445154901s 340202us read from efd success, read 8 bytes(1) at 1445154901s 340202us write 8 bytes(6) at 1445154902s 340702us read from efd success, read 8 bytes(1) at 1445154902s 340702us read from efd success, read 8 bytes(1) at 1445154902s 340702us read from efd success, read 8 bytes(1) at 1445154902s 340702us read from efd success, read 8 bytes(1) at 1445154902s 340702us read from efd success, read 8 bytes(1) at 1445154902s 340702us read from efd success, read 8 bytes(1) at 1445154902s 340702us write 8 bytes(7) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us read from efd success, read 8 bytes(1) at 1445154903s 341202us
因为写线程每次写入的数值都是递加的,但是此时main线程每次读取的值是1,所以会出现写1次数值,然后多次读取的情况。
在学习eventfd的时候,发现linux还提供了signalfd, timerfd。
signalfd: 把信号抽象为一个文件描述符,当有信号发生时,可以对其read, 可以与epoll/select结合,进行监听;
timerfd: 把定时器抽象为一个文件描述符,定时器到期时,可以对其read,所以也是可以通过epoll、select进行监听。
在下面的参考文档中都有详细的说明。
参考:
http://man7.org/linux/man-pages/man2/eventfd.2.html
http://man7.org/linux/man-pages/man2/signalfd.2.html
http://man7.org/linux/man-pages/man2/timerfd_create.2.html