??eventfd 是 Linux 的一個系統(tǒng)調用,創(chuàng)建一個文件描述符用于事件通知,自 Linux 2.6.22 以后開始支持费奸。
# bionic/libc/include/sys/eventfd.h(Android 8.0源碼)
int eventfd(unsigned int initial_value, int flags);
??eventfd() 函數(shù)會創(chuàng)建一個 eventfd 對象召耘,用戶空間的應用程序可以用這個 eventfd 來實現(xiàn)事件的等待或通知機制般渡,也可以用于內(nèi)核通知新的事件到用戶空間應用程序。 這個對象包含一個 64-bit 的整形計數(shù)器具钥,內(nèi)核空間維護這個計數(shù)器滔以,創(chuàng)建這個計數(shù)器的時候使用第一個入?yún)?initial_value 來初始化計數(shù)器。
#include <sys/eventfd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[])
{
int efd, j;
uint64_t u;
ssize_t s;
if (argc < 2) {
fprintf(stderr, "Usage: %s <num>...\n", argv[0]);
exit(EXIT_FAILURE);
}
efd = eventfd(0, EFD_SEMAPHORE);
if (efd == -1)
handle_error("eventfd");
switch (fork()) {
case 0:
for (j = 1; j < argc; j++) {
printf("Child writing %s to efd\n", argv[j]);
u = strtoull(argv[j], NULL, 0);
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 %llu (0x%llx) from efd\n",
(unsigned long long) u, (unsigned long long) u);
exit(EXIT_SUCCESS);
case -1:
handle_error("fork");
}
}
初始化的計數(shù)器值設置為 0 的幾種情況
- 如果 flags 設置為 0氓拼,并且子進程中往文件描述符寫入 4、5抵碟、6 時桃漾,父進程讀取并打印的結果是 15,這個時候計數(shù)器置為 0拟逮;
- 如果 flags 設置 EFD_NONBLOCK撬统,并且子進程中不往文件描述符寫入(注釋 write 代碼),會打印一個錯誤敦迄,同時父進程退出(代碼中執(zhí)行 exit 函數(shù))恋追。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read read: Resource temporarily unavailable
- 如果 flags 沒有設置 EFD_NONBLOCK(可設置為 0 或者 EFD_SEMAPHORE),并且子進程中不往文件描述符寫入(注釋write代碼)罚屋,那么父進程會一直阻塞苦囱,程序會阻塞在那里。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read 一直阻塞在這里......
- 如果 flags 設置了 EFD_SEMAPHORE脾猛,父進程讀取并打印的結果為 1撕彤,計數(shù)器遞減 1。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Child writing 4 to efd Child writing 5 to efd Child writing 6 to efd Child completed write loop Parent about to read Parent read 1 (0x1) from efd
初始化的計數(shù)器值設置為 5 的幾種情況
- 如果 flags 設置為 0猛拴,并且子進程中往文件描述符寫入 4羹铅、5、6 時愉昆,父進程讀取并打印的結果是 20职员,這個時候計數(shù)器置為 0;
- 如果 flags 設置 EFD_NONBLOCK跛溉,并且子進程中不往文件描述符寫入(注釋 write 代碼)焊切,不會報錯,而是將計數(shù)器初始化的非零值(5)給讀出來了芳室,這個時候計數(shù)器置為 0蛛蒙。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read Parent read 5 (0x5) from efd
- 如果 flags 沒有設置 EFD_NONBLOCK,并且子進程中不往文件描述符寫入(注釋write代碼)渤愁。
a) flags 設置為 0牵祟,父進程不會阻塞,讀取并打印的結果為 5抖格,計數(shù)器置為 0诺苹。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read Parent read 5 (0x5) from efd
b) 在 a) 的場景下咕晋,修改代碼,加一個循環(huán)條件收奔,讓其循環(huán)讀 5 次掌呜,父進程將初始值的 5 讀完之后,由于計數(shù)器置為 0 了坪哄,所以第 2 次繼續(xù)讀時质蕉,就一直阻塞在那兒了。
for (int i = 0; i < 5; i++) { s = read(efd, &u, sizeof(uint64_t)); if (s != sizeof(uint64_t)) handle_error("read"); printf("Parent read %llu (0x%llx) from efd\n", (unsigned long long) u, (unsigned long long) u); } wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read Parent read 5 (0x5) from efd 一直阻塞在這里......
c) flags 設置為 EFD_SEMAPHORE翩肌,父進程不會阻塞模暗,讀取并打印的結果為 1,計數(shù)器遞減 1念祭。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read Parent read 1 (0x1) from efd
d) 在 c) 的場景下兑宇,修改代碼,加一個循環(huán)條件粱坤,讓其循環(huán)讀 6 次隶糕,父進程將初始值的 5 讀完之后,由于計數(shù)器置為 0 了站玄,所以第 6 次繼續(xù)讀時枚驻,就一直阻塞在那兒了。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Parent about to read Parent read 1 (0x1) from efd Parent read 1 (0x1) from efd Parent read 1 (0x1) from efd Parent read 1 (0x1) from efd Parent read 1 (0x1) from efd 一直阻塞在這里......
- 如果 flags 設置了 EFD_SEMAPHORE株旷,父進程讀取并打印的結果為 1测秸,計數(shù)器遞減 1。
wufan@Frank-Linux:~/Linux/test$ ./rw 4 5 6 Child writing 4 to efd Child writing 5 to efd Child writing 6 to efd Child completed write loop Parent about to read Parent read 1 (0x1) from efd
小結
通過上述在計數(shù)器初始值為 0 和非 0 兩種情況下灾常,對 flags 的嘗試霎冯,可以得出結論:
- 只要 flags 設置了 EFD_NONBLOCK,不管計數(shù)器初始值是什么值钞瀑,讀到計數(shù)器的值為 0 后沈撞,再繼續(xù)讀,會直接返回一個錯誤值雕什,不會阻塞缠俺;除非有寫入操作使計數(shù)器的值為非 0,才能續(xù)正常地進行讀操作贷岸。
- 只要 flags 沒有設置 EFD_NONBLOCK壹士,不管計數(shù)器初始值是什么值,讀到計數(shù)器的值為 0 后偿警,再繼續(xù)讀躏救,會阻塞;除非有寫入操作使計數(shù)器的值為非 0,才能續(xù)正常地進行讀操作盒使。