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