linux I/O複用(轉載)

  Linux中異步IO等待無非就三個系統調用:select, poll和epoll。不少人沒法理解三種調用的區別,或不夠了解,今天就結合Linux kernel code詳細描述三個的區別!javascript

 

    select:java

    select 的限制就是最大1024個fd,能夠查看kernel中的posix_types.h,裏面定義了fdset數據結構,顯然select不適合poll大量fd的場景(如webserver)。 linux

 

    include/linux/posix_types.h :web

 

 

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:app

    select與poll的共同點是fd有數據後kernel會遍歷全部fd,找到有效fd後初始化相應的revents,用戶空間程序須再次遍歷整個fdset,以找到有效的fd,這樣實際上就遍歷了兩次fd數組表,對於極大量fd的狀況,這樣的性能很是很差,請看一下do_poll代碼:less

 

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。

原文出自:http://bookjovi.iteye.com/blog/1186736

相關文章
相關標籤/搜索