掃碼關註
「
後端架構師
」,選擇
「
星標
」
公眾號
重磅幹貨,第一時間送達!
責編:架構君 | 來源:cloud.tencent.com/developer/article/2384713
上一篇好文:
大家好,我是後端架構師。
最近有小夥伴拿到了一線互聯網企業如 美團、拼多多、極兔、有贊、希音的面試資格,遇到一幾個很重要的面試題:
說說epoll的數據結構
說說epoll的實作原理
協定棧如何與epoll通訊?
epoll執行緒安全如何加鎖?
說說ET與LT的實作
……
這裏給大家做一下系統化、體系化的梳理,使得大家可以充分展示一下大家雄厚的 「技術肌肉」, 讓面試官愛到 「不能自已、口水直流」 。
epoll的數據結構
多種數據結構進行決策
epoll至少需要兩個集合
所有fd的總集
就緒fd的集合
那麽這個總集選用什麽數據結構儲存呢?
我們知道,一個fd,其底層對應一個TCB。那麽也就是說key=fd,val=TCB,是一個典型的kv型數據結構,對於kv型數據結構我們可以使用以下三種進行儲存。
hash
紅黑樹
b/b+tree
如果使用hash進行儲存,其優點是查詢速度很快,O(1)。
但是在我們呼叫epoll_create()的時候,hash底層的陣列建立多大合適呢?
如果我們有百萬的fd,那麽這個陣列越大越好,如果我們僅僅十幾個fd需要管理,在建立陣列的時候,太大的空間就很浪費。而這個fd我們又不能預先知道有多少,所以hash是不合適的。
b/b+tree是多叉樹,一個結點可以存多個key,主要是用於降低層高,用於磁盤索引的,所以在我們這個記憶體場景下也是不適合的。
在記憶體索引的場景下我們一般使用紅黑樹來作為首選的數據結構,首先紅黑樹的尋找速度很快,O(log(N))。其次在呼叫epoll_create()的時候,只需要建立一個紅黑樹樹根即可,無需浪費額外的空間。
那麽就緒集合用什麽數據結構呢,首先就緒集合不是以尋找為主的,就緒集合的作用是將裏面的元素拷貝給使用者進行處理,所以集合裏的元素沒有優先級,那麽就可以采用線性的數據結構,使用佇列來儲存,先進先出,先就緒的先處理。
所有fd的總集 -----> 紅黑樹
就緒fd的集合 -----> 佇列
紅黑樹和就緒佇列的關系
紅黑樹的結點和就緒佇列的結點的同一個節點,所謂的加入就緒佇列,就是將結點的前後指標聯系到一起。所以就緒了不是將紅黑樹結點delete掉然後加入佇列。他們是同一個結點,不需要delete。
structepitem{
RB_ENTRY(epitem) rbn;
LIST_ENTRY(epitem) rdlink;
int rdy; //exist in list
int sockfd;
structepoll_eventevent;
};
structeventpoll {
ep_rb_tree rbr;
int rbcnt;
LIST_HEAD( ,epitem) rdlist;
int rdnum;
int waiting;
pthread_mutex_t mtx; //rbtree update
pthread_spinlock_t lock; //rdlist update
pthread_cond_t cond; //block for event
pthread_mutex_t cdmtx; //mutex for cond
};
epoll的工作環境
應用程式只能透過三個 api介面 來操作epoll。 當一個io準備就緒的時候,epoll是怎麽知道io準備就緒了呢?是由協定棧將數據解析出來觸發回呼通知epoll的。也就是說可以把epoll的工作環境看出三部份,左邊應用程式的api,中間的epoll,右邊是協定棧的回呼(協定棧當然不能直接操作epoll,中間的vfs在此不是重點,就直接省略vfs這一層了)。
協定棧觸發回呼通知epoll的時機
socket有兩類,一類是監聽listenfd,一類是客戶端clientfd。 對於sockfd而言,我們一般比較關註EPOLLIN和EPOLLOUT這兩個事件,所以如果是listenfd,我們通常的做法就是accept。對於clientfd來說,如果可讀我們就recv,如果可寫我們就send。
協定棧將數據解析出來觸發回呼通知epoll。epoll是怎麽知道哪個io就緒了呢?我們從ip頭可以解析出源ip,目的ip和協定,從tcp頭可以解析出源埠和目的埠,此時 五元組 就湊齊了。socket fd --- < 源IP地址 , 源埠 , 目的IP地址 , 目的埠 , 協定 > 一個fd就是一個五元組,知道了fd,我們就能從紅黑樹中找到對應的結點。
那麽這個回呼函式做什麽事情呢?我們傳入fd和具體事件這兩個參數,然後做下面兩個操作
透過fd找到對應的結點
把結點加入到就緒佇列
1、協定棧中,在三次握手完成之後,會往全連線佇列中添加一個TCB結點,然後觸發一個回呼函式,通知到epoll裏面有個 EPOLL IN事件
2、客戶端發送一個封包,協定棧接收後回復ACK,之後觸發一個回呼函式,通知到epoll裏面有個EPOLLIN事件
3、每個連線的TCB裏面都有一個sendbuf,在對端接收到數據並返回ACK以後,sendbuf就可以將這部份確認接收的數據清空,此時sendbuf裏面就有剩余空間,此時觸發一個回呼函式,通知到epoll裏面有個EPOLLOUT事件
4、當對端發送close,在接收到fin後回復ACK,此時會呼叫回呼函式,通知到epoll有個EPOLLIN事件
5、當接收到rst標誌位的時候,回復ack之後也會觸發回呼函式,通知epoll有一個EPOLLERR事件
通知的時機總結
一 個有5個通知的地方
三次握手完成之後
接收數據回復ACK之後
發送數據收到ACK之後
接收FIN回復ACK之後
接收RST回復ACK之後
從回呼機制看epoll 與 select/poll的區別
由於select和poll沒有本質的區別,所以下面統一稱為poll。
// poll跟select類似, 其實poll就是把select三個檔描述符集合變成一個集合了。
intselect(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
intpoll(struct pollfd *fds, nfds_t nfds, int timeout);
我們看到每次呼叫poll,都需要把總集fds拷貝到內核態,檢測完之後,再有內核態拷貝的使用者態,這就是poll。而epoll不是這樣,epoll只要有新的io就呼叫epoll_ctl()加入到紅黑樹裏面,一旦有觸發就用epoll_wait()將有事件的結點帶出來,可以看到他們的第一個區別:poll總是拷貝總集,如果有100w個fd,只有兩三個就緒呢?這會造成大量資源浪費;而epoll總是將需要拷貝的東西進行拷貝,沒有浪費。
第二個區別:我們從上面知道了epoll的事件都是由協定棧進行回呼然後加入到就緒佇列的,而poll呢?內核如何檢測poll的io是否就緒?只能透過遍歷的方法判斷,所以poll檢測io透過遍歷的方法也是比較慢的。
所以兩者的區別:
select/poll需要把總集copy到內核,而epoll不用
實作原理上面,select/poll 需要迴圈遍歷總集是否有就緒,而epoll是那個結點就緒了就加入就緒佇列裏面。
註意:poll不一定就比epoll慢,在io量小的情況下,poll是比epoll快的,而在大io量下,epoll絕對是有主導地位的。至於有多少個io才算多,其實也很難說,一般認為500或者1024為分界點。
epoll執行緒安全如何加鎖
3個api做什麽事情
epoll_create() ===】建立紅黑樹的根節點
epoll_ctl() ===】add,del,mod 增加、刪除、修改結點
epoll_wait() ===】把就緒佇列的結點copy到使用者態放到events裏面,跟recv函式很像
分析加鎖
如果有3個執行緒同時操作epoll,有哪些地方需要加鎖? 我們使用者層面一共就只有3個api可以使用:
如果同時呼叫 epoll_create() ,那就是建立三顆紅黑樹,沒有涉及到資源競爭,沒有關系。
如果同時呼叫 epoll_ctl() ,對同一顆紅黑樹進行,增刪改,這就涉及到資源競爭需要加鎖了,此時我們對整棵樹進行加鎖。
如果同時呼叫epoll_wait() ,其操作的是就緒佇列,所以需要對就緒佇列進行加鎖。
我們要扣住epoll的工作環境,在應用程式呼叫 epoll_ctl() ,協定棧會不會有回呼操作紅黑樹結點?呼叫epoll_wait() copy出來的時候,協定棧會不會操作操作紅黑樹結點加入就緒佇列?綜上所述:
epoll_ctl() 對紅黑樹加鎖
epoll_wait()對就緒佇列加鎖
回呼函式() 對紅黑樹加鎖,對就緒佇列加鎖
那麽紅黑樹加什麽鎖,就緒佇列加什麽鎖呢?
對於紅黑樹這種節點比較多的時候,采用互斥鎖來加鎖。
就緒佇列就跟生產者消費者一樣,結點是從協定棧回呼函式來生產的,消費是epoll_wait()來消費。那麽對於佇列而言,用自旋鎖(對於佇列而言,插入刪除比較簡單,cpu自旋等待比讓出的成本更低,所以用自旋鎖)。
ET與LT如何實作
ET邊沿觸發,只觸發一次
LT水平觸發,如果沒有讀完就一直觸發
程式碼如何實作ET和LT的效果呢?水平觸發和邊沿觸發不是故意設計出來的,這是自然而然,水到渠成的功能。水平觸發和邊沿觸發程式碼只需要改一點點就能實作。從協定棧檢測到接收數據,就呼叫一次回呼,這就是ET,接收到數據,呼叫一次回呼。而LT水平觸發,檢測到recvbuf裏面有數據就呼叫回呼。所以ET和LT就是在使用回呼的次數上面的差異。
那麽具體如何實作呢?協定棧流程裏面觸發回呼,是天然的符合ET只觸發一次的。那麽如果是LT,在recv之後,如果緩沖區還有數據那麽加入到就緒佇列。那麽如果是LT,在send之後,如果緩沖區還有空間那麽加入到就緒佇列。那麽這樣就能實作LT了。
(真實實作程式碼參看:linux-2.6.24/fs/eventpoll.c檔中的 ep_send_events 函式)
說在最後
Linux相關面試題,是非常常見的面試題。
以上的內容,如果大家能對答如流,如數家珍,基本上 面試官會被你 震驚到、吸引到。
最終, 讓面試官愛到 「不能自已、口水直流」 。offer, 也就來了。
你還有什麽想要補充的嗎?
最後,再次推薦下我們的AI星 球 :
為了跟上AI時代我幹了一件事兒,我建立了一個知識星球社群:ChartGPT與副業。想帶著大家一起探索 ChatGPT和新的AI時代 。
有很多小夥伴搞不定ChatGPT帳號,於是我們決定,凡是這三天之內加入ChatPGT的小夥伴,我們直接送一個正常可用的永久ChatGPT獨立帳戶。
不光是增長速度最快,我們的星球品質也絕對經得起考驗,短短一個月時間,我們的課程團隊釋出了 8個專欄、18個副業計畫 :
簡單說下這個星球能給大家提供什麽:
1、不斷分享如何使用ChatGPT來完成各種任務,讓你更高效地使用ChatGPT,以及副業思考、變現思路、創業案例、落地案例分享。
2、分享ChatGPT的使用方法、最新資訊、商業價值。
3、探討未來關於ChatGPT的機遇,共同成長。
4、幫助大家解決ChatGPT遇到的問題。
5、 提供一整年的售後服務,一起搞副業
星球福利:
1、加入星球4天後,就送ChatGPT獨立帳號。
2、邀請你加入ChatGPT會員交流群。
3、贈送一份完整的ChatGPT手冊和66個ChatGPT副業賺錢手冊。
其它福利還在籌劃中... 不過,我給你大家保證,加入星球後,收獲的價值會遠遠大於今天加入的門票費用 !
本星球第一期原價 399 ,目前屬於試營運,早鳥價 139 ,每超過50人漲價10元,星球馬上要來一波大的漲價,如果你還在猶豫,可能最後就要以 更高價格加入了 。。
早就是優勢。 建議大家盡早以便宜的價格加入!
歡迎有需要的同學試試,如果本文對您有幫助,也請幫忙點個 贊 + 在看 啦!❤️
在 還有更多優質計畫系統學習資源,歡迎分享給其他同學吧!
PS:如果覺得我的分享不錯,歡迎大家隨手點贊、轉發、在看。
最後給讀者整理了一份BAT大廠面試真題,需要的可掃碼加微信備註:「面試」獲取。
版權申明:內容來源網路,版權歸原創者所有。除非無法確認,我們都會標明作者及出處,如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝!
END
最近面試BAT,整理一份面試資料【Java面試BAT通關手冊】,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。在這裏,我為大家準備了一份2021年最新最全BAT等大廠Java面試經驗總結。
別找了,想獲取史上最全的Java大廠面試題學習資料
掃下方二維碼回復「面試」就好了
歷史好文:
掃碼關註「後端架構師」,選擇「星標」公眾號
重磅幹貨,第一時間送達
嘿,你在看嗎?