epoll原理_Epoll的简介及原理

epoll 全称 eventpoll,是 linux 内核实现IO多路复用(IO multiplexing)的一个实现。IO多路复用的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。

在 linux 中,和 epoll 类似的有 select 和 poll。网上很多文章着重关注 epoll 与 select 的区别,或者关注 epoll 中 level-triggered 和 edge-triggered 的行为。

epoll 和 select

有不少文章里列出的 epoll 和 select 的区别不够准确,甚至中文维基百科里 select 复杂度 O(n),epoll 复杂度 O(log n) 的说法是错的。epoll 和 select 的主要区别是:

epoll 监听的 fd(file descriptor)集合是常驻内核的,它有 3 个系统调用 (epoll_create, epoll_wait, epoll_ctl),通过 epoll_wait 可以多次监听同一个 fd 集合,只返回可读写那部分

select 只有一个系统调用,每次要监听都要将其从用户态传到内核,有事件时返回整个集合。

从性能上看,如果 fd 集合很大,用户态和内核态之间数据复制的花销是很大的,所以 select 一般限制 fd 集合最大1024。

从使用上看,epoll 返回的是可用的 fd 子集,select 返回的是全部,哪些可用需要用户遍历判断。

尽管如此,epoll 的性能并不必然比 select 高,对于 fd 数量较少并且 fd IO 都非常繁忙的情况 select 在性能上有优势。

level-triggered 和 edge-triggered

关于 level-triggered 和 edge-triggered,Linux man page (epoll(7): I/O event notification facility) 已经讲得很明白了。

epoll 的原理

pollable

首先,linux 的 file 有个 pollable 的概念,只有 pollable 的 file 才可以加入到 epoll 和 select 中。一个 file 是 pollable 的当且仅当其定义了 file->f_op->pollfile->f_op->poll 的形式如下

__poll_t poll(struct file *fp, poll_table *wait)

不同类型的 file 实现不同,但做的事情都差不多:

  1. 通过 fp 拿到其对应的 waitqueue
  2. 通过 wait 拿到外部设置的 callback[[1]]
  3. 执行 callback(fp, waitqueue, wait),在 callback 中会将另外一个 callback2[[2]] 注册到 waitqueue[[3]]中,此后 fp 有触发事件就会调用 callback2

waitqueue 是事件驱动的,与驱动程序密切相关,简单来说 poll 函数在 file 的触发队列中注册了个 callback, 有事件发生时就调用callback。感兴趣可以根据文后 [[4]] 的提示看看 socket 的 poll 实现

了解了 pollable 我们看看 epoll 的三个系统调用 epoll_create, epoll_ctl, epoll_wait

epoll_create 只是在内核初始化一下数据结构然后返回个 fd

epoll_ctl 支持添加移除 fd,我们只看添加的情况。epoll_ctl 的主要操作在 ep_insert, 它做了以下事情:

  1. 初始化一个 epitem,里面包含 fd,监听的事件,就绪链表,关联的 epoll_fd 等信息
  2. 调用 ep_item_poll(epitem, ep_ptable_queue_proc[[1]])ep_item_poll 会调用 vfs_pollvfs_poll 会调用上面说的 file->f_op->pollep_poll_callback[[2]] 注册到 waitqueue
  3. 调用 ep_rbtree_insert(eventpoll, epitem)epitem 插入 evenpoll 对象的红黑树,方便后续查找

ep_poll_callback

在了解 epoll_wait 之前我们还需要知道 ep_poll_callback 做了哪些操作

  1. ep_poll_callback 被调用,说明 epoll 中某个 file 有了新事件
  2. eventpoll 对象有一个 rdllist 字段,用链表存着当前就绪的所有 epitem
  3. ep_poll_callback 被调用的时候将 file 对应的 epitem 加到 rdllist 里(不重复)
  4. 如果当前用户正在 epoll_wait 阻塞状态 ep_poll_callback 还会通过 wake_up_lockedepoll_wait 唤醒

epoll_wait 主要做了以下操作:

  1. 检查 rdllist,如果不为空则去到 7,如果为空则去到 2
  2. 设置 timeout
  3. 开始无限循环
  4. 设置线程状态为 TASK_INTERRUPTIBLE [参看 Sleeping in the Kernal](Kernel Korner - Sleeping in the Kernel)
  5. 检查 rdllist 如果不为空去到 7, 否则去到 6
  6. 调用 schedule_hrtimeout_range 睡到 timeout,中途有可能被 ep_poll_callback 唤醒回到 4,如果真的 timeout 则 break 去到 7
  7. 设置线程状态为 TASK_RUNNING,rdllist如果不为空时退出循环,否则继续循环
  8. 调用 ep_send_eventsrdllist 返回给用户态

epoll 的原理基本上就这些,还有很多细节如红黑树在哪里用,怎样实现 level-triggered 和 edge-triggered... 我还没看。

PS. 普通文件不是 pollable 的,详情请看 epoll_does_not_work_with_file

附录,以下列出的目录都是 linux 源码目录

  • [[1]] 对应 /fs/eventpoll.c 中的 ep_ptable_queue_proc
  • [[2]] 对应 /fs/evenpoll.c 中的 ep_poll_callback
  • [[3]] 通过 add_wait_queue 函数
  • [[4]] /linux/net/sock.csock_poll 是 socket 的 poll 实现,里面最后一步 sock->ops->poll() 对不同设备(比如蓝牙,以太网)不一样,大多会调用 /linux/net/core/datagram.c 里的 datagram_poll 然后去到 /linux/include/net/sock.h 中的 sock_poll_wait 然后去到 /linux/include/linux/poll.h 中的 poll_waitpoll_wait 其实只是调用一下 callback
  • [[5]] /linux/include/linux/poll.h 中的 vfs_poll
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值