(转)epoll源码分析

当系统启动时,epoll进行初始化:

 1 static int __init eventpoll_init(void) 2 { 3     mutex_init(&pmutex); 4     ep_poll_safewake_init(&psw); 5     epi_cache = kmem_cache_create(“eventpoll_epi”,sizeof(struct epitem), 6             0,SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG| 7             SLAB_PANIC,NULL); 8     pwq_cache = kmem_cache_create(“eventpoll_pwq”,sizeof(struct 9              eppoll_entry),0,EPI_SLAB_DEBUG|SLAB_PANIC,NULL);10     11     return 0;12 }

上面的代码实现一些数据结构的初始化,通过fs/eventpoll.c中的注释可以看出,有三种类型的锁机制使用场景:

1).epmutex(mutex):用户关闭文件描述符,但是没有调用EPOLL_CTL_DEL

2).ep->mtx(mutex):用户态与内核态的转换可能会睡眠

3).ep->lock(spinlock):内核态与具体设备中断过程中的转换,poll回调

接下来就是使用slab分配器动态分配内存,第一个结构为当系统中添加一个fd时,就创建一epitem结构体,内核管理的基本数据结构:

 1 struct epitem 2 { 3     struct rb_node rbn;//用于主结构管理的红黑树 4     struct list_head rdllink;//事件就绪队列 5     struct epitem *next;//用于主结构体中的链表 6     struct epoll_filefd ffd;//每个fd生成的一个结构 7     int nwait;// 8     struct list_head pwqlist;//poll等待队列 9     struct eventpoll *ep;//该项属于哪个主结构体10     struct list_head fllink;//链接fd对应的file链表11     struct epoll_event event;//注册的感兴趣的事件,也就是用户空间的epoll_event12 }

而每个epoll fd对应的主要数据结构为:

1 struct eventpoll {2     spin_lock_t lock;//对本数据结构的访问3     struct mutex mtx;//防止使用时被删除4     wait_queue_head_t wq;//sys_epoll_wait() 使用的等待队列5     wait_queue_head_t poll_wait;//file->poll()使用的等待队列6     struct list_head rdllist;//事件满足条件的链表7     struct rb_root rbr;//用于管理所有fd的红黑树8     struct epitem *ovflist;//将事件到达的fd进行链接起来发送至用户空间9 }

该结构主要在epoll_create时进行创建:

 1 //原来使用的是hash表,所以有size,现在改为红黑树,故不使用. 2 long sys_epoll_create(int size) 3 { 4     int error,fd = -1; 5     struct eventpoll *ep; 6     struct inode *inode; 7     struct file *file; 8      9     ….10     error = -EINVAL;11     //分配空间12     if(size <= 0 || (error = ep_alloc(&ep)!=0))13         goto errror_return;14     //创建一个struct file结构,由于没有任何文件系统,为匿名文件,15         并将主结构体放入file->private项中进行保存16     error = anon_inode_getfd(&fd,&inode,&file,”[eventpoll]”,17             &eventpoll_fops,ep);18     if(error)19         goto error_free;20     return fd;21     ...22 }

上面注册的操作eventpoll_fops定义如下:

1 static const struct file_operations eventpoll_fops = {2     .release     =     ep_eventpoll_release;3     .poll        =    ep_eventpoll_poll,4 }

这样说来,内核中维护了一棵红黑树,大致的结构如下:

上面的原型是epoll的fd所维护的主结构,下面是每一个具体的fd结构.

以后每一个fd加入到epoll中,就会创建一个struct epitem结构,并插入至红黑树中。

接着是epoll_ctl函数原型:

 1 asmlinkage long sys_epoll_ctl(int epfd,int op,int fd,struct epoll_event __user *event) 2 { 3     int error; 4     struct file *file,*tfile; 5     struct eventpoll *ep; 6     struct epoll_event epds; 7      8     error = -FAULT; 9     //判断行参的合法性10     if(ep_op_has_event(op) && copy_from_user(&epds,event,sizeof(struct         epoll_event)))11             goto error_return;12 13     error = -EBADF;14     file = fget (epfd);15     if(!file)    goto error_return;16     17     tfile = fget(fd);18     if(!tfile)    goto error_fput;19     20     error = -EPERM;21     //不能没有poll驱动22     if(!tfile->f_op || !tfile->f_op->poll)23         goto error_tgt_fput;24         25     error =-EINVAL;26     //防止自己监听自己27     if(file == tfile || !is_file_poll(file))28         goto error_tgt_fput;29     //在create时存入进去的,现在将其拿出来30     ep = file->private->data;31     32     mutex_lock(&ep->mtx);33     //防止重复添加34     epi = epi_find(ep,tfile,fd);35     error = -EINVAL;36     37     switch(op)38     {39         ….....40         case EPOLL_CTL_ADD:41             if(!epi)42             {43                 epds.events |=EPOLLERR | POLLHUP;44                 error = ep_insert(ep,&epds,tfile,fd);45             } else 46                 error = -EEXIST;47             break;48         …....49     }50     return error;    51 }

下面就是ep插入代码:

 1 static int ep_insert(struct eventpoll *ep,struct epoll_event *event, 2                 struct file *tfile,int fd) 3 { 4     int error ,revents,pwake = 0; 5     unsigned long flags ; 6     struct epitem *epi; 7     /* 8     struct ep_queue{ 9         poll_table pt;10         struct epitem *epi;11     }12 */13     struct ep_pqueue epq;14     15     //分配一个epitem结构体来保存每个加入的fd16     error = -ENOMEM;17     if(!(epi = kmem_cache_alloc(epi_cache,GFP_KERNEL)))18         goto error_return;19     //初始化该结构体20     ep_rb_initnode(&epi->rbn);21     INIT_LIST_HEAD(&epi->rdllink);22     INIT_LIST_HEAD(&epi->fllink);23     INIT_LIST_HEAD(&epi->pwqlist);24     epi->ep = ep;25     ep_set_ffd(&epi->ffd,tfile,fd);26     epi->event = *event;27     epi->nwait = 0;28     epi->next = EP_UNACTIVE_PTR;29     30     epq.epi = epi;31     //安装poll回调函数32     init_poll_funcptr(&epq.pt,ep_ptable_queue_proc);33     //调用poll函数来获取当前事件位,其实是利用它来调用注册函数ep_ptable_queue_proc34     revents = tfile->f_op->poll(tfile,&epq.pt);35 36     if(epi->nwait < 0)37         goto error_unregister;38 39     spin_lock(&tfile->f_ep_lock);40     list_add_tail(&epi->fllink,&tfile->f_ep_lilnks);41     spin_unlock(&tfile->f_ep_lock);42     43     ep_rbtree_insert(ep,epi);44     spin_lock_irqsave(&ep->lock,flags);45 46     if((revents & event->events) && !ep_is_linked(&epi->rdllink))47     {48         list_add_tail(&epi->rdllink,&ep->rdllist);49     if(waitqueue_active(&ep->wq))50         __wake_up_locked(&ep->wq,TAKS_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE);51 52     if(waitqueue_active(&ep->poll_wait))53         pwake++;54     }55     56     spin_unlock_irqrestore(&ep->lock,flags);57     if(pwake) 58         ep_poll_safewake(&psw,&ep->poll_wait);59         …....60     61     return 0;62     63     …...64     65 }

 1 //当poll醒来时就回调用该函数 2 static void ep_ptable_queue_proc(struct file *file,wait_queue_head_t *whead, 3                 poll_table *pt) 4 { 5     //从注册时的结构中struct ep_pqueue中获取项epi 6     struct epitem *epi = ep_item_from_epqueue(pt); 7     /*//epitem的私有项,通过pwqlist来进行链接 8      *struct eppoll_entry 9      {10         struct list_head llink;11         void *base;12         wait_queue_t wait;13         wait_queue_head_t *whead;14      }15 */16     struct eppoll_entry *pwq;//struct epitem的私有项,为每一个fd保存内核poll17 18 //为每一个等待的结构分配一项19     if(epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache,20             GFP_KERNEL)))21     {22         //醒来就调用ep_poll_callback,这里才是真正意义上的poll醒来时的回调函数23         init_waitqueue_func_entry(&pwq->wait,ep_poll_callback);24         pwq->whead = whead;25         pwq->base = epi;26         //加入到该驱动的等待队列27         add_wait_queue(whead,&pwq->wait);28         //将等待链接也放入到epitem链表中去29         list_add_tail(&pwq->llink,&epi->pwqlist);30         epi->nwait ++;        31     } else {32         epi->nwait = -1;33     }34 }35 //当poll监听的事件到达时,就会调用下面的函数36 static int ep_poll_callback(wait_queue_t *wait,unsigned mode,int sync,void *key)37 {38     int pwake = 0;39     unsigned long flags;40     struct epitem *epi = ep_item_from_wait(wait);41     struct eventpoll *ep = epi->ep;42     43     spin_lock_irqsave(&ep->lock,flags);44     //判断注册的感兴趣事件 45 //#define EP_PRIVATE_BITS      (EPOLLONESHOT | EPOLLET)46 //有非EPOLLONESHONT或EPOLLET事件47     if(!(epi->event.events & ~EP_PRIVATE_BITS))48             goto out_unlock;49     50     if(unlikely(ep->ovflist != EP_UNACTIVE_PTR))51     {52         if(epi->next == EP_UNACTIVE_PTR) {53             epi->next = ep->ovflist;54             ep->ovflist = epi;55         }56         goto out_unlock;57     }58 59     if(ep_is_linked(&epi->rdllink))60         goto is_linked;61     //关键是这一句,将该fd加入到epoll监听的就绪链表中62     list_add_tail(&epi->rdllink,&ep->rdllist);63 is_linked:64     if(waitqueue_active(&ep->wq))65         __wake_up_locked(&ep->wq,TASK_UNINTERRUPTIBLE 66             | TASK_INTERRUPTIBLE);    67     if(waitqueue_active(&ep->poll_wait))68         pwake++;69 out_unlock:70     spin_unlock_irqrestore(&ep->lock,flags);71     72     if(pwake)73         ep_poll_safewake(&psw,&ep->poll_wait);74     return 1;75 }

这里采用了两级回调方式,流程如下:

目前为止,整个数据结构就可以描述如下:

epoll_wait系统实现如下:

 1 asmlinkage long sys_epoll_wait(int epfd,struct epoll_event __user *events, 2             int maxevents,int timeout) 3 { 4     int error; 5     struct file *file; 6     struct eventpoll *ep; 7     //#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event)) 8 //178956970(1.7亿) 9     if(maxevents <=0 || maxevents > EP_MAX_EVETNS) 10         return -EINVAL;11     //判断返回事件数组是否合法12     if(!access_ok(VERIFY_WRITE,events,13             maxevents * sizeof(struct epoll_event)))14     {15         error = -EFAULT;16         goto error_return;17     }18 19     error = -EBADF;20     file = fget(epfd);21     22     if(!file)23         goto error_return;24     error = -EINVAL;25     if(!is_file_epoll(file))26         goto error_fput;27     //将epoll注册时设置的数据结构取出来,开始进行判断28     ep = file->private_data;29     error = ep_poll(ep,events,maxevents,timeout);30         ….......31 }

现在又转入了ep_poll函数中:

 1 static int ep_poll(struct eventpoll *ep,struct epoll_event __user *events, 2                 int maxevents,long timeout) 3 { 4     int res,avail; 5     unsigned long flags; 6     long jtimeout; 7     wait_queue_t wait; 8      9     //注册的0ms按0.999 Jiffies处理,并非真正的0s,HZ=100,10 //jiffies/HZ 为s11     jtimeout = (timeout<0 || timeout >= EP_MAX_MSTIMEO)?12         MAX_SCHEDULE_TIMEOUT:(timeout*HZ+999)/1000;13 14 retry:15     spin_lock_irqsave(&ep->lock,flags);16     17     res = 0;18     //事件就绪队列为空,就监听poll19     if(list_empty(&ep->rdllist))20     {21         //让当前进程挂在等待队列wait上,并将该等待队列加入到ep->wq(epoll_wait的            专属队列中),22         init_waitqueue_entry(&wait,current);23         wait.flags |= WQ_FLAG_EXCLUSIVE;24         __add_wait_queue(&ep->wq,&wait);25 26         for(;;){27             //进程设置睡眠状态,等到信息时变唤醒28             set_current_state(TASK_INTERRUPTIBLE);29             if(!list_empty(&ep->rdllist) || !jtimeout)//只要事件到来,就返回30                 break;31             if(signal_pending(current)) {//被信号中断就会返回32                 res = -EINTR;33                 break;34             }35         spin_unlock_irqrestore(&ep->lock,flags);36         //进程进入睡眠状态直到规定的睡眠事件醒来或者注册的fd对应的poll驱动函数唤醒该            进程37         jtimeout = schedule_timeout(jtimeout);38         spin_lock_irqrestore(&ep->lock,flags);39         }40     //poll驱动唤醒了该进程,现在就将对应的poll从等待队列中清除出去,并设置为运行状态41     __remove_wait_queue(&ep->wq,&wait);42     set_current_state(TASK_RUNNING);43     }44     eavail = !list_empty(&ep->rdllist);45     spin_unlock_irqrestore(&ep->lock,flags);46     //没有被中断,有就绪事件,并且向用户空间发送成功,就返回47     if(!res && eavail && !(res = ep_send_events(ep,events,maxevents))48         &&jtimeout)49         goto retry;50 51     return res;52 }

ep_send_events函数向用户空间发送就绪事件:

 1 static int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events,int maxevents) 2 { 3     int eventcnt,error = -EFAULT,pwake = 0; 4     unsigned int revents; 5     unsigned long flags; 6     struct epitem *epi,*nepi; 7     struct list_head txlist; 8  9     INIT_LIST_HEAD(&txlist);10     mutex_lock(&ep->mtx);11 12     spin_lock_irqsave(&ep->lock,flags);13     //将ep->rdllist链表加入到txlist链表中去,这样的话rdllist链表就为空了14     list_splice(&ep->rdllist,&txlist);15     INIT_LIST_HEAD(&ep->rdllist);16     ep->ovflist = NULL;17     spin_unlock_irqrestore(&ep->lock,flags);18     //将rdllist链表中的每一项都发送至用户空间19     for(eventcnt = 0; !list_empty(&txlist) && eventcnt < maxevents;) {20         21         epi = list_first_entry(&txlist,struct epitem,rdllink);22         list_del_init(&epi->rdllink);    23         //立刻返回当前文件的就绪事件24         revents = epi->ffd.file->f_op->poll(epi->ffd.file,NULL);25         revents &= epi->event.events;26         27         if(revents) {28             //将就绪事件的poll_event发送至用户空间29             if(__put_user(revents,&events[eventcnt.].events) ||30              __put_user(epi->event.data,&events[eventcnt].data))31                 32                 goto errxit;33             //#define EP_PRIVATE_BITS (EPOLLONESHOT | EPOLLET)34             if(epi->event.events & EPOLLONESHOT)35                 epi->event.events &= EP_PRIVATE_BITS;36             eventcnt++;37         }38      //非边缘触发,且事件就绪时,就将epi->rdllink加入到rdllist链表中,实际上就是将没有标记为ET模式的fd又放回到rdllist中,这样下次就绪时又能将其发送至用户空间了39      if(!(epi->event.events & EPOLLET) && (revents & 40                 epi->event.events))41             list_add_tail(&epi->rdllink,&ep->rdllist);42 }43     error = 0;44 errixt:45     spin_lock_irqsave(&ep->lock,flags);46     //在执行上面的代码期间,又有可能有就绪事件,这样的话就进入了ovflist队列,这样有需要再一次确认一次    47     for(nepi = ep->ovflist;(epi = nepi)!= NULL;48      nepi = epi->next;epi->next = EP_UNACTIVE_PTR) {49         //链表为空且没有ET事件发生,#define EP_PRIVATE_BITS (EPOLLONESHOT | EPOLLET),这里也和上面的一样50         if(!ep_is_linked(&epi->rdllink) && (epi->event.events & 51             ~EP_PRIVATE_BITS))52             //又将rdllink其加入到rdllist中53                 list_add_tail(&epi->rdllink,&ep->rdllist);54     }55     //#define EP_UNACTIVE_PTR    ((void*) -1L)56     ep->ovflist = EP_UNACTIVE_PTR;57     list_spice(&txlist,&ep->rdllist);//现在又将txlist链表加入到rdllist链表中去58     if(!list_empty(&ep->rdllist))59     {60         //等待的队列不为空61         if(waitqueue_active(&ep->wq))62             63             __wake_up_locked(&ep->wq,TASK_UNINTERRUPTIBLE |64             TASK_INTERRUPTIBLE);65         //如果poll队列不为空,则唤醒的次数加166         if(waitqueue_active(&ep->poll_wait))67             pwake++;68     }69     spin_unlock_irqrestore(&ep->lock,flags);70     mutex_unlock(&ep->mtx);71     if(pwake)72         ep_poll_safewake(&psw,&ep->poll_wait);73     return eventcnt == 0?error:eventcnt;74 }

这样epoll_wait的调用顺序为:

参考资料:

linux-2.6.24.3源代码

http://donghao.org/2009/08/linuxiapolliepollaueouaeaeeio.html

http://blog.chinaunix.net/uid-20687780-id-2105154.html

(转)epoll源码分析

时间: 2024-10-19 09:40:37

(转)epoll源码分析的相关文章

epoll源码分析(基于linux-5.1.4)

API epoll提供给用户进程的接口有如下四个,本文基于linux-5.1.4源码详细分析每个API具体做了啥工作,通过UML时序图理清内核内部的函数调用关系. int epoll_create1(int size): 创建一个epfd句柄,size为0时等价于int epoll_create(0). int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event): 向epfd上添加/修改/删除fd. int epoll_w

Epoll详解及源码分析

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Jan.7th, 2015 1.什么是epoll epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术,按照man手册的说法:是为处理大批量句柄而作了改进的poll. Linux下有以下几个经典的服务器

squid源码分析之--epoll和event驱动机制

最近在看squid的源码,刚开始毫无头绪,后来逐步找到一些感觉,记录之. squid的源码中大概有100多个c文件,一个一个地看明显行不通.我们需要逐步找出设计者的主线. 先从main.c入手,需要关注的,是main.c离结尾比较近的那一段,它是squid的心脏: "for(::){ ... event_run(); ... swich(comm_select()... ... }" event_run()函数定义在event.c文件里,它每次在恰当的时间从event.c的一个全局的e

epoll(2) 使用及源码分析的引子

epoll(2) 使用及源码分析的引子 本文代码取自内核版本 4.17 epoll(2) - I/O 事件通知设施. epoll 是内核在2.6版本后实现的,是对 select(2)/poll(2) 更高效的改进,同时它自身也是一种文件,不恰当的比方可以看作 eventfd + poll. 多路复用也是一直在改进的,经历的几个阶段 select(2) - 只能关注 1024 个文件描述符,并且范围固定在 0 - 1023,每次函数调用都需要把所有关注的数据复制进内核空间,再对所有的描述符集合进行

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

nginx源码分析--进程间通信机制 &amp; 同步机制

Nginx源码分析-进程间通信机制 从nginx的进程模型可以知道,master进程和worker进程需要通信,nginx中通信的方式有套接字.共享内存.信号.对于master进程,从外部接受信号,master进程主要就是监控.接受外部信号,将有必要的信号传递给worker进程,master进程大部分时间都是阻塞在sigsuspend()函数调用上.Worker进程屏蔽了所有的外部信号,那么Master进程就通过套接字和worker进程通信,worker进程修改全局变量,使得worker进程接受

NIO byteBUffer 讲解 及Mina 源码分析

1.传统的socket: 阻塞式通信模式 tcp连接: 与服务器连接时 .必须等到连接成功后 才返回 . udp连接: 客户端发送数据 ,必须等到发送成功后返回 . 每建立一个 Scoket连接时, 同事创建一个新线程对该 Socket进行单独通信(采用阻塞式通信 ) 这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果 对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况 2.1NIO 设计背后的基石:反应器模式,用于事

Android异步消息传递机制源码分析&amp;&amp;相关知识常被问的面试题

1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.postDelay(Runnable r, time)来在指定时间执行msg. 2).线程间通信:在执行较为耗时操作的时候,在子线程中执行耗时任务,然后handler(主线程的)把执行的结果通过sendmessage的方式发送给UI线程去执行用于更新UI. 3.handler源码分析 一.在Activ

libevent高性能网络库源码分析——事件处理框架(四)

event_base结构 event_base的初始化 接口函数 libevent中基于Reactor模式的事件处理框架对应event_base,在event在完成创建后,需要向event_base注册事件,监控事件的当前状态,当事件状态为激活状(EV_ACTIVE)时,调用回调函数执行.本文主要从以下几方面进行分析:event_base的结构,event_base的创建,事件的注册.事件分发.事件注销 event_base结构 struct event_base { //指定某个eventop