圖解?Linux?網(wǎng)絡(luò)包接收過(guò)程
時(shí)間:2021-09-22 14:19:01
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]因?yàn)橐獙?duì)百萬(wàn)、千萬(wàn)、甚至是過(guò)億的用戶(hù)提供各種網(wǎng)絡(luò)服務(wù),所以在一線互聯(lián)網(wǎng)企業(yè)里面試和晉升后端開(kāi)發(fā)同學(xué)的其中一個(gè)重點(diǎn)要求就是要能支撐高并發(fā),要理解性能開(kāi)銷(xiāo),會(huì)進(jìn)行性能優(yōu)化。而很多時(shí)候,如果你對(duì)Linux底層的理解不深的話(huà),遇到很多線上性能瓶頸你會(huì)覺(jué)得狗拿刺猬,無(wú)從下手。我們今天用圖解...
因?yàn)橐獙?duì)百萬(wàn)、千萬(wàn)、甚至是過(guò)億的用戶(hù)提供各種網(wǎng)絡(luò)服務(wù),所以在一線互聯(lián)網(wǎng)企業(yè)里面試和晉升后端開(kāi)發(fā)同學(xué)的其中一個(gè)重點(diǎn)要求就是要能支撐高并發(fā),要理解性能開(kāi)銷(xiāo),會(huì)進(jìn)行性能優(yōu)化。而很多時(shí)候,如果你對(duì)Linux底層的理解不深的話(huà),遇到很多線上性能瓶頸你會(huì)覺(jué)得狗拿刺猬,無(wú)從下手。我們今天用圖解的方式,來(lái)深度理解一下在Linux下網(wǎng)絡(luò)包的接收過(guò)程。還是按照慣例來(lái)借用一段最簡(jiǎn)單的代碼開(kāi)始思考。為了簡(jiǎn)單起見(jiàn),我們用udp來(lái)舉例,如下:
int?main(){
????int?serverSocketFd?=?socket(AF_INET,?SOCK_DGRAM,?0);
????bind(serverSocketFd,?...);
????char?buff[BUFFSIZE];
????int?readCount?=?recvfrom(serverSocketFd,?buff,?BUFFSIZE,?0,?...);
????buff[readCount]?=?'\0';????printf("Receive?from?client:%s\n",?buff);}
上面代碼是一段udp server接收收據(jù)的邏輯。當(dāng)在開(kāi)發(fā)視角看的時(shí)候,只要客戶(hù)端有對(duì)應(yīng)的數(shù)據(jù)發(fā)送過(guò)來(lái),服務(wù)器端執(zhí)行recv_from
后就能收到它,并把它打印出來(lái)。我們現(xiàn)在想知道的是,當(dāng)網(wǎng)絡(luò)包達(dá)到網(wǎng)卡,直到我們的recvfrom
收到數(shù)據(jù),這中間,究竟都發(fā)生過(guò)什么?通過(guò)本文,你將深入理解Linux網(wǎng)絡(luò)系統(tǒng)內(nèi)部是如何實(shí)現(xiàn)的,以及各個(gè)部分之間如何交互。相信這對(duì)你的工作將會(huì)有非常大的幫助。本文基于Linux 3.10,源代碼參見(jiàn)https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/,網(wǎng)卡驅(qū)動(dòng)采用Intel的igb網(wǎng)卡舉例。友情提示,本文略長(zhǎng),可以先Mark后看!一?Linux網(wǎng)絡(luò)收包總覽在TCP/IP網(wǎng)絡(luò)分層模型里,整個(gè)協(xié)議棧被分成了物理層、鏈路層、網(wǎng)絡(luò)層,傳輸層和應(yīng)用層。物理層對(duì)應(yīng)的是網(wǎng)卡和網(wǎng)線,應(yīng)用層對(duì)應(yīng)的是我們常見(jiàn)的Nginx,F(xiàn)TP等等各種應(yīng)用。Linux實(shí)現(xiàn)的是鏈路層、網(wǎng)絡(luò)層和傳輸層這三層。在Linux內(nèi)核實(shí)現(xiàn)中,鏈路層協(xié)議靠網(wǎng)卡驅(qū)動(dòng)來(lái)實(shí)現(xiàn),內(nèi)核協(xié)議棧來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)層和傳輸層。內(nèi)核對(duì)更上層的應(yīng)用層提供socket接口來(lái)供用戶(hù)進(jìn)程訪問(wèn)。我們用Linux的視角來(lái)看到的TCP/IP網(wǎng)絡(luò)分層模型應(yīng)該是下面這個(gè)樣子的。driver/net/ethernet
, 其中intel系列網(wǎng)卡的驅(qū)動(dòng)在driver/net/ethernet/intel
目錄下。協(xié)議棧模塊代碼位于kernel
和net
目錄。內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)是通過(guò)中斷的方式來(lái)處理的。當(dāng)設(shè)備上有數(shù)據(jù)到達(dá)的時(shí)候,會(huì)給CPU的相關(guān)引腳上觸發(fā)一個(gè)電壓變化,以通知CPU來(lái)處理數(shù)據(jù)。對(duì)于網(wǎng)絡(luò)模塊來(lái)說(shuō),由于處理過(guò)程比較復(fù)雜和耗時(shí),如果在中斷函數(shù)中完成所有的處理,將會(huì)導(dǎo)致中斷處理函數(shù)(優(yōu)先級(jí)過(guò)高)將過(guò)度占據(jù)CPU,將導(dǎo)致CPU無(wú)法響應(yīng)其它設(shè)備,例如鼠標(biāo)和鍵盤(pán)的消息。因此Linux中斷處理函數(shù)是分上半部和下半部的。上半部是只進(jìn)行最簡(jiǎn)單的工作,快速處理然后釋放CPU,接著CPU就可以允許其它中斷進(jìn)來(lái)。剩下將絕大部分的工作都放到下半部中,可以慢慢從容處理。2.4以后的內(nèi)核版本采用的下半部實(shí)現(xiàn)方式是軟中斷,由ksoftirqd內(nèi)核線程全權(quán)處理。和硬中斷不同的是,硬中斷是通過(guò)給CPU物理引腳施加電壓變化,而軟中斷是通過(guò)給內(nèi)存中的一個(gè)變量的二進(jìn)制值以通知軟中斷處理程序。好了,大概了解了網(wǎng)卡驅(qū)動(dòng)、硬中斷、軟中斷和ksoftirqd線程之后,我們?cè)谶@幾個(gè)概念的基礎(chǔ)上給出一個(gè)內(nèi)核收包的路徑示意:圖2 Linux內(nèi)核網(wǎng)絡(luò)收包總覽當(dāng)網(wǎng)卡上收到數(shù)據(jù)以后,Linux中第一個(gè)工作的模塊是網(wǎng)絡(luò)驅(qū)動(dòng)。網(wǎng)絡(luò)驅(qū)動(dòng)會(huì)以DMA的方式把網(wǎng)卡上收到的幀寫(xiě)到內(nèi)存里。再向CPU發(fā)起一個(gè)中斷,以通知CPU有數(shù)據(jù)到達(dá)。第二,當(dāng)CPU收到中斷請(qǐng)求后,會(huì)去調(diào)用網(wǎng)絡(luò)驅(qū)動(dòng)注冊(cè)的中斷處理函數(shù)。網(wǎng)卡的中斷處理函數(shù)并不做過(guò)多工作,發(fā)出軟中斷請(qǐng)求,然后盡快釋放CPU。ksoftirqd檢測(cè)到有軟中斷請(qǐng)求到達(dá),調(diào)用poll開(kāi)始輪詢(xún)收包,收到后交由各級(jí)協(xié)議棧處理。對(duì)于UDP包來(lái)說(shuō),會(huì)被放到用戶(hù)socket的接收隊(duì)列中。我們從上面這張圖中已經(jīng)從整體上把握到了Linux對(duì)數(shù)據(jù)包的處理過(guò)程。但是要想了解更多網(wǎng)絡(luò)模塊工作的細(xì)節(jié),我們還得往下看。二?Linux啟動(dòng)Linux驅(qū)動(dòng),內(nèi)核協(xié)議棧等等模塊在具備接收網(wǎng)卡數(shù)據(jù)包之前,要做很多的準(zhǔn)備工作才行。比如要提前創(chuàng)建好ksoftirqd內(nèi)核線程,要注冊(cè)好各個(gè)協(xié)議對(duì)應(yīng)的處理函數(shù),網(wǎng)絡(luò)設(shè)備子系統(tǒng)要提前初始化好,網(wǎng)卡要啟動(dòng)好。只有這些都Ready之后,我們才能真正開(kāi)始接收數(shù)據(jù)包。那么我們現(xiàn)在來(lái)看看這些準(zhǔn)備工作都是怎么做的。2.1 創(chuàng)建ksoftirqd內(nèi)核線程
Linux的軟中斷都是在專(zhuān)門(mén)的內(nèi)核線程(ksoftirqd)中進(jìn)行的,因此我們非常有必要看一下這些進(jìn)程是怎么初始化的,這樣我們才能在后面更準(zhǔn)確地了解收包過(guò)程。該進(jìn)程數(shù)量不是1個(gè),而是N個(gè),其中N等于你的機(jī)器的核數(shù)。系統(tǒng)初始化的時(shí)候在kernel/smpboot.c中調(diào)用了smpboot_register_percpu_thread, 該函數(shù)進(jìn)一步會(huì)執(zhí)行到spawn_ksoftirqd(位于kernel/softirq.c)來(lái)創(chuàng)建出softirqd進(jìn)程。圖3 創(chuàng)建ksoftirqd內(nèi)核線程相關(guān)代碼如下://file:?kernel/softirq.c
static?struct?smp_hotplug_thread?softirq_threads?=?{
????.store??????????=?