epoll是linux内核新特性
来源:互联网 发布:5s能用4g网络吗 编辑:程序博客网 时间:2024/05/22 15:07
相关内核代码:
fs/eventpoll.c
判断一个tcp套接字上是否有激活事件:net/ipv4/tcp.c:tcp_poll函数
每个epollfd在内核中有一个对应的eventpoll结构对象.
其中关键的成员是一个readylist(eventpoll:rdllist)
和一棵红黑树(eventpoll:rbr).
eventpoll的红黑树中.红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd对应的epitem。
epoll_ctl的功能是实现一系列操作,如把文件与eventpollfs文件系统的inode节点关联起来。这里要介绍一下eventpoll结构体,它保存在file->f_private中,记录了eventpollfs文件系统的inode节点的重要信息,其中成员rbr保存了该epoll文件节点监视的所有文件描述符。组织的方式是一棵红黑树,这种结构体在查找节点时非常高效。首先它调用ep_find()从eventpoll中的红黑树获得epitem结构体。然后根据op参数的不同而选择不同的操作。如果op为EPOLL_CTL_ADD,那么正常情况下epitem是不可能在eventpoll的红黑树中找到的,所以调用ep_insert创建一个epitem结构体并插入到对应的红黑树中。
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
......
revents = tfile->f_op->poll(tfile, &epq.pt);
函数先调用init_poll_funcptr注册了一个回调函数ep_ptable_queue_proc,ep_ptable_queue_proc函数会在调用f_op->poll时被执行。
该函数分配一个epoll等待队列结点eppoll_entry:一方面把它挂到文件操作的等待队列中,另一方面把它挂到epitem的队列中。此外,它还注册了一个等待队列的回调函数ep_poll_callback。当文件操作完成,唤醒当前进程之前,会调用ep_poll_callback(),把eventpoll放到epitem的完成队列中(注释:通过查看代码,此处应该是把epitem放到eventpoll的完成队列,只有这样才能在epoll_wait()中只要看eventpoll的完成队列即可得到所有的完成文件描述符),并唤醒等待进程。
如果在执行f_op->poll以后,发现被监视的文件操作已经完成了,那么把它放在完成队列中了,并立即把等待操作的那些进程唤醒。
调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间.之后判断epitem是否需
要重新添加回readylist.
epitem重新添加到readylist必须满足下列条件:
1) epitem上有用户关注的事件触发.
2) epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist
中,在什么时候重新添加到readylist请继续往下看).
注意,如果epitem被设置为EPOLLONESHOT模式,则当这个epitem上的事件拷贝到用户空间之后,会将
这个epitem上的关注事件清空(只是关注事件被清空,并没有从epoll中删除,要删除必须对那个描述符调用
EPOLL_DEL),也就是说即使这个epitem上有触发事件,但是因为没有用户关注的事件所以不会被重新添加到
readylist中.
epitem被添加到readylist中的各种情况(当一个epitem被添加到readylist如果有线程阻塞在epoll_wait中,那
个线程会被唤醒):
1)对一个fd调用EPOLL_ADD,如果这个fd上有用户关注的激活事件,则这个fd会被添加到readylist.
2)对一个fd调用EPOLL_MOD改变关注的事件,如果新增加了一个关注事件且对应的fd上有相应的事件激活,
则这个fd会被添加到readylist.
3)当一个fd上有事件触发时(例如一个socket上有外来的数据)会调用ep_poll_callback(见eventpoll::ep_ptable_queue_proc),
如果触发的事件是用户关注的事件,则这个fd会被添加到readylist中.
了解了epoll的执行过程之后,可以回答一个在使用边界触发时常见的疑问.在一个fd被设置为边界触发的情况下,
调用read/write,如何正确的判断那个fd已经没有数据可读/不再可写.epoll文档中的建议是直到触发EAGAIN
错误.而实际上只要你请求字节数小于read/write的返回值就可以确定那个fd上已经没有数据可读/不再可写.
最后用一个epollfd监听另一个epollfd也是合法的,epoll通过调用eventpoll::ep_eventpoll_poll来判断一个
epollfd上是否有触发的事件(只能是读事件).
以下是个人读代码总结:
1709 SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
1710
1588 SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
1589
epoll_ctl的机制大致如下:
1360
1361
1362
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
http://blog.csdn.net/justlinux2010/article/details/8510507
====================================
源码分析
====================================
一、sys_epoll_wait()函数
源码及分析如下所示:
-
- SYSCALL_DEFINE4(epoll_wait,
int, epfd, structepoll_event __user *, events, -
int, maxevents, int,timeout) - {
-
int error; -
struct file *file; -
struct eventpoll *ep; -
-
-
-
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) -
return -EINVAL; -
-
-
-
if (!access_ok(VERIFY_WRITE, sizeof(structevents, maxevents * epoll_event))) { -
error = -EFAULT; -
goto error_return; -
} -
-
-
-
error = -EBADF; -
file = fget(epfd); -
if (!file) -
goto error_return; -
-
-
-
error = -EINVAL; -
if (!is_file_epoll(file)) -
goto error_fput; -
-
-
ep = file->private_data; -
-
-
error = ep_poll(ep, events, maxevents, timeout); -
- error_fput:
-
fput(file); - error_return:
-
-
return error; - }
sys_epoll_wait()是epoll_wait()对应的系统调用,主要用来获取文件状态已经就绪的事件。
该函数检查参数、获取eventpoll文件后调用ep_poll()来完成主要的工作。在分析ep_poll()函数之前,先介绍一下使用epoll_wait()时可能犯的错误(接下来介绍的就是我犯过的错误):
- struct
file int*fget(unsigned fd) - {
-
struct file *file; -
struct files_struct *files = current->files; -
-
rcu_read_lock(); -
file = fcheck_files(files, fd); -
if (file) { -
if (!atomic_long_inc_not_zero(&file->f_count)) { -
-
rcu_read_unlock(); -
return NULL; -
} -
} -
rcu_read_unlock(); -
-
return file; - }
主要看这句(struct files_struct *files = current->files;),这条语句是获取描述当前进程已经打开的文件的files_struct结构,然后从这个结构中查找传入的fd对应的file实例,如果没有找到,说明当前进程中打开的文件不包括这个fd,所以几乎百分百肯定是程序设计的问题。我的程序出错,就是因为在父进程中创建了文件描述符,但是将子进程变为守护进程了,也就没有继承父进程中打开的文件。
- for(;;)
{ -
...... -
events = epoll_wait(fcluster_epfd, fcluster_wait_events, -
fcluster_wait_size, 3000); -
if (unlikely(events <= 0)) { -
continue; -
} -
....... - }
当epoll_wait()返回EBADF或EFAULT时,就会陷入死循环,因此此时还没有进入睡眠的操作。
二、ep_poll()函数
下面来看获取事件的主要函数ep_poll(),源码及分析如下:
- static
int ep_poll( structeventpoll struct*ep, epoll_event __user *events, -
int maxevents, longtimeout) - {
-
int res, eavail; -
unsigned long flags; -
long jtimeout; -
wait_queue_t wait; -
-
-
-
jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ? -
MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000; -
- retry:
-
spin_lock_irqsave(&ep->lock, flags); -
-
res = 0; -
if (list_empty(&ep->rdllist)) { -
-
init_waitqueue_entry(&wait, current); -
wait.flags |= WQ_FLAG_EXCLUSIVE; -
-
__add_wait_queue(&ep->wq, &wait); -
-
for (;;) { -
-
set_current_state(TASK_INTERRUPTIBLE); -
-
if (!list_empty(&ep->rdllist) || !jtimeout) -
break; -
-
if (signal_pending(current)) { -
res = -EINTR; -
break; -
} -
-
spin_unlock_irqrestore(&ep->lock, flags); -
-
jtimeout = schedule_timeout(jtimeout); -
spin_lock_irqsave(&ep->lock, flags); -
} -
__remove_wait_queue(&ep->wq, &wait); -
-
set_current_state(TASK_RUNNING); -
} -
-
-
eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR; -
-
spin_unlock_irqrestore(&ep->lock, flags); -
-
-
-
if (!res && eavail && -
!(res = ep_send_events(ep, events, maxevents)) && jtimeout) -
goto retry; -
-
-
return res; - }
ep_poll()的主要过程是:首先将超时时间(以毫秒为单位)转换为jiffies时间,然后检查是否有事件发生,如果没有事件发生,则将当前进程加入到eventpoll中的等待队列中,直到事件发生或者超时。如果有事件发生,则调用ep_send_events()将发生的事件传入用户空间的内存。ep_send_events()函数将用户传入的内存简单封装到ep_send_events_data结构中,然后调用ep_scan_ready_list()将就绪队列中的事件传入用户空间的内存。
三、ep_scan_ready_list()函数
源码及分析如下:
-
- static
int ep_scan_ready_list( structeventpoll *ep, -
int (*sproc)( structeventpoll *, -
struct list_head void*, *), -
void *priv) - {
-
int error, pwake = 0; -
unsigned long flags; -
struct epitem *epi, *nepi; -
LIST_HEAD(txlist); -
-
-
-
mutex_lock(&ep->mtx); -
-
-
spin_lock_irqsave(&ep->lock, flags); -
-
list_splice_init(&ep->rdllist, &txlist); -
-
ep->ovflist = NULL; -
spin_unlock_irqrestore(&ep->lock, flags); -
-
-
-
error = (*sproc)(ep, &txlist, priv); -
-
spin_lock_irqsave(&ep->lock, flags); -
-
-
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 = EP_UNACTIVE_PTR; -
-
-
-
list_splice(&txlist, &ep->rdllist); -
-
if (!list_empty(&ep->rdllist)) { -
-
if (waitqueue_active(&ep->wq)) -
wake_up_locked(&ep->wq); -
if (waitqueue_active(&ep->poll_wait)) -
pwake++; -
} -
spin_unlock_irqrestore(&ep->lock, flags); -
-
mutex_unlock(&ep->mtx); -
-
-
if (pwake) -
ep_poll_safewake(&ep->poll_wait); -
-
return error; - }
ep_scan_ready_list()函数的参数sproc指向的函数是ep_send_events_proc(),参见ep_send_events()函数。
四、ep_send_events_proc()函数
-
- static
int ep_send_events_proc( structeventpoll struct*ep, list_head *head, -
void *priv) - {
-
struct ep_send_events_data *esed = priv; -
int eventcnt; -
unsigned int revents; -
struct epitem *epi; -
struct epoll_event __user *uevent; -
-
-
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); -
-
-
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) & -
epi->event.events; -
-
-
if (revents) { -
-
if (__put_user(revents, &uevent->events) || -
__put_user(epi->event.data, &uevent->data)) { -
list_add(&epi->rdllink, head); -
-
return eventcnt ? eventcnt : -EFAULT; -
} -
eventcnt++; -
uevent++; -
if (epi->event.events & EPOLLONESHOT) -
epi->event.events &= EP_PRIVATE_BITS; -
-
else if (!(epi->event.events & EPOLLET)) { -
-
list_add_tail(&epi->rdllink, &ep->rdllist); -
} -
} -
} -
-
return eventcnt; - }
- epoll是linux内核新特性
- Linux-2.6内核新特性
- Atitit.linux 内核 新特性 新功能
- Linux 2.6新内核的设计理念及新特性
- 使用新Linux内核 - 谷歌Android 2.2特性曝光
- Linux内核4.4版本带来的网络新特性
- Atitit.linux 内核 新特性 新功能
- linux内核epoll实现分析
- linux2.6内核新特性
- Linux内核特性inotify
- linux编译新内核
- linux 2.6内核epoll用法举例说明
- linux 2.6内核epoll用法举例说明
- linux 2.6内核epoll用法举例说明(续)-
- Linux 2.6 内核Epoll用法举例说明
- linux 2.6内核epoll用法举例
- Linux 2.6 内核Epoll用法举例说明
- linux 2.6内核epoll用法举例说明
- webservice、soap、wsdl的理解
- mv 移动文件与目录,或更名
- 51nod:1079 中国剩余定理
- 锁与volatile的内存语义
- Java并发编程四:并发(Concurrent)与并行(Parallel)区别(二)
- epoll是linux内核新特性
- kill session 被标记为killed
- JDBC连接数据库并实现增删查改
- [刷题]算法竞赛入门经典(第2版) 4-5/UVa1590 - IP Networks
- 为什么360浏览器兼容模式文档模式默认以ie7标准渲染
- error 2664
- 突然想起来的一件小事。。。。
- 串之学密码学就一定的学程序
- 数据库三范式