什么是eventfd
自内核 2.6.22 起,Linux 通过 eventfd()系统调用额外提供了一种非标准的同步机制。这 个系统调用创建了一个 eventfd 对象,该对象拥有一个相关的由内核维护的 8 字节无符号整 数,它返回一个指向该对象的文件描述符。向这个文件描述符中写入一个整数将会把该整 数加到对象值上。
创建 eventfd
eventfd的创建是通过eventfd函数实现的,返回值即是该eventfd所对应的文件描述符,函数的原型如下所示:
/*
返回值:
返回一个文件描述符
*/
int eventfd (unsigned int __count, int __flags)
/*
count : 创建eventfd时它所对应的64位计数器的初始值;
flags : eventfd文件描述符的标志,可由三种选项组成
- EFD_CLOEXEC 表示返回的eventfd文件描述符在fork后exec其他程序时会自动关闭这个文件描述符;
- EFD_NONBLOCK 设置返回的eventfd非阻塞;
- EFD_SEMAPHORE 表示将eventfd作为一个信号量来使用。
*/
读 eventfd
从上面描述中可以知道以下几点:
int eventfd_read (int __fd, eventfd_t *__value);
/*
1.read函数会从eventfd对应的64位计数器中读取一个8字节的整型变量;
2.read函数设置的接收buf的大小不能低于8个字节,否则read函数会出错,errno为EINVAL;
3.read函数返回的值是按小端字节序的;
4.如果eventfd设置了EFD_SEMAPHORE,那么每次read就会返回1,并且让eventfd对应的计数器减一;
如果eventfd没有设置EFD_SEMAPHORE,那么每次read就会直接返回计数器中的数值,read之后计数器就会置0。
不管是哪一种,当计数器为0时,如果继续read,那么read就会阻塞(如果eventfd没有设置EFD_NONBLOCK)或者返回EAGAIN错误(如果eventfd设置了EFD_NONBLOCK)。
*/
写 eventfd
int eventfd_write (int __fd, eventfd_t __value);
/*
1.在没有设置EFD_SEMAPHORE的情况下,write函数会将发送buf中的数据写入到eventfd对应的计数器中,最大只能写入0xffffffffffffffff,否则返回EINVAL错误;
2.在设置了EFD_SEMAPHORE的情况下,write函数相当于是向计数器中进行“添加”,比如说计数器中的值原本是2,如果write了一个3,那么计数器中的值就变成了5。
如果某一次write后,计数器中的值超过了0xfffffffffffffffe(64位最大值-1),
那么write就会阻塞直到另一个进程/线程从eventfd中进行了read(如果write没有设置EFD_NONBLOCK),
或者返回EAGAIN错误(如果write设置了EFD_NONBLOCK)。
*/
使用示例
- flags不设置 EFD_NONBLOCK ,EFD_SEMAPHORE
#include <sys/eventfd.h>
#include <thread>
#include <iostream>
#include <csignal>
int fd;
eventfd_t value;
void *run(void *) {
sleep(2); // 休眠 2 秒
eventfd_write(fd, 5); // 写 eventfd cout值 为 5
sleep(3); // 休眠 3 秒
eventfd_write(fd, 10); // 写 eventfd cout值 为 10
return (void *) 0;
}
// 获取日期
std::string getTime() {
time_t t = time(0);
char tmp[32] = {0};
strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", localtime(&t));
return tmp;
}
int main() {
// 创建 eventfd 文件描述符 count 初始值 1,flags 为 EFD_CLOEXEC
fd = eventfd(1, EFD_CLOEXEC);
// 读取 eventfd 值,由于值为 1 不会阻塞,读取完 count 会设置为 0
eventfd_read(fd, &value);
std::cout << "value 1 : " << value << " " << getTime() << std::endl;
pthread_t pid;
pthread_create(&pid, NULL, run, NULL); // 创建子线程
// 读取 eventfd 值,由于值为 0 会阻塞 ,等待子线程修改 eventfd 值 唤醒主线程
eventfd_read(fd, &value);
std::cout << "value 2 : " << value << " " << getTime() << std::endl;
// 上次读取后,count 又被设置为 0 ,线程继续阻塞,等待子线程修改 eventfd 值 唤醒主线程
eventfd_read(fd, &value);
std::cout << "value 3 : " << value << " " << getTime() << std::endl;
}
输出
value 1 : 1 2023-09-09 19:41:07
value 2 : 5 2023-09-09 19:41:09
value 3 : 10 2023-09-09 19:41:12
- flags 设置 EFD_SEMAPHORE
#include <sys/eventfd.h>
#include <thread>
#include <iostream>
#include <csignal>
#include "Utils.h"
#include "EventfdSemaphore.h"
void *semaphoreRun(void *) {
sleep(2); // 休眠两秒
// 由于创建 eventfd flags 添加 EFD_SEMAPHORE 写 eventfd_write 会使得 cout值 +2
eventfd_write(fd, 2);
return (void *) 0;
}
int main() {
// 创建 eventfd 文件描述符 count 初始值 2,flags 为 EFD_CLOEXEC | EFD_SEMAPHORE
fd = eventfd(2, EFD_CLOEXEC | EFD_SEMAPHORE);
// 由于创建 eventfd flags 添加 EFD_SEMAPHORE 每次 read 都会使得 count 减 1 而每次返回的 value 都是 1
eventfd_read(fd, &value);
std::cout << "value 1 : " << value << " " << getTime() << std::endl;
pthread_t pid;
pthread_create(&pid, NULL, semaphoreRun, NULL); // 创建子线程
eventfd_read(fd, &value);
std::cout << "value 2 : " << value << " " << getTime() << std::endl;
eventfd_read(fd, &value);
std::cout << "value 3 : " << value << " " << getTime() << std::endl;
eventfd_read(fd, &value);
std::cout << "value 4 : " << value << " " << getTime() << std::endl;
eventfd_read(fd, &value); // 由于count 值为0 会等待阻塞 ...
std::cout << "value 5 : " << value << " " << getTime() << std::endl;
}
输出
value 1 : 1 2023-09-09 20:32:13
value 2 : 1 2023-09-09 20:32:13
value 3 : 1 2023-09-09 20:32:15
value 4 : 1 2023-09-09 20:32:15
- flags 设置 EFD_NONBLOCK
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <stdio.h>
int main() {
int evfd = eventfd(10, EFD_CLOEXEC | EFD_NONBLOCK);
uint64_t wdata = 0;
uint64_t rdata = 0;
if (eventfd_read(evfd, &rdata) == -1) {
perror(NULL);
if (errno != EAGAIN)return 0;
}
std::cout << "Init read : " << rdata << std::endl; //读计数器初始值
wdata = 20;
if (eventfd_write(evfd, wdata) == -1) //父进程写20
{
perror(NULL);
return 0;
}
std::cout << "parent write : " << wdata << std::endl;
if (fork() == 0) {
wdata = 30;
if (eventfd_read(evfd, &rdata) == -1) //子进程读计数器
{
perror(NULL);
return 0;
}
std::cout << "child read : " << rdata << std::endl;
if (eventfd_write(evfd, wdata) == -1) //子进程写30
{
perror(NULL);
return 0;
}
std::cout << "child write : " << wdata << std::endl;
exit(0);
}
wait(NULL);
if (eventfd_read(evfd, &rdata) == -1) //父进程读计数器
{
perror(NULL);
return 0;
}
std::cout << "parent read : " << rdata << std::endl;
return 0;
}
输出
Init read : 10
parent write : 20
child read : 20
child write : 30
parent read : 30