1、epoll_create & epoll_create1node
SYSCALL_DEFINE1(epoll_create, int, size)tcp
sys_epoll_create->sys_epoll_create1函數
SYSCALL_DEFINE1(epoll_create1, int, flags)oop
sys_epoll_create1(入參檢測等)->ep_alloc(分配eventpoll,並初始化鎖、等待隊列等結構)->[sys_epoll_create1]get_unused_fd_flags(分配fd)->[sys_epoll_create1]anon_inode_getfile(分配file)->[sys_epoll_create1]fd_install(關聯file和fd)測試
anon_inode_getfile:spa
該函數建立的文件共享使用一個inode,節省內存避免代碼重複,inode:anon_inode_inode、 sb:anon_inode_mnt->mnt_sb、 fs:anon_inode_fs_type,初始化位置[anon_inode_init]。pwa
使用alloc_file分配一個文件,file->f_op = eventpoll_fops;指針
設置file->f_flags = (O_RDWR | (flags & O_CLOEXEC)) & (O_ACCMODE | O_NONBLOCK); file->private_data =ep;code
其中eventpoll_fops註冊了ep_show_fdinfo函數,容許咱們在proc中查看對應epfd的epi信息orm
lybxin@Inspiron:~$more /proc/1469/task/1469/fdinfo/4pos: 0flags: 02000002mnt_id: 11tfd: 5 events: 19 data: 55b8247c8da0tfd: 13 events: 19 data: 55b8247ccc90tfd: 14 events: 19 data: 55b8248079a0tfd: 9 events: 19 data: 55b8247e9860tfd: 12 events: 1a data: 55b8247e9b40tfd: 8 events: 19 data: 55b8247caf90tfd: 7 events: 19 data: 55b824806490
2、epoll_ctl
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,struct epoll_event __user *, event)
sys_epoll_ctl:
若是不是EPOLL_CTL_DEL操做,則從用戶空間複製epoll_event結構
獲取fd結構,epfd->f,須要操做的fd->tf
目標fd對應的fd結構必須支持tf.file->f_op->poll操做
處理EPOLLWAKEUP標誌
檢驗epfd對應有效的epoll文件描述符,且須要操做的fd與epfd不是對應同一個文件
經過ep_loop_check檢測epfd是否構成閉環或者連續epfd的深度超過5,對應宏EP_MAX_NESTS[4]
經過ep_find查找這個epfd是否已經添加了目標fd文件
ADD/MOD操做自動添加POLLERR | POLLHUP這兩個標誌位
ep_loop_check:
visited_list:表示已經處理過的節點,假設epfd1下掛epfd2和epfd3,而epfd2和epfd3又同時掛epfd4,那麼保證epfd只處理一次
tfile_check_list:保存非epoll文件的fd用於反向檢查
從源碼和下面的測試來看這個閉環和深度檢測只能從添加的fd向下檢測,而不能向上檢測,所以並非全部場景都能有效的檢測出來,以下測試,另外還有一種場景由於會跳過已經visit的節點,因此visit的節點的最大深度也可能會超過5。
---------------test1 start--------------- //正向查找只檢測target fdadd epfd2 to epfd1:add 1 success add epfd3 to epfd2:add 2 success add epfd4 to epfd3:add 3 success add epfd5 to epfd4:add 4 success add epfd6 to epfd5:add 5 success add epfd7 to epfd6:add 6 success add epfd8 to epfd7:add 7 success add epfd9 to epfd8:add 8 success ---------------test1 end--------------- ---------------test2 start--------------- //正向查找只檢測target fdadd epfd1 to epfd2:add 1 success add epfd2 to epfd3:add 2 success add epfd3 to epfd4:add 3 success add epfd4 to epfd5:add 4 success add epfd5 to epfd6:add 5 success add epfd6 to epfd7:epoll_ctl error:Too many levels of symbolic links(errno:40)add epfd7 to epfd8:add 6 success add epfd8 to epfd9:add 7 success ---------------test2 end--------------- ---------------test3 start--------------- //正向查找造成閉環 操做失敗add epfd2 to epfd1:add 1 success add epfd3 to epfd2:add 2 success add epfd1 to epfd3:epoll_ctl error:Too many levels of symbolic links(errno:40)---------------test3 end---------------
ep_insert:
max_user_watches:/proc/sys/fs/epoll/max_user_watches 每一個用戶同時watch的最大fd數目
若是watch的總數超過max_user_watches,則返回ENOSPC
若是從epi_cache分配epitem失敗,則返回ENOMEM
根據EPOLLWAKEUP標誌註冊wake up
經過ep_item_poll把item添加到poll鉤子中,並獲取當前revents。最終會經過ep_ptable_queue_proc函數把eppoll_entry添加到sk->sk_wq->wait的頭部,並經過pwq->llink添加到epi->pwqlist的尾部。這裏每一個epi對應一個pwqlist鏈表的緣由是poll一些文件的時候,須要添加兩次等待隊列,如/dev/bsg/目錄下面的文件。
把epi插入到f_ep_links鏈表的尾部,list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
把epi插入到ep的紅黑樹中,ep_rbtree_insert(ep, epi);
經過reverse_path_check進行反向檢查
若是獲取到的revents中有用戶關注的事件,而且epi未在ready鏈表中,那麼把epi插入ready鏈表尾部 list_add_tail(&epi->rdllink, &ep->rdllist);並嘗試喚醒epoll_wait進程wake_up_locked(&ep->wq);以及file->poll()等待進程ep_poll_safewake(&ep->poll_wait)
自增ep->user->epoll_watches
reverse_path_check:
對於第一層反向檢查不限制數目。
對於第2-5層,限制引用數目分別爲500、100、50、10,以下變量定義了上限,其中該變量第一個成員1000僅做佔位使用,並不限制第一層引用總數,參考path_count_inc。static const int path_limits[PATH_ARR_SIZE] = { 1000, 500, 100, 50, 10 };
對於5層以上則直接返回錯誤
---------------test4 反向查找--------------- //反向查找層數超過5add epfd2 to epfd1:add 1 success add epfd3 to epfd2:add 2 success add epfd4 to epfd3:add 3 success add listen_fd to epfd4:add 4 success add epfd5 to epfd4:add 5 success add listen_fd to epfd5:add 6 success add epfd6 to epfd5:add 7 success add listen_fd to epfd6:epoll_ctl error:Invalid argument(errno:22)add epfd7 to epfd6:add 8 success add listen_fd to epfd7:epoll_ctl error:Invalid argument(errno:22)add epfd8 to epfd7:add 9 success add epfd9 to epfd8:add 10 success add listen_fd to epfd9:epoll_ctl error:Invalid argument(errno:22)---------------test4 end--------------- ---------------test5 反向查找 i:0 --------------- //第一層反向查找直到fd數目的上限纔會失敗添加第一層添加第一層 add error num:1021 error:Bad file descriptor(errno:9)---------------test5 end i:1020 --------------- ---------------test6 反向查找 i:0 --------------- //第二層反向查找的限制爲path_limits[1]第二層 add error i:501 error:Invalid argument(errno:22)---------------test6 end i:500 ---------------
ep_remove:
移除一個epi
從poll wait中移除
從file的f_ep_links鏈表移除
從紅黑樹中移除
從ready鏈表中移除
取消wakeup註冊
自減ep->user->epoll_watches
ep_modify:修改epi
更新epi->event.events和epi->event.data
根據EPOLLWAKEUP更新wake up
刷新內存屏障smp_mb
經過ep_item_poll獲取revents,相比ep_insert差別在於並不會調用ep_ptable_queue_proc從新註冊
若是獲取到的revents中有用戶關注的事件,而且epi未在ready鏈表中,那麼把epi插入ready鏈表尾部 list_add_tail(&epi->rdllink, &ep->rdllist);並嘗試喚醒epoll_wait進程wake_up_locked(&ep->wq);以及file->poll()等待進程ep_poll_safewake(&ep->poll_wait)
3、epoll_wait&epoll_pwait
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,int, maxevents, int, timeout)
sys_epoll_pwait->sys_epoll_wait
sys_epoll_wait:主要作參數檢查和epfd 的校驗,而後經過ep_poll進行操做
ep_poll:
根據入參估計超時時間to和slack,或者設置timed_out標誌位
若是epoll_wait入參定時時間爲0,那麼直接經過ep_events_available判斷當前是否有用戶感興趣的事件發生,若是有則經過ep_send_events進行處理
若是定時時間大於0,而且當前沒有用戶關注的事件發生,則進行休眠,並添加到ep->wq等待隊列的頭部。 對等待事件描述符設置WQ_FLAG_EXCLUSIVE標誌
ep_poll被事件喚醒後會從新檢查是否有關注事件,若是對應的事件已經被搶走,那麼ep_poll會繼續休眠等待。
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$./nesttest-----------test8 測試epoll和accept同時等待的喚醒狀況 epfd1:4,epfd:5,listen_fd:3----------- lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$ss -tlnap | grep 9877LISTEN 0 128 *:9877 *:* users:(("nesttest",pid=6895,fd=3),("nesttest",pid=6894,fd=3),("nesttest",pid=6893,fd=3))lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877epfd2 epoll_wait return: i:0,nfds:1,fd:3,sec:238epfd1 epoll_wait return: i:0,nfds:1,fd:3,sec:238accept return connfd:6,sec:238^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877epfd2 epoll_wait return: accept return connfd:7,sec:240i:0,nfds:1,fd:3,sec:240^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877epfd1 epoll_wait return: accept return connfd:8,sec:242i:0,nfds:1,fd:3,sec:242^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877epfd2 epoll_wait return: accept return connfd:9,sec:244i:0,nfds:1,fd:3,sec:244^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877epfd1 epoll_wait return: i:0,nfds:1,fd:3,sec:246epfd2 epoll_wait return: i:0,nfds:1,fd:3,sec:246accept return connfd:10,sec:246^C
select_estimate_accuracy:
估計slack,最大爲MAX_SLACK(100ms),最小爲current->timer_slack_ns(默認值爲50000ns,即50 usec),timer_slack_ns能夠經過prctl的PR_SET_TIMERSLACK選項設置
nice 進程取定時時間的0.5%,普通進程取0.1%
ep_events_available:
若是ready鏈ep->rdllist非空或者ep->ovflist有效,則表示當前有關注的event發生
ep_scan_ready_list[ep_send_events]:
epoll_wait的時候傳遞函數指針ep_send_events_proc給ep_scan_ready_list,epfd進行poll的時候則傳遞函數指針ep_read_events_proc
把ep->rdllist連接到txlist,並清空ep->rdllist,設置ep->ovflist = NULL,表示當前正在往用戶空間發送數據,新事件觸發的epi插入到ep->ovflist的頭部,參考ep_send_events_proc函數的註釋
調用傳入的函數指針處理txlist
把ep->ovflist插入到ep->rdllist
設置ep->ovflist = EP_UNACTIVE_PTR; 表示當前須要往ready 鏈表插入事件epi
把txlist中剩餘元素插入ep->rdllist
若是ready鏈表非空,嘗試喚醒ep->wq和ep->poll_wait等待隊列
ep_send_events_proc[ep_scan_ready_list]:
讀取txlist中已經ready的事件,獲取事件的events,複製到用戶空間,複製失敗則把epi從新插入到ready鏈表
若是設置了EPOLLONESHOT標誌位,則設置epi->event.events &= EP_PRIVATE_BITS,其定義以下#define EP_PRIVATE_BITS (EPOLLWAKEUP | EPOLLONESHOT | EPOLLET),後續根據EP_PRIVATE_BITS判斷再也不加入ep->rdllist或者ep->ovflist。注意設置了EPOLLONESHOT觸發一次後並無刪除epi,於是經過epoll_ctl進行ADD操做後會提示File exists錯誤。
若是設置了水平觸發(沒有EPOLLET標誌位),那麼即便已經成功把事件傳遞到了用戶空間也會把epi從新添加到ready鏈表尾部,這樣下次進行epoll_wait的時候能夠從新檢查這個epi。注意EPOLLONESHOT優先於水平觸發的處理,即同時設置水平觸發和EPOLLONESHOT並不會把epi添加到ready鏈表。