eventfd 的分析与具体例子

eventfd 介绍

Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的。

接口

#include 
int eventfd(unsigned int initval, int flags);

这个函数会创建一个事件对象(eventfd object),返回一个文件描述符,用来实现进程或线程间的等待/通知(wait/notify)机制。内核为这个对象维护了一个无符号的64位整形计数器 counter,用第一个参数(initval)初始化这个计数器,创建时一般可将其设为0,后面有例子测试这个参数产生的效果。

flags 可以使用三个宏:

  1. EFD_CLOEXEC:给这个新的文件描述符设置 FD_CLOEXEC 标志,即 close-on-exec,这样在调用 exec 后会自动关闭文件描述符。因为通常执行另一个程序后,会用全新的程序替换子进程的正文,数据,堆和栈等,原来的文件描述符变量也不存在了,这样就没法关闭没用的文件描述符了。
  2. EFD_NONBLOCK:设置文件描述符为非阻塞的,设置了这个标志后,如果没有数据可读,就返回一个 EAGAIN 错误,不会一直阻塞。
  3. EFD_SEMAPHORE:不了解。

具体例子

1.我们可以看个父子进程间通信的例子,如下:

/*
 * @filename:    eventfd.c
 * @author:      Tanswer
 * @date:        2018年01月08日 22:04:46
 * @description:
 */

#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, i;

    uint64_t u;
    ssize_t rc;

    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(i=1; iprintf("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 的分析与具体例子_第1张图片

父进程 sleep(2) ,保证子进程向 eventfd 连续写入, 然后父进程从 eventfd 中读取。

2.再看个线程间唤醒的例子

/*
 * @filename:    eventfd_pthread.c
 * @author:      Tanswer
 * @date:        2018年01月08日 22:46:38
 * @description:
 */

#include 
#include 
#include 
#include 
#include 

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);
    }//end while
}

int main()
{
    pthread_t tid;
    int rc;
    uint64_t buf = 1;

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

    //create thread
    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);
    }//end while
    close(efd);
    return 0;
}

eventfd 的分析与具体例子_第2张图片

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

eventfd 的分析与具体例子_第3张图片

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

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

    uint64_t buf = 0;

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

运行效果如下:

eventfd 的分析与具体例子_第4张图片

可以看到唤醒了一次,然后就一直阻塞了。


从上面可以看出来,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。
  • 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),它可以和事件通知机制很好的融合。

你可能感兴趣的:(Linux网络编程,Muduo网络库源码分析)