linux select poll epoll

http://bookjovi.iteye.com/blog/1186736 

 

Linux中异步IO等待无非就三个系统调用:select, poll和epoll。很多人无法理解三种调用的区别,或不够了解,今天就结合Linux kernel code详细描述三个的区别!

 

    select:

    select 的限制就是最大1024个fd,可以查看kernel中的posix_types.h,里面定义了fdset数据结构,显然select不适合poll大量fd的场景(如webserver)。 

 

    include/linux/posix_types.h :

 

 

C代码   
  1. #undef __NFDBITS   
  2. #define __NFDBITS       (8 * sizeof(unsigned long))   
  3.   
  4. #undef __FD_SETSIZE   
  5. #define __FD_SETSIZE    1024   
  6.   
  7. #undef __FDSET_LONGS   
  8. #define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS)   
  9.   
  10. #undef __FDELT   
  11. #define __FDELT(d)      ((d) / __NFDBITS)   
  12.   
  13. #undef __FDMASK   
  14. #define __FDMASK(d)     (1UL << ((d) % __NFDBITS))   
  15.   
  16. typedef struct {   
  17.         unsigned long fds_bits [__FDSET_LONGS];   
  18. } __kernel_fd_set;  
 

 

  poll:

   poll相对于select改进了fdset size的限制,poll没有再使用fdset数组结构,反而使用了pollfd,这样用户可以自定义非常大的pollfd数组,这个pollfd数组在kernel中的表现形式是poll_list链表,这样就不存在了1024的限制了,除此之外poll相比select无太大区别。

 

 

C代码  
  1. int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,   
  2.                 struct timespec *end_time)   
  3. {   
  4.         struct poll_wqueues table;   
  5.         int err = -EFAULT, fdcount, len, size;   
  6.         /* Allocate small arguments on the stack to save memory and be  
  7.            faster - use long to make sure the buffer is aligned properly  
  8.            on 64 bit archs to avoid unaligned access */  
  9.         long stack_pps[POLL_STACK_ALLOC/sizeof(long)];   
  10.         struct poll_list *const head = (struct poll_list *)stack_pps;   
  11.         struct poll_list *walk = head;   
  12.         unsigned long todo = nfds;   
  13.   
  14.         if (nfds > rlimit(RLIMIT_NOFILE))   
  15.                 return -EINVAL;   
  16.   
  17.         len = min_t(unsigned int, nfds, N_STACK_PPS);   
  18.         for (;;) {   
  19.                 walk->next = NULL;   
  20.                 walk->len = len;   
  21.                 if (!len)   
  22.                         break;   
  23.   
  24.                 if (copy_from_user(walk->entries, ufds + nfds-todo,   
  25.                                         sizeof(struct pollfd) * walk->len))   
  26.                         goto out_fds;   
  27.   
  28.                 todo -= walk->len;   
  29.                 if (!todo)   
  30.                         break;   
  31.   
  32.                 len = min(todo, POLLFD_PER_PAGE);   
  33.                 size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;   
  34.                 walk = walk->next = kmalloc(size, GFP_KERNEL);   
  35.                 if (!walk) {   
  36.                         err = -ENOMEM;   
  37.                         goto out_fds;   
  38.                 }   
  39.         }  
 

 

   epoll:

    select与poll的共同点是fd有数据后kernel会遍历所有fd,找到有效fd后初始化相应的revents,用户空间程序须再次遍历整个fdset,以找到有效的fd,这样实际上就遍历了两次fd数组表,对于极大量fd的情况,这样的性能非常不好,请看一下do_poll代码:

 

C代码  
  1. static int do_poll(unsigned int nfds,  struct poll_list *list,   
  2.                    struct poll_wqueues *wait, struct timespec *end_time)   
  3. {   
  4.         poll_table* pt = &wait->pt;   
  5.         ktime_t expire, *to = NULL;   
  6.         int timed_out = 0, count = 0;   
  7.         unsigned long slack = 0;   
  8.   
  9.         /* Optimise the no-wait case */  
  10.         if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {   
  11.                 pt = NULL;   
  12.                 timed_out = 1;   
  13.         }   
  14.   
  15.         if (end_time && !timed_out)   
  16.                 slack = select_estimate_accuracy(end_time);   
  17.   
  18.         for (;;) {   
  19.                 struct poll_list *walk;   
  20.   
  21.                 for (walk = list; walk != NULL; walk = walk->next) {   
  22.                         struct pollfd * pfd, * pfd_end;   
  23.   
  24.                         pfd = walk->entries;   
  25.                         pfd_end = pfd + walk->len;   
  26.                         for (; pfd != pfd_end; pfd++) {   
  27.                                 /*  
  28.                                  * Fish for events. If we found one, record it  
  29.                                  * and kill the poll_table, so we don't  
  30.                                  * needlessly register any other waiters after  
  31.                                  * this. They'll get immediately deregistered  
  32.                                  * when we break out and return.  
  33.                                  */  
  34.                                 if (do_pollfd(pfd, pt)) {   
  35.                                         count++;   
  36.                                         pt = NULL;   
  37.                                 }   
  38.                         }   
  39.                 }  
 

    epoll的出现解决了这种问题,那么epoll是如何做到的呢? 我们知道select, poll和epoll都是使用waitqueue调用callback函数去wakeup你的异步等待线程的,如果设置了timeout的话就起一个hrtimer,select和poll的callback函数并没有做什么事情,但epoll的waitqueue callback函数把当前的有效fd加到ready list,然后唤醒异步等待进程,所以你的epoll函数返回的就是这个ready list, ready list中包含所有有效的fd,这样一来kernel不用去遍历所有的fd,用户空间程序也不用遍历所有的fd,而只是遍历返回有效fd链表,所以epoll自然比select和poll更适合大数量fd的场景。

 

 

C代码  
  1. static int ep_send_events(struct eventpoll *ep,   
  2.                           struct epoll_event __user *events, int maxevents)   
  3. {   
  4.         struct ep_send_events_data esed;   
  5.   
  6.         esed.maxevents = maxevents;   
  7.         esed.events = events;   
  8.   
  9.         return ep_scan_ready_list(ep, ep_send_events_proc, &esed);   
  10. }  
 

    现在大家应该明白select, poll和epoll的区别了吧!有人问既然select和poll有这么明显的缺陷,为什么不改掉kernel中的实现呢? 原因很简单,后向ABI兼容,select和poll的ABI无法返回ready list,只能返回整个fd数组,所以用户只得再次遍历整个fd数组以找到哪些fd是有数据的。

    epoll还包括 “Level-Triggered” 和 “Edge-Triggered”,这两个概念在这里就不多赘述了,因为"man epoll"里面解释的非常详细,还有使用epoll的example。

 

 

 

 

你可能感兴趣的:(linux select poll epoll)