1. 程式人生 > 其它 >Epoll原始碼深度剖析--轉自堅持,每天進步一點點

Epoll原始碼深度剖析--轉自堅持,每天進步一點點

1.基本資料結構

分別是 eventpoll、epitem 和 eppoll_entry。

1.1 eventpoll

我們先看一下 eventpoll 這個資料結構,這個資料結構是我們在呼叫 epoll_create 之後核心側建立的一個控制代碼,表示了一個 epoll 例項。後續如果我們再呼叫 epoll_ctl 和 epoll_wait 等,都是對這個 eventpoll 資料進行操作,這部分資料會被儲存在 epoll_create 建立的匿名檔案 file 的 private_data 欄位中

 1 /*
 2  * This structure is stored inside the "private_data" member of the file
3 * structure and represents the main data structure for the eventpoll 4 * interface. 5 */ 6 struct eventpoll { 7 /* Protect the access to this structure */ 8 spinlock_t lock; 9 10 /* 11 * This mutex is used to ensure that files are not removed 12 * while epoll is using them. This is held during the event
13 * collection loop, the file cleanup path, the epoll file exit 14 * code and the ctl operations. 15 */ 16 struct mutex mtx; 17 18 /* Wait queue used by sys_epoll_wait() */ 19 //這個佇列裡存放的是執行epoll_wait從而等待的程序佇列 20 wait_queue_head_t wq; 21 22 /* Wait queue used by file->poll()
*/ 23 //這個佇列裡存放的是該eventloop作為poll物件的一個例項,加入到等待的佇列 24 //這是因為eventpoll本身也是一個file, 所以也會有poll操作 25 wait_queue_head_t poll_wait; 26 27 /* List of ready file descriptors */ 28 //這裡存放的是事件就緒的fd列表,連結串列的每個元素是下面的epitem 29 struct list_head rdllist; 30 31 /* RB tree root used to store monitored fd structs */ 32 //這是用來快速查詢fd的紅黑樹 33 struct rb_root_cached rbr; 34 35 /* 36 * This is a single linked list that chains all the "struct epitem" that 37 * happened while transferring ready events to userspace w/out 38 * holding ->lock. 39 */ 40 struct epitem *ovflist; 41 42 /* wakeup_source used when ep_scan_ready_list is running */ 43 struct wakeup_source *ws; 44 45 /* The user that created the eventpoll descriptor */ 46 struct user_struct *user; 47 48 //這是eventloop對應的匿名檔案,充分體現了Linux下一切皆檔案的思想 49 struct file *file; 50 51 /* used to optimize loop detection check */ 52 int visited; 53 struct list_head visited_list_link; 54 55 #ifdef CONFIG_NET_RX_BUSY_POLL 56 /* used to track busy poll napi_id */ 57 unsigned int napi_id; 58 #endif 59 };

這個 epitem 結構是幹什麼用的呢?

每當我們呼叫 epoll_ctl 增加一個 fd 時,核心就會為我們創建出一個 epitem 例項,並且把這個例項作為紅黑樹的一個子節點,增加到 eventpoll 結構體中的紅黑樹中,對應的欄位是 rbr。這之後,查詢每一個 fd 上是否有事件發生都是通過紅黑樹上的 epitem 來操作。

 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  * Avoid increasing the size of this struct, there can be many thousands
 5  * of these on a server and we do not want this to take another cache line.
 6  */
 7 struct epitem {
 8     union {
 9         /* RB tree node links this structure to the eventpoll RB tree */
10         struct rb_node rbn;
11         /* Used to free the struct epitem */
12         struct rcu_head rcu;
13     };
14 
15     /* List header used to link this structure to the eventpoll ready list */
16     //將這個epitem連線到eventpoll 裡面的rdllist的list指標
17     struct list_head rdllink;
18 
19     /*
20      * Works together "struct eventpoll"->ovflist in keeping the
21      * single linked chain of items.
22      */
23     struct epitem *next;
24 
25     /* The file descriptor information this item refers to */
26     //epoll監聽的fd
27     struct epoll_filefd ffd;
28 
29     /* Number of active wait queue attached to poll operations */
30     //一個檔案可以被多個epoll例項所監聽,這裡就記錄了當前檔案被監聽的次數
31     int nwait;
32 
33     /* List containing poll wait queues */
34     struct list_head pwqlist;
35 
36     /* The "container" of this item */
37     //當前epollitem所屬的eventpoll
38     struct eventpoll *ep;
39 
40     /* List header used to link this item to the "struct file" items list */
41     struct list_head fllink;
42 
43     /* wakeup_source used when EPOLLWAKEUP is set */
44     struct wakeup_source __rcu *ws;
45 
46     /* The structure that describe the interested events and the source fd */
47     struct epoll_event event;
48 };

每次當一個 fd 關聯到一個 epoll 例項,就會有一個 eppoll_entry 產生。eppoll_entry 的結構如下:

 1 /* Wait structure used by the poll hooks */
 2 struct eppoll_entry {
 3     /* List header used to link this structure to the "struct epitem" */
 4     struct list_head llink;
 5 
 6     /* The "base" pointer is set to the container "struct epitem" */
 7     struct epitem *base;
 8 
 9     /*
10      * Wait queue item that will be linked to the target file wait
11      * queue head.
12      */
13     wait_queue_entry_t wait;
14 
15     /* The wait queue head that linked the "wait" wait queue item */
16     wait_queue_head_t *whead;
17 };

epoll_create:

我們在使用 epoll 的時候,首先會呼叫 epoll_create 來建立一個 epoll 例項。這個函式是如何工作的呢?

首先,epoll_create 會對傳入的 flags 引數做簡單的驗證。

1 /* Check the EPOLL_* constant for consistency.  */
2 BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
3 
4 if (flags & ~EPOLL_CLOEXEC)
5     return -EINVAL;
6 /*

接下來,核心申請分配 eventpoll 需要的記憶體空間

1 /* Create the internal data structure ("struct eventpoll").
2 */
3 error = ep_alloc(&ep);
4 if (error < 0)
5   return error;

在接下來,epoll_create 為 epoll 例項分配了匿名檔案和檔案描述字,其中 fd 是檔案描述字,file 是一個匿名檔案。這裡充分體現了 UNIX 下一切都是檔案的思想。

注意,eventpoll 的例項會儲存一份匿名檔案的引用,通過呼叫 fd_install 函式將匿名檔案和檔案描述字完成了繫結。

這裡還有一個特別需要注意的地方,在呼叫 anon_inode_get_file 的時候,epoll_create 將 eventpoll 作為匿名檔案 file 的 private_data 儲存了起來,這樣,在之後通過 epoll 例項的檔案描述字來查詢時,就可以快速地定位到 eventpoll 物件了。最後,這個檔案描述字作為 epoll 的檔案控制代碼,被返回給 epoll_create 的呼叫者。

 1 /*
 2  * Creates all the items needed to setup an eventpoll file. That is,
 3  * a file structure and a free file descriptor.
 4  */
 5 fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
 6 if (fd < 0) {
 7     error = fd;
 8     goto out_free_ep;
 9 }
10 file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
11              O_RDWR | (flags & O_CLOEXEC));
12 if (IS_ERR(file)) {
13     error = PTR_ERR(file);
14     goto out_free_fd;
15 }
16 ep->file = file;
17 fd_install(fd, file);
18 return fd;

epoll_ctl

  1 /*
  2  * @epfd: epool_create建立的用於eventpoll的fd
  3  * @op: 控制的命令型別
  4  * @fd: 要操作的檔案描述符
  5  * @event:與fd相關的物件.
  6  */
  7 SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
  8         struct epoll_event __user *, event)
  9 {
 10     int error;
 11     struct file *file, *tfile;
 12     struct eventpoll *ep;
 13     struct epitem *epi;
 14     struct epoll_event epds;
 15  
 16     error = -EFAULT;
 17     /*
 18      * 檢查是否需要從使用者空間拷貝event引數,如果需要拷貝,則呼叫
 19      * copy_from_user來拷貝.
 20      */
 21     if (ep_op_has_event(op) &&
 22      copy_from_user(&epds, event, sizeof(struct epoll_event)))
 23         goto error_return;
 24  
 25     /* Get the "struct file *" for the eventpoll file */
 26     error = -EBADF;
 27     /*
 28      * 獲取epfd對應的file例項
 29      */
 30     file = fget(epfd);
 31     if (!file)
 32         goto error_return;
 33  
 34     /* Get the "struct file *" for the target file */
 35     /*
 36      * 獲取要操作的檔案描述符對應的file例項
 37      */
 38     tfile = fget(fd);
 39     if (!tfile)
 40         goto error_fput;
 41  
 42     /* The target file descriptor must support poll */
 43     /*
 44      * 檢查fd對應的檔案是否支援poll
 45      */
 46     error = -EPERM;
 47     if (!tfile->f_op || !tfile->f_op->poll)
 48         goto error_tgt_fput;
 49  
 50     /*
 51      * We have to check that the file structure underneath the file descriptor
 52      * the user passed to us _is_ an eventpoll file. And also we do not permit
 53      * adding an epoll file descriptor inside itself.
 54      */
 55     error = -EINVAL;
 56     /*
 57      * 檢查fd對應的檔案是否是一個eventpoll檔案
 58      */
 59     if (file == tfile || !is_file_epoll(file))
 60         goto error_tgt_fput;
 61  
 62     /*
 63      * At this point it is safe to assume that the "private_data" contains
 64      * our own data structure.
 65      */
 66     /*
 67      * 獲取eventpoll檔案中的私有資料,該資料是在epoll_create中建立的。
 68      */
 69     ep = file->private_data;
 70  
 71     mutex_lock(&ep->mtx);
 72  
 73     /*
 74      * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
 75      * above, we can be sure to be able to use the item looked up by
 76      * ep_find() till we release the mutex.
 77      */
 78      /*
 79       * 在eventpoll中儲存檔案描述符資訊的紅黑樹中查詢指定的fd對應的epitem例項
 80       */
 81     epi = ep_find(ep, tfile, fd);
 82  
 83     error = -EINVAL;
 84     switch (op) {
 85     case EPOLL_CTL_ADD:
 86         /*
 87          * 如果要新增的fd不存在,則呼叫ep_insert()插入到紅黑樹中,
 88          * 如果已存在,則返回EEXIST錯誤.
 89          */
 90         if (!epi) {
 91             epds.events |= POLLERR | POLLHUP;
 92             error = ep_insert(ep, &epds, tfile, fd);
 93         } else
 94             error = -EEXIST;
 95         break;
 96     case EPOLL_CTL_DEL:
 97         if (epi)
 98             error = ep_remove(ep, epi);
 99         else
100             error = -ENOENT;
101         break;
102     case EPOLL_CTL_MOD:
103         if (epi) {
104             epds.events |= POLLERR | POLLHUP;
105             error = ep_modify(ep, epi, &epds);
106         } else
107             error = -ENOENT;
108         break;
109     }
110     mutex_unlock(&ep->mtx);
111  
112 error_tgt_fput:
113     fput(tfile);
114 error_fput:
115     fput(file);
116 error_return:
117  
118     return error;
119 }

查詢 epoll 例項首先,epoll_ctl 函式通過 epoll 例項控制代碼來獲得對應的匿名檔案,這一點很好理解,UNIX 下一切都是檔案,epoll 的例項也是一個匿名檔案。

1 //獲得epoll例項對應的匿名檔案
2 f = fdget(epfd);
3 if (!f.file)
4     goto error_return;

接下來,獲得新增的套接字對應的檔案,這裡 tf 表示的是 target file,即待處理的目標檔案。

1 /* Get the "struct file *" for the target file */
2 //獲得真正的檔案,如監聽套接字、讀寫套接字
3 tf = fdget(fd);
4 if (!tf.file)
5     goto error_fput;

再接下來,進行了一系列的資料驗證,以保證使用者傳入的引數是合法的,比如 epfd 真的是一個 epoll 例項控制代碼,而不是一個普通檔案描述符。

1 /* The target file descriptor must support poll */
2 //如果不支援poll,那麼該檔案描述字是無效的
3 error = -EPERM;
4 if (!tf.file->f_op->poll)
5     goto error_tgt_fput;
6 ...

如果獲得了一個真正的 epoll 例項控制代碼,就可以通過 private_data 獲取之前建立的 eventpoll 例項了。

1 /*
2  * At this point it is safe to assume that the "private_data" contains
3  * our own data structure.
4  */
5 ep = f.file->private_data;

紅黑樹查詢接下來 epoll_ctl 通過目標檔案和對應描述字,在紅黑樹中查詢是否存在該套接字,這也是 epoll 為什麼高效的地方。紅黑樹(RB-tree)是一種常見的資料結構,這裡 eventpoll 通過紅黑樹跟蹤了當前監聽的所有檔案描述字,而這棵樹的根就儲存在 eventpoll 資料結構中

1 /* RB tree root used to store monitored fd structs */
2 struct rb_root_cached rbr;

對於每個被監聽的檔案描述字,都有一個對應的 epitem 與之對應,epitem 作為紅黑樹中的節點就儲存在紅黑樹中。

1 /*
2  * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
3  * above, we can be sure to be able to use the item looked up by
4  * ep_find() till we release the mutex.
5  */
6 epi = ep_find(ep, tf.file, fd);

紅黑樹是一棵二叉樹,作為二叉樹上的節點,epitem 必須提供比較能力,以便可以按大小順序構建出一棵有序的二叉樹。其排序能力是依靠 epoll_filefd 結構體來完成的,epoll_filefd 可以簡單理解為需要監聽的檔案描述字,它對應到二叉樹上的節點

可以看到這個還是比較好理解的,按照檔案的地址大小排序。如果兩個相同,就按照檔案檔案描述字來排序。

 1 struct epoll_filefd {
 2   struct file *file; // pointer to the target file struct corresponding to the fd
 3   int fd; // target file descriptor number
 4 } __packed;
 5 
 6 /* Compare RB tree keys */
 7 static inline int ep_cmp_ffd(struct epoll_filefd *p1,
 8                             struct epoll_filefd *p2)
 9 {
10   return (p1->file > p2->file ? +1:
11        (p1->file < p2->file ? -1 : p1->fd - p2->fd));
12 }

在進行完紅黑樹查詢之後,如果發現是一個 ADD 操作,並且在樹中沒有找到對應的二叉樹節點,就會呼叫 ep_insert 進行二叉樹節點的增加。

1 case EPOLL_CTL_ADD:
2     if (!epi) {
3         epds.events |= POLLERR | POLLHUP;
4         error = ep_insert(ep, &epds, tf.file, fd, full_check);
5     } else
6         error = -EEXIST;
7     if (full_check)
8         clear_tfile_check_list();
9     break;

ep_insert:

  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     /*
 13      * 檢查epoll監視的檔案描述符的個數是否超過max_user_watches,
 14      * max_user_watches用來儲存每個使用者使用epoll可以監視的檔案
 15      * 描述符個數
 16      */
 17     if (unlikely(atomic_read(&ep->user->epoll_watches) >=
 18          max_user_watches))
 19         return -ENOSPC;
 20     /*
 21      * 每個加入到epoll中的檔案都會附加到一個epitem例項中,
 22      * 分配當前檔案對應的epitem例項。
 23      */
 24     if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
 25         return -ENOMEM;
 26  
 27     /*
 28      * 初始化新分配的epitem例項
 29      */
 30     INIT_LIST_HEAD(&epi->rdllink);
 31     INIT_LIST_HEAD(&epi->fllink);
 32     INIT_LIST_HEAD(&epi->pwqlist);
 33     epi->ep = ep;
 34     ep_set_ffd(&epi->ffd, tfile, fd);
 35     epi->event = *event;
 36     epi->nwait = 0;
 37     epi->next = EP_UNACTIVE_PTR;
 38  
 39     /* Initialize the poll table using the queue callback */
 40     epq.epi = epi;
 41     init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
 42  
 43     /*
 44      * 如果fd是套接字,f_op為socket_file_ops,poll函式是
 45      * sock_poll()。如果是TCP套接字的話,進而會呼叫
 46      * 到tcp_poll()函式。此處呼叫poll函式檢視當前
 47      * 檔案描述符的狀態,儲存在revents中。
 48      * 在poll的處理函式(tcp_poll())中,會呼叫sock_poll_wait(),
 49      * 在sock_poll_wait()中會呼叫到epq.pt.qproc指向的函式,
 50      * 也就是ep_ptable_queue_proc()。
 51      */
 52     revents = tfile->f_op->poll(tfile, &epq.pt);
 53  
 54     /*
 55      * ep_ptable_queue_proc()中如果分配記憶體失敗時,會
 56      * 將nwait置為-1。
 57      */
 58     error = -ENOMEM;
 59     if (epi->nwait < 0)
 60         goto error_unregister;
 61  
 62     /* Add the current item to the list of active epoll hook for this file */
 63     spin_lock(&tfile->f_lock);
 64     /*
 65      * 將當前的epitem加入tfile的f_ep_links連結串列中,
 66      * 在從epoll中移除檔案時,使用者清理檔案對應的
 67      * epitem例項。
 68      */
 69     list_add_tail(&epi->fllink, &tfile->f_ep_links);
 70     spin_unlock(&tfile->f_lock);
 71  
 72     /*
 73      * 將當前的epitem加入到儲存監視的所有檔案的紅黑樹中.
 74      */
 75     ep_rbtree_insert(ep, epi);
 76  
 77     /* We have to drop the new item inside our item list to keep track of it */
 78     spin_lock_irqsave(&ep->lock, flags);
 79  
 80     /*
 81      * 如果要監視的檔案狀態已經就緒並且還沒有加入到就緒佇列中,則將當前的
 82      * epitem加入到就緒佇列中.如果有程序正在等待該檔案的狀態就緒,則
 83      * 喚醒一個等待的程序.
 84      */
 85     if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
 86         list_add_tail(&epi->rdllink, &ep->rdllist);
 87  
 88         /* Notify waiting tasks that events are available */
 89         /*
 90          * 如果有程序正在等待檔案的狀態就緒,也就是
 91          * 呼叫epoll_wait睡眠的程序正在等待,則喚醒一個
 92          * 等待程序。
 93          */
 94         if (waitqueue_active(&ep->wq))
 95             wake_up_locked(&ep->wq);
 96         /*
 97          * 如果有程序等待eventpoll檔案本身的事件就緒,
 98          * 則增加臨時變數pwake的值,pwake的值不為0時,
 99          * 在釋放lock後,會喚醒等待程序。
100          */
101         if (waitqueue_active(&ep->poll_wait))
102             pwake++;
103     }
104  
105     spin_unlock_irqrestore(&ep->lock, flags);
106  
107     /*
108      * 增加eventpoll監視的檔案數量。
109      */
110     atomic_inc(&ep->user->epoll_watches);
111  
112     /* We have to call this outside the lock */
113     /*
114      * 喚醒等待eventpoll檔案狀態就緒的程序
115      */
116      * 
117     if (pwake)
118         ep_poll_safewake(&ep->poll_wait);
119  
120     return 0;
121  
122 error_unregister:
123     ep_unregister_pollwait(ep, epi);
124  
125     /*
126      * We need to do this because an event could have been arrived on some
127      * allocated wait queue. Note that we don't care about the ep->ovflist
128      * list, since that is used/cleaned only inside a section bound by "mtx".
129      * And ep_insert() is called with "mtx" held.
130      */
131     spin_lock_irqsave(&ep->lock, flags);
132     if (ep_is_linked(&epi->rdllink))
133         list_del_init(&epi->rdllink);
134     spin_unlock_irqrestore(&ep->lock, flags);
135  
136     kmem_cache_free(epi_cache, epi);
137  
138     return error;
139 }

ep_insert 首先判斷當前監控的檔案值是否超過了 /proc/sys/fs/epoll/max_user_watches 的預設最大值,如果超過了則直接返回錯誤。

1 user_watches = atomic_long_read(&ep->user->epoll_watches);
2 if (unlikely(user_watches >= max_user_watches))
3     return -ENOSPC;

接下來是分配資源和初始化動作。

 1 if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
 2         return -ENOMEM;
 3 
 4     /* Item initialization follow here ... */
 5     INIT_LIST_HEAD(&epi->rdllink);
 6     INIT_LIST_HEAD(&epi->fllink);
 7     INIT_LIST_HEAD(&epi->pwqlist);
 8     epi->ep = ep;
 9     ep_set_ffd(&epi->ffd, tfile, fd);
10     epi->event = *event;
11     epi->nwait = 0;
12     epi->next = EP_UNACTIVE_PTR;

再接下來的事情非常重要,ep_insert 會為加入的每個檔案描述字設定回撥函式。這個回撥函式是通過函式 ep_ptable_queue_proc 來進行設定的。這個回撥函式是幹什麼的呢?其實,對應的檔案描述字上如果有事件發生,就會呼叫這個函式,比如套接字緩衝區有資料了,就會回撥這個函式。這個函式就是 ep_poll_callback。這裡你會發現,原來核心設計也是充滿了事件回撥的原理。

 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,poll_table *pt)
 6 {
 7     struct epitem *epi = ep_item_from_epqueue(pt);
 8     struct eppoll_entry *pwq;
 9 
10     if (epi>nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
11         init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
12         pwq->whead = whead;
13         pwq->base = epi;
14         if (epi->event.events & EPOLLEXCLUSIVE)
15             add_wait_queue_exclusive(whead, &pwq->wait);
16         else
17             add_wait_queue(whead, &pwq->wait);
18         list_add_tail(&pwq->llink, &epi->pwqlist);
19         epi->nwait++;
20     } else {
21         /* We have to signal that an error occurred */
22         epi->nwait = -1;
23     }
24 }

ep_poll_callback

ep_poll_callback 函式的作用非常重要,它將核心事件真正地和 epoll 物件聯絡了起來。它又是怎麼實現的呢?首先,通過這個檔案的 wait_queue_entry_t 物件找到對應的 epitem 物件,因為 eppoll_entry 物件裡儲存了 wait_quue_entry_t,根據 wait_quue_entry_t 這個物件的地址就可以簡單計算出 eppoll_entry 物件的地址,從而可以獲得 epitem 物件的地址。這部分工作在 ep_item_from_wait 函式中完成。一旦獲得 epitem 物件,就可以尋跡找到 eventpoll 例項。

  1 /*
  2   * 如果檔案型別支援epoll並且有事件發生,發生的事件通過
  3   * 引數key來傳送,參見tcp_prequeue()函式中對wake_up_interruptible_poll()
  4   * 的呼叫。
  5   * @wait: 呼叫ep_ptable_queue_proc()加入到檔案中的喚醒佇列時分配的
  6   * eppoll_entry例項的wait成員的地址
  7   * @mode:該引數在回撥函式ep_poll_callback()中沒有使用,其值為程序
  8   * 睡眠時的狀態
  9   * @sync: 喚醒等待程序的標誌
 10   */
 11 static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
 12 {
 13     int pwake = 0;
 14     unsigned long flags;
 15     struct epitem *epi = ep_item_from_wait(wait);
 16     struct eventpoll *ep = epi->ep;
 17  
 18     spin_lock_irqsave(&ep->lock, flags);
 19  
 20     /*
 21      * If the event mask does not contain any poll(2) event, we consider the
 22      * descriptor to be disabled. This condition is likely the effect of the
 23      * EPOLLONESHOT bit that disables the descriptor when an event is received,
 24      * until the next EPOLL_CTL_MOD will be issued.
 25      */
 26     /*
 27      * epi->event.events中儲存的是使用者空間關心的事件,如果該成員
 28      * 沒有包含任何poll事件,則跳轉到out_unlock處處理
 29      */
 30     if (!(epi->event.events & ~EP_PRIVATE_BITS))
 31         goto out_unlock;
 32  
 33     /*
 34      * Check the events coming with the callback. At this stage, not
 35      * every device reports the events in the "key" parameter of the
 36      * callback. We need to be able to handle both cases here, hence the
 37      * test for "key" != NULL before the event match test.
 38      */
 39     /*
 40      * 如果key不為NULL,也就是值不是0,但是使用者關心的
 41      * 事件並沒有發生,則跳轉到out_unlock處處理。引數key
 42      * 應該不會為0
 43      */
 44     if (key && !((unsigned long) key & epi->event.events))
 45         goto out_unlock;
 46  
 47     /*
 48      * If we are trasfering events to userspace, we can hold no locks
 49      * (because we're accessing user memory, and because of linux f_op->poll()
 50      * semantics). All the events that happens during that period of time are
 51      * chained in ep->ovflist and requeued later on.
 52      */
 53     /* 
 54      * ep_scan_ready_list()是向用戶空間傳遞事件的處理函式,
 55      * ep_scan_ready_list()函式執行時會將ovflist連結串列中的元素
 56      * 暫存到一個臨時變數中,然後將ovflist成員置為NULL,
 57      * 而EP_UNACTIVE_PTR的定義如下:
 58      * #define EP_UNACTIVE_PTR ((void *) -1L)
 59      * 因此(ep->ovflist != EP_UNACTIVE_PTR)成立時,正在向用戶空間
 60      * 傳遞事件。
 61      * 如果當前正在向用戶空間傳遞事件,則將
 62      * 當前的事件對應的epitem例項加入到ovflist連結串列中。
 63      */
 64     if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
 65         /*
 66          * 如果epi->next不等於EP_UNACTIVE_PTR,則說明已經
 67          * 新增到ovflist連結串列中,就不用再添加了
 68          */
 69         if (epi->next == EP_UNACTIVE_PTR) {
 70             epi->next = ep->ovflist;
 71             ep->ovflist = epi;
 72         }
 73         goto out_unlock;
 74     }
 75  
 76     /* If this file is already in the ready list we exit soon */
 77     /*
 78      * 如果當前沒有在向用戶空間傳遞事件,使用者
 79      * 關心的事件已經發生,並且還沒有加入到就緒
 80      * 佇列中,則將當前的epitem例項加入到就緒佇列中。
 81      */
 82     if (!ep_is_linked(&epi->rdllink))
 83         list_add_tail(&epi->rdllink, &ep->rdllist);
 84  
 85     /*
 86      * Wake up ( if active ) both the eventpoll wait list and the ->poll()
 87      * wait list.
 88      */
 89     /*
 90      * 喚醒呼叫epoll_wait()函式時睡眠的程序。
 91      */
 92     if (waitqueue_active(&ep->wq))
 93         wake_up_locked(&ep->wq);
 94     /*
 95      * 喚醒等待eventpoll檔案狀態就緒的程序
 96      */
 97     if (waitqueue_active(&ep->poll_wait))
 98         pwake++;
 99  
100 out_unlock:
101     spin_unlock_irqrestore(&ep->lock, flags);
102  
103     /* We have to call this outside the lock */
104     /*
105      * 喚醒等待eventpoll檔案的狀態就緒的程序
106      */
107     if (pwake)
108         ep_poll_safewake(&ep->poll_wait);
109  
110     return 1;
111 
112 }

1.基本資料結構

分別是 eventpoll、epitem 和 eppoll_entry。

1.1 eventpoll

我們先看一下 eventpoll 這個資料結構,這個資料結構是我們在呼叫 epoll_create 之後核心側建立的一個控制代碼,表示了一個 epoll 例項。後續如果我們再呼叫 epoll_ctl 和 epoll_wait 等,都是對這個 eventpoll 資料進行操作,這部分資料會被儲存在 epoll_create 建立的匿名檔案 file 的 private_data 欄位中

 1 /*
 2  * This structure is stored inside the "private_data" member of the file
 3  * structure and represents the main data structure for the eventpoll
 4  * interface.
 5  */
 6 struct eventpoll {
 7     /* Protect the access to this structure */
 8     spinlock_t lock;
 9 
10     /*
11      * This mutex is used to ensure that files are not removed
12      * while epoll is using them. This is held during the event
13      * collection loop, the file cleanup path, the epoll file exit
14      * code and the ctl operations.
15      */
16     struct mutex mtx;
17 
18     /* Wait queue used by sys_epoll_wait() */
19     //這個佇列裡存放的是執行epoll_wait從而等待的程序佇列
20     wait_queue_head_t wq;
21 
22     /* Wait queue used by file->poll() */
23     //這個佇列裡存放的是該eventloop作為poll物件的一個例項,加入到等待的佇列
24     //這是因為eventpoll本身也是一個file, 所以也會有poll操作
25     wait_queue_head_t poll_wait;
26 
27     /* List of ready file descriptors */
28     //這裡存放的是事件就緒的fd列表,連結串列的每個元素是下面的epitem
29     struct list_head rdllist;
30 
31     /* RB tree root used to store monitored fd structs */
32     //這是用來快速查詢fd的紅黑樹
33     struct rb_root_cached rbr;
34 
35     /*
36      * This is a single linked list that chains all the "struct epitem" that
37      * happened while transferring ready events to userspace w/out
38      * holding ->lock.
39      */
40     struct epitem *ovflist;
41 
42     /* wakeup_source used when ep_scan_ready_list is running */
43     struct wakeup_source *ws;
44 
45     /* The user that created the eventpoll descriptor */
46     struct user_struct *user;
47 
48     //這是eventloop對應的匿名檔案,充分體現了Linux下一切皆檔案的思想
49     struct file *file;
50 
51     /* used to optimize loop detection check */
52     int visited;
53     struct list_head visited_list_link;
54 
55 #ifdef CONFIG_NET_RX_BUSY_POLL
56     /* used to track busy poll napi_id */
57     unsigned int napi_id;
58 #endif
59 };

這個 epitem 結構是幹什麼用的呢?

每當我們呼叫 epoll_ctl 增加一個 fd 時,核心就會為我們創建出一個 epitem 例項,並且把這個例項作為紅黑樹的一個子節點,增加到 eventpoll 結構體中的紅黑樹中,對應的欄位是 rbr。這之後,查詢每一個 fd 上是否有事件發生都是通過紅黑樹上的 epitem 來操作。

 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  * Avoid increasing the size of this struct, there can be many thousands
 5  * of these on a server and we do not want this to take another cache line.
 6  */
 7 struct epitem {
 8     union {
 9         /* RB tree node links this structure to the eventpoll RB tree */
10         struct rb_node rbn;
11         /* Used to free the struct epitem */
12         struct rcu_head rcu;
13     };
14 
15     /* List header used to link this structure to the eventpoll ready list */
16     //將這個epitem連線到eventpoll 裡面的rdllist的list指標
17     struct list_head rdllink;
18 
19     /*
20      * Works together "struct eventpoll"->ovflist in keeping the
21      * single linked chain of items.
22      */
23     struct epitem *next;
24 
25     /* The file descriptor information this item refers to */
26     //epoll監聽的fd
27     struct epoll_filefd ffd;
28 
29     /* Number of active wait queue attached to poll operations */
30     //一個檔案可以被多個epoll例項所監聽,這裡就記錄了當前檔案被監聽的次數
31     int nwait;
32 
33     /* List containing poll wait queues */
34     struct list_head pwqlist;
35 
36     /* The "container" of this item */
37     //當前epollitem所屬的eventpoll
38     struct eventpoll *ep;
39 
40     /* List header used to link this item to the "struct file" items list */
41     struct list_head fllink;
42 
43     /* wakeup_source used when EPOLLWAKEUP is set */
44     struct wakeup_source __rcu *ws;
45 
46     /* The structure that describe the interested events and the source fd */
47     struct epoll_event event;
48 };

每次當一個 fd 關聯到一個 epoll 例項,就會有一個 eppoll_entry 產生。eppoll_entry 的結構如下:

 1 /* Wait structure used by the poll hooks */
 2 struct eppoll_entry {
 3     /* List header used to link this structure to the "struct epitem" */
 4     struct list_head llink;
 5 
 6     /* The "base" pointer is set to the container "struct epitem" */
 7     struct epitem *base;
 8 
 9     /*
10      * Wait queue item that will be linked to the target file wait
11      * queue head.
12      */
13     wait_queue_entry_t wait;
14 
15     /* The wait queue head that linked the "wait" wait queue item */
16     wait_queue_head_t *whead;
17 };

epoll_create:

我們在使用 epoll 的時候,首先會呼叫 epoll_create 來建立一個 epoll 例項。這個函式是如何工作的呢?

首先,epoll_create 會對傳入的 flags 引數做簡單的驗證。

1 /* Check the EPOLL_* constant for consistency.  */
2 BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
3 
4 if (flags & ~EPOLL_CLOEXEC)
5     return -EINVAL;
6 /*

接下來,核心申請分配 eventpoll 需要的記憶體空間

1 /* Create the internal data structure ("struct eventpoll").
2 */
3 error = ep_alloc(&ep);
4 if (error < 0)
5   return error;

在接下來,epoll_create 為 epoll 例項分配了匿名檔案和檔案描述字,其中 fd 是檔案描述字,file 是一個匿名檔案。這裡充分體現了 UNIX 下一切都是檔案的思想。

注意,eventpoll 的例項會儲存一份匿名檔案的引用,通過呼叫 fd_install 函式將匿名檔案和檔案描述字完成了繫結。

這裡還有一個特別需要注意的地方,在呼叫 anon_inode_get_file 的時候,epoll_create 將 eventpoll 作為匿名檔案 file 的 private_data 儲存了起來,這樣,在之後通過 epoll 例項的檔案描述字來查詢時,就可以快速地定位到 eventpoll 物件了。最後,這個檔案描述字作為 epoll 的檔案控制代碼,被返回給 epoll_create 的呼叫者。

 1 /*
 2  * Creates all the items needed to setup an eventpoll file. That is,
 3  * a file structure and a free file descriptor.
 4  */
 5 fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
 6 if (fd < 0) {
 7     error = fd;
 8     goto out_free_ep;
 9 }
10 file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
11              O_RDWR | (flags & O_CLOEXEC));
12 if (IS_ERR(file)) {
13     error = PTR_ERR(file);
14     goto out_free_fd;
15 }
16 ep->file = file;
17 fd_install(fd, file);
18 return fd;

epoll_ctl

  1 /*
  2  * @epfd: epool_create建立的用於eventpoll的fd
  3  * @op: 控制的命令型別
  4  * @fd: 要操作的檔案描述符
  5  * @event:與fd相關的物件.
  6  */
  7 SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
  8         struct epoll_event __user *, event)
  9 {
 10     int error;
 11     struct file *file, *tfile;
 12     struct eventpoll *ep;
 13     struct epitem *epi;
 14     struct epoll_event epds;
 15  
 16     error = -EFAULT;
 17     /*
 18      * 檢查是否需要從使用者空間拷貝event引數,如果需要拷貝,則呼叫
 19      * copy_from_user來拷貝.
 20      */
 21     if (ep_op_has_event(op) &&
 22      copy_from_user(&epds, event, sizeof(struct epoll_event)))
 23         goto error_return;
 24  
 25     /* Get the "struct file *" for the eventpoll file */
 26     error = -EBADF;
 27     /*
 28      * 獲取epfd對應的file例項
 29      */
 30     file = fget(epfd);
 31     if (!file)
 32         goto error_return;
 33  
 34     /* Get the "struct file *" for the target file */
 35     /*
 36      * 獲取要操作的檔案描述符對應的file例項
 37      */
 38     tfile = fget(fd);
 39     if (!tfile)
 40         goto error_fput;
 41  
 42     /* The target file descriptor must support poll */
 43     /*
 44      * 檢查fd對應的檔案是否支援poll
 45      */
 46     error = -EPERM;
 47     if (!tfile->f_op || !tfile->f_op->poll)
 48         goto error_tgt_fput;
 49  
 50     /*
 51      * We have to check that the file structure underneath the file descriptor
 52      * the user passed to us _is_ an eventpoll file. And also we do not permit
 53      * adding an epoll file descriptor inside itself.
 54      */
 55     error = -EINVAL;
 56     /*
 57      * 檢查fd對應的檔案是否是一個eventpoll檔案
 58      */
 59     if (file == tfile || !is_file_epoll(file))
 60         goto error_tgt_fput;
 61  
 62     /*
 63      * At this point it is safe to assume that the "private_data" contains
 64      * our own data structure.
 65      */
 66     /*
 67      * 獲取eventpoll檔案中的私有資料,該資料是在epoll_create中建立的。
 68      */
 69     ep = file->private_data;
 70  
 71     mutex_lock(&ep->mtx);
 72  
 73     /*
 74      * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
 75      * above, we can be sure to be able to use the item looked up by
 76      * ep_find() till we release the mutex.
 77      */
 78      /*
 79       * 在eventpoll中儲存檔案描述符資訊的紅黑樹中查詢指定的fd對應的epitem例項
 80       */
 81     epi = ep_find(ep, tfile, fd);
 82  
 83     error = -EINVAL;
 84     switch (op) {
 85     case EPOLL_CTL_ADD:
 86         /*
 87          * 如果要新增的fd不存在,則呼叫ep_insert()插入到紅黑樹中,
 88          * 如果已存在,則返回EEXIST錯誤.
 89          */
 90         if (!epi) {
 91             epds.events |= POLLERR | POLLHUP;
 92             error = ep_insert(ep, &epds, tfile, fd);
 93         } else
 94             error = -EEXIST;
 95         break;
 96     case EPOLL_CTL_DEL:
 97         if (epi)
 98             error = ep_remove(ep, epi);
 99         else
100             error = -ENOENT;
101         break;
102     case EPOLL_CTL_MOD:
103         if (epi) {
104             epds.events |= POLLERR | POLLHUP;
105             error = ep_modify(ep, epi, &epds);
106         } else
107             error = -ENOENT;
108         break;
109     }
110     mutex_unlock(&ep->mtx);
111  
112 error_tgt_fput:
113     fput(tfile);
114 error_fput:
115     fput(file);
116 error_return:
117  
118     return error;
119 }

查詢 epoll 例項首先,epoll_ctl 函式通過 epoll 例項控制代碼來獲得對應的匿名檔案,這一點很好理解,UNIX 下一切都是檔案,epoll 的例項也是一個匿名檔案。

1 //獲得epoll例項對應的匿名檔案
2 f = fdget(epfd);
3 if (!f.file)
4     goto error_return;

接下來,獲得新增的套接字對應的檔案,這裡 tf 表示的是 target file,即待處理的目標檔案。

1 /* Get the "struct file *" for the target file */
2 //獲得真正的檔案,如監聽套接字、讀寫套接字
3 tf = fdget(fd);
4 if (!tf.file)
5     goto error_fput;

再接下來,進行了一系列的資料驗證,以保證使用者傳入的引數是合法的,比如 epfd 真的是一個 epoll 例項控制代碼,而不是一個普通檔案描述符。

1 /* The target file descriptor must support poll */
2 //如果不支援poll,那麼該檔案描述字是無效的
3 error = -EPERM;
4 if (!tf.file->f_op->poll)
5     goto error_tgt_fput;
6 ...

如果獲得了一個真正的 epoll 例項控制代碼,就可以通過 private_data 獲取之前建立的 eventpoll 例項了。

1 /*
2  * At this point it is safe to assume that the "private_data" contains
3  * our own data structure.
4  */
5 ep = f.file->private_data;

紅黑樹查詢接下來 epoll_ctl 通過目標檔案和對應描述字,在紅黑樹中查詢是否存在該套接字,這也是 epoll 為什麼高效的地方。紅黑樹(RB-tree)是一種常見的資料結構,這裡 eventpoll 通過紅黑樹跟蹤了當前監聽的所有檔案描述字,而這棵樹的根就儲存在 eventpoll 資料結構中

1 /* RB tree root used to store monitored fd structs */
2 struct rb_root_cached rbr;

對於每個被監聽的檔案描述字,都有一個對應的 epitem 與之對應,epitem 作為紅黑樹中的節點就儲存在紅黑樹中。

1 /*
2  * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
3  * above, we can be sure to be able to use the item looked up by
4  * ep_find() till we release the mutex.
5  */
6 epi = ep_find(ep, tf.file, fd);

紅黑樹是一棵二叉樹,作為二叉樹上的節點,epitem 必須提供比較能力,以便可以按大小順序構建出一棵有序的二叉樹。其排序能力是依靠 epoll_filefd 結構體來完成的,epoll_filefd 可以簡單理解為需要監聽的檔案描述字,它對應到二叉樹上的節點

可以看到這個還是比較好理解的,按照檔案的地址大小排序。如果兩個相同,就按照檔案檔案描述字來排序。

 1 struct epoll_filefd {
 2   struct file *file; // pointer to the target file struct corresponding to the fd
 3   int fd; // target file descriptor number
 4 } __packed;
 5 
 6 /* Compare RB tree keys */
 7 static inline int ep_cmp_ffd(struct epoll_filefd *p1,
 8                             struct epoll_filefd *p2)
 9 {
10   return (p1->file > p2->file ? +1:
11        (p1->file < p2->file ? -1 : p1->fd - p2->fd));
12 }

在進行完紅黑樹查詢之後,如果發現是一個 ADD 操作,並且在樹中沒有找到對應的二叉樹節點,就會呼叫 ep_insert 進行二叉樹節點的增加。

1 case EPOLL_CTL_ADD:
2     if (!epi) {
3         epds.events |= POLLERR | POLLHUP;
4         error = ep_insert(ep, &epds, tf.file, fd, full_check);
5     } else
6         error = -EEXIST;
7     if (full_check)
8         clear_tfile_check_list();
9     break;

ep_insert:

  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     /*
 13      * 檢查epoll監視的檔案描述符的個數是否超過max_user_watches,
 14      * max_user_watches用來儲存每個使用者使用epoll可以監視的檔案
 15      * 描述符個數
 16      */
 17     if (unlikely(atomic_read(&ep->user->epoll_watches) >=
 18          max_user_watches))
 19         return -ENOSPC;
 20     /*
 21      * 每個加入到epoll中的檔案都會附加到一個epitem例項中,
 22      * 分配當前檔案對應的epitem例項。
 23      */
 24     if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
 25         return -ENOMEM;
 26  
 27     /*
 28      * 初始化新分配的epitem例項
 29      */
 30     INIT_LIST_HEAD(&epi->rdllink);
 31     INIT_LIST_HEAD(&epi->fllink);
 32     INIT_LIST_HEAD(&epi->pwqlist);
 33     epi->ep = ep;
 34     ep_set_ffd(&epi->ffd, tfile, fd);
 35     epi->event = *event;
 36     epi->nwait = 0;
 37     epi->next = EP_UNACTIVE_PTR;
 38  
 39     /* Initialize the poll table using the queue callback */
 40     epq.epi = epi;
 41     init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
 42  
 43     /*
 44      * 如果fd是套接字,f_op為socket_file_ops,poll函式是
 45      * sock_poll()。如果是TCP套接字的話,進而會呼叫
 46      * 到tcp_poll()函式。此處呼叫poll函式檢視當前
 47      * 檔案描述符的狀態,儲存在revents中。
 48      * 在poll的處理函式(tcp_poll())中,會呼叫sock_poll_wait(),
 49      * 在sock_poll_wait()中會呼叫到epq.pt.qproc指向的函式,
 50      * 也就是ep_ptable_queue_proc()。
 51      */
 52     revents = tfile->f_op->poll(tfile, &epq.pt);
 53  
 54     /*
 55      * ep_ptable_queue_proc()中如果分配記憶體失敗時,會
 56      * 將nwait置為-1。
 57      */
 58     error = -ENOMEM;
 59     if (epi->nwait < 0)
 60         goto error_unregister;
 61  
 62     /* Add the current item to the list of active epoll hook for this file */
 63     spin_lock(&tfile->f_lock);
 64     /*
 65      * 將當前的epitem加入tfile的f_ep_links連結串列中,
 66      * 在從epoll中移除檔案時,使用者清理檔案對應的
 67      * epitem例項。
 68      */
 69     list_add_tail(&epi->fllink, &tfile->f_ep_links);
 70     spin_unlock(&tfile->f_lock);
 71  
 72     /*
 73      * 將當前的epitem加入到儲存監視的所有檔案的紅黑樹中.
 74      */
 75     ep_rbtree_insert(ep, epi);
 76  
 77     /* We have to drop the new item inside our item list to keep track of it */
 78     spin_lock_irqsave(&ep->lock, flags);
 79  
 80     /*
 81      * 如果要監視的檔案狀態已經就緒並且還沒有加入到就緒佇列中,則將當前的
 82      * epitem加入到就緒佇列中.如果有程序正在等待該檔案的狀態就緒,則
 83      * 喚醒一個等待的程序.
 84      */
 85     if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
 86         list_add_tail(&epi->rdllink, &ep->rdllist);
 87  
 88         /* Notify waiting tasks that events are available */
 89         /*
 90          * 如果有程序正在等待檔案的狀態就緒,也就是
 91          * 呼叫epoll_wait睡眠的程序正在等待,則喚醒一個
 92          * 等待程序。
 93          */
 94         if (waitqueue_active(&ep->wq))
 95             wake_up_locked(&ep->wq);
 96         /*
 97          * 如果有程序等待eventpoll檔案本身的事件就緒,
 98          * 則增加臨時變數pwake的值,pwake的值不為0時,
 99          * 在釋放lock後,會喚醒等待程序。
100          */
101         if (waitqueue_active(&ep->poll_wait))
102             pwake++;
103     }
104  
105     spin_unlock_irqrestore(&ep->lock, flags);
106  
107     /*
108      * 增加eventpoll監視的檔案數量。
109      */
110     atomic_inc(&ep->user->epoll_watches);
111  
112     /* We have to call this outside the lock */
113     /*
114      * 喚醒等待eventpoll檔案狀態就緒的程序
115      */
116      * 
117     if (pwake)
118         ep_poll_safewake(&ep->poll_wait);
119  
120     return 0;
121  
122 error_unregister:
123     ep_unregister_pollwait(ep, epi);
124  
125     /*
126      * We need to do this because an event could have been arrived on some
127      * allocated wait queue. Note that we don't care about the ep->ovflist
128      * list, since that is used/cleaned only inside a section bound by "mtx".
129      * And ep_insert() is called with "mtx" held.
130      */
131     spin_lock_irqsave(&ep->lock, flags);
132     if (ep_is_linked(&epi->rdllink))
133         list_del_init(&epi->rdllink);
134     spin_unlock_irqrestore(&ep->lock, flags);
135  
136     kmem_cache_free(epi_cache, epi);
137  
138     return error;
139 }

ep_insert 首先判斷當前監控的檔案值是否超過了 /proc/sys/fs/epoll/max_user_watches 的預設最大值,如果超過了則直接返回錯誤。

1 user_watches = atomic_long_read(&ep->user->epoll_watches);
2 if (unlikely(user_watches >= max_user_watches))
3     return -ENOSPC;

接下來是分配資源和初始化動作。

 1 if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
 2         return -ENOMEM;
 3 
 4     /* Item initialization follow here ... */
 5     INIT_LIST_HEAD(&epi->rdllink);
 6     INIT_LIST_HEAD(&epi->fllink);
 7     INIT_LIST_HEAD(&epi->pwqlist);
 8     epi->ep = ep;
 9     ep_set_ffd(&epi->ffd, tfile, fd);
10     epi->event = *event;
11     epi->nwait = 0;
12     epi->next = EP_UNACTIVE_PTR;

再接下來的事情非常重要,ep_insert 會為加入的每個檔案描述字設定回撥函式。這個回撥函式是通過函式 ep_ptable_queue_proc 來進行設定的。這個回撥函式是幹什麼的呢?其實,對應的檔案描述字上如果有事件發生,就會呼叫這個函式,比如套接字緩衝區有資料了,就會回撥這個函式。這個函式就是 ep_poll_callback。這裡你會發現,原來核心設計也是充滿了事件回撥的原理。

 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,poll_table *pt)
 6 {
 7     struct epitem *epi = ep_item_from_epqueue(pt);
 8     struct eppoll_entry *pwq;
 9 
10     if (epi>nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
11         init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
12         pwq->whead = whead;
13         pwq->base = epi;
14         if (epi->event.events & EPOLLEXCLUSIVE)
15             add_wait_queue_exclusive(whead, &pwq->wait);
16         else
17             add_wait_queue(whead, &pwq->wait);
18         list_add_tail(&pwq->llink, &epi->pwqlist);
19         epi->nwait++;
20     } else {
21         /* We have to signal that an error occurred */
22         epi->nwait = -1;
23     }
24 }

ep_poll_callback

ep_poll_callback 函式的作用非常重要,它將核心事件真正地和 epoll 物件聯絡了起來。它又是怎麼實現的呢?首先,通過這個檔案的 wait_queue_entry_t 物件找到對應的 epitem 物件,因為 eppoll_entry 物件裡儲存了 wait_quue_entry_t,根據 wait_quue_entry_t 這個物件的地址就可以簡單計算出 eppoll_entry 物件的地址,從而可以獲得 epitem 物件的地址。這部分工作在 ep_item_from_wait 函式中完成。一旦獲得 epitem 物件,就可以尋跡找到 eventpoll 例項。

  1 /*
  2   * 如果檔案型別支援epoll並且有事件發生,發生的事件通過
  3   * 引數key來傳送,參見tcp_prequeue()函式中對wake_up_interruptible_poll()
  4   * 的呼叫。
  5   * @wait: 呼叫ep_ptable_queue_proc()加入到檔案中的喚醒佇列時分配的
  6   * eppoll_entry例項的wait成員的地址
  7   * @mode:該引數在回撥函式ep_poll_callback()中沒有使用,其值為程序
  8   * 睡眠時的狀態
  9   * @sync: 喚醒等待程序的標誌
 10   */
 11 static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
 12 {
 13     int pwake = 0;
 14     unsigned long flags;
 15     struct epitem *epi = ep_item_from_wait(wait);
 16     struct eventpoll *ep = epi->ep;
 17  
 18     spin_lock_irqsave(&ep->lock, flags);
 19  
 20     /*
 21      * If the event mask does not contain any poll(2) event, we consider the
 22      * descriptor to be disabled. This condition is likely the effect of the
 23      * EPOLLONESHOT bit that disables the descriptor when an event is received,
 24      * until the next EPOLL_CTL_MOD will be issued.
 25      */
 26     /*
 27      * epi->event.events中儲存的是使用者空間關心的事件,如果該成員
 28      * 沒有包含任何poll事件,則跳轉到out_unlock處處理
 29      */
 30     if (!(epi->event.events & ~EP_PRIVATE_BITS))
 31         goto out_unlock;
 32  
 33     /*
 34      * Check the events coming with the callback. At this stage, not
 35      * every device reports the events in the "key" parameter of the
 36      * callback. We need to be able to handle both cases here, hence the
 37      * test for "key" != NULL before the event match test.
 38      */
 39     /*
 40      * 如果key不為NULL,也就是值不是0,但是使用者關心的
 41      * 事件並沒有發生,則跳轉到out_unlock處處理。引數key
 42      * 應該不會為0
 43      */
 44     if (key && !((unsigned long) key & epi->event.events))
 45         goto out_unlock;
 46  
 47     /*
 48      * If we are trasfering events to userspace, we can hold no locks
 49      * (because we're accessing user memory, and because of linux f_op->poll()
 50      * semantics). All the events that happens during that period of time are
 51      * chained in ep->ovflist and requeued later on.
 52      */
 53     /* 
 54      * ep_scan_ready_list()是向用戶空間傳遞事件的處理函式,
 55      * ep_scan_ready_list()函式執行時會將ovflist連結串列中的元素
 56      * 暫存到一個臨時變數中,然後將ovflist成員置為NULL,
 57      * 而EP_UNACTIVE_PTR的定義如下:
 58      * #define EP_UNACTIVE_PTR ((void *) -1L)
 59      * 因此(ep->ovflist != EP_UNACTIVE_PTR)成立時,正在向用戶空間
 60      * 傳遞事件。
 61      * 如果當前正在向用戶空間傳遞事件,則將
 62      * 當前的事件對應的epitem例項加入到ovflist連結串列中。
 63      */
 64     if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
 65         /*
 66          * 如果epi->next不等於EP_UNACTIVE_PTR,則說明已經
 67          * 新增到ovflist連結串列中,就不用再添加了
 68          */
 69         if (epi->next == EP_UNACTIVE_PTR) {
 70             epi->next = ep->ovflist;
 71             ep->ovflist = epi;
 72         }
 73         goto out_unlock;
 74     }
 75  
 76     /* If this file is already in the ready list we exit soon */
 77     /*
 78      * 如果當前沒有在向用戶空間傳遞事件,使用者
 79      * 關心的事件已經發生,並且還沒有加入到就緒
 80      * 佇列中,則將當前的epitem例項加入到就緒佇列中。
 81      */
 82     if (!ep_is_linked(&epi->rdllink))
 83         list_add_tail(&epi->rdllink, &ep->rdllist);
 84  
 85     /*
 86      * Wake up ( if active ) both the eventpoll wait list and the ->poll()
 87      * wait list.
 88      */
 89     /*
 90      * 喚醒呼叫epoll_wait()函式時睡眠的程序。
 91      */
 92     if (waitqueue_active(&ep->wq))
 93         wake_up_locked(&ep->wq);
 94     /*
 95      * 喚醒等待eventpoll檔案狀態就緒的程序
 96      */
 97     if (waitqueue_active(&ep->poll_wait))
 98         pwake++;
 99  
100 out_unlock:
101     spin_unlock_irqrestore(&ep->lock, flags);
102  
103     /* We have to call this outside the lock */
104     /*
105      * 喚醒等待eventpoll檔案的狀態就緒的程序
106      */
107     if (pwake)
108         ep_poll_safewake(&ep->poll_wait);
109  
110     return 1;
111 
112 }