首頁 > 系統教程 > Linux > Linux中的神器:eventfd的原理與應用

Linux中的神器:eventfd的原理與應用

王林
發布: 2024-02-13 20:30:16
轉載
751 人瀏覽過

Linux是一個強大的作業系統,它提供了許多高效的進程間通訊機制,如管道、訊號、訊息佇列、共享記憶體等。但是,有沒有一種更簡單、更靈活、更有效率的溝通方式呢?答案是有的,就是eventfd。 eventfd是Linux 2.6版本引入的一種系統調用,它可以用來實現事件通知,也就是透過一個檔案描述符來傳遞事件。 eventfd包含一個由核心維護的64位元無符號整數計數器,進程可以透過對這個檔案描述子進行read/write來讀取/改變計數器的值,從而實現進程間通訊。 eventfd有什麼優點呢?它有以下幾個特點:

Linux中的神器:eventfd的原理與應用

  • # eventfd不需要建立任何額外的檔案或記憶體空間,只需要一個檔案描述子;
  • eventfd可與select、poll、epoll等多工機制結合使用,實現高效率的事件驅動程式設計;
  • eventfd可以設定為非阻塞或信號量模式,提供了不同的通訊語意;
  • eventfd可以跨越行程或執行緒邊界,實作不同層級的通訊。

那麼,eventfd是如何運作的呢?它又有哪些應用場景呢?本文將從原理和應用兩個面向來介紹eventfd這個神器。

一般來說:Linux進程間通訊有五大方案:管道,訊息隊列,信號量,共享內存,套接字。
管道我不是很熟,只了解一般管道局限與父子進程之間,首先就被我排除了,因為我要做的是相互獨立的進程間通信,命名管道似乎不局限於父子進程,但在內核態怎麼使用不清楚。
訊息隊列完全不了解。
信號量的核心是一個核心變數的原子操作,但介面只體現在用戶態,而且信號量的P V操作更多做的好像是互斥,而不是我想要的通知喚醒機制。
共享記憶體就更麻煩了,介面只在用戶態,如果自己想做內核態與用戶態之間的共享內存,得自己寫file,然後提供mmap接口。
在套接字之前只是用過af_inet的tcp/udp與af_unix的dgram,還是上面的那個問題,內核沒有明確的接口提供,雖然可以自己去用比如sock->ops->recvmsg這樣的函數去調用,但畢竟需要自己構造入參,感覺還是不太安全。

那麼剩下的似乎只有netlink了,這個socket明確地提供了核心的發包函數,因為它明確地export出了netlink_kernel_create函數,所以內核態的函數得以用這個sock來進行發包。但一個是用戶態需要註冊收包函數,另一個核心態發包還是免不了要組裝skb,對於我單純地只想進行通知喚醒來說還是太複雜了。

於是我再次尋找,發現了eventfd這個神器,在KVM與Qemu的通信之間,eventfd被大牛使用的出神入化,仔細地分析了一下源碼,發現這個東西就如名字所說,純是為了通知而存在的。
作為一個file(linux裡有不是file的東西麼~~),它的private_data結構體 eventfd_ctx只有可憐的四個變數。

struct eventfd_ctx {
  struct kref kref;  /* 这个就不多说了,file计数用的,用于get/put */
  wait_queue_head_t wqh; /* 这个用来存放用户态的进程wait项,有了它通知机制才成为可能 */
/*
\* Every time that a write(2) is performed on an eventfd, the
\* value of the __u64 being written is added to "count" and a
\* wakeup is performed on "wqh". A read(2) will return the "count"
\* value to userspace, and will reset "count" to zero. The kernel
\* side eventfd_signal() also, adds to the "count" counter and
\* issue a wakeup.
*/
  __u64 count;  /* 这个就是一个技术器,应用程序可以自己看着办,read就是取出然后清空,write就是把value加上 */
  unsigned int flags;  /* 所有的file都有的吧,用来存放阻塞/非阻塞标识或是O_CLOEXEC之类的东西 */
};
  我之所以选用它是因为它有 eventfd_signal 这个特地为内核态提供的接口,下面的是注释。
 \* This function is supposed to be called by the kernel in paths that do not
 \* allow sleeping. In this function we allow the counter to reach the ULLONG_MAX
 \* value, and we signal this as overflow condition by returining a POLLERR to poll(2).
登入後複製

其實看程式碼會更清晰一些

int eventfd_signal(struct eventfd_ctx *ctx, int n)
{
  unsigned long flags;

  if (n return -EINVAL;
  spin_lock_irqsave(&ctx->wqh.lock, flags);
  if (ULLONG_MAX - ctx->count count);
  ctx->count += n;
  if (waitqueue_active(&ctx->wqh))
    wake_up_locked_poll(&ctx->wqh, POLLIN);
  spin_unlock_irqrestore(&ctx->wqh.lock, flags);

  return n;
}  
登入後複製

本質就是做一次喚醒,不用read,也不用write,與eventfd_write的差別是不用阻塞

#下面說一下我的具體用法:
內核狀態是一個模組,註冊一個misc設備,建立核心執行緒工作(參數為模組的file->private_data)。提供ioctl介面供用戶態進程下發自己eventfd所建立的fd,保存在核心執行緒可以存取的file->private_data中。
當核心態想通知用戶態時,直接使用eventfd_signal,此時用戶態執行緒需要先把自己放在eventfd_ctx->wqh上,有兩個方案,一個是呼叫read,一個是呼叫poll。如果是read,之後會將eventfd_ctx->count清零,下次還能阻塞住。但如果使用poll,之後count並未清零,導致再次poll時,即使核心態沒有eventfd_signal,poll也會即時回傳。
使用者態通知核心態稍微麻煩一點,,首先需要再建立一個eventfd,然後下發給file->private_data(這裡的操作同上面),額外需要在模組裡做一個iotcl,專門負責使用者態來通知核心態,函數裡就做eventfd_signal,內核態線程需要先放在eventfd_ctx->wqh上,可以利用vfs_read,或是自己在核心態做一次poll(似乎又麻煩了)。

本文介紹了eventfd這個Linux中的神器,它是一種簡單、靈活、有效率的進程間通訊機制。我們從原理方面分析了eventfd的創建、讀寫和標誌位等內容,並且給出了相應的程式碼範例。我們也從應用方面介紹了eventfd在用戶態與內核態通訊、定時器和事件觸發器等場景中的使用方法,並且給出了對應的程式碼範例。透過本文的學習,我們可以掌握eventfd的基本用法,並且能夠在實際開發中靈活地運用eventfd來實現不同的通訊需求。希望本文對你有幫助!

以上是Linux中的神器:eventfd的原理與應用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:lxlinux.net
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板