Hey 老司机,知道eventfd吗?

    最近在学习Linux原生支持的异步IO机制(libaio)时又接触到了一个新伙伴 --- "eventfd", 不禁得再次感叹写内核的那帮哥们真的是这个世界上最富有创造力和想象力的一群人了。node

    eventfd()系统调用是在2.6.22版本中引入内核的,在随后的2.6.27版本中又增长了eventfd2(),后者是在前者的基础上增长了对flags参数的支持。在新的内核中eventfd()仍然保留,但它只是对eventfd2()的一个包装。数据结构

Kernel4.5  fs/eventfd.c
420 SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
421 {
422         int fd, error;
423         struct file *file;
424 
425         error = get_unused_fd_flags(flags & EFD_SHARED_FCNTL_FLAGS);
426         if (error < 0)
427                 return error;
428         fd = error;
429 
430         file = eventfd_file_create(count, flags);
431         if (IS_ERR(file)) {
432                 error = PTR_ERR(file);
433                 goto err_put_unused_fd;
434         }
435         fd_install(fd, file);
436 
437         return fd;
438 
439 err_put_unused_fd:
440         put_unused_fd(fd);
441 
442         return error;
443 }
444 
445 SYSCALL_DEFINE1(eventfd, unsigned int, count)
446 {
447         return sys_eventfd2(count, 0);
448 }

    按照man手册的说法,eventfd()的目的在于“建立一个用于进行事件通知的文件描述符”,从上面的系统调用实现也能够看到,eventfd()最终会返回一个文件描述符fd,并在内核中关联一个对应的struct file结构。这个struct file结构就是整个eventfd的精髓了,实际上它既不对应硬盘上的 一个真实文件,也不占用inode,它是对内存中一个数据结构的抽象,这个数据结构的核心是一个64位无符号型整数:异步

 25 struct eventfd_ctx {
 26         struct kref kref;
 27         wait_queue_head_t wqh;
 28         /*
 29          * Every time that a write(2) is performed on an eventfd, the
 30          * value of the __u64 being written is added to "count" and a
 31          * wakeup is performed on "wqh". A read(2) will return the "count"
 32          * value to userspace, and will reset "count" to zero. The kernel
 33          * side eventfd_signal() also, adds to the "count" counter and
 34          * issue a wakeup.
 35          */
 36         __u64 count;
 37         unsigned int flags;
 38 };

    有了这个结构,咱们就能够像操做普通文件同样在eventfd描述符上进行read()和write()操做了。简单来讲,read()操做读count值,write()操做写count值。再结合其余的一些限制条件,还能够实现阻塞/非阻塞的模型。这样咱们就能够用eventfd来实现最基本的进程间通讯功能了,man eventfd里就给出了一个很是直观的例子,而且说明当仅用于实现信号通知的功能时,eventfd()彻底能够替代pipe(),对于内核来讲,eventfd的开销更低,消耗的文件描述符数目更少(eventfd只占用一个描述符,而pipe则须要两个)。
ide

    eventfd不单单支持进程间的通讯,并且能够做为用户进程和内核通讯的桥梁,fs/eventfd.c中提供的eventfd_signal()函数就是经过在事件发生时将eventfd标记为可读,从而达到通知用户进程的目的。man手册中也对此特别进行了说明,并提到Linux内核aio(KAIO)就支持了这种用法,用于将读写操做完成的事件通知到应用程序。函数

    采用eventfd还有一个好处就是能够像对待其它文件描述符同样使用多路复用(select、poll、epoll)机制来对它进行监控,这样在采用了AIO机制的程序中就不用阻塞在某个特定的描述符上了。性能

    运行man eventfd中给出的例子,能够看到进程描述符占用的状况:学习

$ ls -l /proc/5056/fd/
total 0
lrwx------ 1 xxxxx xxxxx 64 2016-04-13 05:45 0 -> /dev/pts/1
lrwx------ 1 xxxxx xxxxx 64 2016-04-13 05:45 1 -> /dev/pts/1
lrwx------ 1 xxxxx xxxxx 64 2016-04-13 05:45 2 -> /dev/pts/1
lrwx------ 1 xxxxx xxxxx 64 2016-04-13 05:45 3 -> anon_inode:[eventfd]


    到这里eventfd的基本原理和应用场景就简单介绍完了,更细节的内容推荐阅读源码文件,eventfd的实现很是小巧和直观,主要函数都集中在fs/eventfd.c中,这个短短几百行的文件中涵盖了VFS系统、阻塞与非阻塞、等待与唤醒等内核机制的实现与应用,很值得学习。spa

    最后,友情提示一下,开篇提到的Linux原生支持的异步IO(io_***)和glibc提供的aio_***不是一个东西,按照淘宝霸爷@褚霸(这里)的话来讲,后者"是glibc用线程+阻塞调用来模拟的,性能不好,千万千万不要用。"线程

相关文章
相关标签/搜索