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 file3 * 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 event13 * 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 }