epoll是由一组系统调用组成。
     int epoll_create(int size);
     int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
     int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
     select/poll的缺点在于:
     1.每次调用时要重复地从用户态读入参数。
     2.每次调用时要重复地扫描文件描述符。
     3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
     在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll

显得不够高效。epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个epoll_wait。

epoll机制实现了自己特有的文件系统eventpoll filesystem

[cpp] view plaincopy
  1. /* File callbacks that implement the eventpoll file behaviour */
  2. static const struct file_operations eventpoll_fops = {
  3. .release    = ep_eventpoll_release,
  4. .poll       = ep_eventpoll_poll
  5. };

epoll_create创建一个属于该文件系统的文件,然后返回其文件描述符。

struct eventpoll 保存了epoll文件节点的扩展信息,该结构保存于file结构体的private_data域中,每个epoll_create创建的epoll

描述符都分配一个该结构体。该结构的各个成员的定义如下,注释也很详细。

[cpp] view plaincopy
  1. /*
  2. * This structure is stored inside the "private_data" member of the file
  3. * structure and rapresent the main data sructure for the eventpoll
  4. * interface.
  5. */
  6. struct eventpoll {
  7. /* Protect the this structure access,可用于中断上下文 */
  8. spinlock_t lock;
  9. /*
  10. * This mutex is used to ensure that files are not removed
  11. * while epoll is using them. This is held during the event
  12. * collection loop, the file cleanup path, the epoll file exit
  13. * code and the ctl operations.用户进程上下文中
  14. */
  15. struct mutex mtx;
  16. /* Wait queue used by sys_epoll_wait() */
  17. wait_queue_head_t wq;
  18. /* Wait queue used by file->poll() */
  19. wait_queue_head_t poll_wait;
  20. /* List of ready file descriptors */
  21. struct list_head rdllist;
  22. /* RB tree root used to store monitored fd structs */
  23. struct rb_root rbr;
  24. /*
  25. * This is a single linked list that chains all the "struct epitem" that
  26. * happened while transfering ready events to userspace w/out
  27. * holding ->lock.
  28. */
  29. struct epitem *ovflist;
  30. /* The user that created the eventpoll descriptor */
  31. struct user_struct *user;
  32. };

而通过epoll_ctl接口加入该epoll描述符监听的套接字则属于socket filesystem,这点一定要注意。每个添加的待监听(这里监听

和listen调用不同)都对应于一个epitem结构体,该结构体已红黑树的结构组织,eventpoll结构中保存了树的根节点(rbr成员)。

同时有监听事件到来的套接字的该结构以双向链表组织起来,链表头也保存在eventpoll中(rdllist成员)。

[c-sharp] view plaincopy
  1. /*
  2. * Each file descriptor added to the eventpoll interface will
  3. * have an entry of this type linked to the "rbr" RB tree.
  4. */
  5. struct epitem {
  6. /* RB tree node used to link this structure to the eventpoll RB tree */
  7. struct rb_node rbn;
  8. /* List header used to link this structure to the eventpoll ready list */
  9. struct list_head rdllink;
  10. /*
  11. * Works together "struct eventpoll"->ovflist in keeping the
  12. * single linked chain of items.
  13. */
  14. struct epitem *next;
  15. /* The file descriptor information this item refers to */
  16. struct epoll_filefd ffd;
  17. /* Number of active wait queue attached to poll operations */
  18. int nwait;
  19. /* List containing poll wait queues */
  20. struct list_head pwqlist;
  21. /* The "container" of this item */
  22. struct eventpoll *ep;
  23. /* List header used to link this item to the "struct file" items list */
  24. struct list_head fllink;
  25. /* The structure that describe the interested events and the source fd */
  26. struct epoll_event event;
  27. };

epoll_create的调用很简单,就是创建一个epollevent的文件,并返回文件描述符。

epoll_ctl用来添加,删除以及修改监听项。

[c-sharp] view plaincopy
  1. /*
  2. * The following function implements the controller interface for
  3. * the eventpoll file that enables the insertion/removal/change of
  4. * file descriptors inside the interest set.
  5. */
  6. SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
  7. struct epoll_event __user *, event)
  8. {
  9. int error;
  10. struct file *file, *tfile;
  11. struct eventpoll *ep;
  12. struct epitem *epi;
  13. struct epoll_event epds;
  14. DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)/n",
  15. current, epfd, op, fd, event));
  16. error = -EFAULT;
  17. if (ep_op_has_event(op) &&
  18. copy_from_user(&epds, event, sizeof(struct epoll_event)))
  19. goto error_return;
  20. /* Get the "struct file *" for the eventpoll file */
  21. error = -EBADF;
  22. file = fget(epfd);
  23. if (!file)
  24. goto error_return;
  25. /* Get the "struct file *" for the target file */
  26. tfile = fget(fd);
  27. if (!tfile)
  28. goto error_fput;
  29. /* The target file descriptor must support poll */
  30. error = -EPERM;
  31. if (!tfile->f_op || !tfile->f_op->poll)
  32. goto error_tgt_fput;
  33. /*
  34. * We have to check that the file structure underneath the file descriptor
  35. * the user passed to us _is_ an eventpoll file. And also we do not permit
  36. * adding an epoll file descriptor inside itself.
  37. */
  38. error = -EINVAL;
  39. if (file == tfile || !is_file_epoll(file))
  40. goto error_tgt_fput;
  41. /*
  42. * At this point it is safe to assume that the "private_data" contains
  43. * our own data structure.
  44. */
  45. ep = file->private_data;
  46. mutex_lock(&ep->mtx);
  47. /*
  48. * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
  49. * above, we can be sure to be able to use the item looked up by
  50. * ep_find() till we release the mutex.
  51. */
  52. epi = ep_find(ep, tfile, fd);
  53. error = -EINVAL;
  54. switch (op) {
  55. case EPOLL_CTL_ADD:
  56. if (!epi) {
  57. epds.events |= POLLERR | POLLHUP;
  58. error = ep_insert(ep, &epds, tfile, fd);
  59. } else
  60. error = -EEXIST;
  61. break;
  62. case EPOLL_CTL_DEL:
  63. if (epi)
  64. error = ep_remove(ep, epi);
  65. else
  66. error = -ENOENT;
  67. break;
  68. case EPOLL_CTL_MOD:
  69. if (epi) {
  70. epds.events |= POLLERR | POLLHUP;
  71. error = ep_modify(ep, epi, &epds);
  72. } else
  73. error = -ENOENT;
  74. break;
  75. }
  76. mutex_unlock(&ep->mtx);
  77. error_tgt_fput:
  78. fput(tfile);
  79. error_fput:
  80. fput(file);
  81. error_return:
  82. DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d/n",
  83. current, epfd, op, fd, event, error));
  84. return error;
  85. }

同样,代码很清楚。先来看看添加流程

[c-sharp] view plaincopy
  1. /*
  2. * Must be called with "mtx" held.
  3. */
  4. static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
  5. struct file *tfile, int fd)
  6. {
  7. int error, revents, pwake = 0;
  8. unsigned long flags;
  9. struct epitem *epi;
  10. struct ep_pqueue epq;
  11. /* 不允许超过最大监听个数*/
  12. if (unlikely(atomic_read(&ep->user->epoll_watches) >=
  13. max_user_watches))
  14. return -ENOSPC;
  15. if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
  16. return -ENOMEM;
  17. /* Item initialization follow here ... */
  18. INIT_LIST_HEAD(&epi->rdllink);
  19. INIT_LIST_HEAD(&epi->fllink);
  20. INIT_LIST_HEAD(&epi->pwqlist);
  21. epi->ep = ep;
  22. ep_set_ffd(&epi->ffd, tfile, fd);
  23. epi->event = *event;
  24. epi->nwait = 0;
  25. epi->next = EP_UNACTIVE_PTR;
  26. /* Initialize the poll table using the queue callback */
  27. epq.epi = epi;
  28. init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
  29. /*
  30. * Attach the item to the poll hooks and get current event bits.
  31. * We can safely use the file* here because its usage count has
  32. * been increased by the caller of this function. Note that after
  33. * this operation completes, the poll callback can start hitting
  34. * the new item.
  35. */
  36. revents = tfile->f_op->poll(tfile, &epq.pt);
  37. /*
  38. * We have to check if something went wrong during the poll wait queue
  39. * install process. Namely an allocation for a wait queue failed due
  40. * high memory pressure.
  41. */
  42. error = -ENOMEM;
  43. if (epi->nwait < 0)
  44. goto error_unregister;
  45. /* Add the current item to the list of active epoll hook for this file */
  46. spin_lock(&tfile->f_ep_lock);
  47. list_add_tail(&epi->fllink, &tfile->f_ep_links);
  48. spin_unlock(&tfile->f_ep_lock);
  49. /*
  50. * Add the current item to the RB tree. All RB tree operations are
  51. * protected by "mtx", and ep_insert() is called with "mtx" held.
  52. */
  53. ep_rbtree_insert(ep, epi);
  54. /* We have to drop the new item inside our item list to keep track of it */
  55. spin_lock_irqsave(&ep->lock, flags);
  56. /* If the file is already "ready" we drop it inside the ready list */
  57. if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
  58. list_add_tail(&epi->rdllink, &ep->rdllist);
  59. /* Notify waiting tasks that events are available */
  60. if (waitqueue_active(&ep->wq))
  61. wake_up_locked(&ep->wq);
  62. if (waitqueue_active(&ep->poll_wait))
  63. pwake++;
  64. }
  65. spin_unlock_irqrestore(&ep->lock, flags);
  66. atomic_inc(&ep->user->epoll_watches);
  67. /* We have to call this outside the lock */
  68. if (pwake)
  69. ep_poll_safewake(&psw, &ep->poll_wait);
  70. DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)/n",
  71. current, ep, tfile, fd));
  72. return 0;
  73. error_unregister:
  74. ep_unregister_pollwait(ep, epi);
  75. /*
  76. * We need to do this because an event could have been arrived on some
  77. * allocated wait queue. Note that we don't care about the ep->ovflist
  78. * list, since that is used/cleaned only inside a section bound by "mtx".
  79. * And ep_insert() is called with "mtx" held.
  80. */
  81. spin_lock_irqsave(&ep->lock, flags);
  82. if (ep_is_linked(&epi->rdllink))
  83. list_del_init(&epi->rdllink);
  84. spin_unlock_irqrestore(&ep->lock, flags);
  85. kmem_cache_free(epi_cache, epi);
  86. return error;
  87. }

init_poll_funcptr函数注册poll table回调函数。然后程序的下一步是调用tfile的poll函数,并且poll函数的第2个参数为poll table,

这是epoll机制中唯一对监听套接字调用poll时第2个参数不为NULL的时机。ep_ptable_queue_proc函数的作用是注册等待函数

并添加到指定的等待队列,所以在第一次调用后,该信息已经存在了,无需在poll函数中再次调用了。

[c-sharp] view plaincopy
  1. /*
  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. if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
  11. /* 为监听套接字注册一个等待回调函数,在唤醒时调用*/
  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. }

那么该poll函数到底是怎样的呢,这就要看我们在传入到epoll_ctl前创建的套接字的类型(socket调用)。对于创建的tcp套接字

来说,可以按照创建流程找到其对应得函数是tcp_poll。

tcp_poll的主要功能为:

  1. 如果poll table回调函数存在(ep_ptable_queue_proc),则调用它来等待。注意这只限第一次调用,在后面的poll中都无需此步
  2. 判断事件的到达。(根据tcp的相关成员)

tcp_poll注册到的等待队列是sock成员的sk_sleep,等待队列在对应的IO事件中被唤醒。当等待队列被唤醒时会调用相应的等待回调函数

,前面看到我们注册的是函数ep_poll_callback。该函数可能在中断上下文中调用。

[c-sharp] view plaincopy
  1. /*
  2. * This is the callback that is passed to the wait queue wakeup
  3. * machanism. It is called by the stored 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. int pwake = 0;
  9. unsigned long flags;
  10. struct epitem *epi = ep_item_from_wait(wait);
  11. struct eventpoll *ep = epi->ep;
  12. DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%p/n",
  13. current, epi->ffd.file, epi, ep));
  14. /* 对eventpoll的spinlock加锁,因为是在中断上下文中*/
  15. spin_lock_irqsave(&ep->lock, flags);
  16. /* 没有事件到来
  17. * If the event mask does not contain any poll(2) event, we consider the
  18. * descriptor to be disabled. This condition is likely the effect of the
  19. * EPOLLONESHOT bit that disables the descriptor when an event is received,
  20. * until the next EPOLL_CTL_MOD will be issued.
  21. */
  22. if (!(epi->event.events & ~EP_PRIVATE_BITS))
  23. goto out_unlock;
  24. /*
  25. * If we are trasfering events to userspace, we can hold no locks
  26. * (because we're accessing user memory, and because of linux f_op->poll()
  27. * semantics). All the events that happens during that period of time are
  28. * chained in ep->ovflist and requeued later on.
  29. */
  30. if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
  31. if (epi->next == EP_UNACTIVE_PTR) {
  32. epi->next = ep->ovflist;
  33. ep->ovflist = epi;
  34. }
  35. goto out_unlock;
  36. }
  37. /* If this file is already in the ready list we exit soon */
  38. if (ep_is_linked(&epi->rdllink))
  39. goto is_linked;
  40. /* 加入ready queue*/
  41. list_add_tail(&epi->rdllink, &ep->rdllist);
  42. is_linked:
  43. /*
  44. * Wake up ( if active ) both the eventpoll wait list and the ->poll()
  45. * wait list.
  46. */
  47. if (waitqueue_active(&ep->wq))
  48. wake_up_locked(&ep->wq);
  49. if (waitqueue_active(&ep->poll_wait))
  50. pwake++;
  51. out_unlock:
  52. spin_unlock_irqrestore(&ep->lock, flags);
  53. /* We have to call this outside the lock */
  54. if (pwake)
  55. ep_poll_safewake(&psw, &ep->poll_wait);
  56. return 1;
  57. }

注意这里有2中队列,一种是在epoll_wait调用中使用的eventpoll的等待队列,用于判断是否有监听套接字可用,一种是对应于每个套接字

的等待队列sk_sleep,用于判断每个监听套接字上事件,该队列唤醒后调用ep_poll_callback,在该函数中又调用wakeup函数来唤醒前一种

队列,来通知epoll_wait调用进程。

[cpp] view plaincopy
  1. static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
  2. int maxevents, long timeout)
  3. {
  4. int res, eavail;
  5. unsigned long flags;
  6. long jtimeout;
  7. wait_queue_t wait;
  8. /*
  9. * Calculate the timeout by checking for the "infinite" value ( -1 )
  10. * and the overflow condition. The passed timeout is in milliseconds,
  11. * that why (t * HZ) / 1000.
  12. */
  13. jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?
  14. MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;
  15. retry:
  16. spin_lock_irqsave(&ep->lock, flags);
  17. res = 0;
  18. if (list_empty(&ep->rdllist)) {
  19. /*
  20. * We don't have any available event to return to the caller.
  21. * We need to sleep here, and we will be wake up by
  22. * ep_poll_callback() when events will become available.
  23. */
  24. init_waitqueue_entry(&wait, current);
  25. wait.flags |= WQ_FLAG_EXCLUSIVE;
  26. __add_wait_queue(&ep->wq, &wait);
  27. for (;;) {
  28. /*
  29. * We don't want to sleep if the ep_poll_callback() sends us
  30. * a wakeup in between. That's why we set the task state
  31. * to TASK_INTERRUPTIBLE before doing the checks.
  32. */
  33. set_current_state(TASK_INTERRUPTIBLE);
  34. if (!list_empty(&ep->rdllist) || !jtimeout)
  35. break;
  36. if (signal_pending(current)) {
  37. res = -EINTR;
  38. break;
  39. }
  40. spin_unlock_irqrestore(&ep->lock, flags);
  41. jtimeout = schedule_timeout(jtimeout);
  42. spin_lock_irqsave(&ep->lock, flags);
  43. }
  44. __remove_wait_queue(&ep->wq, &wait);
  45. set_current_state(TASK_RUNNING);
  46. }
  47. /* Is it worth to try to dig for events ? */
  48. eavail = !list_empty(&ep->rdllist);
  49. spin_unlock_irqrestore(&ep->lock, flags);
  50. /*
  51. * Try to transfer events to user space. In case we get 0 events and
  52. * there's still timeout left over, we go trying again in search of
  53. * more luck.
  54. */
  55. if (!res && eavail &&
  56. !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
  57. goto retry;
  58. return res;
  59. }

该函数是在epoll_wait中调用的等待函数,其等待被ep_poll_callback唤醒,然后调用ep_send_events来把到达事件copy到用户空间,然后

epoll_wait才返回。

最后我们来看看ep_poll_callback函数和ep_send_events函数的同步,因为他们都要操作ready queue。

eventpoll中巧妙地设置了2种类型的锁,一个是mtx,是个mutex类型,是对该描述符操作的基本同步锁,可以睡眠;所以又存在了另外一个

锁,lock,它是一个spinlock类型,不允许睡眠,所以用在ep_poll_callback中,注意mtx不能用于此。

注意由于ep_poll_callback函数中会涉及到对eventpoll的ovflist和rdllist成员的访问,所以在任意其它地方要访问时都要先加mxt,在加lock锁。

由于中断的到来时异步的,为了方便,先看ep_send_events函数。

[cpp] view plaincopy
  1. static int ep_send_events(struct eventpoll *ep, struct epoll_event __user *events,
  2. int maxevents)
  3. {
  4. int eventcnt, error = -EFAULT, pwake = 0;
  5. unsigned int revents;
  6. unsigned long flags;
  7. struct epitem *epi, *nepi;
  8. struct list_head txlist;
  9. INIT_LIST_HEAD(&txlist);
  10. /*
  11. * We need to lock this because we could be hit by
  12. * eventpoll_release_file() and epoll_ctl(EPOLL_CTL_DEL).
  13. */
  14. mutex_lock(&ep->mtx);
  15. /*
  16. * Steal the ready list, and re-init the original one to the
  17. * empty list. Also, set ep->ovflist to NULL so that events
  18. * happening while looping w/out locks, are not lost. We cannot
  19. * have the poll callback to queue directly on ep->rdllist,
  20. * because we are doing it in the loop below, in a lockless way.
  21. */
  22. spin_lock_irqsave(&ep->lock, flags);
  23. list_splice(&ep->rdllist, &txlist);
  24. INIT_LIST_HEAD(&ep->rdllist);
  25. ep->ovflist = NULL;
  26. spin_unlock_irqrestore(&ep->lock, flags);
  27. /*
  28. * We can loop without lock because this is a task private list.
  29. * We just splice'd out the ep->rdllist in ep_collect_ready_items().
  30. * Items cannot vanish during the loop because we are holding "mtx".
  31. */
  32. for (eventcnt = 0; !list_empty(&txlist) && eventcnt < maxevents;) {
  33. epi = list_first_entry(&txlist, struct epitem, rdllink);
  34. list_del_init(&epi->rdllink);
  35. /*
  36. * Get the ready file event set. We can safely use the file
  37. * because we are holding the "mtx" and this will guarantee
  38. * that both the file and the item will not vanish.
  39. */
  40. revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
  41. revents &= epi->event.events;
  42. /*
  43. * Is the event mask intersect the caller-requested one,
  44. * deliver the event to userspace. Again, we are holding
  45. * "mtx", so no operations coming from userspace can change
  46. * the item.
  47. */
  48. if (revents) {
  49. if (__put_user(revents,
  50. &events[eventcnt].events) ||
  51. __put_user(epi->event.data,
  52. &events[eventcnt].data))
  53. goto errxit;
  54. if (epi->event.events & EPOLLONESHOT)
  55. epi->event.events &= EP_PRIVATE_BITS;
  56. eventcnt++;
  57. }
  58. /*
  59. * At this point, noone can insert into ep->rdllist besides
  60. * us. The epoll_ctl() callers are locked out by us holding
  61. * "mtx" and the poll callback will queue them in ep->ovflist.
  62. */
  63. if (!(epi->event.events & EPOLLET) &&
  64. (revents & epi->event.events))
  65. list_add_tail(&epi->rdllink, &ep->rdllist);
  66. }
  67. error = 0;
  68. errxit:
  69. spin_lock_irqsave(&ep->lock, flags);
  70. /*
  71. * During the time we spent in the loop above, some other events
  72. * might have been queued by the poll callback. We re-insert them
  73. * inside the main ready-list here.
  74. */
  75. for (nepi = ep->ovflist; (epi = nepi) != NULL;
  76. nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {
  77. /*
  78. * If the above loop quit with errors, the epoll item might still
  79. * be linked to "txlist", and the list_splice() done below will
  80. * take care of those cases.
  81. */
  82. if (!ep_is_linked(&epi->rdllink))
  83. list_add_tail(&epi->rdllink, &ep->rdllist);
  84. }
  85. /*
  86. * We need to set back ep->ovflist to EP_UNACTIVE_PTR, so that after
  87. * releasing the lock, events will be queued in the normal way inside
  88. * ep->rdllist.
  89. */
  90. ep->ovflist = EP_UNACTIVE_PTR;
  91. /*
  92. * In case of error in the event-send loop, or in case the number of
  93. * ready events exceeds the userspace limit, we need to splice the
  94. * "txlist" back inside ep->rdllist.
  95. */
  96. list_splice(&txlist, &ep->rdllist);
  97. if (!list_empty(&ep->rdllist)) {
  98. /*
  99. * Wake up (if active) both the eventpoll wait list and the ->poll()
  100. * wait list (delayed after we release the lock).
  101. */
  102. if (waitqueue_active(&ep->wq))
  103. wake_up_locked(&ep->wq);
  104. if (waitqueue_active(&ep->poll_wait))
  105. pwake++;
  106. }
  107. spin_unlock_irqrestore(&ep->lock, flags);
  108. mutex_unlock(&ep->mtx);
  109. /* We have to call this outside the lock */
  110. if (pwake)
  111. ep_poll_safewake(&psw, &ep->poll_wait);
  112. return eventcnt == 0 ? error: eventcnt;
  113. }

该函数的注释也很清晰,不过我们从总体上分析下。

首先函数加mtx锁,这时必须的。

然后得工作是要读取ready queue,但是中断会写这个成员,所以要加spinlock;但是接下来的工作会sleep,所以在整个loop都加spinlock显然

会阻塞ep_poll_callback函数,从而阻塞中断,这是个很不好的行为,也不可取。于是epoll中在eventpoll中设置了另一个成员ovflist。在读取ready

queue前,我们设置该成员为NULL,然后就可以释放spinlock了。为什么这样可行呢,因为对应的,在ep_poll_callback中,获取spinlock后,对于

到达的事件并不总是放入ready queue,而是先判断ovflist是否为EP_UNACTIVE_PTR。

[cpp] view plaincopy
  1. if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
  2. /* 进入此处说明用用户进程在调用ep_poll_callback,所以把事件加入ovflist中,而不是ready queue中*/
  3. if (epi->next == EP_UNACTIVE_PTR) {/* 如果此处条件不成立,说明该epi已经在ovflist中,所以直接返回*/
  4. epi->next = ep->ovflist;
  5. ep->ovflist = epi;
  6. }
  7. goto out_unlock;
  8. }

所以在此期间,到达的事件放入了ovflist中。当loop结束后,函数接着遍历该list,添加到ready queue中,最后设置ovflist为EP_UNACTIVE_PTR,

这样下次中断中的事件可以放入ready queue了。最后判断是否有其他epoll_wait调用被阻塞,则唤醒。

从源代码中,可以看出epoll的几大优点:

  1. 用户传入的信息保存在内核中了,无需每次传入
  2. 事件监听机制不在是 整个监听队列,而是每个监听套接字在有事件到达时通过等待回调函数异步通知epoll,然后再返回给用户。

同时epoll中的同步机制也是一个内核编程的设计经典,值得深入理解。

epoll的内核实现相关推荐

  1. CentOS 7 Linux实时内核下的epoll性能分析后续 | 火焰图分析

    在<CentOS 7 Linux实时内核下的epoll性能分析>从源码角度分析了epoll在实时内核和非实时内核之间的差异,为了更好的展示问题所在,这里给出epoll和select在实时内 ...

  2. Linux内核剖析-----IO复用函数epoll内核源码剖析

    本文参考董浩博客 http://donghao.org/uii/   epoll内核实现 (1)内核为epoll做准备工作 这个模块在内核初始化时(操作系统启动)注册了一个新的文件系统,叫" ...

  3. 详细讲解Linux内核epoll内核原理与实现

    第一部分:select和epoll的任务 关键词:应用程序 文件句柄 用户态 内核态 监控者 要比较epoll相比较select高效在什么地方,就需要比较二者做相同事情的方法. 要完成对I/O流的复用 ...

  4. 网络编程(三):Linux 网络IO模型、select、pool、epoll 内核设计

    Linux网络IO模型 同步和异步,阻塞和非阻塞 同步和异步 关注的是调用方是否主动获取结果 同步: 同步的意思就是调用方需要主动等待结果的返回 异步: 异步的意思就是不需要主动等待结果的返回,而是通 ...

  5. linux epoll用法

    epoll 是 linux 特有的 I/O 复用函数.它是把用户关心的文件描述符事件放在内核的一个事件列表中,故而,无须像select和poll一样每次调用都重复传入文件描述符或事件集.但是, epo ...

  6. select,epoll,poll比较

    select,poll,epoll简介 select select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理.这样所带来的缺点是: 1 单个进程可监视的fd数量被限制 2 需要维 ...

  7. epoll 的实现原理以及与poll,select 的对比

    最近面试的时候 被问到epoll的问题,就下来查一查,看到有篇文章不错,就记录下来,供大家参考学习. 以一个生活中的例子来解释. 假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但 ...

  8. select,epoll,poll比较(网络资源总结)

    select,poll,epoll简介 select select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理.这样所带来的缺点是: 1 单个进程可监视的fd数量被限制 2 需要维 ...

  9. Linux下epoll如何实现高效处理百万句柄的

    开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll.大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的 ...

最新文章

  1. WebGIS中基于AGS的画圆查询简析以及通过Polygon来构造圆的算法
  2. rocketmq官网和其它资料
  3. python qq签到_Yii Framework 中文网每天签到 Python 脚本
  4. 汇添富基金总经理张晖:以高质量发展打造中国最受认可的资产管理品牌
  5. 邮件服务器最常见的安全问题及解决办法
  6. 工作流引擎Activiti详解
  7. taptap(fake)
  8. linux cab文件怎么安装包,IE控件cab包手动安装
  9. 中国99%的白领以及他们的家庭即将面临破产
  10. 通俗解释什么是NFT,NFT到底是什么
  11. 人工智能在智慧城市建设的应用场景
  12. JavaScript 面向对象详解
  13. 文献管理软件//Zotero的常用插件——Sci-hub/shortdoi批量下载、Zotfile重命名PDF文件及ZoteroQuickLook快速预览(二)
  14. 提高RM-MEDA局部学习(IRM-MEDA)
  15. 7-11 哥德巴赫猜想
  16. word中公式编辑器的快捷键
  17. FOB指定货操作标准流程及相关经验
  18. TensorFlow1.14或TensorFlow2内部获取mfcc原理探索(matlab复现或python复现)
  19. linux下阅读MHT文件
  20. Hibernate数据查询教程

热门文章

  1. linux powerpc i2c驱动 之 i2c设备层的注册过程
  2. 华北水利水电C 语言实验11,华北水利水电大学C语言实验11.doc
  3. 正则匹配括号里面的内容不包括括号_python正则表达式
  4. 一步步编写操作系统 14 CPU与外设通信——IO接口 上
  5. sap 订单状态修改时间_SAP中对于获取订单的状态
  6. 【探究】信号槽到底能不能有返回值?
  7. 【JS 逆向百例】网洛者反爬练习平台第五题:控制台反调试
  8. Python 数据分析三剑客之 Matplotlib(三):图例 / LaTeX / 刻度 / 子图 / 补丁等基本图像属性
  9. Coding Pages 申请 SSL 证书错误:urn:acme:error:unauthorized: Invalid response from http://xxxxx/
  10. 【POJ - 2752】Seek the Name, Seek the Fame (KMP,公共前缀后缀长度及个数)