epoll_create & epoll_ctl & epoll_wait Kernel实现 -- Kernel 3.0.8

来源:互联网 发布:淘宝怎么能卖出东西 编辑:程序博客网 时间:2024/04/27 17:47

1. 相关数据结构

[html] view plaincopy
  1. <span style="font-size:10px;">#define EPOLLIN          0x00000001  
  2. #define EPOLLPRI         0x00000002  
  3. #define EPOLLOUT         0x00000004  
  4. #define EPOLLERR         0x00000008  
  5. #define EPOLLHUP         0x00000010  
  6. #define EPOLLRDNORM      0x00000040  
  7. #define EPOLLRDBAND      0x00000080  
  8. #define EPOLLWRNORM      0x00000100  
  9. #define EPOLLWRBAND      0x00000200  
  10. #define EPOLLMSG         0x00000400  
  11. #define EPOLLET          0x80000000  
  12.   
  13. #define EPOLL_CTL_ADD    1  
  14. #define EPOLL_CTL_DEL    2  
  15. #define EPOLL_CTL_MOD    3  
  16.   
  17. typedef union epoll_data   
  18. {  
  19.     void *ptr;  
  20.     int fd;  
  21.     unsigned int u32;  
  22.     unsigned long long u64;  
  23. } epoll_data_t;  
  24.   
  25. struct epoll_event   
  26. {  
  27.     unsigned int events;  //如EPOLLIN、EPOLLOUT  
  28.     epoll_data_t data;  
  29. };  
  30.   
  31. int epoll_create(int size);  
  32. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //op为:EPOLL_CTL_ADD、EPOLL_CTL_DEL  
  33. int epoll_wait(int epfd, struct epoll_event *events, int max, int timeout);  
  34. </span>  

常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;

2. epoll_create

在Bionic中的实现见:epoll_create.S,它直接进行系统调用,系统调用表见kernel:src/include/linux/syscalls.h,根据规则在Kernel中Search字符串“epoll_create”,就能找到对应的实现函数:SYSCALL_DEFINE1(epoll_create, int, size) <在Kernel的实现在文件eventpoll.c中>

[html] view plaincopy
  1. SYSCALL_DEFINE1(epoll_create, int, size)  
  2. {  
  3.     if (size <= 0)  
  4.         return -EINVAL;  
  5.   
  6.     return sys_epoll_create1(0);  
  7. }  

看仔细了, 只要传入的参数大于0即可,它并没有别的用处。此函数功能为:

1)从当前进程的files中寻找一个空闲的fd(文件句柄)

2)创建一个struct file实例(其fops为eventpoll_fops,priv为刚为其创建的struct eventpoll对象)

3)当前进程的files->fdt->fd[fd]为新创建的struct file实例

4)返回给用户态的当然是一个fd(文件句柄)

 

3. epoll_ctl

用户态:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

Kernel态:SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,  struct epoll_event __user *, event),它主要实现eventpoll文件的控制接口,用于插入、删除、修改文件集中的文件描述符。其代码处理流程为:<epoll_event用于描述感兴趣的事件和源fd>

1)获取eventpoll文件句柄epfd对应的文件实例(struct file)

2)获取目标文件句柄fd对应的文件实例(struct file)

3)确保目标文件句柄fd对应的文件实例(struct file)支持poll操作(即:(tfile->f_op &&  tfile->f_op->poll))

4)把eventpoll文件的私有数据转换为eventpoll对象,eventpoll红黑树的key为:epoll_filefd

      struct epoll_filefd {
            struct file *file;
            int fd;
      };

5)在红黑树中查找要操作的目标fd和fie实例,从而获取一个struct epitem,红黑树中节点的数据结构

6)根据op,进行对应的INSERT、REMOVE或MODIFY操作,下面说说INSERT(当操作为EPOLL_CTL_ADD时)

7)调用int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)

7.1)创建struct epitem对象(每一个文件描述符增加到eventpoll接口必须有一个epitem对象,且此对象被插入eventpoll的红黑树中)

7.2)初始化epi中的三个链表,保存eventpoll,目标fd,目标文件实例,epoll_event..

7.3)把回调函数注册给目标文件的f_op->poll,相关代码如下:

         struct ep_pqueue epq;
         epq.epi = epi;
         init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
         revents = tfile->f_op->poll(tfile, &epq.pt); //详情可参考pipe_poll处理方式,它最终还是调用ep_ptable_queue_proc函数来处理

       ep_ptable_queue_proc:  is used to add our wait queue to the target file wakeup lists
7.4)把此对象插入红黑树中

[cpp] view plaincopy
  1. <span style="font-size:10px;">/* 
  2.  * This is the callback that is used to add our wait queue to the 
  3.  * target file wakeup lists. 
  4.  */  
  5. static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,  
  6.                  poll_table *pt)  
  7. {  
  8.     struct epitem *epi = ep_item_from_epqueue(pt);  
  9.     struct eppoll_entry *pwq;  
  10.   
  11.     if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {  
  12.         init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);  
  13.         pwq->whead = whead;  
  14.         pwq->base = epi;  
  15.         add_wait_queue(whead, &pwq->wait);  
  16.         list_add_tail(&pwq->llink, &epi->pwqlist);  
  17.         epi->nwait++;  
  18.     } else {  
  19.         /* We have to signal that an error occurred */  
  20.         epi->nwait = -1;  
  21.     }  
  22. }</span>  

 

[cpp] view plaincopy
  1. /* 
  2.  * This is the callback that is passed to the wait queue wakeup 
  3.  * mechanism. It is called by the target file descriptors when they 
  4.  * have events to report. 
  5.  */  
  6. static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)  
  7. {  
  8.    ...  
  9. }  

这ep_poll_callback如何被执行的呢?

下面以pipe为例,假设上面的是检测从pipe中读取数据,哪么写数据时将调用此函数。

[cpp] view plaincopy
  1. init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);  
  2. static inline void init_waitqueue_func_entry(wait_queue_t *q,  
  3.                     wait_queue_func_t func)  
  4. {  
  5.     q->flags = 0;  
  6.     q->private = NULL;  
  7.     q->func = func;  
  8. }  


ep_poll_callback被保存在q->func中。

下面看此q->func的调用流程:

[cpp] view plaincopy
  1. static ssize_t  
  2. pipe_write(struct kiocb *iocb, const struct iovec *_iov,  
  3.         unsigned long nr_segs, loff_t ppos)  
  4. {   
  5.         ...  
  6.     if (do_wakeup) {  
  7.         wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLRDNORM);  
  8.         kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);  
  9.         do_wakeup = 0;  
  10.     }  
  11.         ...  
  12. }  
  13.   
  14. #define wake_up_interruptible_sync_poll(x, m)               \  
  15.     __wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))  
  16.   
  17. /** 
  18.  * __wake_up_sync_key - wake up threads blocked on a waitqueue. 
  19.  */  
  20. void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,  
  21.             int nr_exclusive, void *key)  
  22. {  
  23.         ...  
  24.     __wake_up_common(q, mode, nr_exclusive, wake_flags, key);  
  25.         ...  
  26.   
  27. }  
  28. /* 
  29.  * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just 
  30.  * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve 
  31.  * number) then we wake all the non-exclusive tasks and one exclusive task. 
  32.  * 
  33.  * There are circumstances in which we can try to wake a task which has already 
  34.  * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns 
  35.  * zero in this (rare) case, and we handle it by continuing to scan the queue. 
  36.  */  
  37. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  38.             int nr_exclusive, int wake_flags, void *key)  
  39. {  
  40.     wait_queue_t *curr, *next;  
  41.   
  42.     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
  43.         unsigned flags = curr->flags;  
  44.   
  45.         if (curr->func(curr, mode, wake_flags, key) &&  
  46.                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
  47.             break;  
  48.     }  
  49. }  



总结一下:目标文件以pipefd为例。为新的目标文件生成一个epitem包含 pipe fd和需要监听的事件,并将epitem与函数ep_ptable_queue_proc的地址捆绑成一个ep_pqueue结构,然后用结构中的函数地址字段作为参数执行pipe fd对应的poll函数(pipe_poll),在pipe_poll执行时函数ep_ptable_queue_proc被执行,同时函数体中可以根据传入的函数地址计算偏移来得到epitem指针,函数ep_ptable_queue_proc将epoll回调函数ep_poll_callback函数与epitem指针捆绑成另一个结构eppoll_entry,然后把eppoll_entry中的函数地址生成一个wait_queue_t,插入到目标pipe fd的wait queue中,当pipe由于状态改变而触发激活wait_queue时<在pipe_write中调用wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLRDNORM);它将wake up threads blocked on the waitqueue(pipe->wait)>,包含在队列中的ep_poll_callback函数就会被调用,同时根据其函数地址参数,用偏移量来得到epitem,回调函数在调用时会再执行pipe_poll函数,来明确是不是指定的关注事件发生,若成立则将 epitem插入到eventpoll中的rdlist,并激活在epoll fd上wait的进程,并将事件回传至用户态.这样就能实现对目标fd的事件监听.

 

4. epoll_wait

从epfd中读取epoll_event并保存到events数组中。

用户态:int epoll_wait(int epfd, struct epoll_event *events, int max, int timeout);

Kernel态:SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)

1)获取epfd对应的文件实例

2)把eventpoll文件的私有数据转换为eventpoll对象

3)调用int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
     int maxevents, long timeout)获取epoll_event。此函数获取已经准备好的事件,然后把他们保存到调用都提供的events buffer中。

3.1)ep_poll中调用hrtimer来实现其超时功能

3.2)调用int ep_events_available(struct eventpoll *ep)来check是否有事件

3.3)调用int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events, int maxevents)来真正地获取事件,并copy到用户空间的events buffer中

3.3.1)调用ep_scan_ready_list(ep, ep_send_events_proc, &esed);

3.3.2)在回调函数中,获取epoll_event,epoll_event两个域的数据来源如下:

  uevent是用户提供的epoll_event,

  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;
   }

   ...

}


原创粉丝点击