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