2017-07-20


eventfd在linux中是一个较新的进程通信方式,和信号量等不同的是event不仅可以用于进程间的通信,还可以用户内核发信号给用户层的进程。eventfd在virtIO后端驱动vHost的实现中作为vhost和KVM交互的媒介,起到了重大作用。本节结合linux源码就eventfd的具体实现坐下简要分析。

eventfd在用户层下有函数

#include <sys/eventfd.h>

 int eventfd(unsigned int initval, int flags);

该函数返回一个文件描述符,类似于其他的文件描述符操作,可以对该描述符进行一系列的操作,如读、写、poll、select等,当然这里我们仅仅考虑read、write。看下该函数的内核实现

  1. SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
  2. {
  3. int fd, error;
  4. struct file *file;
  5. error = get_unused_fd_flags(flags & EFD_SHARED_FCNTL_FLAGS);
  6. if (error < )
  7. return error;
  8. fd = error;
  9. file = eventfd_file_create(count, flags);
  10. if (IS_ERR(file)) {
  11. error = PTR_ERR(file);
  12. goto err_put_unused_fd;
  13. }
  14. fd_install(fd, file);
  15. return fd;
  16. err_put_unused_fd:
  17. put_unused_fd(fd);
  18. return error;
  19. }

代码本身很是简单,首先 获取一个空闲的文件描述符,这个和普通的文件描述符没有区别。然后调用eventfd_file_create创建了一个file结构。该函数中有针对eventfd的一系列操作,看下该函数

  1. struct file *eventfd_file_create(unsigned int count, int flags)
  2. {
  3. struct file *file;
  4. struct eventfd_ctx *ctx;
  5.  
  6. /* Check the EFD_* constants for consistency. */
  7. BUILD_BUG_ON(EFD_CLOEXEC != O_CLOEXEC);
  8. BUILD_BUG_ON(EFD_NONBLOCK != O_NONBLOCK);
  9.  
  10. if (flags & ~EFD_FLAGS_SET)
  11. return ERR_PTR(-EINVAL);
  12.  
  13. ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
  14. if (!ctx)
  15. return ERR_PTR(-ENOMEM);
  16.  
  17. kref_init(&ctx->kref);
  18. init_waitqueue_head(&ctx->wqh);
  19. ctx->count = count;
  20. ctx->flags = flags;
  21.  
  22. file = anon_inode_getfile("[eventfd]", &eventfd_fops, ctx,
  23. O_RDWR | (flags & EFD_SHARED_FCNTL_FLAGS));
  24. if (IS_ERR(file))
  25. eventfd_free_ctx(ctx);
  26.  
  27. return file;
  28. }

这里说明下,每个eventfd在内核对应一个eventfd_ctx结构,该结构后面咱们再细讲,函数中首先给该结构分配 了内存然后做初始化,注意有个等待队列和count,等待队列就是当进程需要阻塞的时候挂在对应evnetfd的等待队列上,而count就是read、write操作的值。接着就调用anon_inode_getfile获取一个file对象,具体也没什么好说的,只是注意这里把刚才分配好的eventfd_ctx作为file结构的私有成员即private_data,并且关联了eventfd自身的操作函数表eventfd_fops, 里面实现的函数不多,如下

  1. static const struct file_operations eventfd_fops = {
  2. #ifdef CONFIG_PROC_FS
  3. .show_fdinfo = eventfd_show_fdinfo,
  4. #endif
  5. .release = eventfd_release,
  6. .poll = eventfd_poll,
  7. .read = eventfd_read,
  8. .write = eventfd_write,
  9. .llseek = noop_llseek,
  10. };

我们重点看read和write函数。当用户空间对eventfd文件描述符发起read操作时,最终要调用到上面函数表中的eventfd_read函数,

  1. static ssize_t eventfd_read(struct file *file, char __user *buf, size_t count,
  2. loff_t *ppos)
  3. {
  4. struct eventfd_ctx *ctx = file->private_data;
  5. ssize_t res;
  6. __u64 cnt;
  7. if (count < sizeof(cnt))
  8. return -EINVAL;
  9. res = eventfd_ctx_read(ctx, file->f_flags & O_NONBLOCK, &cnt);
  10. if (res < )
  11. return res;
  12. return put_user(cnt, (__u64 __user *) buf) ? -EFAULT : sizeof(cnt);
  13. }

首先从private_data获取eventfd_ctx,然后判断请求读取的大小是否满足条件,这里count是64位即8个字节,所以最小读取8个字节,如果不足则错误。没问题就调用eventfd_ctx_read,该函数实际返回eventfd_ctx中的count计数,并清零,如果读取有问题则返回,否则把值写入到用户空间。前面eventfd_ctx_read是读取的核心,什么时候会返回小于0的值呢,我们看下该函数的实现

  1. ssize_t eventfd_ctx_read(struct eventfd_ctx *ctx, int no_wait, __u64 *cnt)
  2. {
  3. ssize_t res;
  4. DECLARE_WAITQUEUE(wait, current);
  5.  
  6. spin_lock_irq(&ctx->wqh.lock);
  7. *cnt = ;
  8. res = -EAGAIN;
  9. if (ctx->count > )
  10. res = ;
  11. else if (!no_wait) {
  12. /*add to wait queue*/
  13. __add_wait_queue(&ctx->wqh, &wait);
  14. for (;;) {
  15. /*设置阻塞状态*/
  16. set_current_state(TASK_INTERRUPTIBLE);
  17. /*如果信号变为有状态。则break*/
  18. if (ctx->count > ) {
  19. res = ;
  20. break;
  21. }
  22. /*如果有未处理的信号,也break,进行处理*/
  23. if (signal_pending(current)) {
  24. res = -ERESTARTSYS;
  25. break;
  26. }
  27. /*否则触发调度器执行调度*/
  28. spin_unlock_irq(&ctx->wqh.lock);
  29. schedule();
  30. spin_lock_irq(&ctx->wqh.lock);
  31. }
  32. /*remove from the wait queue*/
  33. __remove_wait_queue(&ctx->wqh, &wait);
  34. /*set processs state*/
  35. __set_current_state(TASK_RUNNING);
  36. }
  37. if (likely(res == )) {
  38. /*read fdcount again*/
  39. eventfd_ctx_do_read(ctx, cnt);
  40. /**/
  41. if (waitqueue_active(&ctx->wqh))
  42. wake_up_locked_poll(&ctx->wqh, POLLOUT);
  43. }
  44. spin_unlock_irq(&ctx->wqh.lock);
  45.  
  46. return res;
  47. }

该函数比较长,我们慢慢分析,首先操作eventfd_ctx要加锁保证安全。起初res初始化为-EAGAIN,如果count计数大于0,那么对res置0,否则意味着count=0(count不会小于0),这种情况下看传递进来的参数标志,如果设置了O_NONBLOCK,则就不需等待,直接返回res.这正是前面说的返回值小于0的情况。如果没有指定O_NONBLOCK标志,此时由于读取不到count值(count值为0),就会在这里阻塞。具体把当前进程加入到eventfd_ctx的等待队列,这里有必要说下DECLARE_WAITQUEUE(wait, current),该宏声明并初始化一个wait_queue_t对象,其关联的函数为default_wake_function,是作为唤醒函数存在。OK,接下上面,加入到队列后进入一个死循环,设置当前进程状态为TASK_INTERRUPTIBLE,并不断检查count值,如果count大于0了,意味着有信号了,就设置res=0,然后break,然后把进程从等待队列去掉,然后设置状态TASK_RUNNING。如果count值为0,则检查是否有挂起的信号,如果有信号,同样需要先对信号进行处理,不过这就以为这read失败了。都么有的话就正常阻塞,调用调度器进行调度。break之后,如果res==0,对count值进行读取,这里对应上面循环中判断count值大于0的情况。具体读取通过eventfd_ctx_do_read函数,该函数很简单

  1. static void eventfd_ctx_do_read(struct eventfd_ctx *ctx, __u64 *cnt)
  2. {
  3. *cnt = (ctx->flags & EFD_SEMAPHORE) ? : ctx->count;
  4. ctx->count -= *cnt;
  5. }

如果没有指定EFD_SEMAPHORE标志就返回count值,该标志是指定eventfd像信号量一样使用,不过在2.6之后的内核都设置为0了。然后对count做减法,实际上减去之后就为0了。在读取值之后count值就变小了,之前如果有在该eventfd上阻塞的write进程,现在就可以唤醒了,所以这里检查了下,如果等待队列还有进程,则调用wake_up_locked_poll对对应的进程进行唤醒。

用户空间的write操作最终要调用到eventfd_write,不过该函数的实现和上面read操作类似,这里就不重复,感兴趣可以自行分析源码。前面说内核也可以主动的对eventfd发送信号,这里就是通过eventfd_signal函数实现

  1. __u64 eventfd_signal(struct eventfd_ctx *ctx, __u64 n)
  2. {
  3. unsigned long flags;
  4.  
  5. spin_lock_irqsave(&ctx->wqh.lock, flags);
  6. if (ULLONG_MAX - ctx->count < n)
  7. n = ULLONG_MAX - ctx->count;
  8. ctx->count += n;
  9. /*mainly judge if wait is empty*/
  10. if (waitqueue_active(&ctx->wqh))
  11. wake_up_locked_poll(&ctx->wqh, POLLIN);
  12. spin_unlock_irqrestore(&ctx->wqh.lock, flags);
  13.  
  14. return n;
  15. }

该函数和write函数类似,不过不会阻塞,如果指定的n太大导致count加上之后超过ULLONG_MAX,就去n为当前count和ULLONG_MAX的差值,即不会让count溢出。然后如果等待队列有等待的进程,则对其进程唤醒,当然唤醒的应该是需要读操作的进程。

到这里对于eventfd的介绍基本就完成了,总的来说很简单的一个东西,不过经过上面分析不难发现,eventfd应该归结于低级通信行列,即不适用于传递大量数据,仅仅用于通知或者同步操作,还要注意的是,该文件描述符并不对应固定的磁盘文件,故类似于无名管道,这里也仅仅用于有亲缘关系之间的进程通信!。

关于eventfd的使用方法,参考手册:https://linux.die.net/man/2/eventfd

以马内利

参考资料:

linux内核3.10.1源码

Linux eventfd分析的更多相关文章

  1. linux内核分析作业8:理解进程调度时机跟踪分析进程调度与进程切换的过程

    1. 实验目的 选择一个系统调用(13号系统调用time除外),系统调用列表,使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 分析汇编代码调用系统调用的工作过程,特别是参数的传递的方 ...

  2. Linux内核分析作业7:Linux内核如何装载和启动一个可执行程序

            1.可执行文件的格式 在 Linux 平台下主要有以下三种可执行文件格式: 1.a.out(assembler and link editor output 汇编器和链接编辑器的输出) ...

  3. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...

  4. linux内核分析作业5:分析system_call中断处理过程

    1.增加 Menu 内核命令行 调试系统调用. 步骤:删除menu git clone        (tab) make rootfs 这就是我们将 fork 函数写入 Menu 系统内核后的效果, ...

  5. linux内核分析作业:以一简单C程序为例,分析汇编代码理解计算机如何工作

    一.实验 使用gcc –S –o main.s main.c -m32 命令编译成汇编代码,如下代码中的数字请自行修改以防与他人雷同 int g(int x) { return x + 3; } in ...

  6. linux内核分析作业:操作系统是如何工作的进行:完成一个简单的时间片轮转多道程序内核代码

    计算机如何工作 三个法宝:存储程序计算机.函数调用堆栈.中断机制. 堆栈 函数调用框架 传递参数 保存返回地址 提供局部变量空间 堆栈相关的寄存器 Esp 堆栈指针  (stack pointer) ...

  7. linux内核分析作业3:跟踪分析Linux内核的启动过程

    内核源码目录 1. arch:录下x86重点关注 2. init:目录下main.c中的start_kernel是启动内核的起点 3. ipc:进程间通信的目录 实验 使用实验楼的虚拟机打开shell ...

  8. linux内核分析作业4:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    系统调用:库函数封装了系统调用,通过库函数和系统调用打交道 用户态:低级别执行状态,代码的掌控范围会受到限制. 内核态:高执行级别,代码可移植性特权指令,访问任意物理地址 为什么划分级别:如果全部特权 ...

  9. 《Linux内核分析》期末总结

    Linux内核设计期中总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 前八周博客汇总及总结 Linux内核设计第一周——从汇编语言出发理解计算机工作原理 我们学习了汇编语言的基础知识,这一 ...

随机推荐

  1. localhost 和 127.0.0.1

    转自:http://ordinarysky.cn/?p=431localhost与127.0.0.1的区别是什么?相信有人会说是本地ip,曾有人说,用127.0.0.1比localhost好,可以减少 ...

  2. java web 复选框checked

    熟悉web前端开发的人都知道,判断复选框是否选中是经常做的事情,判断的方法很多,但是开发过程中常常忽略了这些方法的兼容性,而是实现效果就好了.博主之前用户不少方法,经常Google到一些这个不好那个不 ...

  3. 第五章 使用 SqlSession(MyBatis)

    在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession.一旦你获得一个 session 之后,你可以使用它来执行映射语句,提交或回滚连接,最后,当不再需要 ...

  4. VS2013+opencv2.4.9配置

    VS2013+opencv2.4.9(10)配置[zz] - yifeier12 - 博客园 http://www.cnblogs.com/cuteshongshong/p/4057193.html ...

  5. mysql数据库中,flush logs语句的作用是什么呢?

    需求描述: 今天在研究mysql数据库的备份和恢复,用到了flush logs这个SQL语句. 所以,在此进行测试,并且记录该SQL语句的作用. 概念描述: 在mysql数据库,如果数据库启动的时候, ...

  6. ubuntu 16.04 appstreamcli 问题

    http://blog.csdn.net/zhbpd/article/details/77508675

  7. easyui-textbox 只读设置取消

    <script> $(function () { $("#txt_beginAmount").attr('readonly', true); $("#txt_ ...

  8. Swift coreAnimation 加计时器写的游戏《飞机大战》

    近期在学习swift的动画和图层.就用现学的东西写了个游戏,基本思想 基本功能都实现了.图片都是在网上找得.希望能帮助大家更好的理解动画和图层. 声明下,我是刚開始学习的人,代码写的不好.大家应该都能 ...

  9. BigDecimal类(精度计算类)的加减乘除

    BigDecimal类 对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数 ...

  10. pow()

    pow() 如果接收两个参数,如 pow(x, y),则结果相当于 x**y,也就是 x 的 y 次方pow() 如果接收三个参数,如 pow(x, y, z),则结果相当于 (x**y) % z,也 ...