linux eventfd

    eventfd是在linux kernel 2.6.22之后提供的,通过创建一个文件描述符来进行事件通知。可以用于应用程序之间的事件通知,也可用于kernel通知应用程序。

    在linux多线程应用开发中,线程之间的通信,经常都是通过消息队列来完成的,当一个线程向队列中写入消息,然后通过pthread_cond_t条件变量来通知那些等待中的其他线程。有了eventfd object,我们就可以通过向eventfd object写入数据,这样就可以通知到那些等待中的线程。最重要的是,eventfd 可以很好的与 select/epoll结合,这样子可以大大提高线程的效率,不至于线程只能 wait unitl contiditon is ready. 在以前没有eventfd时,其实是可以通过管道来解决的,但是管道需要占用两个fd(一个读,一个写), eventfd object只占用一个fd。

    1. 创建一个eventfd object: create a file descriptor for event notification

#include <sys/eventfd.h>

int eventfd(unsigned int initval, int flags);

    每一个eventfd object,包含有一个无符号的64位整数计数器,并且这个计数器的初始值为initval。

    flags的取值有:

1) EFD_NONBLOCK: 设置为非阻塞

2) EFD_CLOEXEC: close on exec

3) EFD_SEMAPHORE: 在2.6.30中提供,如果设置了这个标志,则在读取上会与多线程中的信号量有些类似。

    通过返回的文件描述符fd, 我们可以读写此eventfd object,直接通过read, write即可完成读写操作。

    2. 读取eventfd

    如果eventfd object 的计数器不为0, 则read() 返回 当前的计数值,并将计数器置为0。如果计数器已经为0,此时读取的话,则会被阻塞,直至计数器不为0时,若设置了EFD_NONBLOCK,则会立即返回,并置errno=EAGAIN。

    若设置了EFD_SEMAPHORE, 如果eventfd object的计数器不为0,则read()返回1,同时计数器减1.

    3. 写eventfd

    写eventfd object,会将其计数器加上当前写入值。如果写入后的值超过了64位整数的最大值,则会阻塞,直至被读取,若设置了EFD_NONBLOCK,则立即返回,并置errno=EAGAIN。

    4. 当我们不再使用eventfd object时,调用close将其关闭释放。

Note:

    在读写eventfd object时,一定要用uint64_t,也就是大小必须是8字节的,否则会返回错误。

    example: 采用EFD_NONBLOCK, 写线程每隔1秒写eventfd, main线程通过epoll监听eventfd是否可读,若可读,则读取eventfd的值,并将其打印。

static void *_write_thread(void *arg)
{
    int efd = (int)arg;
    int i;
    int ret;
    uint64_t count;
    
    for(i = 0; i < 100; ++i){
        count = i + 1;
        ret = write(efd, &count, sizeof(count));
        if(ret <= 0){
            ERR("write eventfd fail");
            break;
        }

        struct timeval tv;
        gettimeofday(&tv, NULL);
        printf("write %d bytes(%llu) at %lds %ldus\n",
               ret, count, tv.tv_sec, tv.tv_usec);
        sleep(1);
    }

    return NULL;
}

int eventfd_test(void)
{
    printf("** eventfd test:\n");

    int efd = eventfd(0, EFD_NONBLOCK);
    assert(efd >= 0);
    
    epoll_fd_t epfd;
    assert(epoll_fd_create(&epfd, 1024) == 0);

    task_t *write_task = task_create("write_task", _write_thread, (void *)efd);
    assert(write_task);

    uint64_t count = 0;
    assert(epoll_fd_add(&epfd, efd, EPOLLIN, NULL) == 0);
    
    while(1){
        int ret = epoll_fd_wait(&epfd, -1);
        if(ret <= 0){
            ERR("epoll_fd_wait fail");
            break;
        }

        if((ret = read(efd, &count, sizeof(count))) < 0){
            ERR("read eventfd fail");
            break;
        }

        struct timeval tv;
        gettimeofday(&tv, NULL);
        printf("read from efd success, read %d bytes(%llu) at %lds %ldus\n",
               ret, count, tv.tv_sec, tv.tv_usec);
    }

    task_destroy(&write_task, NULL, 1);
    epoll_fd_release(&epfd);
    close(efd);
    printf("** eventfd test success\n");
    return 0;
}

    程序运行输出:

** eventfd test:
read from efd success, read 8 bytes(1) at 1445153954s 567052us
write 8 bytes(1) at 1445153954s 567052us
read from efd success, read 8 bytes(2) at 1445153955s 567552us
write 8 bytes(2) at 1445153955s 567552us
read from efd success, read 8 bytes(3) at 1445153956s 568052us
write 8 bytes(3) at 1445153956s 568052us
read from efd success, read 8 bytes(4) at 1445153957s 568552us
write 8 bytes(4) at 1445153957s 568552us
read from efd success, read 8 bytes(5) at 1445153958s 569052us
write 8 bytes(5) at 1445153958s 569052us
read from efd success, read 8 bytes(6) at 1445153959s 569552us
write 8 bytes(6) at 1445153959s 569552us
read from efd success, read 8 bytes(7) at 1445153960s 570052us
write 8 bytes(7) at 1445153960s 570052us
read from efd success, read 8 bytes(8) at 1445153961s 570552us
write 8 bytes(8) at 1445153961s 570552us
read from efd success, read 8 bytes(9) at 1445153962s 571052us
write 8 bytes(9) at 1445153962s 571052us
read from efd success, read 8 bytes(10) at 1445153963s 571552us
write 8 bytes(10) at 1445153963s 571552us
read from efd success, read 8 bytes(11) at 1445153964s 572052us
write 8 bytes(11) at 1445153964s 572052us
read from efd success, read 8 bytes(12) at 1445153965s 572552us
write 8 bytes(12) at 1445153965s 572552us
read from efd success, read 8 bytes(13) at 1445153966s 573052us
write 8 bytes(13) at 1445153966s 573052us
.......

    如果eventfd_create()时设置了EFD_SEMAPHORE时,程序输出结果如下:

** eventfd test:
read from efd success, read 8 bytes(1) at 1445154897s 318192us
write 8 bytes(1) at 1445154897s 322194us
write 8 bytes(2) at 1445154898s 322694us
read from efd success, read 8 bytes(1) at 1445154898s 322694us
read from efd success, read 8 bytes(1) at 1445154898s 322694us
read from efd success, read 8 bytes(1) at 1445154899s 327196us
read from efd success, read 8 bytes(1) at 1445154899s 327196us
read from efd success, read 8 bytes(1) at 1445154899s 327196us
write 8 bytes(3) at 1445154899s 339202us
write 8 bytes(4) at 1445154900s 339702us
read from efd success, read 8 bytes(1) at 1445154900s 339702us
read from efd success, read 8 bytes(1) at 1445154900s 339702us
read from efd success, read 8 bytes(1) at 1445154900s 339702us
read from efd success, read 8 bytes(1) at 1445154900s 339702us
write 8 bytes(5) at 1445154901s 340202us
read from efd success, read 8 bytes(1) at 1445154901s 340202us
read from efd success, read 8 bytes(1) at 1445154901s 340202us
read from efd success, read 8 bytes(1) at 1445154901s 340202us
read from efd success, read 8 bytes(1) at 1445154901s 340202us
read from efd success, read 8 bytes(1) at 1445154901s 340202us
write 8 bytes(6) at 1445154902s 340702us
read from efd success, read 8 bytes(1) at 1445154902s 340702us
read from efd success, read 8 bytes(1) at 1445154902s 340702us
read from efd success, read 8 bytes(1) at 1445154902s 340702us
read from efd success, read 8 bytes(1) at 1445154902s 340702us
read from efd success, read 8 bytes(1) at 1445154902s 340702us
read from efd success, read 8 bytes(1) at 1445154902s 340702us
write 8 bytes(7) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us
read from efd success, read 8 bytes(1) at 1445154903s 341202us

    因为写线程每次写入的数值都是递加的,但是此时main线程每次读取的值是1,所以会出现写1次数值,然后多次读取的情况。

    在学习eventfd的时候,发现linux还提供了signalfd, timerfd。

    signalfd: 把信号抽象为一个文件描述符,当有信号发生时,可以对其read, 可以与epoll/select结合,进行监听;

    timerfd: 把定时器抽象为一个文件描述符,定时器到期时,可以对其read,所以也是可以通过epoll、select进行监听。

    在下面的参考文档中都有详细的说明。


参考:

    http://man7.org/linux/man-pages/man2/eventfd.2.html

    http://man7.org/linux/man-pages/man2/signalfd.2.html

    http://man7.org/linux/man-pages/man2/timerfd_create.2.html



你可能感兴趣的:(linux eventfd)