epoll
epoll_create
需要知道epoll_create和epoll_create1的功能是在内核空间返回epoll实例,并将相应的文件描述符返回到用户态;
关于epoll_create的具体实现原理看这篇:https://zhuanlan.zhihu.com/p/463541274
直接上源码:https://elixir.bootlin.com/linux/v5.19/source/fs/eventpoll.c#L2008
可以看到两个系统调用最终都直接调用了do_epoll_create函数,只不过是flags不一样,epoll_create的flags是默认的0,而epoll_create1的flags则是有用户传入的。
下面看do_epoll_create函数:
可以看到首先就是通过ep_alloc分配一个eventpoll类型的结构体,然后搞一个可用文件描述符和匿名inode安装上:
eventpoll结构体定义如下:https://elixir.bootlin.com/linux/v5.19/source/fs/eventpoll.c#L177
eventpoll结构有三个字段是比较重要的,分别是:wq、rdllist和rbr。
- wq用于保存有哪些进程在等待这个epoll返回。
- rdllist用于保存可读写的文件。
- rbr用于建立一个快速查找文件句柄的红黑树。
epoll_ctl
创建epoll实例大致的过程我们已经知道了,但是这个实例后续又该怎么用呢?
先看epoll_ctl系统调用的入口:https://elixir.bootlin.com/linux/v5.19/source/fs/eventpoll.c#L2183
到do_epoll_ctl函数:https://elixir.bootlin.com/linux/v5.19/source/fs/eventpoll.c#L2033
先梳理结构:
这里的ep是一个eventpoll结构体,可以看到这就是从我们epoll_create创建的文件中直接取private_data,这也就是之前创建的epoll实例;
直接看op==EPOLL_CTL_ADD的情况,先进行相关的检查(直接跳过😊):
真正的处理在这里:
可以看到主要就是调用了ep_insert函数:
其主要作用就是分配一个新的epitem并初始化,然后插入到红黑树中;
之后通过init_poll_funcptr函数将poll_table的_qproc设置成ep_ptable_queue_proc函数的地址:
之后会调用ep_item_poll:
这里会有如下调用链:
ep_item_poll
vfs_poll //通过is_file_epoll(file, pt) == 0进入
file->f_op->poll //走file的poll指针了
timerfd_poll //下面我们通过timerfd的文件来分析😊
poll_wait
poll_table->_qproc
ep_ptable_queue_proc
在ep_table_queue_proc函数中,https://elixir.bootlin.com/linux/v5.19/source/fs/eventpoll.c#L1239
主要分五个步骤进行:
第一步,利用container_of宏得到epq,进而解析出epi;
第二步,分配一个新的eppoll_entry;
第三步,给新分配的epoll_entry的wait成员初始化;
第四步,将epoll_entry的wait加入到wqh的链表中;
第五步,将epoll_entry加入到epi的pwqlist链表中;
总结
首先epoll_create创建epoll实例(eventpoll结构体)
然后在epoll_ctl,op==EPOLL_CTL_ADD的情况下进入到ep_insert中,在这里在栈上分配一个ep_pqueue,然后堆上分配其epitem,然后插入到eventpoll实例的红黑树中;
之后将poll_table的函数指针进行初始化,并通过ep_item_poll以及后续步骤最终调用到这个函数指针;
在这个处理函数中,分配一个新的eppoll_entry结构,其wait.func初始化为poll的处理函数(这个后续事件触发的时候会被调用),然后将eppoll_entry插到epitem的链表中,将eppoll_entry的watch成员挂到timerfd_ctx.wqh上;(事件触发后会将这个wqh上的所有的watch的func一次进行调用)