Hey 老司机,知道eventfd吗?

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

    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则需要两个)。

    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系统、阻塞与非阻塞、等待与唤醒等内核机制的实现与应用,很值得学习。

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




转载于:https://my.oschina.net/u/2310891/blog/666127

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值