精华内容
下载资源
问答
  • Eventfd 包装器。 安装 下载并安装eventfd go get github.com/sahne/eventfd 用法 package main import ( "log" "github.com/sahne/eventfd" ) func main () { efd , err := eventfd . New () if err != nil {...
  • 完整的linux使用eventfd进行用户态与内核态通信代码,里面还涉及linux用户态线程亲核,以及对应的内核态线程亲核问题。初学者,写了好几天,亲测,可用
  • eventfd

    千次阅读 2019-08-02 19:01:17
    #include <stdio.h> #include <stdlib.h> #include <unistd.h>...sys/eventfd.h> #include <stdint.h> /* Definition of uint64_t */ #define handle_error(msg) d...
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/eventfd.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, i;
        uint64_t u;
        ssize_t rc;
    
        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 (i = 1; i < argc; i++)
            {
                printf("Child writing %s to efd\n", argv[i]);
                u = atoll(argv[i]);
                rc = write(efd, &u, 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, &u, sizeof(uint64_t));
            if (rc != sizeof(uint64_t)) handle_error("read");
    
            printf("Parent read %llu from efd\n", (unsigned long long)u);
        case -1:
            handle_error("fork");
        }
        return 0;
    }

    从上面可以看出来,eventfd 支持三种操作:read、write、close。

    read 返回值的情况如下:

    • 读取 8 字节值,如果当前 counter > 0,那么返回 counter 值,并重置 counter 为 0。
    • 如果调用 read 时 counter 为 0,那么 1)阻塞直到 counter 大于 0;2)非阻塞,直接返回 -1,并设置 errno 为 EAGAIN。如果 buffer 的长度小于 8 字节,那么 read 会失败,并设置 errno 为 EINVAL。
    • 可以看出来 eventfd 只允许一次 read,对应两种状态:0和非0。

    write :

    • 写入一个 64 bit(8字节)的整数 value 到 eventfd。
    • 返回值:counter 最大能存储的值是 0xffff ffff ffff fffe,write 尝试将 value 加到 counter 上,如果结果超过了 max,那么 write 一直阻塞直到 read 操作发生,或者返回 -1 并设置 errno 为 EAGAIN。
    • 可多次 write,一次 read。close 就是关掉 fd。

    以上大概就是我了解的 eventfd,它相比于 pipe来说,少用了一个文件描述符,而且不必管理缓冲区,单纯的事件通知的话,方便很多(它的名字就叫做 eventfd),它可以和事件通知机制很好的融合。

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/eventfd.h>
    #include <pthread.h>
    #include <unistd.h>
    
    int efd;
    
    void* threadFunc()
    {
        uint64_t buffer;
        int rc;
        while (1) 
        {
            rc = read(efd, &buffer, sizeof(buffer));
            if (rc == 8) 
            {
                printf("notify success\n");
            }
    
            printf("rc = %llu, buffer = %lu\n", (unsigned long long)rc, buffer);
        }
    }
    
    int main()
    {
        pthread_t tid;
        int rc;
        uint64_t buf = 1;
    
        efd = eventfd(0, 0); 
        if (efd == -1) 
        {
            perror("eventfd");
        }
    
        if (pthread_create(&tid, NULL, threadFunc, NULL) < 0) 
        {
            perror("pthread_create");
        }
    
        while (1) 
        {
            rc = write(efd, &buf, sizeof(buf));
            if (rc != 8) 
            {
                perror("write");
            }
            sleep(2);
        }
        close(efd);
        return 0;
    }

    输出结果:

    下面我们改下 initval 参数,设为 3,即efd = eventfd(3,0),其他代码不变,运行程序结果如下:

     

    可以看到我们改变initval 这个计数器的初始值,只会影响第一次读到的 buffer 的值,后面都还是 1。

    下面我们把 main 函数的 buf 设为 0,即 counter 每次不变,initval 还是设为 3,看下。

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

    https://blog.csdn.net/Tanswer_/article/details/79008322

    展开全文
  • 主要介绍了Linux 新的API signalfd、timerfd、eventfd使用说明的相关资料,非常不错具有参考借鉴价值,需要的朋友可以参考下
  • Eventfd

    2021-01-07 09:15:48
    eventfd是一个用来通知事件的文件描述符.内核与用户空间互动,肯定要通信.一种方式是内核通知用户空间,告诉这个文件描述符在内核的状态,以便让用户空间采取了相应的方法去处理.epoll机制就是这样,把连接源源不断地...

    eventfd是一个用来通知事件的文件描述符.内核与用户空间互动,肯定要通信.一种方式是内核通知用户空间,告诉这个文件描述符在内核的状态,以便让用户空间采取了相应的方法去处理.epoll机制就是这样,把连接源源不断地交给内核协议栈处理,通过eventfd 等好了去批量处理.

    展开全文
  • 坚持思考,就会很酷在上一期 深入理解 Linux Epoll 池 中随便对 eventfd 提了一嘴,这是一个很妙的 fd 。下面娓娓道来。一切皆文件,但 fd 区分类型?Linux 一切...

    坚持思考,就会很酷

    在上一期 深入理解 Linux Epoll 池 中随便对 eventfd 提了一嘴,这是一个很妙的 fd 。下面娓娓道来。

    一切皆文件,但 fd 区分类型?

    Linux 一切皆文件,但这个文件 fd 也是有类型的,绝大部分人都知道“文件 fd”,知道 socket fd,甚至知道 pipe fd,可能都不知道 fd 还有这么一种叫做 eventfd 的类型。

    eventfd 是什么的?

    不妨拆开来看,event fd ,也就是事件 fd 类型。顾名思义,就是专门用于事件通知的文件描述符( fd )。很多人可能没怎么用,但是用过的人都说:香 !

    哪个版本引入的?

    Linux 2.6.22

    代码位于:fs/eventfd.c

    “事件传递”就是通信嘛。eventfd 不仅可以用于进程间的通信,还能用于用户态和内核态的通信。

    思考一个小问题:我们知道“文件”里是保存东西的,eventfd 既然对应了一个“文件”,那么这个“文件”的内容是什么呢?

    划重点:eventfd 是一个计数相关的fd。计数不为零是有可读事件发生,read 之后计数会清零,write 则会递增计数器。

    这个怎么理解?

    在之前自制文件系统系列中提到过:文件系统的“文件”是抽象的概念,你看到的一切知识文件系统想让你看到的东西。比如 hellofs 中我们没写过任何数据,也会返回 “hello world” 的内容。这个仅仅 hook 到 read/write 调用,然后根据逻辑返回数据而已。

    eventfd 也是如此,eventfd 实现了 read/write 的调用,在调用里面实现了一套计数器的逻辑。write 仅仅是加计数,read 是读计数,并且清零。

    长什么样子呢?笔者找了个进程来观摩下。

    root@ubuntu:~# ll /proc/14168/fd
    lrwx------ 1 root root 64 Jul 10 22:12 3 -> anon_inode:[eventfd]
    

    在 Linux  的 /proc 下每个进程都会有个目录,目录名为进程 ID 号,在这个目录能看到使用的资源信息,其中有个 fd 目录,就是进程打开的所有文件。看出猫腻了不?有个叫做 [eventfd] 的 fd 句柄。

    怎么使用它呢?

     1   句柄创建

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

    举个栗子:

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

    这样就创建出了一个 eventfd 类型的 fd 啦。会在你的 /proc/${pid}/fd/ 目录中有一个 eventfd 类型的句柄。

     2   eventfd api 调用?

    eventfd new 出来之后,总结来说,可以对它做四个事情:

    1. 可以读这个 fd;

    2. 可以写这个 fd;

    3. 可以监听这个 fd;

    4. 可以关闭这个 fd;

    我怎么知道这个知识点的?

    因为在 Linux 内核代码中,我看到了呀。eventfd 就实现了这几个调用。

    static const struct file_operations eventfd_fops = {
    #ifdef CONFIG_PROC_FS
        .show_fdinfo = eventfd_show_fdinfo,
    #endif
        .release = eventfd_release,
        .poll  = eventfd_poll,
        .read  = eventfd_read,
        .write  = eventfd_write,
        .llseek  = noop_llseek,
    };
    

    很明显就能看到以上实现的几个调用就是 eventfd 全部的内容所在。

    简单看下 eventfd 的读写究竟做了什么?

    eventfd 对应的文件内容是一个 8 字节的数字,这个数字是 read/write 操作维护的计数。

    首先,write 的时候,累加计数,read 的时候读取计数,并且清零。

    uint64_t u;
    ssize_t n;
    
    // 写 eventfd,内部 buffer 必须是 8 字节大小;
    n = write(efd, &u, sizeof(uint64_t));
    
    // 读 eventfd
    n = read(efd, &u, sizeof(uint64_t));
    

    读写也就是 read/write,读写这个 fd 很容易理解,但是请注意了,只能 8 个字节。这个读写的内容其实是计数。

    举个栗子:如下,我们连续写 3 次

    // 写 3 次
    write(efd, &u /* u = 1 */ , 8)
    write(efd, &u /* u = 2 */ , 8)
    write(efd, &u /* u = 3 */ , 8)
    

    你猜猜读的时候,是多少?

    read(ebd, &x, 8)
    

    读到的值是 6(因为 1+2+3),理解了吧。



    小结:

    1. 写的时候,写进去一个 8 字节的整数,eventfd 实现的逻辑是累计计数;

    2. 读的时候,读到总计数,并且会清零;

    3. 实现在 eventfd_writeeventfd_read 函数中;

     3   监听 fd

    在 深入理解 Linux Epoll 池 提到过,不是所有的 fd 类型都可用 epoll 池来监听事件的,只有实现了 file_operation->poll 的调用的“文件” fd 才能被 epoll 管理。eventfd 刚好就实现了这个接口。

    eventfd 是专门用来传递事件的 fd ,而 epoll 池则是专门用来管理事件的池子,它们两结合就妙了。

    我们知道 epoll 监听的是可读可写事件。那么你想过 eventfd 的可读可写事件是啥吗?

    可读可写事件”这是个有趣的问题,我们可以去发散下,对比思考下 socket fd,文件 fd:

    • socket fd:可以写入发送数据,那么触发可写事件,网卡数据来了,可以读,触发可读事件;

    • 文件 fd:文件 fd 的可读可写事件就更有意思了,因为文件一直是可写的,所以一直都触发可写事件,文件里的数据也一直是可读的,所以一直触发可读事件。这个也是为什么类似 ext4 这种文件不实现 poll 接口的原因。因为文件 fd 一直是可读可写的,poll 监听没有任何意义;

    回到最初问题:eventfd 呢?它的可读可写事件是什么?

    我们之前说过,eventfd 实现的是计数的功能。所以 eventfd 计数不为 0 ,那么 fd 是可读的。

    由于 eventfd 一直可写(可以一直累计计数),所以一直有可写事件。

    所以,这里有个什么隐藏知识点呢?

    eventfd 如果用 epoll 监听事件,那么都是监听读事件,因为监听写事件无意义。

    关闭 fd

    关闭这个很容易理解,就是不需要这个 fd 了,主动调用一把 Close ,当没有人使用的时候,内核会释放这个 fd 的资源。

    fd 的阻塞属性

    我们知道读写 fd 的时候,可能会遇到阻塞,对于 socket fd 来说,没有数据的时候来读,则会阻塞。写 buffer 满了的时候来写,则会阻塞。

    那么对于 eventfd 呢?它的阻塞有可能是怎么样的?

    read eventfd 的时候,如果计数器的值为 0,就会阻塞(这种就等同于没“文件”内容)。

    这种可以设置 fd 的属性为非阻塞类型,这样读的时候,如果计数器为 0 ,返回 EAGAIN 即可,这样就不会阻塞整个系统。

    通常的用途

    单独的 eventfd 看似平平无奇,但其实有非常重要的应用。下面列举几个小例子:

     1   磁盘的异步 IO( libaio )

    我们之前说过,类似于 ext4 这种文件系统的文件 fd ,其实是不能用 epoll 来管理的,网络 fd 才可以。因为磁盘文件一直可读可写。

    难道文件就自绝于此吗?用不了事件机制吗?只能同步 IO 吗?

    非也。Linux 内核提供了一个叫做 libaio 的机制,能够同时提交多个 io 请求给内核(这种批量递交能提高优化的概率,大量IO堆积到设备的队列中时, 内核可以发挥 IO 调度算法的优势,比如合并 IO 等)。

    aio 请求完成之后,走异步的事件通知。这个事件通知的原理就是把一个 eventfd 和这个 aio 的上下文绑定起来。aio 完成,就会往 eventfd 里面写计数,从而触发可读事件。

     2   kvm 的 ioeventfd 机制

    QEMU 可以将 VM 特定地址关联一个 eventfd,对进行监听,当Guest 进行 IO 操作 exit 到 kvm 后,kvm 可以判断本次exit 是否发生在这段特定地址中,如果是则会通过使用 eventfd 进行事件通知,进行 IO 操作,这种方式对比能节省一些时间。

     3   还有什么朴实的用法?

    最简单的例子,一个消费者和多个生产者,这种就可以借助 eventfd 优雅的完成事件通知。

    生产者:

    多个线程,会把请求投递到一个 list 中,然后唤醒生产者。

    producer:
        // 投递请求到链表
        list_add( global_list, request )
        // 唤醒消费者处理
        write(eventfd, &cnt /* 1 */ , 8)
    

    消费者:

    是一个线程,后台 loop 处理。使用 epoll 监听 eventfd 的可读事件,这样能做到一旦有请求入队,消费者就立马唤醒处理。

    consumer 
        // 添加 eventfd 到监听池
        epoll_ctl(ep, EPOLL_CTL_ADD, eventfd, &ee);
    
    loop:
        // 等待唤醒
        epoll_wait(ep, ... );
        
        // 读取新添加到列表里的元素个数,并且进行处理;
        n = read(eventfd, ... )
        // 遍历链表处理
        for each global_list:
            // do something
    


    总结

    1. Linux 一切皆文件,但 fd 各有不同;

    2. eventfd 实现了 read/write 的接口,本质是一个计数器的实现;

    3. eventfd 实现了 poll 接口,所以可以和 epoll 双剑合璧,实现事件的通知管理;

    4. eventfd 可以和 libaio & epoll 一起,实现 Linux 下的纯异步 IO;

    5. eventfd 监听可读事件才有意义;

    6. ext4 这种文件 fd 一直可读可写,所以实现 poll 毫无意义。eventfd 一直可写,所以监听可写毫无意义;

    7. eventfd 可以结合业务,做一个事件通知的通信机制,非常巧妙;

    后记

    哈哈,奇伢想把 Linux 的句柄类型写个遍,你觉得呢?你最想看了解的是哪种类型的句柄?

    ~完~

    往期推荐

    往期推荐

    深入理解 Linux 的 epoll 机制

    自制文件系统 —— 05 总结:一切都为了狙击“文件”

    存储基础 — 文件描述符 fd 究竟是什么?

    坚持思考,方向比努力更重要。关注我:奇伢云存储

    展开全文
  • qemu中的eventfd——用法与原理

    千次阅读 2019-12-30 22:54:53
    主要介绍eventfd机制,包括用户态的用法和内核态的实现原理

    eventfd可以用于线程或者父子进程间通信,内核通过eventfd也可以向用户空间进程发消息。其核心实现是在内核空间维护一个计数器,向用户空间暴露一个与之关联的匿名fd。不同线程通过读写该fd通知或等待对方,内核通过写该fd通知用户程序

    eventfd用法

    • eventfd机制接口简单,核心只有4个,分别是创建eventfd(eventfd),写eventfd(write),读eventfd(read),监听eventfd(poll/select)。
    1. int eventfd(unsigned int initval, int flags):创建一个eventfd,它的返回值是一个文件fd,可以读写。该接口传入一个初始值initval用于内核初始化计数器,flags用于控制返回的eventfd的read行为。flags如果包含EFD_NONBLOCK,read eventfd将不会阻塞,如果包含EFD_SEMAPHORE,read eventfd每次读之后内核计数器都减1。
    2. ssize_t write(int fd, const void *buf, size_t count):写eventfd,传入一个8字节的buffer,buffer的值增加到内核维护的计数器中。
    3. ssize_t read(int fd, void *buf, size_t count):读eventfd,如果计数器非0,信号量方式返回1,否则返回计数器的值。如果计数器为0,读失败,阻塞模式下会阻塞直到计数器非0,非阻塞模式下返回EAGAIN错误。
    4. int poll(struct pollfd *fds, nfds_t nfds, int timeout):监听eventfd是否可读。

    demo

    • 代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/eventfd.h>
    #include <pthread.h>
    #include <unistd.h>
    
    int efd;
    
    void *threadFunc()
    {
        uint64_t buffer;
        int rc;
        int i = 0;
        while(i++ < 2){
            /* 如果计数器非0,read成功,buffer返回计数器值。成功后有两种行为:信号量方式计数器每次减,其它每次清0。
             * 如果计数器0,read失败,由两种返回方式:EFD_NONBLOCK方式会阻塞,反之返回EAGAIN 
             */
            rc = read(efd, &buffer, sizeof(buffer));
    
            if (rc == 8) {
                printf("notify success, eventfd counter = %lu\n", buffer);
            } else {
                perror("read");
            }
        }
    }
    
    static void
    open_eventfd(unsigned int initval, int flags)
    {
        efd = eventfd(initval, flags);
        if (efd == -1) {
            perror("eventfd");
        }
    }
    
    static void
    close_eventfd(int fd)
    {
        close(fd);
    }
    /* counter表示写eventfd的次数,每次写入值为2 */
    static void test(int counter)
    {
        int rc;
        pthread_t tid;
        void *status;
        int i = 0;
        uint64_t buf = 2;
    
        /* create thread */
        if(pthread_create(&tid, NULL, threadFunc, NULL) < 0){
            perror("pthread_create");
        }
    
        while(i++ < counter){
            rc = write(efd, &buf, sizeof(buf));
            printf("signal to subscriber success, value = %lu\n", buf);
    
            if(rc != 8){
                perror("write");
            }
            sleep(2);
        }
    
        pthread_join(tid, &status);
    }
    
    int main()
    {
        unsigned int initval;
    
        printf("NON-SEMAPHORE BLOCK way\n");
        /* 初始值为4, flags为0,默认blocking方式读取eventfd */
        initval = 4;
        open_eventfd(initval, 0);
        printf("init counter = %lu\n", initval);
    
        test(2);
    
        close_eventfd(efd);
    
        printf("change to SEMAPHORE way\n");
    
        /* 初始值为4, 信号量方式维护counter */
        initval = 4;
        open_eventfd(initval, EFD_SEMAPHORE);
        printf("init counter = %lu\n", initval);
    
        test(2);
    
        close_eventfd(efd);
    
        printf("change to NONBLOCK way\n");
    
        /* 初始值为4, NONBLOCK方式读eventfd */
        initval = 4;
        open_eventfd(initval, EFD_NONBLOCK);
        printf("init counter = %lu\n", initval);
    
        test(2);
    
        close_eventfd(efd);
    
        return 0;
    }
    

    分析

    • demo中创建eventfd使用了三种方式,分别如下:
    1. 阻塞非信号量:以非信号量方式创建的eventfd,在读eventfd之后,内核的计数器归零,下一次再读就会阻塞,除非有进程再次写eventfd。
      在这里插入图片描述
      内核计数器初始值为4,主线程第1次写入2,计数器增至6
      读线程返回6,之后计数器清0,读线程阻塞
      下一次主线程写入2,计数器增至2,读线程返回2
    2. 阻塞信号量:以信号量方式创建的eventfd,在读eventfd之后,内核的计数器减1
      在这里插入图片描述
      内核计数器初始值为4,主线程第一次写入2,计数器增至6
      读线程返回1,计数器减1变成5,读线程循环读返回1,计数器再减1变成4
      主线程写入2计数器增至6
    3. 非阻塞非信号量:读eventfd之后,计数器清0,再次读eventfd返回EAGAIN
      在这里插入图片描述
      内核计数器初始值为4,主线程第一次写入2,计数器增至6
      读线程返回6,计数器清0,读线程循环非阻塞读返回错误码EAGAIN
      主线程写入2计数器增至2
    • demo阻塞读模式下,信号量和非信号量方式如下图所示:
      在这里插入图片描述

    eventfd内核实现

    创建eventfd

    系统调用

    • eventfd系统调用有三个步骤:
    SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
    {
    	......
    	get_unused_fd_flags(flags & EFD_SHARED_FCNTL_FLAGS);		/* 1 */
        file = eventfd_file_create(count, flags);					/* 2 */
        fd_install(fd, file);										/* 3 */
        ......
    }
    
    1. 从进程的文件描述符表(fdt)中查询可用的fd
    2. 在匿名节点文件系统(anon_inodefs)中创建一个文件结构体
    3. 安装文件描述符,将fd和file关联起来
    

    在这里插入图片描述

    eventfd_ctx

    • eventfd_ctx结构体是eventfd实现的核心,如下:
    struct eventfd_ctx {
        struct kref kref;
        wait_queue_head_t wqh;
        /*            
         * Every time that a write(2) is performed on an eventfd, the
         * value of the __u64 being written is added to "count" and a
         * wakeup is performed on "wqh". A read(2) will return the "count"
         * value to userspace, and will reset "count" to zero. The kernel
         * side eventfd_signal() also, adds to the "count" counter and
         * issue a wakeup.
         */
        __u64 count;
        unsigned int flags;
    }; 
    
    1. wqh:等待队列头,所有阻塞在eventfd上的读进程挂在该等待队列上
    2. count:eventfd计数器,当用户程序write eventfd时内核会将值加在计数器上,用户程序read eventfd之后,内核会将值减1或者清0(由EFD_SEMAPHORE标志决定),当计数器为0时,内核会将read进程挂载等待队列头wqh指向的队列上。
      两种方式可以唤醒等待在eventfd上的进程,一个是用户态write,另一个是内核态的eventfd_signal。从这里可以看出eventfd不仅可以用于用户进程相互通信,也可以用作内核通知用户进程的手段。
    3. flags:决定用户read后内核的处理方式,EFD_SEMAPHORE,EFD_CLOEXEC,EFD_NONBLOCK三个取值
    • eventfd_ctx的初始化在eventfd_file_create中实现,如下:
    struct file *eventfd_file_create(unsigned int count, int flags)
    {   
        struct file *file;
        struct eventfd_ctx *ctx;
    	......    
        ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
        kref_init(&ctx->kref);
        init_waitqueue_head(&ctx->wqh);									/* 1 */
        ctx->count = count;
        ctx->flags = flags;
        file = anon_inode_getfile("[eventfd]", &eventfd_fops, ctx,		/* 2 */
                      O_RDWR | (flags & EFD_SHARED_FCNTL_FLAGS));
    	......
        return file;
    }
    
    1. 初始化等待队列
    2. 把eventfd_ctx作为file的private_data存放在file结构体中,这样通过fd可以在进程的fd表中找到相应file结构体,最终找到eventfd_ctx
    
    • eventfd的file操作由eventfd_fops实现,如下:
    static const struct file_operations eventfd_fops = {
    	......
        .read       = eventfd_read,
        .write      = eventfd_write,
    	......
    };  
    

    读eventfd

    • 用户进程读eventfd时,最终会进入 eventfd_read,如下:
    static ssize_t eventfd_read(struct file *file, char __user *buf, size_t count,
                    loff_t *ppos)
    {   
        struct eventfd_ctx *ctx = file->private_data;
    	......
        res = eventfd_ctx_read(ctx, file->f_flags & O_NONBLOCK, &cnt);	/* 如果count非0,将其放在cnt中返回给用户进程 */
    	......
    }
    
    • eventfd_read的核心是eventfd_ctx_read,看一下它的说明和实现:
    /**
     * eventfd_ctx_read - Reads the eventfd counter or wait if it is zero.
     * @ctx: [in] Pointer to eventfd context.
     * @no_wait: [in] Different from zero if the operation should not block.
     * @cnt: [out] Pointer to the 64-bit counter value.
     *
     * Returns %0 if successful, or the following error codes:
     *
     *  - -EAGAIN      : The operation would have blocked but @no_wait was non-zero.
     *  - -ERESTARTSYS : A signal interrupted the wait operation.
     *
     * If @no_wait is zero, the function might sleep until the eventfd internal
     * counter becomes greater than zero.
     */ 
    ssize_t eventfd_ctx_read(struct eventfd_ctx *ctx, int no_wait, __u64 *cnt)
    {               
        ssize_t res;
        /* 声明一个等待队列项并关联本进程的task 
    	 * 等待队列关联的函数为default_wake_function,作为唤醒函数存在
    	 */
        DECLARE_WAITQUEUE(wait, current);
        
        spin_lock_irq(&ctx->wqh.lock);
        *cnt = 0;
        res = -EAGAIN;
        /* 如果count大于0,读进程不阻塞 */
        if (ctx->count > 0)
            res = 0;	
        else if (!no_wait) {
            __add_wait_queue(&ctx->wqh, &wait);	/* 将等待队列项添加到eventfd的等待队列头中 */
            for (;;) {
            	/* 设置阻塞状态 */
                set_current_state(TASK_INTERRUPTIBLE);
                /* 如果count大于0,break,这种情况在第一次循环之后可能发生 
    			 * eventfd的写进程在写完计数器后,会唤醒阻塞在eventfd上的读进程
    			 * 这时读进程重新被调度器调度,进入新一轮的循环,来到这里,重新检查内核计数器
    			 */
                if (ctx->count > 0) {
                    res = 0;
                    break;
                }
                /* 如果有未处理的信号,也break,进行处理 */
                if (signal_pending(current)) {
                    res = -ERESTARTSYS;
                    break;
                }
              
                spin_unlock_irq(&ctx->wqh.lock);
                /* 主动请求调度,当前进程被挂起 */
                schedule();		  
                /* 挂起的进程重新运行 */
                spin_lock_irq(&ctx->wqh.lock);
            }
          	/* 从循环中跳出,将当前进程从eventfd的等待队列中删除 */
            __remove_wait_queue(&ctx->wqh, &wait);
              /* 设置运行状态 */
            __set_current_state(TASK_RUNNING);
        }
        if (likely(res == 0)) {
        	/* 读取counter */
            eventfd_ctx_do_read(ctx, cnt);
            /* 如果eventfd上有阻塞的写进程,将其唤醒 */
            if (waitqueue_active(&ctx->wqh))
                wake_up_locked_poll(&ctx->wqh, POLLOUT);
        }
        spin_unlock_irq(&ctx->wqh.lock);
    
        return res;
    }
    
    • eventfd_ctx_read函数中有一个地方要注意,当执行eventfd_ctx_do_read函数之后,表示真正获取到了计数器的值,这之后会检查阻塞在eventfd上的写进程,因为写进程可能会因为计数器超过范围而阻塞,读进程完成之后计数器值会变小,这个时候可以唤醒写进程重新检查写入的值是否超过范围。这个在写eventfd中会介绍。
    • 看一下真正获取内核计数器的函数:
    static void eventfd_ctx_do_read(struct eventfd_ctx *ctx, __u64 *cnt)
    {           
    	/* 如果是信号量方式,返回给用户进程的counter始终是1,之后内核counter减1
    	 * 如果是非信号量方式,返回给用户进程的counter就是内核维护的counter
    	 * 之后内核counter清0
    	 */
        *cnt = (ctx->flags & EFD_SEMAPHORE) ? 1 : ctx->count;
        ctx->count -= *cnt;
    }
    

    写eventfd

    • 从eventfd_ctx 的数据结构解释中我们了解到,写eventfd有两种场景,一是用户态写,二是内核态写。这两种场景分别用于实现用户态通知和内核态的通知。这节主要介绍用户态发起的eventfd写操作。
    • 写eventfd的主要动作是往计数器加用户态传入的值,有一种情况会阻塞写进程,就是计数器值到达或者超出范围。这时写进程阻塞直到计数器在正常范围内,注意,用户态写eventfd也不允许到达计数器的最大值,因为这个最大值要为内核态的写保留,后面的内核态写会介绍
    static ssize_t eventfd_write(struct file *file, const char __user *buf, size_t count,
                     loff_t *ppos)
    {   
        struct eventfd_ctx *ctx = file->private_data;
        ssize_t res;
        __u64 ucnt;
        /* 声明等待队列项,default_wake_function为唤醒函数 */
        DECLARE_WAITQUEUE(wait, current);
    	/* 用户态传入的buffer长度必须>=8字节,否则返回错误 */
        if (count < sizeof(ucnt))
            return -EINVAL;
        if (copy_from_user(&ucnt, buf, sizeof(ucnt)))
            return -EFAULT;
        /* 如果写入的值等于64bit所能表示的最大值,返回错误 */
        if (ucnt == ULLONG_MAX)
            return -EINVAL;
        spin_lock_irq(&ctx->wqh.lock);
        res = -EAGAIN;
        /* 写入的ucnt后内核计数器不会溢出 */
        if (ULLONG_MAX - ctx->count > ucnt)
            res = sizeof(ucnt);
        /* 写入后内核计数器溢出,并且是阻塞方式打开的eventfd */
        else if (!(file->f_flags & O_NONBLOCK)) {
            __add_wait_queue(&ctx->wqh, &wait);	// 写进程加入等待队列
            for (res = 0;;) {
            	/* 设置阻塞状态 */
                set_current_state(TASK_INTERRUPTIBLE);
                /* 当有读进程read eventfd之后,内核计数器会减少
                 * 读进程读取计数器成功后,会唤醒阻塞在evenfd上的写进程
                 * 这时写进程重新被调度进入新一轮的循环检查,走到这里
                 * */
                if (ULLONG_MAX - ctx->count > ucnt) {
                    res = sizeof(ucnt);
                    break;
                }
                if (signal_pending(current)) {
                    res = -ERESTARTSYS;
                    break;
                }
                spin_unlock_irq(&ctx->wqh.lock);
                /* 主动让出CPU,申请调度器调度 */
                schedule();
                spin_lock_irq(&ctx->wqh.lock);
            }
            /* 跳出循环,从等待队列中退出 */
            __remove_wait_queue(&ctx->wqh, &wait);
            /* 设置运行状态 */
            __set_current_state(TASK_RUNNING);
        }
        if (likely(res > 0)) {
        	/* 增加内核计数器 */
            ctx->count += ucnt;
            /* 如果eventfd上有阻塞的读进程,将其唤醒 */
            if (waitqueue_active(&ctx->wqh))
                wake_up_locked_poll(&ctx->wqh, POLLIN);
        }
        spin_unlock_irq(&ctx->wqh.lock);
    
        return res;
    }
    
    • 在读eventfd的流程中,我们提到读完eventfd之后,会唤醒阻塞在其上的写进程,同样,在写eventfd的流程中,写完eventfd之后会唤醒阻塞在其上的读进程。但是写进程阻塞的场景比较少,它只在计数器到达最大值或者溢出的情况才出现。
    展开全文
  • 通俗易懂说多路复用(3)eventfd 事件通知参考 参考 https://www.cnblogs.com/zhengchunhao/p/5335914.html https://blog.csdn.net/qq_28114615/article/details/97929524
  • linux之eventfd机制-epoll

    2020-10-12 14:47:51
    eventfd机制机制结构体创建eventfdeventfd的操作集show_fdinfoeventfd_releaseeventfd_pollreadwritenoop_llseek 最近在看linux内核驱动,想实现一个支持epoll的机制,看到了eventfd机制,所以就自己记录下eventfd的...
  • qemu中的eventfd——ioeventfd

    千次阅读 2020-04-19 17:28:57
    主要介绍eventfd在qemu中的应用
  • 起源来自于单线程epoll_wait内部处理queue的思考,后来发现了linux支持一种自定义事件的fd,查找资料之余又发现了eventfd还有多进程信号灯的用处。。。本文翻译了eventfd的用法,并在文末附带demo进行实践。 新的...
  • eventfd(2) 结合 select(2) 源码分析 本文代码选自内核 4.17 eventfd(2) - 创建一个文件描述符用于事件通知。 使用 源码分析 参考 #include <sys/eventfd.h> int eventfd(unsigned int initval, int ...
  • 浅谈eventfd

    2019-06-04 15:44:08
    函数定义和头文件 #include <...eventfd()函数会创建一个“eventfd”对象,用户空间的应用程序可以用这个eventfd来实现事件的等待或通知机制,也可以用于内核通知新的事件到用户空间应用程序。...
  • 通过eventfd实现的事件通知机制eventfd的使用eventfd系统函数eventfd - 事件通知文件描述符#include int eventfd(unsigned int initval ,int flags );创建一个能被用户应用程序用于时间等待唤醒机制的eventfd对象....
  • fs 的 eventfd机制

    2019-12-19 14:34:36
    eventfd 主要用于实现线程间通讯,可以用于用户态和内核通讯.主要涉及两个系统调用 SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags) { return do_eventfd(count, flags); } SYSCALL_DEFINE1(eventfd...
  • eventfd原理通俗解析

    2021-03-28 17:26:39
    eventfd原理通俗解析 职能分离:IO线程专门处理数据的收发,计算线程专门处理业务逻辑。 问题:当计算线程接收到要发送给客户端的数据时,不能直接发送,必须由IO线程发送怎么办? 计算线程要通知IO线程发送数据,...
  • eventfd(一)

    千次阅读 2019-08-05 14:08:50
    函数原型:创建的时候可以传入一个计数器的初始值initval。 第二个参数flags在linux 2.6.26之前的版本是...sys/eventfd.h> int eventfd(unsigned int initval, int flags); 分析: flags 可以是以下值的 OR...
  • eventfd阅读笔记

    2020-01-09 11:35:21
    eventfd变种有两个,都会调用到do_eventfd。do_eventfd里分配了eventfd_ctx。eventfd_ctx里重要的东东有waitqueuehead,一个count计数器。然后调用anon_inode_getfd分配file结构,fd,并将fd,file,dentry与匿名...
  • 文章目录未来Eventfd的替代品 User Interrupts详细介绍什么是 User Interrupts底层是如何工作的?内核管理相关的数据结构User IPI应用接口具体的例子 未来Eventfd的替代品 User Interrupts     ...
  • 文章目录前言:为什么需要eventfd?eventfd 设计原理eventfd测试用例C++ 封装eventfd 前言:为什么需要eventfd? 在我们之前的学习中,进行进程/线程间通信的方法有两个: 条件变量 需要使用锁,线程的互斥,唤醒等...
  • 内核态与用户态通信之eventfd使用

    千次阅读 2018-12-28 10:24:28
    首先需要确定eventfd已经被编译进内核,其次还要确定所使用的交叉编译器支持eventfd。 函数原型: #include &lt;sys/eventfd.h&gt; int eventfd(unsigned int initval, int flags); 说明:initval的范围是0...
  • eventfd 的分析与具体例子

    千次阅读 2018-01-08 23:22:55
    eventfd 介绍 Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的。 接口 #include int eventfd(unsigned int initval, int flags); 这个函数会创建一个事件对象...
  • 让事件飞——Linux eventfd 原理 ——如何利用Linux内核资源实现高效优雅的消息通知? Linux eventfd 原理简介与最佳实践 eventfd/timerfd 简介 目前越来越多的应用程序采用事件驱动的方式实现功能,如何高效地利用...
  • Linux进程间通信——eventfd

    万次阅读 2019-07-31 17:19:45
    Table of Contents 什么是eventfd 创建eventfdeventfd ...eventfd包含一个由内核维护的64位无符号整型计数器,创建eventfd时会返回一个文件描述符,进程可以通过对这个文件描述符进行read/writ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,156
精华内容 1,262
关键字:

eventfd

友情链接: 553030.rar