五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

一文帶你了解Linux內(nèi)核epoll實(shí)現(xiàn)原理與機(jī)制!

2022-03-09 20:10 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

一、epoll_create()

  • 系統(tǒng)調(diào)用epoll_create()會創(chuàng)建一個epoll實(shí)例并返回該實(shí)例對應(yīng)的文件描述符fd。在內(nèi)核中,每個epoll實(shí)例會和一個struct eventpoll類型的對象一一對應(yīng),該對象是epoll的核心,其聲明在fs/eventpoll.c文件中.

  • epoll_create的接口定義在這里,主要源碼分析如下:

  • 首先創(chuàng)建一個struct eventpoll對象:

  • 然后創(chuàng)建一個struct file對象,將file中的struct file_operations *f_op設(shè)置為全局變量eventpoll_fops,將void *private指向剛創(chuàng)建的eventpoll對象ep:


  • 然后設(shè)置eventpoll中的file指針:

  • ep->file = file;

  • 最后將文件描述符添加到當(dāng)前進(jìn)程的文件描述符表中,并返回給用戶

  • fd_install(fd, file);<br data-filtered="filtered">return fd;

  • 操作結(jié)束后主要結(jié)構(gòu)關(guān)系如下圖:

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。∏?00名進(jìn)群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)

二、epoll_ctl()

  • 系統(tǒng)調(diào)用epoll_ctl()在內(nèi)核中的定義如下,各個參數(shù)的含義可參見epoll_ctl的man手冊

  • SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event)

  • epoll_ctl()首先判斷op是不是刪除操作,如果不是則將event參數(shù)從用戶空間拷貝到內(nèi)核中:

接下來判斷用戶是否設(shè)置了EPOLLEXCLUSIVE標(biāo)志,這個標(biāo)志是4.5版本內(nèi)核才有的,主要是為了解決同一個文件描述符同時被添加到多個epoll實(shí)例中造成的“驚群”問題,詳細(xì)描述可以看這里。 這個標(biāo)志的設(shè)置有一些限制條件,比如只能是在EPOLL_CTL_ADD操作中設(shè)置,而且對應(yīng)的文件描述符本身不能是一個epoll實(shí)例,下面代碼就是對這些限制的檢查:


接下來從傳入的文件描述符開始,一步步獲得struct file對象,再從struct file中的private_data字段獲得struct eventpoll對象:


  • 如果要添加的文件描述符本身也代表一個epoll實(shí)例,那么有可能會造成死循環(huán),內(nèi)核對此情況做了檢查,如果存在死循環(huán)則返回錯誤。這部分的代碼目前我還沒細(xì)看,這里不再貼出。

  • 接下來會從epoll實(shí)例的紅黑樹里尋找和被監(jiān)控文件對應(yīng)的epollitem對象,如果不存在,也就是之前沒有添加過該文件,返回的會是NULL。

ep_find()函數(shù)本質(zhì)是一個紅黑樹查找過程,紅黑樹查找和插入使用的比較函數(shù)是ep_cmp_ffd(),先比較struct file對象的地址大小,相同的話再比較文件描述符大小。struct file對象地址相同的一種情況是通過dup()系統(tǒng)調(diào)用將不同的文件描述符指向同一個struct file對象。


接下來會根據(jù)操作符op的不同做不同的處理,這里我們只看op等于EPOLL_CTL_ADD時的添加操作。首先會判斷上一步操作中返回的epollitem對象地址是否為NULL,不是NULL說明該文件已經(jīng)添加過了,返回錯誤,否則調(diào)用ep_insert()函數(shù)進(jìn)行真正的添加操作。在添加文件之前內(nèi)核會自動為該文件增加POLLERR和POLLHUP事件。

三、ep_insert()

  • ep_insert()函數(shù)中,首先判斷epoll實(shí)例中監(jiān)視的文件數(shù)量是否已超過限制,沒問題則為待添加的文件創(chuàng)建一個epollitem對象:

接下來是對epollitem的初始化:


接下來是比較重要的操作:將epollitem對象添加到被監(jiān)視文件的等待隊列上去。等待隊列實(shí)際上就是一個回調(diào)函數(shù)鏈表,定義在/include/linux/wait.h文件中。因?yàn)椴煌募到y(tǒng)的實(shí)現(xiàn)不同,無法直接通過struct file對象獲取等待隊列,因此這里通過struct file的poll操作,以回調(diào)的方式返回對象的等待隊列,這里設(shè)置的回調(diào)函數(shù)是ep_ptable_queue_proc:

  • 上面代碼中結(jié)構(gòu)體ep_queue的作用是能夠在poll的回調(diào)函數(shù)中取得對應(yīng)的epollitem對象,這種做法在Linux內(nèi)核里非常常見。

  • 在回調(diào)函數(shù)ep_ptable_queue_proc中,內(nèi)核會創(chuàng)建一個struct eppoll_entry對象,然后將等待隊列中的回調(diào)函數(shù)設(shè)置為ep_poll_callback()。也就是說,當(dāng)被監(jiān)控文件有事件到來時,比如socker收到數(shù)據(jù)時,ep_poll_callback()會被回調(diào)。ep_ptable_queue_proc()代碼如下:

  • ppoll_entry和epitem等結(jié)構(gòu)關(guān)系如下圖:


  • 在回到ep_insert()函數(shù)中。ep_item_poll()調(diào)用完成之后,會將epitem中的fllink字段添加到struct file中的f_ep_links鏈表中,這樣就可以通過struct file找到所有對應(yīng)的struct epollitem對象,進(jìn)而通過struct epollitem找到所有的epoll實(shí)例對應(yīng)的struct eventpoll。

spin_lock(&tfile->f_lock);?

list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);?

spin_unlock(&tfile->f_lock);


然后就是將epollitem插入到紅黑樹中:1 ep_rbtree_insert(ep, epi) 最后再更新下狀態(tài)就返回了,插入操作也就完成了。 在返回之前還會判斷一次剛才添加的文件是不是當(dāng)前已經(jīng)有事件就緒了,如果是就將其加入到epoll的就緒鏈表中,關(guān)于就緒鏈表放到下一部分中講,這里略過。

  • 最后是我畫的幾個結(jié)構(gòu)體之間的結(jié)構(gòu)圖。


四、分析如下

  • 在通過epoll_ctl(2)向epoll中添加被監(jiān)視文件描述符時,會將ep_poll_callback()作為回調(diào)函數(shù)添加被監(jiān)視文件的等待隊列中。下面分析ep_poll_callback()函數(shù)

  • 判斷返回的事件掩碼里是否設(shè)置了標(biāo)志位POLLFREE(什么時候會設(shè)置該標(biāo)志?),如果是則將當(dāng)前等待對象從文件描述符的等待隊列中刪除(疑問:注釋是什么意思?為什么不需要加鎖?)。

  • 接下來對epoll的實(shí)例加鎖:

接下來判斷epitem中的事件掩碼是不是并沒有包括任何poll(2)事件,如果是的話,則解鎖后直接返回:

什么時候會出現(xiàn)上述情況呢?注釋里也說了,就是在設(shè)置了EPOLLONESHOT標(biāo)志的時候。對EPOLLONESHOT標(biāo)志的處理是在epoll_wait()的返回過程,調(diào)用ep_send_events_proc()的時候,如果設(shè)置了EPOLLONESHOT標(biāo)志則將EP_PRIVATE_BITS以外的標(biāo)志位全部清0:

接下來判斷返回的事件里是否有用戶真正感興趣的事件,沒有則解鎖后返回,否則繼續(xù)。

如果此時就緒鏈表rdllist沒有被其他進(jìn)程訪問,則直接將當(dāng)前文件描述符添加到rdllist鏈表中,否則的話添加到ovflist鏈表中。ovflist默認(rèn)值是EP_UNACTIVE_PTR,epoll_wait()遍歷rdllist之前會把ovflist設(shè)置為NULL,遍歷完再恢復(fù)為EP_UNACTIVE_PTR,因此通過判斷ovflist的值是不是EP_UNACTIVE_PTR可知此時rdllist是不是正在被訪問。

如果是描述符是添加到ovflist鏈表中,說明此時已經(jīng)有ep_wait()準(zhǔn)備返回了,因此不用再喚醒epoll實(shí)例的等待隊列,因此1062行直接跳到解鎖處;否則的話,則喚醒因?yàn)檎{(diào)用epoll_wait()而等待在epoll實(shí)例等待隊列上的進(jìn)程(這里最多只會喚醒一個進(jìn)程):

如果epoll實(shí)例的poll隊列非空,也會喚醒等待在poll隊列上的進(jìn)程,不過是在解鎖后才會進(jìn)行喚醒操作。

最后解鎖并返回:

注意到ep_poll_callback()的返回值和EPOLLEXCLUSIVE標(biāo)志有關(guān),該標(biāo)志是用來處理這種情況:當(dāng)多個進(jìn)程中的不同epoll實(shí)例在監(jiān)視同一個文件描述符時,如果該文件描述符上有事件發(fā)生,則所有的epoll實(shí)例所在進(jìn)程都將被喚醒,這樣有可能造成“驚群”(thundering herd)。


五、epoll驚群原因分析

  • 考慮如下情況(實(shí)際一般不會做,這里只是舉個例子):

在主線程中創(chuàng)建一個socket、綁定到本地端口并監(jiān)聽?

在主線程中創(chuàng)建一個epoll實(shí)例(epoll_create(2))?

將監(jiān)聽socket添加到epoll中(epoll_ctl(2))?

創(chuàng)建多個子線程,每個子線程都共享步驟2里創(chuàng)建的同一個epoll文件描述符,然后調(diào)用epoll_wait(2)等待事件到來accept(2)?

請求到來,新連接建立?

這里的問題就是,在第5步的時候,會有多少個線程被喚醒而從epoll_wait()調(diào)用返回?答案是不一定,可能只有一個,也可能有部分,也可能是全部。當(dāng)然在多個線程都喚醒的情況下,只會有一個線程accept()調(diào)用會成功。

  • 為何如此?從內(nèi)核代碼分析,原因如下:

  • 因?yàn)開_wake_up_common()的調(diào)用是從wake_up_locked()開始的,__wake_up_common的各個參數(shù)值為:

  • 局部變量curr的值可以通過epoll_wait()的源碼得到,具體為:

curr->flags: WQ_FLAG_EXCLUSIVE?

curr->func: default_wake_function?

default_wake_function調(diào)用的是try_to_wake_up。而try_to_wake_up只有在要喚醒的進(jìn)程狀態(tài)不是

TASK_NORMAL時才會返回0,TASK_NORMAL的定義是(TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)。

  • 因此__wake_up_common里的if條件會在第一次判斷的時候就滿足,喚醒一個進(jìn)程后便返回了,那為什么實(shí)際測試會發(fā)現(xiàn)有多個進(jìn)程被喚醒呢?

  • 原因就在于這個唯一被喚醒的進(jìn)程。

  • 當(dāng)某個等待在epoll實(shí)例上的進(jìn)程被喚醒后,最終會進(jìn)入到ep_scan_ready_list() 這個函數(shù)中,ep_scan_ready_list()會以回調(diào)方式調(diào)用ep_send_events_proc()來將數(shù)據(jù)復(fù)制到用戶空間。而ep_scan_ready_list()函數(shù)在返回之前會再次判斷epoll的就緒鏈表rdllist是否為空,如果不為空的話,就會再喚醒其他進(jìn)程!下面就是ep_scan_ready_list()返回之前的判斷操作:

  • 而在水平觸發(fā)方式下,從就緒鏈表中移出來的文件描述符,如果當(dāng)前仍有事件就緒(可讀、可寫等),會在復(fù)制到用戶空間后被再次添加到就緒鏈表中:

  • 因此在水平觸發(fā)模式下,被喚醒的進(jìn)程又會去喚醒其他進(jìn)程,除非當(dāng)前事件已經(jīng)被處理完或者所有進(jìn)程都已經(jīng)被喚醒(被喚醒的進(jìn)程會從epoll等待隊列上移除)。


一文帶你了解Linux內(nèi)核epoll實(shí)現(xiàn)原理與機(jī)制!的評論 (共 條)

分享到微博請遵守國家法律
增城市| 德清县| 潮州市| 鹤山市| 邮箱| 泗阳县| 乃东县| 台东县| 清远市| 桃园市| 望奎县| 溧水县| 洪泽县| 朝阳县| 信阳市| 成都市| 宜兰市| 满洲里市| 分宜县| 江源县| 芜湖县| 尚义县| 繁峙县| 乌兰察布市| 通化县| 庆元县| 紫云| 顺义区| 怀宁县| 西城区| 沛县| 肃北| 中超| 两当县| 永昌县| 镇巴县| 铜川市| 绿春县| 濮阳县| 沛县| 靖江市|