eventfd - 為event notification創建文件描述符
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);
eventfd() 創建了一個“eventfd 對象”,它可以被用戶空間應用程序用作事件等待/通知機制,內核可以將事件通知用戶空間應用程序。 該對象包含一個由內核維護的無符號 64 位整數 (uint64_t) 計數器。 此計數器使用參數 initval
中指定的值進行初始化。
eventfd() 將返回一個文件描述符,指向“eventfd 對象”。
參數 flag 可以是以下值按位或運算:
-
EFD_CLOEXEC
對應于open的 O_CLOEXEC 參數,即在執行execve是關閉此fd -
EFD_NONBLOCK
對應于open的 O_NONBLOCK 參數,即將其設置非阻塞的fd,如果fd處于不可讀寫的狀態,讀寫非阻塞的fd將不會阻塞線程,而是返回 EAGAIN -
EFD_SEMAPHORE
賦予eventfd類似于信號量的語義。 見下文。
在 Linux 2.6.26 之前的版本中,flags
參數未使用,必須指定為零。
可以對 eventfd() 返回的文件描述符執行以下操作:
read(2)
成功讀取會返回一個 8 字節整數。 如果提供的緩沖區的大小小于 8 字節,則 read(2) 失敗并顯示錯誤 EINVAL。
read(2) 返回的值是主機字節順序,即主機上整數的本機字節順序。換句話說,返回的值可以直接轉化為整數。
read(2) 的語義取決于 eventfd 計數器當前是否具有非零值以及在創建 eventfd 文件描述符時是否指定了 EFD_SEMAPHORE 標志:
- 如果未指定 EFD_SEMAPHORE 并且 eventfd 計數器具有非零值,則 read(2) 返回 eventfd 計數器的值,同時將計數器置零。
- 如果指定了 EFD_SEMAPHORE 并且 eventfd 計數器具有非零值,則 read(2) 返回1,同時將計數器的值減 1。
- 如果在調用 read(2) 時 eventfd 計數器為零則:
- 未配置EFD_NONBLOCK flags,則調用阻塞,直到計數器變為非零(此時,read(2) 如上所述返回結果)
- 若配置了EFD_NONBLOCK flags,則調用失敗并返回錯誤 EAGAIN
write(2)
write(2) 調用將其緩沖區中提供的 8 字節整數值添加到計數器(計數器的值加上write提供的值)。計數器的最大值為2^64-1(即0xffffffffffffffffe),如果相加的結果超過最大值,那么將阻塞write(),直到執行read(),當然,如果設置了EFD_NONBLOCK flags,則調用失敗并返回錯誤 EAGAIN
poll(2), select(2),epoll(7)
- 如果計數器的值大于 0,則文件描述符是可讀的(epoll(2) POLLIN 標志)。
- 如果可以在不阻塞的情況下寫入至少為“1”的值,則文件描述符是可寫的(epoll(2) POLLOUT 標志),即當前計數器不是最大值。
- 如果檢測到計數器值溢出,則 select(2) 將文件描述符指示為可讀和可寫,并且 poll(2) 返回一個 POLLERR 事件。 如上所述,write(2) 永遠不會溢出計數器。 但是,如果 KAIO 子系統執行了 2^64 個 eventfd“信號發布”,則可能會發生溢出(理論上可能,但實際上不太可能)。 如果發生溢出,則 read(2) 將返回該最大 uint64_t 值(即 0xffffffffffffffff)。
eventfd 文件描述符還支持其他文件描述符多路復用 API:pselect(2) 和 ppoll(2)。
close(2)
當不再需要文件描述符時,應將其關閉。 當與同一個 eventfd 對象關聯的所有文件描述符都已關閉時,內核會釋放對象的資源。
由 eventfd() 創建的文件描述符的副本由 fork(2) 生成的子進程繼承。 重復的文件描述符與相同的 eventfd 對象相關聯。 除非設置了 close-on-exec 標志,否則 eventfd() 創建的文件描述符將在 execve(2) 中保留。
成功時, eventfd() 返回一個新的 eventfd 文件描述符。 出錯時,返回 -1 并設置 errno 以指示錯誤。
EINVAL
在FLAGS
中指定了不支持的值。EMFILE
達到了每個進程可打開的文件描述符數目的上限EMFILE
達到了系統可打開的文件描述符數目的上限ENODEV
無法掛載(內部)匿名 inode 設備。ENOMEM
內存不足,無法創建新的 eventfd 文件描述符。
eventfd() is available on Linux since kernel 2.6.22. Working support is provided in glibc since version 2.8. The eventfd2() system call (see NOTES) is available on Linux since kernel 2.6.27. Since version 2.9, the glibc eventfd() wrapper will employ the eventfd2() system call, if it is supported by the kernel.
Interface | Attribute | Value |
---|---|---|
eventfd() | Thread safety | MT-Safe |
eventfd() and eventfd2() are Linux-specific.
在管道僅用于發送事件信號的所有情況下,應用程序都可以使用 eventfd 文件描述符而不是管道(請參閱 pipe(2))。 eventfd 文件描述符的內核開銷遠低于管道,并且只需要一個文件描述符(而管道需要兩個)。
當在內核中使用時,eventfd 文件描述符可以提供從內核到用戶空間的橋梁,例如,允許諸如 KAIO(內核 AIO)之類的功能向文件描述符發出信號,表明某些操作已完成。
eventfd 文件描述符的一個關鍵點是它可以像任何其他文件描述符一樣使用 select(2)、poll(2) 或 epoll(7) 進行監視。這意味著應用程序可以同時監視“傳統”文件的準備情況和支持 eventfd 接口的其他內核機制的準備情況。 (沒有 eventfd() 接口,這些機制無法通過 select(2)、poll(2) 或 epoll(7) 進行多路復用。)
在進程的 /proc/[pid]/fdinfo 目錄中,可以通過相應文件描述符的條目查看 eventfd 計數器的當前值。有關更多詳細信息,請參閱 proc(5)。
C library/kernel differences
有兩個底層的 Linux 系統調用:eventfd() 和最近的 eventfd2()。 前一個系統調用沒有實現 flags 參數。 后一個系統調用實現了上述標志值。 glibc 包裝器函數將在可用的地方使用 eventfd2()。
Additional glibc features
GNU C 庫定義了一個額外的類型和兩個函數,它們試圖抽象一個 eventfd 文件描述符的一些讀寫細節:
typedef uint64_t eventfd_t;
int eventfd_read(int fd, eventfd_t *value);
int eventfd_write(int fd, eventfd_t value);
這些函數對 eventfd 文件描述符執行讀寫操作,如果傳輸的字節數正確則返回 0,否則返回 -1。
下面的程序創建一個 eventfd 文件描述符,然后 fork 創建一個子進程。 當父進程短暫休眠時,子進程將程序命令行參數中提供的每個整數寫入 eventfd 文件描述符。 當父進程完成睡眠后,它從 eventfd 文件描述符中讀取。
以下 shell 會話顯示了程序的示例運行:
$ ./a.out 1 2 4 7 14
Child writing 1 to efd
Child writing 2 to efd
Child writing 4 to efd
Child writing 7 to efd
Child writing 14 to efd
Child completed write loop
Parent about to read
Parent read 28 (0x1c) from efd
源代碼:
#include <sys/eventfd.h>
#include <unistd.h>
#include <inttypes.h> /* Definition of PRIu64 & PRIx64 */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h> /* Definition of uint64_t */
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
int efd;
uint64_t u;
ssize_t s;
if (argc < 2) {
fprintf(stderr, "Usage: %s <num>...\n", argv[0]);
exit(EXIT_FAILURE);
}
efd = eventfd(0, 0);
if (efd == -1)
handle_error("eventfd");
switch (fork()) {
case 0:
for (int j = 1; j < argc; j++) {
printf("Child writing %s to efd\n", argv[j]);
u = strtoull(argv[j], NULL, 0);
/* strtoull() allows various bases */
s = write(efd, &u, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("write");
}
printf("Child completed write loop\n");
exit(EXIT_SUCCESS);
default:
sleep(2);
printf("Parent about to read\n");
s = read(efd, &u, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("read");
printf("Parent read %"PRIu64" (%#"PRIx64") from efd\n", u, u);
exit(EXIT_SUCCESS);
case -1:
handle_error("fork");
}
}