eventfd 在内核版本,2.6.22以后有效。查看内核版本可以用命令 uname -r
在看muduo源码时,项目中使用eventfd机制实现线程间的唤醒(通知)。觉得效率比较高,所以拿来学习下。其也可以实现父子进程间的通信。
首先使用: man eventfd
name:eventfd–创建一个用于事件通知的描述符
SYNOPSIS:
#include
int eventfd(unsigned int initval,int flags);
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文件描述符的开销远小于一个管道,并且只需要一个文件描述符。**
多线程例程:
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";
}
}
父子进程之间通知:
#include
#include
#include
#include
#include /* 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 ...\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