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用線程+阻塞調用來模擬的,性能不好,千萬千萬不要用。"線程

相關文章
相關標籤/搜索