select、poll、epoll實現原理分析

 

本文轉載自:https://blog.csdn.net/lishenglong666/article/details/45536611html

poll/select/epoll的實現都是基於文件提供的poll方法(f_op->poll),
該方法利用poll_table提供的_qproc方法向文件內部事件掩碼_key對應的的一個或多個等待隊列(wait_queue_head_t)上添加包含喚醒函數(wait_queue_t.func)的節點(wait_queue_t),並檢查文件當前就緒的狀態返回給poll的調用者(依賴於文件的實現)。
當文件的狀態發生改變時(例如網絡數據包到達),文件就會遍歷事件對應的等待隊列並調用回調函數(wait_queue_t.func)喚醒等待線程。node

一般的file.f_ops.poll實現及相關結構體以下安全

C代碼   收藏代碼
  1. struct file {  
  2.     const struct file_operations    *f_op;  
  3.     spinlock_t          f_lock;  
  4.     // 文件內部實現細節  
  5.     void               *private_data;  
  6. #ifdef CONFIG_EPOLL  
  7.     /* Used by fs/eventpoll.c to link all the hooks to this file */  
  8.     struct list_head    f_ep_links;  
  9.     struct list_head    f_tfile_llink;  
  10. #endif /* #ifdef CONFIG_EPOLL */  
  11.     // 其餘細節....  
  12. };  
  13.   
  14. // 文件操做  
  15. struct file_operations {  
  16.     // 文件提供給poll/select/epoll  
  17.     // 獲取文件當前狀態, 以及就緒通知接口函數  
  18.     unsigned int (*poll) (struct file *, struct poll_table_struct *);  
  19.     // 其餘方法read/write 等... ...  
  20. };  
  21.   
  22. // 一般的file.f_ops.poll 方法的實現  
  23. unsigned int file_f_op_poll (struct file *filp, struct poll_table_struct *wait)  
  24. {  
  25.     unsigned int mask = 0;  
  26.     wait_queue_head_t * wait_queue;  
  27.   
  28.     //1. 根據事件掩碼wait->key_和文件實現filep->private_data 取得事件掩碼對應的一個或多個wait queue head  
  29.     some_code();  
  30.   
  31.     // 2. 調用poll_wait 向得到的wait queue head 添加節點  
  32.     poll_wait(filp, wait_queue, wait);  
  33.   
  34.     // 3. 取得當前就緒狀態保存到mask  
  35.     some_code();  
  36.   
  37.     return mask;  
  38. }  
  39.   
  40. // select/poll/epoll 向文件註冊就緒後回調節點的接口結構  
  41. typedef struct poll_table_struct {  
  42.     // 向wait_queue_head 添加回調節點(wait_queue_t)的接口函數  
  43.     poll_queue_proc _qproc;  
  44.     // 關注的事件掩碼, 文件的實現利用此掩碼將等待隊列傳遞給_qproc  
  45.     unsigned long   _key;  
  46. } poll_table;  
  47. typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);  
  48.   
  49.   
  50. // 通用的poll_wait 函數, 文件的f_ops->poll 一般會調用此函數  
  51. static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)  
  52. {  
  53.     if (p && p->_qproc && wait_address) {  
  54.         // 調用_qproc 在wait_address 上添加節點和回調函數  
  55.         // 調用 poll_table_struct 上的函數指針向wait_address添加節點, 並設置節點的func  
  56.         // (若是是select或poll 則是 __pollwait, 若是是 epoll 則是 ep_ptable_queue_proc),  
  57.         p->_qproc(filp, wait_address, p);  
  58.     }  
  59. }  
  60.   
  61.   
  62. // wait_queue 頭節點  
  63. typedef struct __wait_queue_head wait_queue_head_t;  
  64. struct __wait_queue_head {  
  65.     spinlock_t lock;  
  66.     struct list_head task_list;  
  67. };  
  68.   
  69. // wait_queue 節點  
  70. typedef struct __wait_queue wait_queue_t;  
  71. struct __wait_queue {  
  72.     unsigned int flags;  
  73. #define WQ_FLAG_EXCLUSIVE   0x01  
  74.     void *private;  
  75.     wait_queue_func_t func;  
  76.     struct list_head task_list;  
  77. };  
  78. typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);  
  79.   
  80.   
  81. // 當文件的狀態發生改變時, 文件會調用此函數,此函數經過調用wait_queue_t.func通知poll的調用者  
  82. // 其中key是文件當前的事件掩碼  
  83. void __wake_up(wait_queue_head_t *q, unsigned int mode,  
  84.                int nr_exclusive, void *key)  
  85. {  
  86.     unsigned long flags;  
  87.   
  88.     spin_lock_irqsave(&q->lock, flags);  
  89.     __wake_up_common(q, mode, nr_exclusive, 0, key);  
  90.     spin_unlock_irqrestore(&q->lock, flags);  
  91. }  
  92. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  93.                              int nr_exclusive, int wake_flags, void *key)  
  94. {  
  95.     wait_queue_t *curr, *next;  
  96.     // 遍歷並調用func 喚醒, 一般func會喚醒調用poll的線程  
  97.     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
  98.         unsigned flags = curr->flags;  
  99.   
  100.         if (curr->func(curr, mode, wake_flags, key) &&  
  101.                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) {  
  102.             break;  
  103.         }  
  104.     }  
  105. }  

 poll 和 select

poll和select的實現基本上是一致的,只是傳遞參數有所不一樣,他們的基本流程以下:cookie

1. 複製用戶數據到內核空間網絡

2. 估計超時時間數據結構

3. 遍歷每一個文件並調用f_op->poll 取得文件當前就緒狀態, 若是前面遍歷的文件都沒有就緒,向文件插入wait_queue節點app

4. 遍歷完成後檢查狀態:ide

        a). 若是已經有就緒的文件轉到5;函數

        b). 若是有信號產生,重啓poll或select(轉到 1或3);oop

        c). 不然掛起進程等待超時或喚醒,超時或被喚醒後再次遍歷全部文件取得每一個文件的就緒狀態

5. 將全部文件的就緒狀態複製到用戶空間

6. 清理申請的資源

 

關鍵結構體 

下面是poll/select共用的結構體及其相關功能:

poll_wqueues 是 select/poll 對poll_table接口的具體化實現,其中的table, inline_index和inline_entries都是爲了管理內存。
poll_table_entry 與一個文件相關聯,用於管理插入到文件的wait_queue節點。

C代碼   收藏代碼
  1. // select/poll 對poll_table的具體化實現  
  2. struct poll_wqueues {  
  3.     poll_table pt;  
  4.     struct poll_table_page *table;     // 若是inline_entries 空間不足, 從poll_table_page 中分配  
  5.     struct task_struct *polling_task;  // 調用poll 或select 的進程  
  6.     int triggered;                     // 已觸發標記  
  7.     int error;  
  8.     int inline_index;                  // 下一個要分配的inline_entrie 索引  
  9.     struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];//  
  10. };  
  11. // 幫助管理select/poll  申請的內存  
  12. struct poll_table_page {  
  13.     struct poll_table_page  * next;       // 下一個 page  
  14.     struct poll_table_entry * entry;      // 指向第一個entries  
  15.     struct poll_table_entry entries[0];  
  16. };  
  17. // 與一個正在poll /select 的文件相關聯,  
  18. struct poll_table_entry {  
  19.     struct file *filp;               // 在poll/select中的文件  
  20.     unsigned long key;  
  21.     wait_queue_t wait;               // 插入到wait_queue_head_t 的節點  
  22.     wait_queue_head_t *wait_address; // 文件上的wait_queue_head_t 地址  
  23. };  

 

公共函數

 下面是poll/select公用的一些函數,這些函數實現了poll和select的核心功能。

poll_initwait 用於初始化poll_wqueues,

__pollwait 實現了向文件中添加回調節點的邏輯,

pollwake 當文件狀態發生改變時,由文件調用,用來喚醒線程,

poll_get_entry,free_poll_entry,poll_freewait用來申請釋放poll_table_entry 佔用的內存,並負責釋放文件上的wait_queue節點。

 

C代碼   收藏代碼
  1. // poll_wqueues 的初始化:  
  2. // 初始化 poll_wqueues , __pollwait會在文件就緒時被調用  
  3. void poll_initwait(struct poll_wqueues *pwq)  
  4. {  
  5.     // 初始化poll_table, 至關於調用基類的構造函數  
  6.     init_poll_funcptr(&pwq->pt, __pollwait);  
  7.     /* 
  8.      * static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) 
  9.      * { 
  10.      *     pt->_qproc = qproc; 
  11.      *     pt->_key   = ~0UL; 
  12.      * } 
  13.      */  
  14.     pwq->polling_task = current;  
  15.     pwq->triggered = 0;  
  16.     pwq->error = 0;  
  17.     pwq->table = NULL;  
  18.     pwq->inline_index = 0;  
  19. }  
  20.   
  21.   
  22. // wait_queue設置函數  
  23. // poll/select 向文件wait_queue中添加節點的方法  
  24. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,  
  25.                        poll_table *p)  
  26. {  
  27.     struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);  
  28.     struct poll_table_entry *entry = poll_get_entry(pwq);  
  29.     if (!entry) {  
  30.         return;  
  31.     }  
  32.     get_file(filp); //put_file() in free_poll_entry()  
  33.     entry->filp = filp;  
  34.     entry->wait_address = wait_address; // 等待隊列頭  
  35.     entry->key = p->key;  
  36.     // 設置回調爲 pollwake  
  37.     init_waitqueue_func_entry(&entry->wait, pollwake);  
  38.     entry->wait.private = pwq;  
  39.     // 添加到等待隊列  
  40.     add_wait_queue(wait_address, &entry->wait);  
  41. }  
  42.   
  43. // 在等待隊列(wait_queue_t)上回調函數(func)  
  44. // 文件就緒後被調用,喚醒調用進程,其中key是文件提供的當前狀態掩碼  
  45. static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)  
  46. {  
  47.     struct poll_table_entry *entry;  
  48.     // 取得文件對應的poll_table_entry  
  49.     entry = container_of(wait, struct poll_table_entry, wait);  
  50.     // 過濾不關注的事件  
  51.     if (key && !((unsigned long)key & entry->key)) {  
  52.         return 0;  
  53.     }  
  54.     // 喚醒  
  55.     return __pollwake(wait, mode, sync, key);  
  56. }  
  57. static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)  
  58. {  
  59.     struct poll_wqueues *pwq = wait->private;  
  60.     // 將調用進程 pwq->polling_task 關聯到 dummy_wait  
  61.     DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);  
  62.     smp_wmb();  
  63.     pwq->triggered = 1;// 標記爲已觸發  
  64.     // 喚醒調用進程  
  65.     return default_wake_function(&dummy_wait, mode, sync, key);  
  66. }  
  67.   
  68. // 默認的喚醒函數,poll/select 設置的回調函數會調用此函數喚醒  
  69. // 直接喚醒等待隊列上的線程,即將線程移到運行隊列(rq)  
  70. int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,  
  71.                           void *key)  
  72. {  
  73.     // 這個函數比較複雜, 這裏就不具體分析了  
  74.     return try_to_wake_up(curr->private, mode, wake_flags);  
  75. }  

 

poll,select對poll_table_entry的申請和釋放採用的是相似內存池的管理方式,先使用預分配的空間,預分配的空間不足時,分配一個內存頁,使用內存頁上的空間。

 

C代碼   收藏代碼
  1. // 分配或使用已先前申請的 poll_table_entry,  
  2. static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p) {  
  3.     struct poll_table_page *table = p->table;  
  4.   
  5.     if (p->inline_index < N_INLINE_POLL_ENTRIES) {  
  6.         return p->inline_entries + p->inline_index++;  
  7.     }  
  8.   
  9.     if (!table || POLL_TABLE_FULL(table)) {  
  10.         struct poll_table_page *new_table;  
  11.         new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);  
  12.         if (!new_table) {  
  13.             p->error = -ENOMEM;  
  14.             return NULL;  
  15.         }  
  16.         new_table->entry = new_table->entries;  
  17.         new_table->next = table;  
  18.         p->table = new_table;  
  19.         table = new_table;  
  20.     }  
  21.     return table->entry++;  
  22. }  
  23.   
  24. // 清理poll_wqueues 佔用的資源  
  25. void poll_freewait(struct poll_wqueues *pwq)  
  26. {  
  27.     struct poll_table_page * p = pwq->table;  
  28.     // 遍歷全部已分配的inline poll_table_entry  
  29.     int i;  
  30.     for (i = 0; i < pwq->inline_index; i++) {  
  31.         free_poll_entry(pwq->inline_entries + i);  
  32.     }  
  33.     // 遍歷在poll_table_page上分配的inline poll_table_entry  
  34.     // 並釋放poll_table_page  
  35.     while (p) {  
  36.         struct poll_table_entry * entry;  
  37.         struct poll_table_page *old;  
  38.         entry = p->entry;  
  39.         do {  
  40.             entry--;  
  41.             free_poll_entry(entry);  
  42.         } while (entry > p->entries);  
  43.         old = p;  
  44.         p = p->next;  
  45.         free_page((unsigned long) old);  
  46.     }  
  47. }  
  48. static void free_poll_entry(struct poll_table_entry *entry)  
  49. {  
  50.     // 從等待隊列中刪除, 釋放文件引用計數  
  51.     remove_wait_queue(entry->wait_address, &entry->wait);  
  52.     fput(entry->filp);  
  53. }  

 

 poll/select核心結構關係

下圖是 poll/select 實現公共部分的關係圖,包含了與文件直接的關係,以及函數之間的依賴。


 

 poll的實現

 

C代碼   收藏代碼
  1. // poll 使用的結構體  
  2. struct pollfd {  
  3.     int fd;        // 描述符  
  4.     short events;  // 關注的事件掩碼  
  5.     short revents; // 返回的事件掩碼  
  6. };  
  7. // long sys_poll(struct pollfd *ufds, unsigned int nfds, long timeout_msecs)  
  8. SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,  
  9.                 long, timeout_msecs)  
  10. {  
  11.     struct timespec end_time, *to = NULL;  
  12.     int ret;  
  13.     if (timeout_msecs >= 0) {  
  14.         to = &end_time;  
  15.         // 將相對超時時間msec 轉化爲絕對時間  
  16.         poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,  
  17.                                 NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));  
  18.     }  
  19.     // do sys poll  
  20.     ret = do_sys_poll(ufds, nfds, to);  
  21.     // do_sys_poll 被信號中斷, 從新調用, 對使用者來講 poll 是不會被信號中斷的.  
  22.     if (ret == -EINTR) {  
  23.         struct restart_block *restart_block;  
  24.         restart_block = &current_thread_info()->restart_block;  
  25.         restart_block->fn = do_restart_poll; // 設置重啓的函數  
  26.         restart_block->poll.ufds = ufds;  
  27.         restart_block->poll.nfds = nfds;  
  28.         if (timeout_msecs >= 0) {  
  29.             restart_block->poll.tv_sec = end_time.tv_sec;  
  30.             restart_block->poll.tv_nsec = end_time.tv_nsec;  
  31.             restart_block->poll.has_timeout = 1;  
  32.         } else {  
  33.             restart_block->poll.has_timeout = 0;  
  34.         }  
  35.         // ERESTART_RESTARTBLOCK 不會返回給用戶進程,  
  36.         // 而是會被系統捕獲, 而後調用 do_restart_poll,  
  37.         ret = -ERESTART_RESTARTBLOCK;  
  38.     }  
  39.     return ret;  
  40. }  
  41. int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,  
  42.                 struct timespec *end_time)  
  43. {  
  44.     struct poll_wqueues table;  
  45.     int err = -EFAULT, fdcount, len, size;  
  46.     /* 首先使用棧上的空間,節約內存,加速訪問 */  
  47.     long stack_pps[POLL_STACK_ALLOC/sizeof(long)];  
  48.     struct poll_list *const head = (struct poll_list *)stack_pps;  
  49.     struct poll_list *walk = head;  
  50.     unsigned long todo = nfds;  
  51.     if (nfds > rlimit(RLIMIT_NOFILE)) {  
  52.         // 文件描述符數量超過當前進程限制  
  53.         return -EINVAL;  
  54.     }  
  55.     // 複製用戶空間數據到內核  
  56.     len = min_t(unsigned int, nfds, N_STACK_PPS);  
  57.     for (;;) {  
  58.         walk->next = NULL;  
  59.         walk->len = len;  
  60.         if (!len) {  
  61.             break;  
  62.         }  
  63.         // 複製到當前的 entries  
  64.         if (copy_from_user(walk->entries, ufds + nfds-todo,  
  65.                            sizeof(struct pollfd) * walk->len)) {  
  66.             goto out_fds;  
  67.         }  
  68.         todo -= walk->len;  
  69.         if (!todo) {  
  70.             break;  
  71.         }  
  72.         // 棧上空間不足,在堆上申請剩餘部分  
  73.         len = min(todo, POLLFD_PER_PAGE);  
  74.         size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;  
  75.         walk = walk->next = kmalloc(size, GFP_KERNEL);  
  76.         if (!walk) {  
  77.             err = -ENOMEM;  
  78.             goto out_fds;  
  79.         }  
  80.     }  
  81.     // 初始化 poll_wqueues 結構, 設置函數指針_qproc  爲__pollwait  
  82.     poll_initwait(&table);  
  83.     // poll  
  84.     fdcount = do_poll(nfds, head, &table, end_time);  
  85.     // 從文件wait queue 中移除對應的節點, 釋放entry.  
  86.     poll_freewait(&table);  
  87.     // 複製結果到用戶空間  
  88.     for (walk = head; walk; walk = walk->next) {  
  89.         struct pollfd *fds = walk->entries;  
  90.         int j;  
  91.         for (j = 0; j < len; j++, ufds++)  
  92.             if (__put_user(fds[j].revents, &ufds->revents)) {  
  93.                 goto out_fds;  
  94.             }  
  95.     }  
  96.     err = fdcount;  
  97. out_fds:  
  98.     // 釋放申請的內存  
  99.     walk = head->next;  
  100.     while (walk) {  
  101.         struct poll_list *pos = walk;  
  102.         walk = walk->next;  
  103.         kfree(pos);  
  104.     }  
  105.     return err;  
  106. }  
  107. // 真正的處理函數  
  108. static int do_poll(unsigned int nfds,  struct poll_list *list,  
  109.                    struct poll_wqueues *wait, struct timespec *end_time)  
  110. {  
  111.     poll_table* pt = &wait->pt;  
  112.     ktime_t expire, *to = NULL;  
  113.     int timed_out = 0, count = 0;  
  114.     unsigned long slack = 0;  
  115.     if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
  116.         // 已經超時,直接遍歷全部文件描述符, 而後返回  
  117.         pt = NULL;  
  118.         timed_out = 1;  
  119.     }  
  120.     if (end_time && !timed_out) {  
  121.         // 估計進程等待時間,納秒  
  122.         slack = select_estimate_accuracy(end_time);  
  123.     }  
  124.     // 遍歷文件,爲每一個文件的等待隊列添加喚醒函數(pollwake)  
  125.     for (;;) {  
  126.         struct poll_list *walk;  
  127.         for (walk = list; walk != NULL; walk = walk->next) {  
  128.             struct pollfd * pfd, * pfd_end;  
  129.             pfd = walk->entries;  
  130.             pfd_end = pfd + walk->len;  
  131.             for (; pfd != pfd_end; pfd++) {  
  132.                 // do_pollfd 會向文件對應的wait queue 中添加節點  
  133.                 // 和回調函數(若是 pt 不爲空)  
  134.                 // 並檢查當前文件狀態並設置返回的掩碼  
  135.                 if (do_pollfd(pfd, pt)) {  
  136.                     // 該文件已經準備好了.  
  137.                     // 不須要向後面文件的wait queue 中添加喚醒函數了.  
  138.                     count++;  
  139.                     pt = NULL;  
  140.                 }  
  141.             }  
  142.         }  
  143.         // 下次循環的時候不須要向文件的wait queue 中添加節點,  
  144.         // 由於前面的循環已經把該添加的都添加了  
  145.         pt = NULL;  
  146.   
  147.         // 第一次遍歷沒有發現ready的文件  
  148.         if (!count) {  
  149.             count = wait->error;  
  150.             // 有信號產生  
  151.             if (signal_pending(current)) {  
  152.                 count = -EINTR;  
  153.             }  
  154.         }  
  155.   
  156.         // 有ready的文件或已經超時  
  157.         if (count || timed_out) {  
  158.             break;  
  159.         }  
  160.         // 轉換爲內核時間  
  161.         if (end_time && !to) {  
  162.             expire = timespec_to_ktime(*end_time);  
  163.             to = &expire;  
  164.         }  
  165.         // 等待事件就緒, 若是有事件發生或超時,就再循  
  166.         // 環一遍,取得事件狀態掩碼並計數,  
  167.         // 注意這次循環中, 文件 wait queue 中的節點依然存在  
  168.         if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) {  
  169.             timed_out = 1;  
  170.         }  
  171.     }  
  172.     return count;  
  173. }  
  174.   
  175.   
  176. static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)  
  177. {  
  178.     unsigned int mask;  
  179.     int fd;  
  180.     mask = 0;  
  181.     fd = pollfd->fd;  
  182.     if (fd >= 0) {  
  183.         int fput_needed;  
  184.         struct file * file;  
  185.         // 取得fd對應的文件結構體  
  186.         file = fget_light(fd, &fput_needed);  
  187.         mask = POLLNVAL;  
  188.         if (file != NULL) {  
  189.             // 若是沒有 f_op 或 f_op->poll 則認爲文件始終處於就緒狀態.  
  190.             mask = DEFAULT_POLLMASK;  
  191.             if (file->f_op && file->f_op->poll) {  
  192.                 if (pwait) {  
  193.                     // 設置關注的事件掩碼  
  194.                     pwait->key = pollfd->events | POLLERR | POLLHUP;  
  195.                 }  
  196.                 // 註冊回調函數,並返回當前就緒狀態,就緒後會調用pollwake  
  197.                 mask = file->f_op->poll(file, pwait);  
  198.             }  
  199.             mask &= pollfd->events | POLLERR | POLLHUP; // 移除不須要的狀態掩碼  
  200.             fput_light(file, fput_needed);// 釋放文件  
  201.         }  
  202.     }  
  203.     pollfd->revents = mask; // 更新事件狀態  
  204.     return mask;  
  205. }  
  206.   
  207.   
  208. static long do_restart_poll(struct restart_block *restart_block)  
  209. {  
  210.     struct pollfd __user *ufds = restart_block->poll.ufds;  
  211.     int nfds = restart_block->poll.nfds;  
  212.     struct timespec *to = NULL, end_time;  
  213.     int ret;  
  214.     if (restart_block->poll.has_timeout) {  
  215.         // 獲取先前的超時時間  
  216.         end_time.tv_sec = restart_block->poll.tv_sec;  
  217.         end_time.tv_nsec = restart_block->poll.tv_nsec;  
  218.         to = &end_time;  
  219.     }  
  220.     ret = do_sys_poll(ufds, nfds, to); // 從新調用 do_sys_poll  
  221.     if (ret == -EINTR) {  
  222.         // 又被信號中斷了, 再次重啓  
  223.         restart_block->fn = do_restart_poll;  
  224.         ret = -ERESTART_RESTARTBLOCK;  
  225.     }  
  226.     return ret;  
  227. }  

 

 

 

select 實現

 

C代碼   收藏代碼
  1. typedef struct {  
  2.     unsigned long *in, *out, *ex;  
  3.     unsigned long *res_in, *res_out, *res_ex;  
  4. } fd_set_bits;  
  5. //  long sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)  
  6. SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,  
  7.                 fd_set __user *, exp, struct timeval __user *, tvp)  
  8. {  
  9.     struct timespec end_time, *to = NULL;  
  10.     struct timeval tv;  
  11.     int ret;  
  12.     if (tvp) {  
  13.         if (copy_from_user(&tv, tvp, sizeof(tv))) {  
  14.             return -EFAULT;  
  15.         }  
  16.         // 計算超時時間  
  17.         to = &end_time;  
  18.         if (poll_select_set_timeout(to,  
  19.                                     tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),  
  20.                                     (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC)) {  
  21.             return -EINVAL;  
  22.         }  
  23.     }  
  24.     ret = core_sys_select(n, inp, outp, exp, to);  
  25.     // 複製剩餘時間到用戶空間  
  26.     ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);  
  27.     return ret;  
  28. }  
  29.   
  30. int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,  
  31.                     fd_set __user *exp, struct timespec *end_time)  
  32. {  
  33.     fd_set_bits fds;  
  34.     void *bits;  
  35.     int ret, max_fds;  
  36.     unsigned int size;  
  37.     struct fdtable *fdt;  
  38.     //小對象使用棧上的空間,節約內存, 加快訪問速度  
  39.     long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];  
  40.   
  41.     ret = -EINVAL;  
  42.     if (n < 0) {  
  43.         goto out_nofds;  
  44.     }  
  45.   
  46.     rcu_read_lock();  
  47.     // 取得進程對應的 fdtable  
  48.     fdt = files_fdtable(current->files);  
  49.     max_fds = fdt->max_fds;  
  50.     rcu_read_unlock();  
  51.     if (n > max_fds) {  
  52.         n = max_fds;  
  53.     }  
  54.   
  55.     size = FDS_BYTES(n);  
  56.     bits = stack_fds;  
  57.     if (size > sizeof(stack_fds) / 6) {  
  58.         // 棧上的空間不夠, 申請內存, 所有使用堆上的空間  
  59.         ret = -ENOMEM;  
  60.         bits = kmalloc(6 * size, GFP_KERNEL);  
  61.         if (!bits) {  
  62.             goto out_nofds;  
  63.         }  
  64.     }  
  65.     fds.in     = bits;  
  66.     fds.out    = bits +   size;  
  67.     fds.ex     = bits + 2*size;  
  68.     fds.res_in  = bits + 3*size;  
  69.     fds.res_out = bits + 4*size;  
  70.     fds.res_ex  = bits + 5*size;  
  71.   
  72.     // 複製用戶空間到內核  
  73.     if ((ret = get_fd_set(n, inp, fds.in)) ||  
  74.             (ret = get_fd_set(n, outp, fds.out)) ||  
  75.             (ret = get_fd_set(n, exp, fds.ex))) {  
  76.         goto out;  
  77.     }  
  78.     // 初始化fd set  
  79.     zero_fd_set(n, fds.res_in);  
  80.     zero_fd_set(n, fds.res_out);  
  81.     zero_fd_set(n, fds.res_ex);  
  82.   
  83.     ret = do_select(n, &fds, end_time);  
  84.   
  85.     if (ret < 0) {  
  86.         goto out;  
  87.     }  
  88.     if (!ret) {  
  89.         // 該返回值會被系統捕獲, 並以一樣的參數從新調用sys_select()  
  90.         ret = -ERESTARTNOHAND;  
  91.         if (signal_pending(current)) {  
  92.             goto out;  
  93.         }  
  94.         ret = 0;  
  95.     }  
  96.   
  97.     // 複製到用戶空間  
  98.     if (set_fd_set(n, inp, fds.res_in) ||  
  99.             set_fd_set(n, outp, fds.res_out) ||  
  100.             set_fd_set(n, exp, fds.res_ex)) {  
  101.         ret = -EFAULT;  
  102.     }  
  103.   
  104. out:  
  105.     if (bits != stack_fds) {  
  106.         kfree(bits);  
  107.     }  
  108. out_nofds:  
  109.     return ret;  
  110. }  
  111.   
  112. int do_select(int n, fd_set_bits *fds, struct timespec *end_time)  
  113. {  
  114.     ktime_t expire, *to = NULL;  
  115.     struct poll_wqueues table;  
  116.     poll_table *wait;  
  117.     int retval, i, timed_out = 0;  
  118.     unsigned long slack = 0;  
  119.   
  120.     rcu_read_lock();  
  121.     // 檢查fds中fd的有效性, 並獲取當前最大的fd  
  122.     retval = max_select_fd(n, fds);  
  123.     rcu_read_unlock();  
  124.   
  125.     if (retval < 0) {  
  126.         return retval;  
  127.     }  
  128.     n = retval;  
  129.   
  130.     // 初始化 poll_wqueues 結構, 設置函數指針_qproc    爲__pollwait  
  131.     poll_initwait(&table);  
  132.     wait = &table.pt;  
  133.     if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
  134.         wait = NULL;  
  135.         timed_out = 1;  
  136.     }  
  137.   
  138.     if (end_time && !timed_out) {  
  139.         // 估計須要等待的時間.  
  140.         slack = select_estimate_accuracy(end_time);  
  141.     }  
  142.   
  143.     retval = 0;  
  144.     for (;;) {  
  145.         unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;  
  146.   
  147.         inp = fds->in;  
  148.         outp = fds->out;  
  149.         exp = fds->ex;  
  150.         rinp = fds->res_in;  
  151.         routp = fds->res_out;  
  152.         rexp = fds->res_ex;  
  153.         // 遍歷全部的描述符, i 文件描述符  
  154.         for (i = 0; i < n; ++rinp, ++routp, ++rexp) {  
  155.             unsigned long in, out, ex, all_bits, bit = 1, mask, j;  
  156.             unsigned long res_in = 0, res_out = 0, res_ex = 0;  
  157.             const struct file_operations *f_op = NULL;  
  158.             struct file *file = NULL;  
  159.             // 檢查當前的 slot 中的描述符  
  160.             in = *inp++;  
  161.             out = *outp++;  
  162.             ex = *exp++;  
  163.             all_bits = in | out | ex;  
  164.             if (all_bits == 0) { // 沒有須要監聽的描述符, 下一個slot  
  165.                 i += __NFDBITS;  
  166.                 continue;  
  167.             }  
  168.   
  169.             for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {  
  170.                 int fput_needed;  
  171.                 if (i >= n) {  
  172.                     break;  
  173.                 }  
  174.                 // 不須要監聽描述符 i  
  175.                 if (!(bit & all_bits)) {  
  176.                     continue;  
  177.                 }  
  178.                 // 取得文件結構  
  179.                 file = fget_light(i, &fput_needed);  
  180.                 if (file) {  
  181.                     f_op = file->f_op;  
  182.                     // 沒有 f_op 的話就認爲一直處於就緒狀態  
  183.                     mask = DEFAULT_POLLMASK;  
  184.                     if (f_op && f_op->poll) {  
  185.                         // 設置等待事件的掩碼  
  186.                         wait_key_set(wait, in, out, bit);  
  187.                         /* 
  188.                         static inline void wait_key_set(poll_table *wait, unsigned long in, 
  189.                         unsigned long out, unsigned long bit) 
  190.                         { 
  191.                         wait->_key = POLLEX_SET;// (POLLPRI) 
  192.                         if (in & bit) 
  193.                         wait->_key |= POLLIN_SET;//(POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR) 
  194.                         if (out & bit) 
  195.                         wait->_key |= POLLOUT_SET;//POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) 
  196.                         } 
  197.                         */  
  198.                         // 獲取當前的就緒狀態, 並添加到文件的對應等待隊列中  
  199.                         mask = (*f_op->poll)(file, wait);  
  200.                         // 和poll徹底同樣  
  201.                     }  
  202.                     fput_light(file, fput_needed);  
  203.                     // 釋放文件  
  204.                     // 檢查文件 i 是否已有事件就緒,  
  205.                     if ((mask & POLLIN_SET) && (in & bit)) {  
  206.                         res_in |= bit;  
  207.                         retval++;  
  208.                         // 若是已有就緒事件就再也不向其餘文件的  
  209.                         // 等待隊列中添加回調函數  
  210.                         wait = NULL;  
  211.                     }  
  212.                     if ((mask & POLLOUT_SET) && (out & bit)) {  
  213.                         res_out |= bit;  
  214.                         retval++;  
  215.                         wait = NULL;  
  216.                     }  
  217.                     if ((mask & POLLEX_SET) && (ex & bit)) {  
  218.                         res_ex |= bit;  
  219.                         retval++;  
  220.                         wait = NULL;  
  221.                     }  
  222.                 }  
  223.             }  
  224.             if (res_in) {  
  225.                 *rinp = res_in;  
  226.             }  
  227.             if (res_out) {  
  228.                 *routp = res_out;  
  229.             }  
  230.             if (res_ex) {  
  231.                 *rexp = res_ex;  
  232.             }  
  233.             cond_resched();  
  234.         }  
  235.         wait = NULL; // 該添加回調函數的都已經添加了  
  236.         if (retval || timed_out || signal_pending(current)) {  
  237.             break;   // 信號發生,監聽事件就緒或超時  
  238.         }  
  239.         if (table.error) {  
  240.             retval = table.error; // 產生錯誤了  
  241.             break;  
  242.         }  
  243.         // 轉換到內核時間  
  244.         if (end_time && !to) {  
  245.             expire = timespec_to_ktime(*end_time);  
  246.             to = &expire;  
  247.         }  
  248.         // 等待直到超時, 或由回調函數喚醒, 超時後會再次遍歷文件描述符  
  249.         if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,  
  250.                                    to, slack)) {  
  251.             timed_out = 1;  
  252.         }  
  253.     }  
  254.   
  255.     poll_freewait(&table);  
  256.   
  257.     return retval;  
  258. }  

 

 epoll實現

 

epoll 的實現比poll/select 複雜一些,這是由於:
1.  epoll_wait, epoll_ctl 的調用徹底獨立開來,內核須要鎖機制對這些操做進行保護,而且須要持久的維護添加到epoll的文件
2.  epoll自己也是文件,也能夠被poll/select/epoll監視,這可能致使epoll之間循環喚醒的問題
3.  單個文件的狀態改變可能喚醒過多監聽在其上的epoll,產生喚醒風暴

epoll各個功能的實現要很是當心面對這些問題,使得複雜度大大增長。

 

epoll的核心數據結構

 

C代碼   收藏代碼
  1. // epoll的核心實現對應於一個epoll描述符  
  2. struct eventpoll {  
  3.     spinlock_t lock;  
  4.     struct mutex mtx;  
  5.     wait_queue_head_t wq; // sys_epoll_wait() 等待在這裏  
  6.     // f_op->poll()  使用的, 被其餘事件通知機制利用的wait_address  
  7.     wait_queue_head_t poll_wait;  
  8.     /* 已就緒的須要檢查的epitem 列表 */  
  9.     struct list_head rdllist;  
  10.     /* 保存全部加入到當前epoll的文件對應的epitem*/  
  11.     struct rb_root rbr;  
  12.     // 當正在向用戶空間複製數據時, 產生的可用文件  
  13.     struct epitem *ovflist;  
  14.     /* The user that created the eventpoll descriptor */  
  15.     struct user_struct *user;  
  16.     struct file *file;  
  17.     /*優化循環檢查,避免循環檢查中重複的遍歷 */  
  18.     int visited;  
  19.     struct list_head visited_list_link;  
  20. }  
  21.   
  22. // 對應於一個加入到epoll的文件  
  23. struct epitem {  
  24.     // 掛載到eventpoll 的紅黑樹節點  
  25.     struct rb_node rbn;  
  26.     // 掛載到eventpoll.rdllist 的節點  
  27.     struct list_head rdllink;  
  28.     // 鏈接到ovflist 的指針  
  29.     struct epitem *next;  
  30.     /* 文件描述符信息fd + file, 紅黑樹的key */  
  31.     struct epoll_filefd ffd;  
  32.     /* Number of active wait queue attached to poll operations */  
  33.     int nwait;  
  34.     // 當前文件的等待隊列(eppoll_entry)列表  
  35.     // 同一個文件上可能會監視多種事件,  
  36.     // 這些事件可能屬於不一樣的wait_queue中  
  37.     // (取決於對應文件類型的實現),  
  38.     // 因此須要使用鏈表  
  39.     struct list_head pwqlist;  
  40.     // 當前epitem 的全部者  
  41.     struct eventpoll *ep;  
  42.     /* List header used to link this item to the &quot;struct file&quot; items list */  
  43.     struct list_head fllink;  
  44.     /* epoll_ctl 傳入的用戶數據 */  
  45.     struct epoll_event event;  
  46. };  
  47.   
  48. struct epoll_filefd {  
  49.     struct file *file;  
  50.     int fd;  
  51. };  
  52.   
  53. // 與一個文件上的一個wait_queue_head 相關聯,由於同一文件可能有多個等待的事件,這些事件可能使用不一樣的等待隊列  
  54. struct eppoll_entry {  
  55.     // List struct epitem.pwqlist  
  56.     struct list_head llink;  
  57.     // 全部者  
  58.     struct epitem *base;  
  59.     // 添加到wait_queue 中的節點  
  60.     wait_queue_t wait;  
  61.     // 文件wait_queue 頭  
  62.     wait_queue_head_t *whead;  
  63. };  
  64.   
  65. // 用戶使用的epoll_event  
  66. struct epoll_event {  
  67.     __u32 events;  
  68.     __u64 data;  
  69. } EPOLL_PACKED;  

 

 

文件系統初始化和epoll_create

 

C代碼   收藏代碼
  1. // epoll 文件系統的相關實現  
  2. // epoll 文件系統初始化, 在系統啓動時會調用  
  3.   
  4. static int __init eventpoll_init(void)  
  5. {  
  6.     struct sysinfo si;  
  7.   
  8.     si_meminfo(&si);  
  9.     // 限制可添加到epoll的最多的描述符數量  
  10.   
  11.     max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /  
  12.                        EP_ITEM_COST;  
  13.     BUG_ON(max_user_watches < 0);  
  14.   
  15.     // 初始化遞歸檢查隊列  
  16.    ep_nested_calls_init(&poll_loop_ncalls);  
  17.     ep_nested_calls_init(&poll_safewake_ncalls);  
  18.     ep_nested_calls_init(&poll_readywalk_ncalls);  
  19.     // epoll 使用的slab分配器分別用來分配epitem和eppoll_entry  
  20.     epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),  
  21.                                   0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);  
  22.     pwq_cache = kmem_cache_create("eventpoll_pwq",  
  23.                                   sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);  
  24.   
  25.     return 0;  
  26. }  
  27.   
  28.   
  29. SYSCALL_DEFINE1(epoll_create, int, size)  
  30. {  
  31.     if (size <= 0) {  
  32.         return -EINVAL;  
  33.     }  
  34.   
  35.     return sys_epoll_create1(0);  
  36. }  
  37.   
  38. SYSCALL_DEFINE1(epoll_create1, int, flags)  
  39. {  
  40.     int error, fd;  
  41.     struct eventpoll *ep = NULL;  
  42.     struct file *file;  
  43.   
  44.     /* Check the EPOLL_* constant for consistency.  */  
  45.     BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);  
  46.   
  47.     if (flags & ~EPOLL_CLOEXEC) {  
  48.         return -EINVAL;  
  49.     }  
  50.     /* 
  51.      * Create the internal data structure ("struct eventpoll"). 
  52.      */  
  53.     error = ep_alloc(&ep);  
  54.     if (error < 0) {  
  55.         return error;  
  56.     }  
  57.     /* 
  58.      * Creates all the items needed to setup an eventpoll file. That is, 
  59.      * a file structure and a free file descriptor. 
  60.      */  
  61.     fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));  
  62.     if (fd < 0) {  
  63.          error = fd;  
  64.          goto out_free_ep;  
  65.       }  
  66.       // 設置epfd的相關操做,因爲epoll也是文件也提供了poll操做  
  67.     file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,  
  68.                               O_RDWR | (flags & O_CLOEXEC));  
  69.     if (IS_ERR(file)) {  
  70.         error = PTR_ERR(file);  
  71.         goto out_free_fd;  
  72.     }  
  73.     fd_install(fd, file);  
  74.     ep->file = file;  
  75.     return fd;  
  76.   
  77. out_free_fd:  
  78.     put_unused_fd(fd);  
  79. out_free_ep:  
  80.     ep_free(ep);  
  81.     return error;  
  82. }  

 

 

 

epoll中的遞歸死循環和深度檢查

 

遞歸深度檢測(ep_call_nested)

epoll自己也是文件,也能夠被poll/select/epoll監視,若是epoll之間互相監視就有可能致使死循環。epoll的實現中,全部可能產生遞歸調用的函數都由函函數ep_call_nested進行包裹,遞歸調用過程當中出現死循環或遞歸過深就會打破死循環和遞歸調用直接返回。該函數的實現依賴於一個外部的全局鏈表nested_call_node(不一樣的函數調用使用不一樣的節點),每次調用可能發生遞歸的函數(nproc)就向鏈表中添加一個包含當前函數調用上下文ctx(進程,CPU,或epoll文件)和處理的對象標識cookie的節點,經過檢測是否有相同的節點就能夠知道是否發生了死循環,檢查鏈表中同一上下文包含的節點個數就能夠知道遞歸的深度。如下就是這一過程的源碼。

C代碼   收藏代碼
  1. struct nested_call_node {  
  2.     struct list_head llink;  
  3.     void *cookie;   // 函數運行標識, 任務標誌  
  4.     void *ctx;      // 運行環境標識  
  5. };  
  6. struct nested_calls {  
  7.     struct list_head tasks_call_list;  
  8.     spinlock_t lock;  
  9. };  
  10.   
  11. // 全局的不一樣調用使用的鏈表  
  12. // 死循環檢查和喚醒風暴檢查鏈表  
  13. static nested_call_node poll_loop_ncalls;  
  14. // 喚醒時使用的檢查鏈表  
  15. static nested_call_node poll_safewake_ncalls;  
  16. // 掃描readylist 時使用的鏈表  
  17. static nested_call_node poll_readywalk_ncalls;  
  18.   
  19.   
  20. // 限制epoll 中直接或間接遞歸調用的深度並防止死循環  
  21. // ctx: 任務運行上下文(進程, CPU 等)  
  22. // cookie: 每一個任務的標識  
  23. // priv: 任務運行須要的私有數據  
  24. // 若是用面嚮對象語言實現應該就會是一個wapper類  
  25. static int ep_call_nested(struct nested_calls *ncalls, int max_nests,  
  26.                           int (*nproc)(void *, void *, int), void *priv,  
  27.                           void *cookie, void *ctx)  
  28. {  
  29.     int error, call_nests = 0;  
  30.     unsigned long flags;  
  31.     struct list_head *lsthead = &ncalls->tasks_call_list;  
  32.     struct nested_call_node *tncur;  
  33.     struct nested_call_node tnode;  
  34.     spin_lock_irqsave(&ncalls->lock, flags);  
  35.     // 檢查原有的嵌套調用鏈表ncalls, 查看是否有深度超過限制的狀況  
  36.     list_for_each_entry(tncur, lsthead, llink) {  
  37.         // 同一上下文中(ctx)有相同的任務(cookie)說明產生了死循環  
  38.         // 同一上下文的遞歸深度call_nests 超過限制  
  39.         if (tncur->ctx == ctx &&  
  40.                 (tncur->cookie == cookie || ++call_nests > max_nests)) {  
  41.             error = -1;  
  42.         }  
  43.         goto out_unlock;  
  44.     }  
  45.     /* 將當前的任務請求添加到調用列表*/  
  46.     tnode.ctx = ctx;  
  47.     tnode.cookie = cookie;  
  48.     list_add(&tnode.llink, lsthead);  
  49.     spin_unlock_irqrestore(&ncalls->lock, flags);  
  50.     /* nproc 可能會致使遞歸調用(直接或間接)ep_call_nested 
  51.          * 若是發生遞歸調用, 那麼在此函數返回以前, 
  52.          * ncalls 又會被加入額外的節點, 
  53.          * 這樣經過前面的檢測就能夠知道遞歸調用的深度 
  54.       */  
  55.     error = (*nproc)(priv, cookie, call_nests);  
  56.     /* 從鏈表中刪除當前任務*/  
  57.     spin_lock_irqsave(&ncalls->lock, flags);  
  58.     list_del(&tnode.llink);  
  59. out_unlock:  
  60.     spin_unlock_irqrestore(&ncalls->lock, flags);  
  61.     return error;  
  62. }  

 

循環檢測(ep_loop_check)

循環檢查(ep_loop_check),該函數遞歸調用ep_loop_check_proc利用ep_call_nested來實現epoll之間相互監視的死循環。由於ep_call_nested中已經對死循環和過深的遞歸作了檢查,實際的ep_loop_check_proc的實現只是遞歸調用本身。其中的visited_list和visited標記徹底是爲了優化處理速度,若是沒有visited_list和visited標記函數也是可以工做的。該函數中得上下文就是當前的進程,cookie就是正在遍歷的epoll結構。

 

C代碼   收藏代碼
  1. static LIST_HEAD(visited_list);  
  2. // 檢查 file (epoll)和ep 之間是否有循環  
  3. static int ep_loop_check(struct eventpoll *ep, struct file *file)  
  4. {  
  5.     int ret;  
  6.     struct eventpoll *ep_cur, *ep_next;  
  7.   
  8.     ret = ep_call_nested(&poll_loop_ncalls, EP_MAX_NESTS,  
  9.                          ep_loop_check_proc, file, ep, current);  
  10.     /* 清除鏈表和標誌 */  
  11.     list_for_each_entry_safe(ep_cur, ep_next, &visited_list,  
  12.                              visited_list_link) {  
  13.         ep_cur->visited = 0;  
  14.         list_del(&ep_cur->visited_list_link);  
  15.     }  
  16.     return ret;  
  17. }  
  18.   
  19. static int ep_loop_check_proc(void *priv, void *cookie, int call_nests)  
  20. {  
  21.     int error = 0;  
  22.     struct file *file = priv;  
  23.     struct eventpoll *ep = file->private_data;  
  24.     struct eventpoll *ep_tovisit;  
  25.     struct rb_node *rbp;  
  26.     struct epitem *epi;  
  27.   
  28.     mutex_lock_nested(&ep->mtx, call_nests + 1);  
  29.     // 標記當前爲已遍歷  
  30.     ep->visited = 1;  
  31.     list_add(&ep->visited_list_link, &visited_list);  
  32.     // 遍歷全部ep 監視的文件  
  33.     for (rbp = rb_first(&ep->rbr); rbp; rbp = rb_next(rbp)) {  
  34.         epi = rb_entry(rbp, struct epitem, rbn);  
  35.         if (unlikely(is_file_epoll(epi->ffd.file))) {  
  36.             ep_tovisit = epi->ffd.file->private_data;  
  37.             // 跳過先前已遍歷的, 避免循環檢查  
  38.             if (ep_tovisit->visited) {  
  39.                 continue;  
  40.             }  
  41.             // 全部ep監視的未遍歷的epoll  
  42.             error = ep_call_nested(&poll_loop_ncalls, EP_MAX_NESTS,  
  43.                                    ep_loop_check_proc, epi->ffd.file,  
  44.                                    ep_tovisit, current);  
  45.             if (error != 0) {  
  46.                 break;  
  47.             }  
  48.         } else {  
  49.             // 文件不在tfile_check_list 中, 添加  
  50.             // 最外層的epoll 須要檢查子epoll監視的文件  
  51.             if (list_empty(&epi->ffd.file->f_tfile_llink))  
  52.                 list_add(&epi->ffd.file->f_tfile_llink,  
  53.                          &tfile_check_list);  
  54.         }  
  55.     }  
  56.     mutex_unlock(&ep->mtx);  
  57.   
  58.     return error;  
  59. }  

 

 

 

 喚醒風暴檢測(reverse_path_check)

 當文件狀態發生改變時,會喚醒監聽在其上的epoll文件,而這個epoll文件還可能喚醒其餘的epoll文件,這種連續的喚醒就造成了一個喚醒路徑,全部的喚醒路徑就造成了一個有向圖。若是文件對應的epoll喚醒有向圖的節點過多,那麼文件狀態的改變就會喚醒全部的這些epoll(可能會喚醒不少進程,這樣的開銷是很大的),而實際上一個文件通過少數epoll處理之後就可能從就緒轉到未就緒,剩餘的epoll雖然認爲文件已就緒而實際上通過某些處理後已不可用。epoll的實現中考慮到了此問題,在每次添加新文件到epoll中時,就會首先檢查是否會出現這樣的喚醒風暴。

該函數的實現邏輯是這樣的,遞歸調用reverse_path_check_proc遍歷監聽在當前文件上的epoll文件,在reverse_pach_check_proc中統計並檢查不一樣路徑深度上epoll的個數,從而避免產生喚醒風暴。

 

C代碼   收藏代碼
  1. #define PATH_ARR_SIZE 5  
  2. // 在EPOLL_CTL_ADD 時, 檢查是否有可能產生喚醒風暴  
  3. // epoll 容許的單個文件的喚醒深度小於5, 例如  
  4. // 一個文件最多容許喚醒1000個深度爲1的epoll描述符,  
  5. //容許全部被單個文件直接喚醒的epoll描述符再次喚醒的epoll描述符總數是500  
  6. //  
  7.   
  8. // 深度限制  
  9. static const int path_limits[PATH_ARR_SIZE] = { 1000, 500, 100, 50, 10 };  
  10. // 計算出來的深度  
  11. static int path_count[PATH_ARR_SIZE];  
  12.   
  13. static int path_count_inc(int nests)  
  14. {  
  15.     /* Allow an arbitrary number of depth 1 paths */  
  16.     if (nests == 0) {  
  17.         return 0;  
  18.     }  
  19.   
  20.     if (++path_count[nests] > path_limits[nests]) {  
  21.         return -1;  
  22.     }  
  23.     return 0;  
  24. }  
  25.   
  26. static void path_count_init(void)  
  27. {  
  28.     int i;  
  29.   
  30.     for (i = 0; i < PATH_ARR_SIZE; i++) {  
  31.         path_count[i] = 0;  
  32.     }  
  33. }  
  34.   
  35. // 喚醒風暴檢查函數  
  36. static int reverse_path_check(void)  
  37. {  
  38.     int error = 0;  
  39.     struct file *current_file;  
  40.   
  41.     /* let's call this for all tfiles */  
  42.     // 遍歷全局tfile_check_list 中的文件, 第一級  
  43.     list_for_each_entry(current_file, &tfile_check_list, f_tfile_llink) {  
  44.         // 初始化  
  45.         path_count_init();  
  46.         // 限制遞歸的深度, 並檢查每一個深度上喚醒的epoll 數量  
  47.         error = ep_call_nested(&poll_loop_ncalls, EP_MAX_NESTS,  
  48.                                reverse_path_check_proc, current_file,  
  49.                                current_file, current);  
  50.         if (error) {  
  51.             break;  
  52.         }  
  53.     }  
  54.     return error;  
  55. }  
  56. static int reverse_path_check_proc(void *priv, void *cookie, int call_nests)  
  57. {  
  58.     int error = 0;  
  59.     struct file *file = priv;  
  60.     struct file *child_file;  
  61.     struct epitem *epi;  
  62.   
  63.     list_for_each_entry(epi, &file->f_ep_links, fllink) {  
  64.         // 遍歷監視file 的epoll  
  65.         child_file = epi->ep->file;  
  66.         if (is_file_epoll(child_file)) {  
  67.             if (list_empty(&child_file->f_ep_links)) {  
  68.                 // 沒有其餘的epoll監視當前的這個epoll,  
  69.                 // 已是葉子了  
  70.                 if (path_count_inc(call_nests)) {  
  71.                     error = -1;  
  72.                     break;  
  73.                 }  
  74.             } else {  
  75.                 // 遍歷監視這個epoll 文件的epoll,  
  76.                 // 遞歸調用  
  77.                 error = ep_call_nested(&poll_loop_ncalls,  
  78.                                        EP_MAX_NESTS,  
  79.                                        reverse_path_check_proc,  
  80.                                        child_file, child_file,  
  81.                                        current);  
  82.             }  
  83.             if (error != 0) {  
  84.                 break;  
  85.             }  
  86.         } else {  
  87.             // 不是epoll , 不可能吧?  
  88.             printk(KERN_ERR "reverse_path_check_proc: "  
  89.                    "file is not an ep!\n");  
  90.         }  
  91.     }  
  92.     return error;  
  93. }  

 

 epoll 的喚醒過程

 

 

C代碼   收藏代碼
  1. static void ep_poll_safewake(wait_queue_head_t *wq)  
  2. {  
  3.     int this_cpu = get_cpu();  
  4.   
  5.     ep_call_nested(&poll_safewake_ncalls, EP_MAX_NESTS,  
  6.                    ep_poll_wakeup_proc, NULL, wq, (void *) (long) this_cpu);  
  7.   
  8.     put_cpu();  
  9. }  
  10.   
  11. static int ep_poll_wakeup_proc(void *priv, void *cookie, int call_nests)  
  12. {  
  13.     ep_wake_up_nested((wait_queue_head_t *) cookie, POLLIN,  
  14.                       1 + call_nests);  
  15.     return 0;  
  16. }  
  17.   
  18. static inline void ep_wake_up_nested(wait_queue_head_t *wqueue,  
  19.                                      unsigned long events, int subclass)  
  20. {  
  21.     // 這回喚醒全部正在等待此epfd 的select/epoll/poll 等  
  22.     // 若是喚醒的是epoll 就可能喚醒其餘的epoll, 產生連鎖反應  
  23.     // 這個極可能在中斷上下文中被調用  
  24.     wake_up_poll(wqueue, events);  
  25. }  

 

 

 epoll_ctl

 

C代碼   收藏代碼
  1. // long epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
  2.   
  3. SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,  
  4.                 struct epoll_event __user *, event)  
  5. {  
  6.     int error;  
  7.     int did_lock_epmutex = 0;  
  8.     struct file *file, *tfile;  
  9.     struct eventpoll *ep;  
  10.     struct epitem *epi;  
  11.     struct epoll_event epds;  
  12.   
  13.     error = -EFAULT;  
  14.     if (ep_op_has_event(op) &&  
  15.             // 複製用戶空間數據到內核  
  16.             copy_from_user(&epds, event, sizeof(struct epoll_event))) {  
  17.         goto error_return;  
  18.     }  
  19.   
  20.     // 取得 epfd 對應的文件  
  21.     error = -EBADF;  
  22.     file = fget(epfd);  
  23.     if (!file) {  
  24.         goto error_return;  
  25.     }  
  26.   
  27.     // 取得目標文件  
  28.     tfile = fget(fd);  
  29.     if (!tfile) {  
  30.         goto error_fput;  
  31.     }  
  32.   
  33.     // 目標文件必須提供 poll 操做  
  34.     error = -EPERM;  
  35.     if (!tfile->f_op || !tfile->f_op->poll) {  
  36.         goto error_tgt_fput;  
  37.     }  
  38.   
  39.     // 添加自身或epfd 不是epoll 句柄  
  40.     error = -EINVAL;  
  41.     if (file == tfile || !is_file_epoll(file)) {  
  42.         goto error_tgt_fput;  
  43.     }  
  44.   
  45.     // 取得內部結構eventpoll  
  46.     ep = file->private_data;  
  47.   
  48.     // EPOLL_CTL_MOD 不須要加全局鎖 epmutex  
  49.     if (op == EPOLL_CTL_ADD || op == EPOLL_CTL_DEL) {  
  50.         mutex_lock(&epmutex);  
  51.         did_lock_epmutex = 1;  
  52.     }  
  53.     if (op == EPOLL_CTL_ADD) {  
  54.         if (is_file_epoll(tfile)) {  
  55.             error = -ELOOP;  
  56.             // 目標文件也是epoll 檢測是否有循環包含的問題  
  57.             if (ep_loop_check(ep, tfile) != 0) {  
  58.                 goto error_tgt_fput;  
  59.             }  
  60.         } else  
  61.         {  
  62.             // 將目標文件添加到 epoll 全局的tfile_check_list 中  
  63.             list_add(&tfile->f_tfile_llink, &tfile_check_list);  
  64.         }  
  65.     }  
  66.   
  67.     mutex_lock_nested(&ep->mtx, 0);  
  68.   
  69.     // 以tfile 和fd 爲key 在rbtree 中查找文件對應的epitem  
  70.     epi = ep_find(ep, tfile, fd);  
  71.   
  72.     error = -EINVAL;  
  73.     switch (op) {  
  74.     case EPOLL_CTL_ADD:  
  75.         if (!epi) {  
  76.             // 沒找到, 添加額外添加ERR HUP 事件  
  77.             epds.events |= POLLERR | POLLHUP;  
  78.             error = ep_insert(ep, &epds, tfile, fd);  
  79.         } else {  
  80.             error = -EEXIST;  
  81.         }  
  82.         // 清空文件檢查列表  
  83.         clear_tfile_check_list();  
  84.         break;  
  85.     case EPOLL_CTL_DEL:  
  86.         if (epi) {  
  87.             error = ep_remove(ep, epi);  
  88.         } else {  
  89.             error = -ENOENT;  
  90.         }  
  91.         break;  
  92.     case EPOLL_CTL_MOD:  
  93.         if (epi) {  
  94.             epds.events |= POLLERR | POLLHUP;  
  95.             error = ep_modify(ep, epi, &epds);  
  96.         } else {  
  97.             error = -ENOENT;  
  98.         }  
  99.         break;  
  100.     }  
  101.     mutex_unlock(&ep->mtx);  
  102.   
  103. error_tgt_fput:  
  104.     if (did_lock_epmutex) {  
  105.         mutex_unlock(&epmutex);  
  106.     }  
  107.   
  108.     fput(tfile);  
  109. error_fput:  
  110.     fput(file);  
  111. error_return:  
  112.   
  113.     return error;  
  114. }  

 

 

EPOLL_CTL_ADD 實現

 

C代碼   收藏代碼
  1. // EPOLL_CTL_ADD  
  2. static int ep_insert(struct eventpoll *ep, struct epoll_event *event,  
  3.                      struct file *tfile, int fd)  
  4. {  
  5.     int error, revents, pwake = 0;  
  6.     unsigned long flags;  
  7.     long user_watches;  
  8.     struct epitem *epi;  
  9.     struct ep_pqueue epq;  
  10.     /* 
  11.     struct ep_pqueue { 
  12.         poll_table pt; 
  13.         struct epitem *epi; 
  14.     }; 
  15.     */  
  16.   
  17.     // 增長監視文件數  
  18.     user_watches = atomic_long_read(&ep->user->epoll_watches);  
  19.     if (unlikely(user_watches >= max_user_watches)) {  
  20.         return -ENOSPC;  
  21.     }  
  22.   
  23.     // 分配初始化 epi  
  24.     if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL))) {  
  25.         return -ENOMEM;  
  26.     }  
  27.   
  28.     INIT_LIST_HEAD(&epi->rdllink);  
  29.     INIT_LIST_HEAD(&epi->fllink);  
  30.     INIT_LIST_HEAD(&epi->pwqlist);  
  31.     epi->ep = ep;  
  32.     // 初始化紅黑樹中的key  
  33.     ep_set_ffd(&epi->ffd, tfile, fd);  
  34.     // 直接複製用戶結構  
  35.     epi->event = *event;  
  36.     epi->nwait = 0;  
  37.     epi->next = EP_UNACTIVE_PTR;  
  38.   
  39.     // 初始化臨時的 epq  
  40.     epq.epi = epi;  
  41.     init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);  
  42.     // 設置事件掩碼  
  43.     epq.pt._key = event->events;  
  44.     //  內部會調用ep_ptable_queue_proc, 在文件對應的wait queue head 上  
  45.     // 註冊回調函數, 並返回當前文件的狀態  
  46.     revents = tfile->f_op->poll(tfile, &epq.pt);  
  47.   
  48.     // 檢查錯誤  
  49.     error = -ENOMEM;  
  50.     if (epi->nwait < 0) { // f_op->poll 過程出錯  
  51.         goto error_unregister;  
  52.     }  
  53.     // 添加當前的epitem 到文件的f_ep_links 鏈表  
  54.     spin_lock(&tfile->f_lock);  
  55.     list_add_tail(&epi->fllink, &tfile->f_ep_links);  
  56.     spin_unlock(&tfile->f_lock);  
  57.   
  58.     // 插入epi 到rbtree  
  59.     ep_rbtree_insert(ep, epi);  
  60.   
  61.     /* now check if we've created too many backpaths */  
  62.     error = -EINVAL;  
  63.     if (reverse_path_check()) {  
  64.         goto error_remove_epi;  
  65.     }  
  66.   
  67.     spin_lock_irqsave(&ep->lock, flags);  
  68.   
  69.     /* 文件已經就緒插入到就緒鏈表rdllist */  
  70.     if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {  
  71.         list_add_tail(&epi->rdllink, &ep->rdllist);  
  72.   
  73.   
  74.         if (waitqueue_active(&ep->wq))  
  75.             // 通知sys_epoll_wait , 調用回調函數喚醒sys_epoll_wait 進程  
  76.         {  
  77.             wake_up_locked(&ep->wq);  
  78.         }  
  79.         // 先不通知調用eventpoll_poll 的進程  
  80.         if (waitqueue_active(&ep->poll_wait)) {  
  81.             pwake++;  
  82.         }  
  83.     }  
  84.   
  85.     spin_unlock_irqrestore(&ep->lock, flags);  
  86.   
  87.     atomic_long_inc(&ep->user->epoll_watches);  
  88.   
  89.     if (pwake)  
  90.         // 安全通知調用eventpoll_poll 的進程  
  91.     {  
  92.         ep_poll_safewake(&ep->poll_wait);  
  93.     }  
  94.   
  95.     return 0;  
  96.   
  97. error_remove_epi:  
  98.     spin_lock(&tfile->f_lock);  
  99.     // 刪除文件上的 epi  
  100.     if (ep_is_linked(&epi->fllink)) {  
  101.         list_del_init(&epi->fllink);  
  102.     }  
  103.     spin_unlock(&tfile->f_lock);  
  104.   
  105.     // 從紅黑樹中刪除  
  106.     rb_erase(&epi->rbn, &ep->rbr);  
  107.   
  108. error_unregister:  
  109.     // 從文件的wait_queue 中刪除, 釋放epitem 關聯的全部eppoll_entry  
  110.     ep_unregister_pollwait(ep, epi);  
  111.   
  112.     /* 
  113.      * We need to do this because an event could have been arrived on some 
  114.      * allocated wait queue. Note that we don't care about the ep->ovflist 
  115.      * list, since that is used/cleaned only inside a section bound by "mtx". 
  116.      * And ep_insert() is called with "mtx" held. 
  117.      */  
  118.     // TODO:  
  119.     spin_lock_irqsave(&ep->lock, flags);  
  120.     if (ep_is_linked(&epi->rdllink)) {  
  121.         list_del_init(&epi->rdllink);  
  122.     }  
  123.     spin_unlock_irqrestore(&ep->lock, flags);  
  124.   
  125.     // 釋放epi  
  126.     kmem_cache_free(epi_cache, epi);  
  127.   
  128.     return error;  
  129. }  

 

 EPOLL_CTL_DEL

EPOLL_CTL_DEL 的實現調用的是 ep_remove 函數,函數只是清除ADD時, 添加的各類結構,EPOLL_CTL_MOD 的實現調用的是ep_modify,在ep_modify中用新的事件掩碼調用f_ops->poll,檢測事件是否已可用,若是可用就直接喚醒epoll,這兩個的實現與EPOLL_CTL_ADD 相似,代碼上比較清晰,這裏就不具體分析了。

 

 

C代碼   收藏代碼
  1. static int ep_remove(struct eventpoll *ep, struct epitem *epi)  
  2. {  
  3.     unsigned long flags;  
  4.     struct file *file = epi->ffd.file;  
  5.   
  6.     /* 
  7.      * Removes poll wait queue hooks. We _have_ to do this without holding 
  8.      * the "ep->lock" otherwise a deadlock might occur. This because of the 
  9.      * sequence of the lock acquisition. Here we do "ep->lock" then the wait 
  10.      * queue head lock when unregistering the wait queue. The wakeup callback 
  11.      * will run by holding the wait queue head lock and will call our callback 
  12.      * that will try to get "ep->lock". 
  13.      */  
  14.     ep_unregister_pollwait(ep, epi);  
  15.   
  16.     /* Remove the current item from the list of epoll hooks */  
  17.     spin_lock(&file->f_lock);  
  18.     if (ep_is_linked(&epi->fllink))  
  19.         list_del_init(&epi->fllink);  
  20.     spin_unlock(&file->f_lock);  
  21.   
  22.     rb_erase(&epi->rbn, &ep->rbr);  
  23.   
  24.     spin_lock_irqsave(&ep->lock, flags);  
  25.     if (ep_is_linked(&epi->rdllink))  
  26.         list_del_init(&epi->rdllink);  
  27.     spin_unlock_irqrestore(&ep->lock, flags);  
  28.   
  29.     /* At this point it is safe to free the eventpoll item */  
  30.     kmem_cache_free(epi_cache, epi);  
  31.   
  32.     atomic_long_dec(&ep->user->epoll_watches);  
  33.   
  34.     return 0;  
  35. }  
 

 

 

 

 

C代碼   收藏代碼
  1. /* 
  2.  * Modify the interest event mask by dropping an event if the new mask 
  3.  * has a match in the current file status. Must be called with "mtx" held. 
  4.  */  
  5. static int ep_modify(struct eventpoll *ep, struct epitem *epi, struct epoll_event *event)  
  6. {  
  7.     int pwake = 0;  
  8.     unsigned int revents;  
  9.     poll_table pt;  
  10.   
  11.     init_poll_funcptr(&pt, NULL);  
  12.   
  13.     /* 
  14.      * Set the new event interest mask before calling f_op->poll(); 
  15.      * otherwise we might miss an event that happens between the 
  16.      * f_op->poll() call and the new event set registering. 
  17.      */  
  18.     epi->event.events = event->events;  
  19.     pt._key = event->events;  
  20.     epi->event.data = event->data; /* protected by mtx */  
  21.   
  22.     /* 
  23.      * Get current event bits. We can safely use the file* here because 
  24.      * its usage count has been increased by the caller of this function. 
  25.      */  
  26.     revents = epi->ffd.file->f_op->poll(epi->ffd.file, &pt);  
  27.   
  28.     /* 
  29.      * If the item is "hot" and it is not registered inside the ready 
  30.      * list, push it inside. 
  31.      */  
  32.     if (revents & event->events) {  
  33.         spin_lock_irq(&ep->lock);  
  34.         if (!ep_is_linked(&epi->rdllink)) {  
  35.             list_add_tail(&epi->rdllink, &ep->rdllist);  
  36.   
  37.             /* Notify waiting tasks that events are available */  
  38.             if (waitqueue_active(&ep->wq))  
  39.                 wake_up_locked(&ep->wq);  
  40.             if (waitqueue_active(&ep->poll_wait))  
  41.                 pwake++;  
  42.         }  
  43.         spin_unlock_irq(&ep->lock);  
  44.     }  
  45.   
  46.     /* We have to call this outside the lock */  
  47.     if (pwake)  
  48.         ep_poll_safewake(&ep->poll_wait);  
  49.   
  50.     return 0;  
  51. }  
 

 

 

 

epoll_wait

 

 

C代碼   收藏代碼
  1. /* 
  2. epoll_wait實現 
  3. */  
  4.   
  5. SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,  
  6.                 int, maxevents, int, timeout)  
  7. {  
  8.     int error;  
  9.     struct file *file;  
  10.     struct eventpoll *ep;  
  11.   
  12.     // 檢查輸入數據有效性  
  13.     if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) {  
  14.         return -EINVAL;  
  15.     }  
  16.   
  17.     if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {  
  18.         error = -EFAULT;  
  19.         goto error_return;  
  20.     }  
  21.   
  22.     /* Get the "struct file *" for the eventpoll file */  
  23.     error = -EBADF;  
  24.     file = fget(epfd);  
  25.     if (!file) {  
  26.         goto error_return;  
  27.     }  
  28.   
  29.     error = -EINVAL;  
  30.     if (!is_file_epoll(file)) {  
  31.         goto error_fput;  
  32.     }  
  33.     // 取得ep 結構  
  34.     ep = file->private_data;  
  35.   
  36.     // 等待事件  
  37.     error = ep_poll(ep, events, maxevents, timeout);  
  38.   
  39. error_fput:  
  40.     fput(file);  
  41. error_return:  
  42.   
  43.     return error;  
  44. }  
  45.   
  46. static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,  
  47.                    int maxevents, long timeout)  
  48. {  
  49.     int res = 0, eavail, timed_out = 0;  
  50.     unsigned long flags;  
  51.     long slack = 0;  
  52.     wait_queue_t wait;  
  53.     ktime_t expires, *to = NULL;  
  54.   
  55.     if (timeout > 0) {  
  56.         // 轉換爲內核時間  
  57.         struct timespec end_time = ep_set_mstimeout(timeout);  
  58.   
  59.         slack = select_estimate_accuracy(&end_time);  
  60.         to = &expires;  
  61.         *to = timespec_to_ktime(end_time);  
  62.     } else if (timeout == 0) {  
  63.         // 已經超時直接檢查readylist  
  64.         timed_out = 1;  
  65.         spin_lock_irqsave(&ep->lock, flags);  
  66.         goto check_events;  
  67.     }  
  68.   
  69. fetch_events:  
  70.     spin_lock_irqsave(&ep->lock, flags);  
  71.   
  72.     // 沒有可用的事件,ready list 和ovflist 都爲空  
  73.     if (!ep_events_available(ep)) {  
  74.   
  75.         // 添加當前進程的喚醒函數  
  76.         init_waitqueue_entry(&wait, current);  
  77.         __add_wait_queue_exclusive(&ep->wq, &wait);  
  78.   
  79.         for (;;) {  
  80.             /* 
  81.              * We don't want to sleep if the ep_poll_callback() sends us 
  82.              * a wakeup in between. That's why we set the task state 
  83.              * to TASK_INTERRUPTIBLE before doing the checks. 
  84.              */  
  85.             set_current_state(TASK_INTERRUPTIBLE);  
  86.             if (ep_events_available(ep) || timed_out) {  
  87.                 break;  
  88.             }  
  89.             if (signal_pending(current)) {  
  90.                 res = -EINTR;  
  91.                 break;  
  92.             }  
  93.   
  94.             spin_unlock_irqrestore(&ep->lock, flags);  
  95.             // 掛起當前進程,等待喚醒或超時  
  96.             if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) {  
  97.                 timed_out = 1;  
  98.             }  
  99.   
  100.             spin_lock_irqsave(&ep->lock, flags);  
  101.         }  
  102.       
  103.         __remove_wait_queue(&ep->wq, &wait);  
  104.   
  105.         set_current_state(TASK_RUNNING);  
  106.     }  
  107. check_events:  
  108.     // 再次檢查是否有可用事件  
  109.     eavail = ep_events_available(ep);  
  110.   
  111.     spin_unlock_irqrestore(&ep->lock, flags);  
  112.   
  113.     /* 
  114.      * Try to transfer events to user space. In case we get 0 events and 
  115.      * there's still timeout left over, we go trying again in search of 
  116.      * more luck. 
  117.      */  
  118.     if (!res && eavail   
  119.             && !(res = ep_send_events(ep, events, maxevents)) // 複製事件到用戶空間  
  120.             && !timed_out) // 複製事件失敗而且沒有超時,從新等待。  
  121.             {  
  122.         goto fetch_events;  
  123.     }  
  124.   
  125.     return res;  
  126. }  
  127.   
  128.   
  129. static inline int ep_events_available(struct eventpoll *ep)  
  130. {  
  131.     return !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;  
  132. }  
  133.   
  134. struct ep_send_events_data {  
  135.     int maxevents;  
  136.     struct epoll_event __user *events;  
  137. };  
  138.   
  139. static int ep_send_events(struct eventpoll *ep,  
  140.                           struct epoll_event __user *events, int maxevents)  
  141. {  
  142.     struct ep_send_events_data esed;  
  143.   
  144.     esed.maxevents = maxevents;  
  145.     esed.events = events;  
  146.   
  147.     return ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0);  
  148. }  
  149.   
  150. static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,  
  151.                                void *priv)  
  152. {  
  153.     struct ep_send_events_data *esed = priv;  
  154.     int eventcnt;  
  155.     unsigned int revents;  
  156.     struct epitem *epi;  
  157.     struct epoll_event __user *uevent;  
  158.   
  159.     // 遍歷已就緒鏈表  
  160.     for (eventcnt = 0, uevent = esed->events;  
  161.             !list_empty(head) && eventcnt < esed->maxevents;) {  
  162.         epi = list_first_entry(head, struct epitem, rdllink);  
  163.   
  164.         list_del_init(&epi->rdllink);  
  165.         // 獲取ready 事件掩碼  
  166.         revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &  
  167.                   epi->event.events;  
  168.   
  169.         /* 
  170.          * If the event mask intersect the caller-requested one, 
  171.          * deliver the event to userspace. Again, ep_scan_ready_list() 
  172.          * is holding "mtx", so no operations coming from userspace 
  173.          * can change the item. 
  174.          */  
  175.         if (revents) {  
  176.             // 事件就緒, 複製到用戶空間  
  177.             if (__put_user(revents, &uevent->events) ||  
  178.                     __put_user(epi->event.data, &uevent->data)) {  
  179.                 list_add(&epi->rdllink, head);  
  180.                 return eventcnt ? eventcnt : -EFAULT;  
  181.             }  
  182.             eventcnt++;  
  183.             uevent++;  
  184.             if (epi->event.events & EPOLLONESHOT) {  
  185.                 epi->event.events &= EP_PRIVATE_BITS;  
  186.             } else if (!(epi->event.events & EPOLLET)) {  
  187.                 // 不是邊緣模式, 再次添加到ready list,  
  188.                 // 下次epoll_wait 時直接進入此函數檢查ready list是否仍然繼續  
  189.                 list_add_tail(&epi->rdllink, &ep->rdllist);  
  190.             }  
  191.             // 若是是邊緣模式, 只有當文件狀態發生改變時,  
  192.             // 才文件會再次觸發wait_address 上wait_queue的回調函數,  
  193.         }  
  194.     }  
  195.   
  196.     return eventcnt;  
  197. }  

 

 

 

 

eventpoll_poll

 因爲epoll自身也是文件系統,其描述符也能夠被poll/select/epoll監視,所以須要實現poll方法。

C代碼   收藏代碼
  1. static const struct file_operations eventpoll_fops = {  
  2.     .release = ep_eventpoll_release,  
  3.     .poll    = ep_eventpoll_poll,  
  4.     .llseek  = noop_llseek,  
  5. };  
  6.   
  7. static unsigned int ep_eventpoll_poll(struct file *file, poll_table *wait)  
  8. {  
  9.     int pollflags;  
  10.     struct eventpoll *ep = file->private_data;  
  11.     // 插入到wait_queue  
  12.     poll_wait(file, &ep->poll_wait, wait);  
  13.     // 掃描就緒的文件列表, 調用每一個文件上的poll 檢測是否真的就緒,  
  14.     // 而後複製到用戶空間  
  15.     // 文件列表中有可能有epoll文件, 調用poll的時候有可能會產生遞歸,  
  16.     // 調用因此用ep_call_nested 包裝一下, 防止死循環和過深的調用  
  17.     pollflags = ep_call_nested(&poll_readywalk_ncalls, EP_MAX_NESTS,  
  18.                                ep_poll_readyevents_proc, ep, ep, current);  
  19.     // static struct nested_calls poll_readywalk_ncalls;  
  20.     return pollflags != -1 ? pollflags : 0;  
  21. }  
  22.   
  23. static int ep_poll_readyevents_proc(void *priv, void *cookie, int call_nests)  
  24. {  
  25.     return ep_scan_ready_list(priv, ep_read_events_proc, NULL, call_nests + 1);  
  26. }  
  27.   
  28. static int ep_scan_ready_list(struct eventpoll *ep,  
  29.                               int (*sproc)(struct eventpoll *,  
  30.                                       struct list_head *, void *),  
  31.                               void *priv,  
  32.                               int depth)  
  33. {  
  34.     int error, pwake = 0;  
  35.     unsigned long flags;  
  36.     struct epitem *epi, *nepi;  
  37.     LIST_HEAD(txlist);  
  38.   
  39.     /* 
  40.      * We need to lock this because we could be hit by 
  41.      * eventpoll_release_file() and epoll_ctl(). 
  42.      */  
  43.     mutex_lock_nested(&ep->mtx, depth);  
  44.   
  45.     spin_lock_irqsave(&ep->lock, flags);  
  46.     // 移動rdllist 到新的鏈表txlist  
  47.     list_splice_init(&ep->rdllist, &txlist);  
  48.     // 改變ovflist 的狀態, 若是ep->ovflist != EP_UNACTIVE_PTR,  
  49.     // 當文件激活wait_queue時,就會將對應的epitem加入到ep->ovflist  
  50.     // 不然將文件直接加入到ep->rdllist,  
  51.     // 這樣作的目的是避免丟失事件  
  52.     // 這裏不須要檢查ep->ovflist 的狀態,由於ep->mtx的存在保證此處的ep->ovflist  
  53.     // 必定是EP_UNACTIVE_PTR  
  54.     ep->ovflist = NULL;  
  55.     spin_unlock_irqrestore(&ep->lock, flags);  
  56.   
  57.     // 調用掃描函數處理txlist  
  58.     error = (*sproc)(ep, &txlist, priv);  
  59.   
  60.     spin_lock_irqsave(&ep->lock, flags);  
  61.   
  62.     // 調用 sproc 時可能有新的事件,遍歷這些新的事件將其插入到ready list  
  63.     for (nepi = ep->ovflist; (epi = nepi) != NULL;  
  64.             nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {  
  65.         // #define EP_UNACTIVE_PTR (void *) -1  
  66.         // epi 不在rdllist, 插入  
  67.         if (!ep_is_linked(&epi->rdllink)) {  
  68.             list_add_tail(&epi->rdllink, &ep->rdllist);  
  69.         }  
  70.     }  
  71.     // 還原ep->ovflist的狀態  
  72.     ep->ovflist = EP_UNACTIVE_PTR;  
  73.   
  74.     // 將處理後的 txlist 連接到 rdllist  
  75.     list_splice(&txlist, &ep->rdllist);  
  76.   
  77.     if (!list_empty(&ep->rdllist)) {  
  78.         // 喚醒epoll_wait  
  79.         if (waitqueue_active(&ep->wq)) {  
  80.             wake_up_locked(&ep->wq);  
  81.         }  
  82.         // 當前的ep有其餘的事件通知機制監控  
  83.         if (waitqueue_active(&ep->poll_wait)) {  
  84.             pwake++;  
  85.         }  
  86.     }  
  87.     spin_unlock_irqrestore(&ep->lock, flags);  
  88.   
  89.     mutex_unlock(&ep->mtx);  
  90.   
  91.     if (pwake) {  
  92.         // 安全喚醒外部的事件通知機制  
  93.         ep_poll_safewake(&ep->poll_wait);  
  94.     }  
  95.   
  96.     return error;  
  97. }  
  98.   
  99. static int ep_read_events_proc(struct eventpoll *ep, struct list_head *head,  
  100.                                void *priv)  
  101. {  
  102.     struct epitem *epi, *tmp;  
  103.     poll_table pt;  
  104.     init_poll_funcptr(&pt, NULL);  
  105.     list_for_each_entry_safe(epi, tmp, head, rdllink) {  
  106.         pt._key = epi->event.events;  
  107.         if (epi->ffd.file->f_op->poll(epi->ffd.file, &pt) &  
  108.                 epi->event.events) {  
  109.             return POLLIN | POLLRDNORM;  
  110.         } else {  
  111.              // 這個事件雖然在就緒列表中,  
  112.              // 可是實際上並無就緒, 將他移除  
  113.          // 這有多是水平觸發模式中沒有將文件從就緒列表中移除  
  114.          // 也多是事件插入到就緒列表後有其餘的線程對文件進行了操做  
  115.             list_del_init(&epi->rdllink);  
  116.         }  
  117.     }  
  118.     return 0;  
  119. }  

 

 epoll全景

如下是epoll使用的所有數據結構之間的關係圖,採用的是一種類UML圖,但願對理解epoll的內部實現有所幫助。

 

 

 

 

 

 

 poll/select/epoll 對比

經過以上的分析能夠看出,poll和select的實現基本是一致,只是用戶到內核傳遞的數據格式有所不一樣,

 

 

select和poll即便只有一個描述符就緒,也要遍歷整個集合。若是集合中活躍的描述符不多,遍歷過程的開銷就會變得很大,而若是集合中大部分的描述符都是活躍的,遍歷過程的開銷又能夠忽略。

epoll的實現中每次只遍歷活躍的描述符(若是是水平觸發,也會遍歷先前活躍的描述符),在活躍描述符較少的狀況下就會頗有優點,在代碼的分析過程當中能夠看到epoll的實現過於複雜而且其實現過程當中須要同步處理(鎖),若是大部分描述符都是活躍的,epoll的效率可能不如select或poll。(參見epoll 和poll的性能測試 http://jacquesmattheij.com/Poll+vs+Epoll+once+again)

select可以處理的最大fd沒法超出FDSETSIZE。

select會複寫傳入的fd_set 指針,而poll對每一個fd返回一個掩碼,不更改原來的掩碼,從而能夠對同一個集合屢次調用poll,而無需調整。

select對每一個文件描述符最多使用3個bit,而poll採用的pollfd須要使用64個bit,epoll採用的 epoll_event則須要96個bit

若是事件須要循環處理select, poll 每一次的處理都要將所有的數據複製到內核,而epoll的實現中,內核將持久維護加入的描述符,減小了內核和用戶複製數據的開銷。

相關文章
相關標籤/搜索