go语言架构师面试 go语言面试题( 三 )


接下来,调用list_add_tail_rcu将当前监听项添加到目标文件的f_ep_links链表里面 , 该链表是目标文件的epoll钩子链表,所有对该目标文件进行监听的监听项都会加入到该链表里面 。
然后就是调用ep_rbtree_insert , 将epi监听项添加到ep维护的红黑树里面,这里不做解释,代码如下:
sys_epoll_ctl - ep_insert - ep_rbtree_insert:
前面提到,ep_insert有调用ep_item_poll去获取目标文件产生的事件位,在调用epoll_ctl前这段时间,可能会产生相关进程需要监听的事件,如果有监听的事件产生,(reventsevent-events 为 true),并且目标文件相关的监听项没有链接到ep的准备链表rdlist里面的话 , 就将该监听项添加到ep的rdlist准备链表里面,rdlist链接的是该epoll描述符监听的所有已经就绪的目标文件的监听项 。并且,如果有任务在等待产生事件时 , 就调用wake_up_locked函数唤醒所有正在等待的任务 , 处理相应的事件 。当进程调用epoll_wait时,该进程就出现在ep的wq等待队列里面 。接下来讲解epoll_wait函数 。
总结epoll_ctl函数:该函数根据监听的事件 , 为目标文件申请一个监听项,并将该监听项挂人到eventpoll结构的红黑树里面 。
epoll_wait等待事件的产生,内核代码如下:
sys_epoll_wait:
首先是对进程传进来的一些参数的检查:
参数全部检查合格后,接下来就调用ep_poll函数进行真正的处理:
sys_epoll_wait - ep_poll:
ep_poll中首先是对等待时间的处理,timeout超时时间以ms为单位,timeout大于0,说明等待timeout时间后超时,如果timeout等于0,函数不阻塞,直接返回,小于0的情况,是永久阻塞,直到有事件产生才返回 。
当没有事件产生时((!ep_events_available(ep))为true),调用__add_wait_queue_exclusive函数将当前进程加入到ep-wq等待队列里面 , 然后在一个无限for循环里面,首先调用set_current_state(TASK_INTERRUPTIBLE) , 将当前进程设置为可中断的睡眠状态,然后当前进程就让出cpu,进入睡眠,直到有其他进程调用wake_up或者有中断信号进来唤醒本进程,它才会去执行接下来的代码 。
如果进程被唤醒后 , 首先检查是否有事件产生 , 或者是否出现超时还是被其他信号唤醒的 。如果出现这些情况,就跳出循环 , 将当前进程从ep-wp的等待队列里面移除 , 并且将当前进程设置为TASK_RUNNING就绪状态 。
如果真的有事件产生 , 就调用ep_send_events函数,将events事件转移到用户空间里面 。
sys_epoll_wait - ep_poll - ep_send_events:
ep_send_events没有什么工作,真正的工作是在ep_scan_ready_list函数里面:
sys_epoll_wait - ep_poll - ep_send_events - ep_scan_ready_list:
ep_scan_ready_list首先将ep就绪链表里面的数据链接到一个全局的txlist里面,然后清空ep的就绪链表 , 同时还将ep的ovflist链表设置为NULL,ovflist是用单链表,是一个接受就绪事件的备份链表 , 当内核进程将事件从内核拷贝到用户空间时,这段时间目标文件可能会产生新的事件,这个时候,就需要将新的时间链入到ovlist里面 。
仅接着,调用sproc回调函数(这里将调用ep_send_events_proc函数)将事件数据从内核拷贝到用户空间 。
sys_epoll_wait - ep_poll - ep_send_events - ep_scan_ready_list - ep_send_events_proc:
ep_send_events_proc回调函数循环获取监听项的事件数据,对每个监听项 , 调用ep_item_poll获取监听到的目标文件的事件,如果获取到事件 , 就调用__put_user函数将数据拷贝到用户空间 。
回到ep_scan_ready_list函数,上面说到 , 在sproc回调函数执行期间,目标文件可能会产生新的事件链入ovlist链表里面,所以,在回调结束后,需要重新将ovlist链表里面的事件添加到rdllist就绪事件链表里面 。

推荐阅读