图文详解Linux EPOLL

SELECT/POLL存在的问题

Linux EPOLL是对select和poll的改进,因此在分析epoll之前需要对select/poll有一定的了解。

select存在的几个问题:

  1. 每次调用select,都需要把监控的fds集合从用户态拷贝到内核态,高并发场景下这样的拷贝会使得消耗的资源很大;
  2. 监听的端口数量有限;
  3. 有事件返回时,需要遍历fds集合,找到可读可写的事件;

poll对监听端口数量限制做了改进,但还是存在2个问题:

  1. 每次调用poll,都需要把监控的pollfds集合从用户态拷贝到内核态,高并发场景下这样的拷贝会使得消耗的资源很大;
  2. 有事件返回时,需要遍历pollfds集合,找到可读可写的事件;

什么是EPOLL

取维基百科上的一段话 (epollis a Linux kernelsystem call for a scalable I/O event notification mechanism, first introduced in version 2.5.44 of the Linux kernel.[1] Its function is to monitor multiple file descriptors to see whether I/O is possible on any of them. It is meant to replace the older POSIXselect(2)andpoll(2)system calls, to achieve better performance in more demanding applications, where the number of watched file descriptorsis large (unlike the older system calls, which operate in O(n) time,epolloperates inO(1) time[2]).)

EPOLL的使用

  1. epoll_create: 负责创建一个池子,一个监控和管理文件描述符句柄的池子;
  2. epoll_ctl: 负责管理这个池子里的fd增、删、改;(驱动要支持epoll,必须要实现poll ops);
  3. epoll_wait: 负责去就绪队列拿出就绪的事件元素,如果没有,则让出cpu,但是只要有监控的文件描述符发生事件,则立马唤醒,并返回事件元素;

 

format,png

 

format,png

红黑树 – RB-tree

epoll中最重要的数据结构就是红黑树,因此在讨论epoll前先简单的介绍一下红黑树的基本概念。

红黑树的基本规则:

  1. 每个节点都有一个颜色,红色或黑色;
  2. 树的根节点为黑色;
  3. 树中不存在两个相邻的红色节点;
  4. 从任意一个节点到其后代NULL节点的每条路径都具有相同数量的黑色节点;

 

format,png

应用场景:

  1. Linux/RTOS CFS进程调度
  2. Java Hashmap中链表<->红黑树
  3. Eventpoll使用红黑树管理事件块

对于一颗有 ​ n 个结点的红黑树而言,不论查找、删除、查找最大值、最小值等等的时间复杂度都是 ​ O(logn) .

!单从查找效率上讲,Hashtable的效率肯定是最高的,红黑树只是为了平衡查找、插入、内存开销等

找到一个在线的红黑树模拟网站,有兴趣的可以看一下:Red/Black Tree Visualization

epoll_create实现

在用户进程调用 epoll_create 时,内核会创建一个 struct eventpoll 的内核对象。并把它关联到当前进程的已打开文件列表中。

 

format,png

eventpoll 这个结构体中的几个成员的含义如下:

poll_wait:epoll_create返回一个文件描述符fd给用户层,因此这个fd也可以被poll;

wq:等待队列链表。epoll_wait时如果就绪队列为空,则将当前进程挂载到wq,并阻塞;

rbr:一棵红黑树。为了支持对大量监听事件元素的高效查找、插入和删除,eventpoll 内部使用了一棵红黑树。通过这棵树来管理用户进程下添加进来的所有fd;

rdllist:就绪的epitem的链表。当有事件就绪的时候,内核会把就绪的epitem挂载到rdllist 链表里。这样应用进程只需要判断链表就能找出就绪进程,而不用去遍历整棵树。

 

format,png

 

format,png

epoll_ctl实现

epoll的事件注册函数,epoll_ctl向 epoll对象中添加修改或者删除感兴趣的事件; 它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

 

format,png

epfd: epoll_create的返回值

op:

EPOLL_CTL_ADD:注册新的fd到epfd中

EPOLL_CTL_MOD:修改已经注册的fd的监听事件

EPOLL_CTL_DEL:从epfd中删除一个fd

fd:需要监听的文件描述符

event:需要监听的事件

uint32_t events: 监听的事件集合

epoll_data_t data: 用户自定义数据

epoll_ctl添加eventfd

在使用 epoll_ctl 注册每一个监听的文件描述符的时候,内核会做如下三件事情:

1. 分配一个红黑树节点对象 epitem

2. 添加等待事件到 eventfd 的等待队列中,其回调函数是 ep_poll_callback

3. 将 epitem 插入到 epoll 对象的红黑树里 (根据fd确认节点存放位置,左节点值永远小于右节点,父节点永远大于子节点)

 

format,png

epoll_wait实现

epoll_wait用于等待事件的产生,当它被调用时它先观察 eventpoll的rdllist 链表里有没有数据。有数据就立即返回,没有数据就创建一个等待队列项,将其添加到 eventpoll 的等待队列上,然后让出cpu。

 

format,png

epfd: epoll_create的返回值

events:events是用户层分配好的epoll_event结构体数组,内核会把发生的事件复制到events数组中

maxevents:表示本次可以返回的最大事件数目

timeout:表示如果没有检测到事件发生时最多等待的事件,如果timeout为0,表示epoll_wait时如果rdllist链表为空,也立即返回,不等待

 

format,png

之前epoll_ctl – EPOLL_CTL_ADD后,内核为每一个eventfd上都添加了一个等待队列项。在epoll_wait运行完之后,又在eventpoll对象上添加了等待队列元素。至此,内核中有关epoll的数据结构如下:

 

format,png

驱动有数据后唤醒

假设之前我们使用epoll_ctl给eventfd注册的事件为EPOLLIN,在向eventfd写入数据后就会遍历eventfd等待队列wqh上等待元素,最终调用等待队列元素中的function,对于epoll而言,默认的function为ep_poll_callback。

 

format,png

回调函数ep_poll_callback

 

format,png

ep_poll_callback做了3件事:

  1. 根据等待队列项找到epitem,进而找到eventpoll;
  2. 把属于自己的epitem添加到eventpoll的rdllist上,不会重复添加;
  3. 唤醒eventpoll对象上的等待队列的等待项;

 

format,png

水平触发和边沿触发如何实现

对于驱动而言,它不关心是水平触发还是边沿触发,驱动要做的事就是在有数据可读时唤醒读事件task,当有数据可写时唤醒写事件task。

 

format,png

水平触发和边沿触发的逻辑在ep_send_events中实现

 

format,png

 

format,png

 

format,png

 

format,png

 

format,png

 

format,png

ex: Worker Pool With Epoll and Eventfd

 

format,png

 

format,png

 

format,png

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值