C/C++编程:eventfd 的分析与具体例子

本文详细介绍了Linux 2.6.27引入的eventfd特性,用于进程间事件通知,包括接口说明、操作方法、阻塞与非阻塞模式、信号量行为以及父子进程和线程间的通信示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

理论

Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的,也可以由内核通知用户空间应用程序事件。

接口

eventfd的创建是通过eventfd函数实现的,返回值即是该eventfd所对应的文件描述符,函数的原型如下所示:

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

eventfd在内核里的核心是一个计数器counter,它是一个uint64_t的整形变量counter,初始值为initval。

  • initval:创建eventfd时它所对应的64位计数器的初始值;
  • flags: eventfd文件描述符的标志,用以改变 eventfd 的行为
    • 如果是2.6.26或之前版本的内核,flags 必须设置为0
    • 2.6.27以上内核有效
      • EFD_CLOEXEC (since Linux 2.6.27)
        • 文件被设置成 O_CLOEXEC,创建子进程 (fork) 时不继承父进程的文件描述符。
        • FD_CLOEXEC 标志,即 close-on-exec,这样在调用 exec 后会自动关闭文件描述符。因为通常执行另一个程序后,会用全新的程序替换子进程的正文,数据,堆和栈等,原来的文件描述符变量也不存在了,这样就没法关闭没用的文件描述符了。
      • EFD_NONBLOCK(since Linux 2.6.27)
        • 设置文件描述符为非阻塞
        • 设置了这个标志后,如果没有数据可读,就返回一个 EAGAIN 错误,不会一直阻塞。
      • EFD_SEMAPHORE (since Linux 2.6.30)
        • 提供类似信号量语义的 read 操作,简单说就是计数值 count 递减 1。可以多次read

操作方法

一切皆为文件是 Linux 内核设计的一种高度抽象,eventfd 的实现也不例外,我们可以使用操作文件的方法操作 eventfd。

read

消费者需要对信号量进行down操作时,调用read从eventfd读即可。

  • read函数会从eventfd对应的64位计数器中读取一个8字节的整型变量;
  • read函数设置的接收buf的大小不能低于8个字节,否则read函数会出错,errno为EINVAL;
  • read函数返回的值是按小端字节序的;

read返回值:

  • 如果eventfd设置了EFD_SEMAPHORE,那么每次read就会返回1,并且让eventfd对应的计数器减一
  • 如果eventfd没有设置EFD_SEMAPHORE,那么每次read就会直接返回计数器中的数值,read之后计数器就会置0。
  • 不管是哪一种,当计数器为0时,如果继续read,那么read就会阻塞(如果eventfd没有设置EFD_NONBLOCK)或者返回EAGAIN错误(如果eventfd设置了EFD_NONBLOCK)。
    在这里插入图片描述

write

  • 在没有设置EFD_SEMAPHORE的情况下,write函数会将发送buf中的数据写入到eventfd对应的计数器中,最大只能写入0xffffffffffffffff,否则返回EINVAL错误;

  • 在设置了EFD_SEMAPHORE的情况下,write函数相当于是向计数器中进行“添加”,比如说计数器中的值原本是2,如果write了一个3,那么计数器中的值就变成了5。如果某一次write后,计数器中的值超过了0xfffffffffffffffe(64位最大值-1),那么write就会阻塞直到另一个进程/线程从eventfd中进行了read(如果write没有设置EFD_NONBLOCK),或者返回EAGAIN错误(如果write设置了EFD_NONBLOCK)。

    除此之外,eventfd还支持select和poll,与一般的读写描述符相类似,这里就不多说了,如下所示:
    在这里插入图片描述

实践

测试标志

阻塞标志

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)
int main(int argc, char** argv)
{
    int  efd;

    ssize_t rc;
    efd = eventfd(0, 0);
    if(-1 == efd){
        handle_error("eventfd");
    }

    uint64_t buf;
    printf("wait for read:\n");
    rc = read(efd, &buf, sizeof(uint64_t));
    printf("read finished: %zd\n", rc);
    printf("Parent read %llu from efd\n",(unsigned long long)buf);
    return 0;
}

从下图中可以看出,默认是阻塞的,也就是read没有数据可读时会一直停在read处

在这里插入图片描述

如果将其设置为非阻塞标志EFD_NONBLOCK,就不会阻塞了,如果没有数据就直接过EFD_NONBLOCK

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)
int main(int argc, char** argv)
{
    int  efd;

    ssize_t rc;
    efd = eventfd(0, EFD_NONBLOCK);
    if(-1 == efd){
        handle_error("eventfd");
    }

    uint64_t buf = 0;
    printf("wait for read:\n");
    rc = read(efd, &buf, sizeof(uint64_t));
    if(rc == -1){
        printf("read finished: %zd, error: %s\n", rc, strerror(errno));
    }else{
        printf("Parent read %llu from efd\n",(unsigned long long)buf);
    }

    return 0;
}

在这里插入图片描述

EFD_SEMAPHORE

在没有设置EFD_SEMAPHORE 之前:

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)
int main(int argc, char** argv)
{
    int  efd;

    ssize_t rc;
    efd = eventfd(0, 0);
    if(-1 == efd){
        handle_error("eventfd");
    }

    uint64_t buf = 11;
    rc = write(efd, &buf, sizeof(uint64_t));
    if(rc != sizeof(uint64_t)){
        handle_error("write");
    }


    printf("Line[%d] wait for read:\n", __LINE__);
    rc = read(efd, &buf, sizeof(uint64_t));
    if(rc == -1){
        printf("Line[%d] read finished: %zd, error: %s\n",__LINE__, rc, strerror(errno));
    }else{
        printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
    }

    printf("Line[%d] wait for read:\n", __LINE__);
    rc = read(efd, &buf, sizeof(uint64_t));
    if(rc == -1){
        printf("Line[%d] read finished: %zd, error: %s\n",__LINE__, rc, strerror(errno));
    }else{
        printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
    }


    return 0;
}

从下面可以看出,不设置EFD_SEMAPHORE ,如果有数据可读,read会一次读完。第二次read时因为没有数据可读,就一直阻塞住
在这里插入图片描述

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)
int main(int argc, char** argv)
{
    int  efd;

    ssize_t rc;
    efd = eventfd(0, EFD_SEMAPHORE );
    if(-1 == efd){
        handle_error("eventfd");
    }

    uint64_t buf = 3;
    rc = write(efd, &buf, sizeof(uint64_t));
    if(rc != sizeof(uint64_t)){
        handle_error("write");
    }

    int i = 0;
    while (1){
        printf(" Line[%d] order [%d] wait for read:\n", __LINE__, i);
        rc = read(efd, &buf, sizeof(uint64_t));
        if(rc == -1){
            printf("Line[%d] read finished: %zd, error: %s\n",__LINE__, rc, strerror(errno));
            break;
        }else{
            printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
        }
        i++;
    }
    
    return 0;
}

在这里插入图片描述
如上面可以看出,设置 EFD_SEMAPHORE ,如果有数据可读, read每次都会读出1来,直到读取完才阻塞。

当然,如果想没有数据读取就退出,那么可以设置非阻塞标志EFD_NONBLOCK

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)
int main(int argc, char** argv)
{
    int  efd;

    ssize_t rc;
    efd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK );
    if(-1 == efd){
        handle_error("eventfd");
    }

    uint64_t buf = 3;
    rc = write(efd, &buf, sizeof(uint64_t));
    if(rc != sizeof(uint64_t)){
        handle_error("write");
    }

    int i = 0;
    while (1){
        printf(" Line[%d] order [%d] wait for read:\n", __LINE__, i);
        rc = read(efd, &buf, sizeof(uint64_t));
        if(rc == -1){

            printf("Line[%d] read finished: %zd, error [%d]: %s ; EAGAIN =  [%d]\n",__LINE__, rc,errno, strerror(errno), EAGAIN);
            break;
        }else{
            printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
        }
        i++;
    }

    return 0;
}

在这里插入图片描述

父子进程间通信

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>

#define handle_error(msg)   \
    do { perror(msg); exit(EXIT_FAILURE);  } while(0)
int main(int argc, char** argv)
{
    int  efd, i;
    uint64_t buf;
    ssize_t rc;

    if(argc < 2){
        fprintf(stderr, "Usage: %s <num>...\n",argv[0]);
        exit(EXIT_FAILURE);
    }

    efd = eventfd(0, 0);
    if(-1 == efd){
        handle_error("eventfd");
    }

    switch(fork()){
        case 0:
            for(i = 1;  i < argc; i++){
                printf("Child writing %s to efd\n",argv[i]);
                buf = atoll(argv[i]);
                rc = write(efd, &buf, sizeof(uint64_t));
                if(rc != sizeof(uint64_t)){
                    handle_error("write");
                }
            }
            printf("Child completed write loop\n");
            exit(EXIT_SUCCESS);
        default:
            sleep(2);
            printf("Parent about to read\n");
            rc = read(efd, &buf, sizeof(uint64_t));
            if(rc != sizeof(uint64_t))
                handle_error("read");
            printf("Parent read %llu from efd\n",(unsigned long long)buf);
        case -1:
            handle_error("fork");
    }
    return 0;
}

在这里插入图片描述

线程间唤醒

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>



void * threadFunc(void *args){
    uint64_t  buf;
    int rc;

    while (1){
        rc = read(*(int *)args, &buf, sizeof(buf));
        if(rc == 8){
            printf("notify success\n");
        }
        printf("rc = %llu, buffer = %lu\n",(unsigned long long)rc, buf);
    }
}


int main(int argc, char** argv)
{
    pthread_t tid;
    int rc;
    uint64_t buf = 1;
    int efd;

    efd = eventfd(0,0);     // blocking
    if(efd == -1){
        perror("eventfd");
    }

    if(pthread_create(&tid, NULL, threadFunc, &efd) != 0){
        perror("pthread_create");
        exit(-1);
    }
    while(1){
        rc = write(efd, &buf, sizeof(buf));

        if(rc != 8){
            perror("write");
        }
        sleep(2);
    }//end while
    close(efd);
}

在这里插入图片描述

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>




int main(int argc, char** argv)
{
    int efd = eventfd(10, 0);
    uint64_t  wdata = 0, rdata = 0;

    if(read(efd, &rdata, 8) == -1){
        perror(NULL);
        if(errno != EAGAIN){
            return 0;
        }
    }
    printf("Init data %lu\n", rdata);  // 10
        // 读完了就没有了

    wdata = 20;
    if(write(efd,&wdata,8) == -1) //父进程写20
    {
        perror(NULL);
        return 0;
    }
    printf("write data %lu\n", wdata);  // 10

    if(fork() == 0){
        if(read(efd,&rdata,8) == -1) //子进程读计数器
        {
            perror(NULL);
            return 0;
        }
        printf("child read  %lu\n", rdata);
        wdata = 30;
        if(write(efd,&wdata,8) == -1)  //子进程写30
        {
            perror(NULL);
            return 0;
        }
        printf("child write  %lu\n", wdata);
        exit(0);
    }
    wait(NULL);
    if(read(efd,&rdata,8) == -1)   //父进程读计数器
    {
        perror(NULL);
        return 0;
    }
    printf("parant read  %lu\n", wdata);


    close(efd);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值