通过十个问题助你彻底理解linux epoll工作原理( 四 )


struct epitem {struct rb_node rbn;// 用于加入红黑树struct list_head rdllink; // 用于加入rdlliststruct epoll_filefd ffd; // 包含被监视文件的文件指针和fd信息struct list_head pwqlist; // poll等待队列struct eventpoll *ep; // 所属的epoll实例struct epoll_event event;// 关注的事件/* 其他成员省略 */};回忆一下上文说到,每当用户调用 epoll_ctl()新增一个监视文件,都要给这个文件注册一个回调函数 ep_poll_callback, 当网卡收到数据后软中断会调用这个 ep_poll_callback 把这个 epitem 加入到 ep->rdllist 中 。
pwdlist 就是跟 ep_poll_callback 注册相关的 。
当调用 epoll_ctl()新增一个监视文件后,内核会为这个 epitem 创建一个 eppoll_entry 对象,通过 eppoll_entry->wait_queue_t->wait_queue_func_t 来设置 ep_poll_callback 。pwdlist 为什么要做成一个队列呢,直接设置成 eppoll_entry 对象不就行了吗?实际上不同文件类型实现 file_operations->poll 用到等待队列数量可能不同 。虽然大多数都是 1 个,但也有例外 。比如“scullpipe”类型的文件就用到了 2 个等待队列 。
pwqlist、epitem、fd、epoll_entry、ep_poll_callback 间的关系是这样:

通过十个问题助你彻底理解linux epoll工作原理

文章插图
 
Question 8:epmutex、ep->mtx、ep->lock 3 把锁的区别是?答案:锁的粒度和使用目的不同 。
  1. epmutex 是一个全局互斥锁,epoll 中一共只有 3 个地方用到这把锁 。分别是 ep_free() 销毁一个 epoll 实例时、eventpoll_release_file() 清理从 epoll 中已经关闭的文件时、epoll_ctl() 时避免 epoll 间嵌套调用时形成死锁 。我的理解是 epmutex 的锁粒度最大,用来处理跨 epoll 实例级别的同步操作 。
  2. ep->mtx 是一个 epoll 内部的互斥锁,在 ep_scan_ready_list() 扫描就绪列表、eventpoll_release_file() 中执行 ep_remove()删除一个被监视文件、ep_loop_check_proc()检查 epoll 是否有循环嵌套或过深嵌套、还有 epoll_ctl() 操作被监视文件增删改等处有使用 。可以看出上述的函数里都会涉及对 epoll 实例中 rdllist 或红黑树的访问,因此我的理解是 ep->mtx 是一个 epoll 实例内的互斥锁,用来保护 epoll 实例内部的数据结构的线程安全 。
  3. ep->lock 是一个 epoll 实例内部的自旋锁,用来保护 ep->rdllist 的线程安全 。自旋锁的特点是得不到锁时不会引起进程休眠,所以在 ep_poll_callback 中只能使用 ep->lock,否则就会丢事件 。
Question 9:epoll 使用红黑树的目的是什么?答案:用来维护一个 epoll 实例中所有的 epitem 。
用户态调用 epoll_ctl()来操作 epoll 的监视文件时,需要增、删、改、查等动作有着比较高的效率 。尤其是当 epoll 监视的文件数量达到百万级的时候,选用不同的数据结构带来的效率差异可能非常大 。
通过十个问题助你彻底理解linux epoll工作原理

文章插图
 
从时间(增、删、改、查、按序遍历)、空间(存储空间大小、扩展性)等方面考量,红黑树都是非常优秀的数据结构(当然这以红黑树比较高的实现复杂度作为代价) 。epoll 红黑树中的 epitem 是按什么顺序组织的 。阅读代码可以发现是先比较 2 个文件指针的地址大小,如果相同再比较文件 fd 的大小 。
/* Compare RB tree keys */static inline int ep_cmp_ffd(struct epoll_filefd *p1, struct epoll_filefd *p2){return (p1->file > p2->file ? +1 : (p1->file < p2->file ? -1 : p1->fd - p2->fd));}epoll、epitem、和红黑树间的组织关系是这样:
通过十个问题助你彻底理解linux epoll工作原理

文章插图
 
Question 10:什么是水平触发、边缘触发?答案:水平触发(LT)和边缘触发(ET)是 epoll_wait 的 2 种工作模式 。水平触发:关注点是数据(读操作缓冲区不为空,写操作缓冲区不为满),epoll_wait 总会返回就绪 。LT 是 epoll 的默认工作模式 。
边缘触发:关注点是变化,只有监视的文件上有数据变化发生(读操作关注有数据写进缓冲区,写操作关注数据从缓冲区取走),epoll_wait 才会返回 。
看一个实验 ,直观感受下 2 种模式的区别, 客户端都是输入“abcdefgh” 8 个字符,服务端每次接收 2 个字符 。
水平触发时,客户端输入 8 个字符触发了一次读就绪事件,由于被监视文件上还有数据可读故一直返回读就绪,服务端 4 次循环每次都能取到 2 个字符,直到 8 个字符全部读完 。
通过十个问题助你彻底理解linux epoll工作原理

文章插图


推荐阅读