最高效的进(线)程间通信机制--eventfd


我们常用的进程(线程)间通信机制有管道,信号,消息队列,信号量,共享内存,socket等等,其中主要作为进程(线程)间通知/等待的有管道pipe和socketpair。线程还有特别的condition。

今天来看一个liunx较新的系统调用,它是从LINUX 2.6.27版本开始增加的,主要用于进程或者线程间的通信(如通知/等待机制的实现)。


首先来看一下函数原型:

#include <sys/eventfd.h>

       int eventfd(unsigned int initval, int flags);

下面是它man手册中的描述,我照着翻译了一遍(我英语四级434,你们要是怀疑下文的话,可以直接去man eventfd ^^):


eventfd()创建了一个"eventfd object",能在用户态用做事件wait/notify机制,通过内核取唤醒用户态的事件。这个对象保存了一个内核维护的uint64_t类型的整型counter。这个counter初始值被参数initval指定,一般初值设置为0。


它的标记可以有以下属性:

EFD_CLOECEX,EFD_NONBLOCK,EFD_SEMAPHORE。

在linux直到版本2.6.26,这个flags参数是没用的,必须指定为0。


它返回了一个引用eventfd object的描述符。这个描述符可以支持以下操作:

read:如果计数值counter的值不为0,读取成功,获得到该值。如果counter的值为0,非阻塞模式,会直接返回失败,并把errno的值指纹EINVAL。如果为阻塞模式,一直会阻塞到counter为非0位置。

write:会增加8字节的整数在计数器counter上,如果counter的值达到0xfffffffffffffffe时,就会阻塞。直到counter的值被read。阻塞和非阻塞情况同上面read一样。

close:这个操作不用说了。


重点是支持这个:

 poll(2), select(2) (and similar)
              The returned file descriptor supports poll(2) (and analogously epoll(7)) and select(2), as follows:
              *  The file descriptor is readable (the select(2) readfds argument; the poll(2) POLLIN flag) if the  counter  has  a
                 value greater than 0.
              *  The  file descriptor is writable (the select(2) writefds argument; the poll(2) POLLOUT flag) if it is possible to
                 write a value of at least "1" without blocking.
              *  If an overflow of the counter value was detected, then select(2) indicates the  file  descriptor  as  being  both
                 readable  and  writable,  and  poll(2)  returns a POLLERR event.  As noted above, write(2) can never overflow the
                 counter.  However an overflow can occur if 2^64 eventfd "signal posts" were performed by the KAIO subsystem (the‐
                 oretically possible, but practically unlikely).  If an overflow has occurred, then read(2) will return that maxi‐
                 mum uint64_t value (i.e., 0xffffffffffffffff).
              The eventfd file descriptor also supports the other file-descriptor multiplexing APIs: pselect(2) and ppoll(2)
.


它的内核代码实现是这样子的:

int eventfd_signal(struct eventfd_ctx *ctx, int n)
{
    unsigned long flags;

    if (n < 0)
        return -EINVAL;
    spin_lock_irqsave(&ctx->wqh.lock, flags);
    if (ULLONG_MAX - ctx->count < n)
        n = (int) (ULLONG_MAX - ctx->count);
    ctx->count += n;
    if (waitqueue_active(&ctx->wqh))
        wake_up_locked_poll(&ctx->wqh, POLLIN);
    spin_unlock_irqrestore(&ctx->wqh.lock, flags);

    return n;
}

本质就是做了一次唤醒,不用read,也不用write,与eventfd_write的区别是不用阻塞。


说了这么多,我们来看一个例子,理解理解其中的含义:

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.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, j;
    uint64_t u;
    ssize_t s;
    
    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 (j = 1; j < argc; j++) {
            printf("Child writing %s to efd\n", argv[j]);
            u = strtoull(argv[j], NULL, 0); 
            /* strtoull() allows various bases */
     s = write(efd, &u, sizeof(uint64_t));
            if (s != sizeof(uint64_t))
                handle_error("write");
            }
            printf("Child completed write loop\n");
            exit(EXIT_SUCCESS);
        default:
            sleep(2);

            printf("Parent about to read\n");
            s = read(efd, &u, sizeof(uint64_t));
            if (s != sizeof(uint64_t))
                handle_error("read");
            printf("Parent read %llu (0x%llx) from efd\n",
                   (unsigned long long) u, (unsigned long long) u);
            exit(EXIT_SUCCESS);

        case -1:
            handle_error("fork");
  }
}
输出:

$ ./a.out 1 2 4 7 14
           Child writing 1 to efd
           Child writing 2 to efd
           Child writing 4 to efd
           Child writing 7 to efd
           Child writing 14 to efd
           Child completed write loop
           Parent about to read
           Parent read 28 (0x1c) from efd

注意:这里用了sleep(2)保证子进程循环写入完毕,得到的值就是综合28。如果不用sleep(2)来保证时序,当子进程写入一个值,父进程会立马从eventfd读出该值。


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Linux 系统中,eventfd 可以通过事件循环机制实现。事件循环机制是一个通用的机制,可以用于管理多种类型的事件和文件描述符,包括 socket、pipe、timer 等。它的核心是一个等待事件发生的循环,当有事件发生时,循环将调用相应的处理函数行处理。 eventfd 本质上是一个文件描述符,它可以被添加到事件循环中行监控。在 Linux 系统中,事件循环机制通过 epoll 系统调用来实现。 下面是一个使用事件循环机制实现的 eventfd 示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/epoll.h> #include <sys/eventfd.h> #define MAX_EVENTS 10 int main() { int efd, nfds, i; struct epoll_event ev, events[MAX_EVENTS]; uint64_t count = 0; // 创建 eventfd 文件描述符 efd = eventfd(0, EFD_NONBLOCK); if (efd == -1) { perror("eventfd"); exit(EXIT_FAILURE); } // 创建 epoll 实例 int epfd = epoll_create1(0); if (epfd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } // 将 eventfd 添加到 epoll 监控列表中 ev.events = EPOLLIN | EPOLLET; ev.data.fd = efd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &ev) == -1) { perror("epoll_ctl: efd"); exit(EXIT_FAILURE); } // 等待事件发生 while (1) { nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } // 处理所有事件 for (i = 0; i < nfds; i++) { if (events[i].data.fd == efd) { // 读取 eventfd 中的计数器值 if (read(efd, &count, sizeof(uint64_t)) == -1) { perror("read"); exit(EXIT_FAILURE); } printf("eventfd count: %llu\n", (unsigned long long) count); } } } close(efd); return 0; } ``` 在上面的代码中,首先创建了一个 eventfd 文件描述符,并将其添加到 epoll 实例中行监控。然后,入一个无限循环,等待事件的发生。当有事件发生时,通过 epoll_wait 函数获取事件,然后处理所有事件。如果是 eventfd 文件描述符的事件,则读取其中的计数器值,并行处理。 需要注意的是,在使用 eventfd 时,必须将其设置为非阻塞模式(EFD_NONBLOCK),否则可能会导致阻塞。此外,eventfd 的计数器值必须是一个 64 位的无符号整数,因此在读取时需要使用 uint64_t 类型行存储。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值