Linux eventfd

167 阅读5分钟

什么是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