9、epoll其他花絮
epoll是做为一个虚拟文件系统来实现的,这样做至少有以下两个好处:
1、可以在内核里维护一些信息,这些信息在多次epoll_wait间是保持的,比如所有受监控的文件描述符。
2、epoll本身也可以被poll/epoll;
【1】epoll的实现中,所等待的设备就绪后,便调用call_back函数,把该设备加入到就绪队列中,避免了像poll那样设备就绪后再次轮询所有设备找就绪者,由O(n)降到O(1)。
传统的poll函数相当于每次调用都重起炉灶,从用户空间完整读入ufds,完成后再次完全拷贝到用户空间,另外每次poll都需要对所有设备做至少做一次加入和删除等待队列操作,这些都是低效的原因。
3、epoll的实现代码在fs/EventPoll.c中。【2】中最后简略介绍了一下实现函数,可以看到,epoll等待队列及调用函数。
Linux的等待队列,实质上是回调函数队列;【3】的后文,有epoll与poll等形象对比。
add_wait_queue(whead, &pwq->wait);
4、epoll保存拷入的fd,通过epoll_ctl把所有fd传入内核再一起"wait",这就省掉了不必要的重复拷贝。其次,在epoll_wait时,也不是把current轮流的加入fd对应的设备等待队列,而是在设备等待队列醒来时调用一个回调函数(当然,这就需要“唤醒回调”机制),把产生事件的fd归入一个链表,然后返回这个链表上的fd。
一个新创建的epoll文件带有一个struct eventpoll结构,这个结构上再挂一个红黑树,而这个红黑树就是每次epoll_ctl时fd存放的地方!【4,5】
5、创建struct eppoll_entry,设置其唤醒回调函数为ep_poll_callback,然后加入设备等待队列。只有这样,当设备就绪,唤醒等待队列上的等待fd时,ep_poll_callback就会被调用。每次调用poll系统调用,操作系统都要把current(当前进程)挂到fd对应的所有设备的等待队列上,可以想象,fd多到上千的时候,这样“挂”法很费事;而每次调用epoll_wait则没有这么罗嗦,epoll只在epoll_ctl时把current挂一遍(这第一遍是免不了的)并给每个fd一个命令“好了就调回调函数”,如果设备有事件了,通过回调函数,会把fd放入rdllist,而每次调用epoll_wait就只是收集rdllist里的fd就可以了——epoll巧妙的利用回调函数,实现了更高效的事件驱动模型。
ep_poll_callback会干什么了——肯定是把红黑树上的收到event的epitem(代表每个fd)插入ep->rdllist中,这样,当epoll_wait返回时,rdllist里就都是就绪的fd了!
比喻如下【3】
就像收本子的班长,以前(poll)得一个个学生地去问有没有本子,如果没有,它还得等待一段时间而后又继续问(轮询),现在好了,只走一次(epoll_ctl),如果没有本子,班长就告诉大家去那里交本子(等待队列,回调函数),当班长想起要取本子,就去那里看看或者等待一定时间后离开(epoll_wait),有本子到了就叫醒他,然后取走。
6、linux系统的优点:everything is a file。
参考
【1】 http://hi.baidu.com/zty598416146/blog/item/a8a75da5347266ee9152ee3e.html
【2】 http://www.cnblogs.com/xuxm2007/archive/2011/08/15/2139809.html
【3】 http://blog.csdn.net/hairetz/article/details/6337817
【5】 http://download.csdn.net/source/3571754
【6】 讲的详细