當前位置:首頁 > 公眾號精選 > CPP開發(fā)者
[導讀]因為要對百萬、千萬、甚至是過億的用戶提供各種網(wǎng)絡服務,所以在一線互聯(lián)網(wǎng)企業(yè)里面試和晉升后端開發(fā)同學的其中一個重點要求就是要能支撐高并發(fā),要理解性能開銷,會進行性能優(yōu)化。而很多時候,如果你對Linux底層的理解不深的話,遇到很多線上性能瓶頸你會覺得狗拿刺猬,無從下手。我們今天用圖解...


因為要對百萬、千萬、甚至是過億的用戶提供各種網(wǎng)絡服務,所以在一線互聯(lián)網(wǎng)企業(yè)里面試和晉升后端開發(fā)同學的其中一個重點要求就是要能支撐高并發(fā),要理解性能開銷,會進行性能優(yōu)化。而很多時候,如果你對Linux底層的理解不深的話,遇到很多線上性能瓶頸你會覺得狗拿刺猬,無從下手。

我們今天用圖解的方式,來深度理解一下在Linux下網(wǎng)絡包的接收過程。還是按照慣例來借用一段最簡單的代碼開始思考。為了簡單起見,我們用udp來舉例,如下:

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ù)的邏輯。當在開發(fā)視角看的時候,只要客戶端有對應的數(shù)據(jù)發(fā)送過來,服務器端執(zhí)行recv_from后就能收到它,并把它打印出來。我們現(xiàn)在想知道的是,當網(wǎng)絡包達到網(wǎng)卡,直到我們的recvfrom收到數(shù)據(jù),這中間,究竟都發(fā)生過什么?

通過本文,你將深入理解Linux網(wǎng)絡系統(tǒng)內部是如何實現(xiàn)的,以及各個部分之間如何交互。相信這對你的工作將會有非常大的幫助。本文基于Linux 3.10,源代碼參見https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/,網(wǎng)卡驅動采用Intel的igb網(wǎng)卡舉例。

友情提示,本文略長,可以先Mark后看!

一?Linux網(wǎng)絡收包總覽

在TCP/IP網(wǎng)絡分層模型里,整個協(xié)議棧被分成了物理層、鏈路層、網(wǎng)絡層,傳輸層和應用層。物理層對應的是網(wǎng)卡和網(wǎng)線,應用層對應的是我們常見的Nginx,F(xiàn)TP等等各種應用。Linux實現(xiàn)的是鏈路層、網(wǎng)絡層和傳輸層這三層。

在Linux內核實現(xiàn)中,鏈路層協(xié)議靠網(wǎng)卡驅動來實現(xiàn),內核協(xié)議棧來實現(xiàn)網(wǎng)絡層和傳輸層。內核對更上層的應用層提供socket接口來供用戶進程訪問。我們用Linux的視角來看到的TCP/IP網(wǎng)絡分層模型應該是下面這個樣子的。

圖1 Linux視角的網(wǎng)絡協(xié)議棧
在Linux的源代碼中,網(wǎng)絡設備驅動對應的邏輯位于driver/net/ethernet, 其中intel系列網(wǎng)卡的驅動在driver/net/ethernet/intel目錄下。協(xié)議棧模塊代碼位于kernelnet目錄。

內核和網(wǎng)絡設備驅動是通過中斷的方式來處理的。當設備上有數(shù)據(jù)到達的時候,會給CPU的相關引腳上觸發(fā)一個電壓變化,以通知CPU來處理數(shù)據(jù)。對于網(wǎng)絡模塊來說,由于處理過程比較復雜和耗時,如果在中斷函數(shù)中完成所有的處理,將會導致中斷處理函數(shù)(優(yōu)先級過高)將過度占據(jù)CPU,將導致CPU無法響應其它設備,例如鼠標和鍵盤的消息。

因此Linux中斷處理函數(shù)是分上半部和下半部的。上半部是只進行最簡單的工作,快速處理然后釋放CPU,接著CPU就可以允許其它中斷進來。剩下將絕大部分的工作都放到下半部中,可以慢慢從容處理。2.4以后的內核版本采用的下半部實現(xiàn)方式是軟中斷,由ksoftirqd內核線程全權處理。

和硬中斷不同的是,硬中斷是通過給CPU物理引腳施加電壓變化,而軟中斷是通過給內存中的一個變量的二進制值以通知軟中斷處理程序。

好了,大概了解了網(wǎng)卡驅動、硬中斷、軟中斷和ksoftirqd線程之后,我們在這幾個概念的基礎上給出一個內核收包的路徑示意:

圖2 Linux內核網(wǎng)絡收包總覽

當網(wǎng)卡上收到數(shù)據(jù)以后,Linux中第一個工作的模塊是網(wǎng)絡驅動。網(wǎng)絡驅動會以DMA的方式把網(wǎng)卡上收到的幀寫到內存里。再向CPU發(fā)起一個中斷,以通知CPU有數(shù)據(jù)到達。第二,當CPU收到中斷請求后,會去調用網(wǎng)絡驅動注冊的中斷處理函數(shù)。網(wǎng)卡的中斷處理函數(shù)并不做過多工作,發(fā)出軟中斷請求,然后盡快釋放CPU。ksoftirqd檢測到有軟中斷請求到達,調用poll開始輪詢收包,收到后交由各級協(xié)議棧處理。對于UDP包來說,會被放到用戶socket的接收隊列中。

我們從上面這張圖中已經(jīng)從整體上把握到了Linux對數(shù)據(jù)包的處理過程。但是要想了解更多網(wǎng)絡模塊工作的細節(jié),我們還得往下看。

二?Linux啟動

Linux驅動,內核協(xié)議棧等等模塊在具備接收網(wǎng)卡數(shù)據(jù)包之前,要做很多的準備工作才行。比如要提前創(chuàng)建好ksoftirqd內核線程,要注冊好各個協(xié)議對應的處理函數(shù),網(wǎng)絡設備子系統(tǒng)要提前初始化好,網(wǎng)卡要啟動好。只有這些都Ready之后,我們才能真正開始接收數(shù)據(jù)包。那么我們現(xiàn)在來看看這些準備工作都是怎么做的。

2.1 創(chuàng)建ksoftirqd內核線程

Linux的軟中斷都是在專門的內核線程(ksoftirqd)中進行的,因此我們非常有必要看一下這些進程是怎么初始化的,這樣我們才能在后面更準確地了解收包過程。該進程數(shù)量不是1個,而是N個,其中N等于你的機器的核數(shù)。

系統(tǒng)初始化的時候在kernel/smpboot.c中調用了smpboot_register_percpu_thread, 該函數(shù)進一步會執(zhí)行到spawn_ksoftirqd(位于kernel/softirq.c)來創(chuàng)建出softirqd進程。

圖3 創(chuàng)建ksoftirqd內核線程

相關代碼如下:

//file:?kernel/softirq.c

static?struct?smp_hotplug_thread?softirq_threads?=?{

????.store??????????=?
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉
關閉