eventfd 在內核版本,2.6.22以後有效。查看內核版本可以用命令 uname -r
在看muduo源碼時,項目中使用eventfd機制實現線程間的喚醒(通知)。覺得效率比較高,所以拿來學習下。其也可以實現父子進程間的通信。
首先使用: man eventfd
name:eventfd–創建一個用於事件通知的描述符
SYNOPSIS:
#include<sys/eventfd.h>
int eventfd(unsigned int initval,int flags);
- 1
- 2
DESCRIPTION:
eventfd是用於創建一個“eventfd object”,它可以用於用戶態-用戶態和內核態-用戶態之間實現事件通知的機制,這個object包括一個64位計數器(initval,內核維護)。
flags–可以改變eventfd的行爲,
EFD_CLOEXEC(從linux 2.6.27開始支持)–類似於close-on-exec(FD_CLOEXEC)在open函數的作用,即這個句柄我在fork子進程後執行exec時就關閉。
EFD_NONBLOCK(從linux 2.6.27開始支持)–當使用eventfd創建一個新的描述符設置了該狀態時,將該描述符設爲非阻塞狀態。
EFD_SEMAPHORE(從linux 2.6.30開始支持)–對於read該文件描述符是提供類似信號的語義,具體含義如下會介紹。
return values 返回值:eventfd()返回一個新的文件描述符(efd)可以用於引用eventfd對象,可以對efd執行以下操作:
read(2):如果讀成功,則返回一個8-byte的整數,如果讀入的數小於8-byte,則會導致讀錯誤,並設置error 爲EIVAL。返回的整數是主機序的。read(2)返回值的語義是由當前的initval非零和是否設置EFD_SEMAPHORE flag決定的。
* 如果EFD_SEMAPHORE flag沒有被設置,並且eventfd 的counter是一個非零值,則read(2)會返回該counter 的數值,並且將該counter置零。
* 如果設置了EFD_SEMAPHORE flag並且counter的值是非零的,則read(2)會返回一個8-byte 的爲1的整數,並且將counter的值減1.
* 如果在read(2)的時候,counter的值爲0,則將會阻塞直到counter大於0,如果設置爲 EFD_NONBLOCK,則會立刻返回,並將error設置爲EAGAIN。
write(2):改操作會將當前的一個8-byte的數字累加到counter的緩存中,counter的最大值爲2^64-1。如果write導致counter值超過最大值,則會阻塞直到對該描述符執行read(2),否則會導致寫失敗,返回錯誤 EAGAIN。如果寫入的數值字節小於8-byte,則會返回錯誤,EINVAL。
poll(2),select(2),epoll(2)都可以監聽該描述符。
close(2) – 當這個文件描述符不需要的時候,我們需要關閉它。當所有的關聯的文件描述符都被關閉時,內核中的eventfd對象纔會被釋放。
當fork()時,這個文件描述符是會被子進程繼承,重複的文件描述符被關聯到相同的eventfd對象。如果創建eventfd時沒有設置EFD_CLOEXEC,則子進程調用execve(2)後,該文件描述符將會被保留。
返回值:成功?efd:(-1);
**注意: 和pipe的比較:
在使用pipe作爲簡單的信號通信功能的所有情況都可以使用eventfd替代。一個eventfd文件描述符的開銷遠小於一個管道,並且只需要一個文件描述符。**
多線程例程:
- 1
- 2
- 3
- 4
- 5
- 6
muduo源碼中,用於喚醒一個線程
EventLoop::EventLoop()
: looping_(false),
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
iteration_(0),
threadId_(CurrentThread::tid()),
poller_(Poller::newDefaultPoller(this)),
timerQueue_(new TimerQueue(this)),
wakeupFd_(createEventfd()),
wakeupChannel_(new Channel(this, wakeupFd_)),
currentActiveChannel_(NULL)
int createEventfd()
{
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (evtfd < 0)
{
LOG_SYSERR << "Failed in eventfd";
abort();
}
return evtfd;
}
void EventLoop::wakeup()
{
uint64_t one = 1;
ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
父子進程之間通知:
#include <sys/eventfd.h>
#include <unistd.h>
#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, j;
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 (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 %llu (0x%llx) from efd\n",
(unsigned long long) u, (unsigned long long) u);
exit(EXIT_SUCCESS);
case -1:
handle_error("fork");
}
}
gcc main.c -o main
./main 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