介绍
解说中存在一些函数和数据结构,具体出处可以参照上一篇关于select的分析 《IO端口复用之select的底层实现》,里面提及了一些储备知识点,本篇不再赘述。
由于tcp过于复杂,取个巧,全篇以udp连接来说明一下,内核版本依旧对应2.6.32。本篇在加上之前的《IO端口复用之poll的底层实现》,算是集齐了linux系统常用的端口复用技术三贱客。
从上述两篇可以看出,select和poll在底层轮训技术上没有本质的区别,而epoll则有了很大的改进,使用了事件触发机制。
epoll系统调用
接口说明
epoll在使用上与select和poll有本质的区别,select和poll是典型的一撸到底,一个api里面进行轮训判断、搞定所有,出了api就拿到了相应的结果。而epoll则需要多个部分相互合作来完成事件的监听触发工作。
对epoll系统调用的接口分析可分epoll_create、epoll_ctl、epoll_wait以及就绪事件触发等几个部分。
源码追踪
epoll_create
epoll_create主要创建了一个fd文件描述符,通过该fd可以找到对应的file结构体和eventpoll结构体 。
sys_epoll_create, int, size)
{
......
return sys_epoll_create1(0);
}
epoll_create主要调用了内核系统调用函数sys_epoll_create1,而sys_epoll_create1的函数定义原型为sys_epoll_create1(int flags)。
sys_epoll_create1(int flags)
{
/* 传递进来的flags为0, 为epoll_create传递的size大小没有起作用 */
int error;
struct eventpoll *ep = NULL;
......
error = ep_alloc(&ep);
/* eventpoll结构体主要在ep_alloc中开辟的. */
error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
flags & O_CLOEXEC);
......
}
在anon_inode_getfd中构造epoll_create返回的套接字的file和dentry资源,此处给该fd的f_op参数赋值为event_poll_fops,具体结构如下:
static const struct file_operations eventpoll_fops = {
.release= ep_eventpoll_release,
.poll = ep_eventpoll_poll
};
接上文中的函数调用,接下来是ep_alloc函数。
static int ep_alloc(struct eventpoll **pep)
{
......
user = get_current_user();
/*get_current_user引用struct user_struct结构体(用户结构体), 此处不做讨论*/
ep = kzalloc(sizeof(*ep), GFP_KERNEL);
/* ep 为开辟的struct eventpoll结构体, 失败之后将调用free_uid*/
/* 锁的初始化 */
spin_lock_init(&ep->lock);
mutex_init(&ep->mtx);
/*初始化等待队列(基础结构为struct list_head), 一般sys_epoll_wait()函数用来使用*/
init_waitqueue_head(&ep->wq);
/*初始化等待队列(基础结构为struct list_head), 一般file->poll()函数用来使用*/
init_waitqueue_head(&ep->poll_wait);
/*初始化就绪队列*/
INIT_LIST_HEAD(&ep->rdllist);
/* epoll的底层数据结构红黑树, 红黑树的主要作用是进行ep_find操作,
* 用户快速查找事件是否已监听, 或快速的修改监听事件的状态集 */
ep->rbr = RB_ROOT;
/* ovflist初始化, ovflist将在内核触发通知和epoll_wait中说明*/
ep->ovflist = EP_UNACTIVE_PTR;
/* 创建epoll的用户结构体*/
ep->user = user;
*pep = ep;
......
free_uid:
/*释放引用计数,当引用为0时, 释放该结构, 此处不讨论*/
free_uid(user);
......
}
接下来在anon_inode_getfd函数中调用了anon_inode_getfile函数,申请了对应的fd值,并开辟了与之关联的file(struct file结构)描述符,将file结构中的成员f_op设置为全局的eventpoll_fops,将file的private_data成员赋值为ep指针(上述开辟的struct eventpoll结构指针)。
epoll_create系统调用就是创建一个eventpoll全局结构,一个文件描述符fd,一个file描述符结构,并通过fd查找对应的file结构,通过file->private_data找到对应的ep,从而在epoll_ctl和epoll_wait中使用。
epoll_ctl
epoll_ctl是一个辅助接口,主要用于增加、删除和修改监听集。第一个参数epfd(int类型,epoll_create所创建的文件描述符),第二个参数fd(int类型,被操作的监听套接字文件描述符)。
- 在sys_epoll_ctl函数中,通过ep_op_has_event判断操作类型,当操作类型不等于EPOLL_CTL_DEL时,说明有监听类型变化或者新的监听套接字加入,将应用层空间参数event拷贝给epds(struct epoll_event结构)。
- 根据第二个参数fd,找到文件描述符file结构,并提取对应文件描述符的f_op,从select和poll剖析的socket及bind系统调用可知,此处的f_op指向 socket_file_ops。而f_op->poll函数,既socket_file_ops中的poll函数为sock_poll。而在sock_poll函数中,可通过file->private_data提取出来sock指针(struct socket结构指针)。而sock中的ops指向的是inet_dgram_op(以UDP为例),sock->ops->poll实际上调用了inet_dgram_ops中的poll函数udp_poll。当监听套接字对应的f_op结构或者f_op->poll函数不存在时,是不可以进行epoll_ctl操作的。
- 从epfd提取对应的file结构,从file->private_data中提取出来ep指针。而后续所操作的红黑树的头节点就是ep的rbr成员。
- 在ep_find函数中,遍历红黑树,看对应的文件文件描述符是否已经存在,返回epi(struct epitem结构)。epi是红黑树的节点结构是struct epitem指针,epitem结构中的rbn(struct rb_node结构)则为红黑树挂载点。其中ep_find的具体比较函数是ep_cmp_ffd,该函数中主要是比较epi中的ffd和当前的ffd,其中ffd是struct epoll_filefd结构,里面包括fd(int)和file(struct file指针)两个成员。
- 如果操作类型是EPOLL_CTL_DEL,调用ep_remove从红黑树中移除监听的文件描述符对应的epi(struct epitem)相关信息。
先详细看一下epitem相关信息。
struct epitem {
struct rb_node rbn;
struct list_head rdllink;
struct epitem *next;
struct epoll_filefd ffd;
int nwait;
struct list_head pwqlist;
struct eventpoll *ep;
struct list_head fllink;
struct epoll_event event;
}
执行ep_remove除了将epi从ep的红黑树中移除,还会执行ep_unregister_pollwait函数,剥离与之相关的挂载点。具体挂载点在ep_insert中介绍。
- 如果操作类型是EPOLL_CTL_ADD,调用ep_insert。
在ep_insert中创建epi,并对epi进行初始化操作,并定义epq变量(struct ep_pqueue结构)。
struct ep_pqueue {
poll_table pt;
struct epitem *epi;
}
epi->ep = ep
epi->nwait = 0
epi->next = EP_UNACTIVE_PTR
调用init_poll_funcptr对epq中的pt进行初始化,其中pt的qproc成员初始化为函数ep_ptable_queue_proc,pt的key成员初始化~0UL。epq中的epi既为当前创建的epi。
调用对应f_op中的poll函数,返回就绪结果集revents。主要是针对当前要添加的文件描述符进行就绪查询,从UDP的角度来看,本质执行的是udp_poll函数,在udp_poll中调用的datagram_poll函数。在datagram_poll函数中会调用sock_poll_wait函数,sock_poll_wait会调用epq中的pt中的qproc,既ep_ptable_queue_proc函数。
在函数ep_ptable_tqueue_proc函数中,用whead(wait_queue_head_t指针)代表sk->sk_sleep,从poll_table结构中提取出epi。创建pwq(struct eppoll_entry结构指针),调用init_waitqueue_func_entry对pwq的wait(wait_queue_t结构)进行初始化,wait中的func赋值成ep_poll_callback。pwq的whead赋值成whead(sk->sk_sleep),pwq的base赋值成epi,将whead(sk->sk_sleep)挂载到pwq的wait上,将pwd的llink挂载到epi的pwqlist上,epi的nwait进行累加。
随后将epi的epi_fllink挂载到对应file文件的f_ep_links节点中,将epi插入到ep的红黑树中,并根据poll函数的结果集进行判断,当前文件描述符已经有就绪事件且epi的rdllink是还没有挂载,将epi->rdllink挂载到ep的rdllist就绪链表节点上,供epoll_wait使用。
然后用waitqueue_active(&ep->wq) 判断ep的wq队列是否为空, 不为空说明此时已经有epoll_wait在监听该eventpoll, 并处于沉睡状态。则调用ep->wq上注册的default_wake_function函数进行epoll_wait唤醒,让epoll_wait继续执行for(;;)循环,关于default_wake_function的说明在内核触发上也有提到。
- 如果操作类型是EPOLL_CTL_MOD,调用ep_modify。
ep_modify函数的操作跟ep_insert有一定相似。
ep_find主要用来查找当前插入的fd套接字是否已经在epoll监听的红黑树中。
static struct epitem *ep_find(struct eventpoll *ep, struct file *file, int fd)
{
int kcmp;
struct rb_node *rbp;
struct epitem *epi, *epir = NULL;
struct epoll_filefd ffd;
ep_set_ffd(&ffd, file, fd);
/*遍历红黑树*/
for (rbp = ep->rbr.rb_node; rbp; ) {
epi = rb_entry(rbp, struct epitem, rbn);
/*红黑树中的比较函数, 以file结构体指针和fd大小进行比较*/
kcmp = ep_cmp_ffd(&ffd, &epi->ffd);
if (kcmp > 0)
rbp = rbp->rb_right;
else if (kcmp < 0)
rbp = rbp->rb_left;
else {
/*在红黑树中找到了要插入的fd套接字*/
epir = epi;
break;
}
}
return epir;
}
ep_insert是插入操作, 插入操作主要是将事件epi插入到红黑树一份, 用于快速查找。
同时插入到每个套接字的sk_sleep中一份, 用于套接字数据到来时候, 进行通知。
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd)
{
......
将函数地址ep_ptable_queue_proc赋值给epq(struct ep_pqueue)中的函数指针,
该函数指针主要在下面的f_op->poll中调用。
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
运行tfile->f_op->poll函数, 返回当前待插入的事件对应的套接字的状态集.
f_op->poll主要调用了sock_poll函数.
sock_poll函数主要调用了sock->ops->poll函数.
sock->ops->poll函数根据不同的协议有对应的函数.
以tcp_poll函数为例:
-----------------------------------------------------------
tcp_poll{
调用了上文init_poll_funcptr中赋值进去的ep_ptable_queue_proc函数。
ep_ptable_queue_proc函数是内核触发功能的核心函数,
该函数将加入epoll监听中的事件挂载到了对应的网络套接字的sk_sleep上。
sock_poll_wait(file, sk->sk_sleep, wait);
----------------------------------------------------------------
ep_ptable_queue_proc{
挂载到sk_sleep上的节点结构
struct eppoll_entry *pwq;
......
给wait_queue_t结构中的func函数指针赋值,
ep_poll_callback将会在内核触发的时候使用。
ep_poll_callback函数的分析在内核触发那块进行。
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
针对eppoll_entry各种初始化
pwq->whead = whead;
pwq->base = epi;
此处的whead是通过ep_ptable_queue_proc参数传递进来的,
实参既为sock结构中的sk_sleep元素。
这里将套接字结构与监听事件联系在了一起。
在sock->sk_sleep上(whead)挂载了与之有关的监听事件集合。
add_wait_queue(whead, &pwq->wait);
......
将poll使用的eppoll_entry结构挂在到epi的pwqlist元素上
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
......
}
----------------------------------------------------------------
.......
}
-----------------------------------------------------------
revents = tfile->f_op->poll(tfile, &epq.pt);
将当前epi事件插入到红黑树中.
ep_rbtree_insert(ep, epi);
......
如果刚刚通过epoll_ctl插入的事件已经满足条件(就绪)
if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
将该事件挂载到eventpoll的就绪事件链表中去, 供epoll_wait使用。
list_add_tail(&epi->rdllink, &ep->rdllist);
如果此时已经有epoll_wait在监听该eventpoll, 并处于沉睡状态.
则调用ep->wq上注册的default_wake_function函数进行epoll_wait唤醒,
让epoll_wait继续执行for(;;)循环, 关于default_wake_function的说明在内核触发上也有提到。
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
......
}
......
要是当前epi事件已经挂在到了eventpoll的rdllist上, 则进行剥离, 避免重复挂载.
if (ep_is_linked(&epi->rdllink))
list_del_init(&epi->rdllink);
}
ep_remove和ep_modify操作和插入操作大同小异, 此处不只做具体分析。
其中ep_modify更改了事件状态之后, 会即刻唤醒epoll_wait函数。
epoll_wait
********************************************************************
***** epoll_wait *****
********************************************************************
epoll_wait系统调用的入口函数既为YSCALL_DEFINE4(epoll_wait, ......
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
......
判断传递进来的epfd是否是epoll_create函数创建的, epoll_wait只处理epoll_create创建的.
if (!is_file_epoll(file))
goto error_fput;
从fd得到file结构,从file结构中拿到ep结构(epoll_create创建的)
ep = file->private_data;
接下来调用ep_poll函数.
ep_poll(ep, events, maxevents, timeout)
......
}
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
对ep的自旋锁进行了加锁,并关闭软中断。在ep_poll入口处加了锁,
就不会存在多个任务实体同时调用ep_poll的可能性。当其他任务实体准备调用ep_poll的时候,
如果发现了其他任务已经拿到自旋锁,则进行睡眠等待,等待其释放自旋锁。
在后续的代码中可以发现,如果未发现网络事件,会进行schedule调度,此处会释放了自旋锁。
spin_lock_irqsave(&ep->lock, flags);
......
针对每个epoll_wait初始化一个wait(wait_queue_t)结构,
并将其flags设置为WQ_FLAG_EXCLUSIVE,将current赋值给了wait中的private。
WQ_FLAG_EXCLUSIVE标记主要用于内核唤醒时只唤醒一个,避免惊群现象。
此处的值需要跟网络数据包到来时进行的触发唤醒区分开,此处唤醒主要用于当处理一波数据之后,
如果发现rdllist还有数据(未处理完或者LT模式下又重新将epi加入进来),则进行唤醒A。
init_waitqueue_entry(&wait, current);
wait.flags |= WQ_FLAG_EXCLUSIVE;
将wait(wait_queue_t结构)加入到eventpoll的wq链表元素上, 主要在内核唤醒时使用
__add_wait_queue(&ep->wq, &wait)
for (;;) {
设置当前睡眠的task_struct 可以被打断
set_current_state(TASK_INTERRUPTIBLE);
如果当前eventpoll事件上的监听事件已经就绪或者监听已经超时,
则跳出循环.
ep->rdllist的就绪节点是内核在数据到来时,执行触发的时候加入的.
if (!list_empty(&ep->rdllist) || !jtimeout)
break;
检查是否有信号需要处理
if (signal_pending(current)) {
res = -EINTR;
break;
}
进入延时唤醒状态, 让出cpu, 同时会对超时计时jtimeout进行减法操作.
这时候会释放锁,锁就有可能被别人拿到。
spin_unlock_irqrestore(&ep->lock, flags);
jtimeout = schedule_timeout(jtimeout);
spin_lock_irqsave(&ep->lock, flags);
}
事件来临时, 跳出for(;;)循环后从链表上删除wait
__remove_wait_queue(&ep->wq, &wait);
......
epoll_wait进程唤醒后, 设置task标记为running态
set_current_state(TASK_RUNNING);
判断ovflist是否未空或者rdllist是否为空,并以次为根据进行网络事件处理。
!list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;
释放掉自旋锁,接下来开始处理rdllist和ovflist。
如果有的话,其他任务实体可以紧着获取自旋锁进行事件等待。
spin_unlock_irqrestore(&ep->lock, flags);
调用ep_send_events函数,将就绪的event拷贝到用户态的events数组中来.
返回的时数据就绪的event事件数.
res = ep_send_events(ep, events, maxevents)
return res;
}
static int ep_send_events(struct eventpoll *ep,
struct epoll_event __user *events, int maxevents)
{
struct ep_send_events_data esed;
esed.maxevents = maxevents;
esed.events = events;
调用 ep_scan_ready_list函数
return ep_scan_ready_list(ep, ep_send_events_proc, &esed);
}
static int ep_scan_ready_list(struct eventpoll *ep,
int (*sproc)(struct eventpoll *,
struct list_head *, void *),
void *priv)
{
整个处理过程都会加上mutex锁。同时在处理ep相应的数据时候也会进行ep->lock自旋锁的加锁和解锁。
mutex_lock(&ep->mtx);
将就绪事件的链表转移给txlist.
list_splice_init(&ep->rdllist, &txlist);
......
将ep的ovflist从初始化的EP_UNACTIVE_PTR变为NULL.
因为在sproc函数调用中要对rdllist链表进行遍历,
并实现从内核态向用户态的拷贝操作,更改ovflist的值,
并配合内核触发部分,实现类似锁的保护.
ep->ovflist = NULL;
执行sproc传入的回调函数, 既调用ep_send_events_proc函数, 在下面进行了该函数分析.
error = (*sproc)(ep, &txlist, priv)
当执行sproc进行内核态向用户态的拷贝时, ep的rdllist链表是不允许插入的.
此时内核触发部分会将满足条件的epi事件插入到ovflist中.
下面则是将ovflist链表上的部分加入到eventpoll的rdllist上,并做稍后处理.
for (nepi = ep->ovflist; (epi = nepi) != NULL;
nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {
if (!ep_is_linked(&epi->rdllink))
list_add_tail(&epi->rdllink, &ep->rdllist);
}
拷贝操作结束, 将ep->ovflist的值还原为初始化值,
此时触发部分通过ovflist初始值判断,即可将到来的时间插入到rdllist队列中。
ep->ovflist = EP_UNACTIVE_PTR;
将sproc中未完全剥离的剩余链表上的epi事件加入到rdllist上.
list_splice(&txlist, &ep->rdllist);
如果ep->rdllist上还有节点数据, 有可能是ovflist上拷贝过来的,
有可能是txlist上剩余的, 也有可能是使用LT模式重新挂载过来的等,
则进行进程唤醒操作, 唤醒之后睡眠的任务实体则会继续将epoll_wait重新执行起来,
将rdllist上的数据进行及时处理。此处的唤醒就是上面提到的唤醒A。
if (!list_empty(&ep->rdllist)) {
//此处主要是通过!list_empty(&q->task_list)来检测ep->wq的任务队列是否为空。
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
......
}
......
}
tatic int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
void *priv)
{
其中priv指向了传递进来的struct ep_send_events_data结构.
......
遍历head链表, 将就绪的事件以此拷贝到esed结构中.
for (eventcnt = 0, uevent = esed->events;
!list_empty(head) && eventcnt < esed->maxevents;) {
从链表上依次取下每个节点结构
epi = list_first_entry(head, struct epitem, rdllink);
将该节点从链表上进行剥离
list_del_init(&epi->rdllink);
返回当前套接字的event状态, 此处不进行具体分析.
在epoll_ctl函数中也调用了poll函数.
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &
epi->event.events;
if (revents) {
如果从内核态向用户态拷贝失败, 则将剥离下来的epi事件重新加入到txlist中.
if (__put_user(revents, &uevent->events) ||
__put_user(epi->event.data, &uevent->data)) {
list_add(&epi->rdllink, head);
return eventcnt ? eventcnt : -EFAULT;
}
eventcnt++;
uevent++;
当监听的event事件类型中加入了EPOLLONESHOT态,
既除去了EPOLLIN或者EPOLLOUT等基本类型, 内核就不会进行第二次触发.
相见内核触发分析.
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
当监听的event事件类型中没有加入EPOLLET类型,
则将该事件继续添加到ep的rdllist中.也就是在水平触发模式下,
如果通过poll函数得到了对应的网络事件的话,epi->rdllink会接着加入到txlist中。
而在后续的操作中,剩余的txlist会继续加入到rdllist。
else if (!(epi->event.events & EPOLLET)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
}
}
......
}
就绪事件触发
********************************************************************
***** 内核触发入口 *****
********************************************************************
epoll机制是通过内核唤醒来进行触发的, 其基本的操作函数主要是sock_def_readable和sock_def_write_space.
其中sock_def_readable是EPOLLIN的触发入口,sock_def_write_space是EPOLLOUT的触发入口.
一般当有数据到来时候会执行sock_def_readable函数,
缓冲区有空间可写入时候会执行sock_def_write_space函数,
此处不对函数调用上下文进行分析.
sock_def_readable
{
sk_has_sleeper(sk);
其中的sk_has_sleeper函数主要用来判断当前sock套接字结构中的sk_sleep元素上是否有具体的挂载节点。
挂载点结构为wait_queue_head_t结构, 其中的task_list(struct list_head)为真正的挂载点。
挂载chain上的节点结构为eppoll_entry结构,
其钩子为内部元素wait(wait_queue_t结构),eppoll_entry中存储有元素base(struct epitem结构)。
每个挂载点都可以查找到对应的已加入的监听事件, 具体挂载操作在epoll_ctl函数中。
......
wake_up_interruptible_sync_poll --> 宏定义 __wake_up_sync_key
......
}
__wake_up_sync_key
{
调用了__wake_up_common函数
}
__wake_up_common
{
遍历sock结构中的sk_sleep上的task_list循环链表, 当当前的套接字缓冲区有数据时,
需要遍历注册到该sk_sleep上的所有的监听事件。
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
执行每一个wait_queue_t结构中的func函数, func函数也是在epoll_ctl中进行初始化的。
此处的func函数对应的函数为ep_poll_callback, 当func函数为default_wake_function时,
WQ_FLAG_EXCLUSIVE可用于防止部分唤醒惊群使用,见上述唤醒A。
此处的func函数对应ep_poll_callback。
做惊群使用时, 每个epoll_wait的时候, 会生成一个wait结构, 挂载到ep->wq链表上。
既多个epoll_wait共同进行时,ep->wq会挂在多个节点,
但是wake唤醒的时候遇到WQ_FLAG_EXCLUSIVE标记退出, 则只唤醒了第一个。
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) &&
!--nr_exclusive)
}
}
curr->func是在ep_ptable_queue_proc函数中进行赋值的,
此处针对ep_ptable_queue_proc函数的调用发生在epoll_ctl中, 具体分析在epoll_ctl函数中进行。
ep_poll_callback
{
从sk_sleep(也有可能是从ep->wq)上得到每一个wait_queue_t结构,
从wait_queue_t结构可以得到包含该结构的eppoll_entry结构。
eppoll_entry是由对应的wait_queue_t和epitem结构组成的。
struct epitem *epi = ep_item_from_wait(wait);
如果注册过的epi事件中,
除了EPOLLONESHOT和EPOLLET等特殊标识之外,没有其他的基础标识如EPOLLOUT和EPOLLIN,
则直接跳出不对此epi事件进行处理。
if (!(epi->event.events & ~EP_PRIVATE_BITS))
goto out_unlock;
匹配chain上的每一个epitem里面的events类型(EPOLLIN, EPOLLOUT, EPOLLET,EPOLLLT)是否跟key匹配。
key里的类型在此处为POLLIN类型, 当然在sock_def_write_space函数里传递过来的为POLLOUT类型。
if (key && !((unsigned long) key & epi->event.events))
goto out_unlock;
如果此时ep->ovflist的值不是EP_UNACTIVE_PTR,
说明此刻epoll_wait正在调用ep_scan_ready_list,
而在ep_scan_ready_list中正在进行内核态向用户态的拷贝操作。
此时不能直接将epi挂在到ep的rdllist上, 暂时性的挂载到ovflist上。
if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
if (epi->next == EP_UNACTIVE_PTR) {
epi->next = ep->ovflist;
ep->ovflist = epi;
}
goto out_unlock;
}
判断该epi事件的rdllink链表元素是否未挂载, 未挂载则将其挂载到eventpoll主监听集上, 供epoll_wait使用。
要是该事件的rdllink为非空(已经在rdllist上了), 则不进行挂载。
if (!ep_is_linked(&epi->rdllink))
list_add_tail(&epi->rdllink, &ep->rdllist);
当有epoll_wait监听的时候, 会在ep链表上挂载一个wait进程事件,
当数据到来时候好唤醒沉睡的epoll_wait。
挂载操作在epoll_wait函数的for(;;)循环之前进行的,
具体操作在epoll_wait中进行分析。
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
其中wake_up_locked也会调用上文中提到的__wake_up_common。
但此处__wake_up_common函数中调用的curr->func则与上文不同。
ep->wq链表上的节点挂载是在epoll_wait中执行的,在挂载之前对元素func进行了赋值,
此处调用的是default_wake_function, 该函数主要用于唤醒沉睡的epoll_wait进程。
......
}
sock_def_write_space函数分析基本同sock_def_readable一致, 此处不再进行具体分析.