Linux內(nèi)核接收?qǐng)?bào)文流程學(xué)習(xí)

1、關(guān)鍵數(shù)據(jù)結(jié)構(gòu)

struct sk_buff{

....

}

1.1 嵌套字緩沖區(qū)

一個(gè)數(shù)據(jù)封包就存儲(chǔ)在這里,所有網(wǎng)絡(luò)分層都會(huì)使用這個(gè)結(jié)構(gòu)來(lái)存儲(chǔ)其報(bào)頭、有關(guān)的用戶數(shù)據(jù)信息(有效載荷),以及用來(lái)協(xié)調(diào)其工作的其他內(nèi)部信息。

當(dāng)該結(jié)構(gòu)從一個(gè)分層由上至下傳到另一個(gè)分層時(shí),其不同的字段會(huì)隨之發(fā)生變化,在首部附加上該層自己的報(bào)頭。

附加報(bào)頭比起在分層之間拷貝更有效率

附加報(bào)頭就要在緩沖區(qū)開(kāi)端新增空間,也就是要改變指向該緩沖區(qū)的變量,內(nèi)核提供了skb_reserve()函數(shù),為該協(xié)議的報(bào)頭預(yù)留空間。

當(dāng)緩沖區(qū)由下至上傳經(jīng)各個(gè)網(wǎng)絡(luò)分層時(shí),每個(gè)源自舊分層的報(bào)頭就不再有用處,把指向有效載荷的指針向前移動(dòng)到上層報(bào)頭的開(kāi)端。

1.2 布局字段

內(nèi)核在一個(gè)雙向鏈表中維護(hù)所有的sk_buff結(jié)構(gòu),但是該表的組織逼傳統(tǒng)的雙向鏈表更為復(fù)雜

每個(gè)sk_buff結(jié)構(gòu)必須能夠迅速找出這個(gè)表的頭,在表的開(kāi)端額外增加一個(gè)sk_buff_head結(jié)構(gòu)作為一種啞元元素

struct sk_buff_head{
        /* 這兩個(gè)成員必須在最前 */
        struct sk_buff    *next;
        struct sk_buff    *prev;

        __u32              qlen;  //代表表中元素的數(shù)目
        spinlock_t         lock;  //用于防止對(duì)表的并發(fā)訪問(wèn)
}

1.3 緩沖區(qū)的克隆和拷貝

當(dāng)同一個(gè)緩沖區(qū)需要由不同消費(fèi)者個(gè)別處理時(shí),那些消費(fèi)者可能需要修改sk_buff掃描符的內(nèi)容(指向協(xié)議報(bào)頭的h和nh指針),但內(nèi)核不需要完全拷貝sk_buff結(jié)構(gòu)和相關(guān)聯(lián)的數(shù)據(jù)緩沖區(qū)。

為了提高效率,內(nèi)核可以克?。╟lone)原始值,也就是只拷貝sk_buff結(jié)構(gòu),然后使用引用計(jì)數(shù),以避免過(guò)早釋放共享的數(shù)據(jù)塊。緩沖區(qū)的克隆由skb_clone函數(shù)實(shí)現(xiàn)。

2、網(wǎng)絡(luò)接口

2.1 網(wǎng)絡(luò)接口的命名

  • eth0: ethernet的簡(jiǎn)寫,一般用于以太網(wǎng)接口
  • wifi0: wifi是無(wú)線局域網(wǎng),因此wifi0一般指無(wú)線網(wǎng)絡(luò)接口
  • ath0: Atheros的簡(jiǎn)寫,一般指Atheros芯片所包含的無(wú)線網(wǎng)絡(luò)接口
  • lo: local的簡(jiǎn)寫,一般指本地環(huán)回接口

2.2 網(wǎng)絡(luò)接口如何工作

網(wǎng)絡(luò)接口是用來(lái)發(fā)送和接收數(shù)據(jù)包的基本設(shè)備。系統(tǒng)中所有網(wǎng)絡(luò)接口組成一盒連撞節(jié)后,應(yīng)用層程序使用時(shí)按名稱調(diào)用。每個(gè)網(wǎng)絡(luò)接口在linux系統(tǒng)中對(duì)應(yīng)一個(gè)struct net_device結(jié)構(gòu)體,包含name,mac,mask,mtu...信息。每個(gè)硬件網(wǎng)卡(一個(gè)MAC)對(duì)應(yīng)一個(gè)網(wǎng)絡(luò)接口,其工作完全由相應(yīng)的驅(qū)動(dòng)程序控制。

2.3 虛擬網(wǎng)絡(luò)接口

虛擬網(wǎng)絡(luò)接口的應(yīng)用范圍非常廣泛,最著名的是“l(fā)o”,基本上每個(gè)linux系統(tǒng)都有這個(gè)接口

虛擬網(wǎng)絡(luò)接口并不真實(shí)地從外界接收和發(fā)送數(shù)據(jù)包,而是在系統(tǒng)內(nèi)部接受和發(fā)送數(shù)據(jù)包,因此虛擬網(wǎng)絡(luò)接口不需要驅(qū)動(dòng)程序。虛擬網(wǎng)絡(luò)接口和真實(shí)存在的網(wǎng)絡(luò)接口在使用上是一致的

硬件網(wǎng)卡的網(wǎng)絡(luò)接口由驅(qū)動(dòng)程序創(chuàng)建,而虛擬的網(wǎng)絡(luò)接口由系統(tǒng)創(chuàng)建貨通過(guò)應(yīng)用層程序創(chuàng)建

2.4 linux中的lo(回環(huán)接口)

用于本地進(jìn)程間數(shù)據(jù)包通信的虛擬網(wǎng)絡(luò)接口

在linux系統(tǒng)中,除了網(wǎng)絡(luò)接口eth0,還可以有別的接口,比如lo(本地環(huán)路接口)

假如包是由一個(gè)本地進(jìn)程為另一個(gè)進(jìn)程產(chǎn)生的,他們將通過(guò)外出鏈的“l(fā)o”接口,具體參考包過(guò)濾器的相關(guān)內(nèi)容

負(fù)責(zé)接收幀的代碼分成兩部分:首先,驅(qū)動(dòng)程序把該幀拷貝到內(nèi)核可訪問(wèn)的輸入隊(duì)列,然后內(nèi)核在予以處理,通常是把把那個(gè)幀傳給一個(gè)相關(guān)協(xié)議(如IP)專用的處理函數(shù)。第一部分會(huì)在中斷環(huán)境中執(zhí)行,而且可以搶占第二部分的執(zhí)行。接收輸入幀并將其拷貝到隊(duì)列的代碼比實(shí)際處理幀的代碼的優(yōu)先級(jí)要高。

接收-活鎖(receive-livelock)

在高流量負(fù)載下,中斷代碼會(huì)持續(xù)搶占正在處理的代碼。輸入隊(duì)列已滿,但是由于應(yīng)該讓幀退出隊(duì)列并予以處理的代碼的優(yōu)先級(jí)過(guò)低而沒(méi)有機(jī)會(huì)執(zhí)行,結(jié)果系統(tǒng)就崩潰了。新的幀無(wú)法排入隊(duì)列,而舊的幀無(wú)法被處理

通常的Linux報(bào)文處理方式中,報(bào)文分組到達(dá)內(nèi)核,觸發(fā)設(shè)備中斷,中斷處理程序?yàn)樾聢?bào)文創(chuàng)建套接字緩沖區(qū),分組內(nèi)容以DMA的方式從網(wǎng)卡傳輸?shù)骄彌_區(qū)中指向的物理內(nèi)存。然后內(nèi)核分析新報(bào)文的首部,此時(shí)報(bào)文處理已由網(wǎng)卡驅(qū)動(dòng)代碼轉(zhuǎn)到網(wǎng)絡(luò)層的通用接口,同時(shí)該函數(shù)將接收的報(bào)文送入特定的CPU等待隊(duì)列中,觸發(fā)該CPU的NET_RX_SOFTIRQ軟中斷,并且退出中斷上下文。特定CPU的NET_RX_SOFTIRQ軟中斷處理對(duì)應(yīng)等待隊(duì)列中的報(bào)文的嵌套字緩沖區(qū)。

正式由此開(kāi)始協(xié)議棧之旅:由netif_receive_skb分析報(bào)文類型,skb_buff結(jié)構(gòu)經(jīng)過(guò)網(wǎng)絡(luò)層報(bào)文分類和分別處理(解析、分片重組等)進(jìn)入傳輸層,由傳輸層進(jìn)一步處理,最后用戶空間應(yīng)用程序通過(guò)socket API調(diào)用獲取報(bào)文內(nèi)容,亦或是由自定義的內(nèi)核模塊獲取報(bào)文進(jìn)行處理轉(zhuǎn)發(fā)或統(tǒng)計(jì)。自定義內(nèi)核模塊可在協(xié)議棧多個(gè)點(diǎn)通過(guò)不同方式獲取報(bào)文,如netfilter方式。

屏幕快照 2016-11-28 上午10.06.49.png

DPDK支持Run to Completion和Pipeline兩種報(bào)文處理模式,用戶可以依據(jù)需求靈活選擇,或者混合使用。Run to Completion是一種水平調(diào)度方式,利用網(wǎng)卡的多隊(duì)列,將報(bào)文分發(fā)給多個(gè)CPU核處理,每個(gè)核均獨(dú)立處理到達(dá)該隊(duì)列的報(bào)文,資源分配相對(duì)固定,減少了報(bào)文在核間的傳遞開(kāi)銷,可以隨著核的數(shù)目靈活擴(kuò)展處理能力; Pipeline模式則通過(guò)共享環(huán)在核間傳遞數(shù)據(jù)報(bào)文或消息,將系統(tǒng)處理任務(wù)分解到不同的CPU核上處理, 通過(guò)任務(wù)分發(fā)來(lái)減少處理等待時(shí)延。

3、原始套接字SOCK_RAW

我們常用的網(wǎng)絡(luò)編程都是在應(yīng)用層的報(bào)文首發(fā)操作,大多數(shù)接觸到的流式套接字SOCK_STREAM和數(shù)據(jù)包套接字SOCK_DGRAM。而哲學(xué)數(shù)據(jù)包都是由系統(tǒng)提供的協(xié)議棧實(shí)現(xiàn),用戶只需要填充應(yīng)用層報(bào)文即可,由系統(tǒng)完成底層報(bào)文頭的填充并發(fā)送。

然而在某些情況下需要執(zhí)行更底層的操作,比如修改報(bào)文頭,避開(kāi)系統(tǒng)協(xié)議棧等,這個(gè)時(shí)候就需要使用其他方法來(lái)實(shí)現(xiàn)。

SOCK_RAW實(shí)現(xiàn)于系統(tǒng)核心,普通的套接字無(wú)法處理ICMP、IGMP等網(wǎng)絡(luò)報(bào)文,而SOCK_RAW可以,其次,SOCK_RAW也可以處理特殊的IPv4報(bào)文??傮w來(lái)說(shuō),SOCK_RAW可以處理普通的網(wǎng)絡(luò)報(bào)文之外,還可以處理一些特殊協(xié)議報(bào)文以及操作IP層及其以上的數(shù)據(jù)。

原始套接字可以用來(lái)自行組裝IP數(shù)據(jù)包,然后將數(shù)據(jù)包發(fā)送到其他終端。必須在管理員權(quán)限下才能使用原始套接字,這么做可以防止普通用戶往網(wǎng)絡(luò)寫出他們自行構(gòu)造的IP數(shù)據(jù)報(bào)。

3.1、原始套接字的輸入

  • 接收到的UDP或者TCP分組絕不傳遞到任何原始套接字,如果一個(gè)進(jìn)程想要讀取含有UDP分組或TCP分組的IP數(shù)據(jù)報(bào),它就必須在數(shù)據(jù)鏈路層讀取這些分組
  • 如果某個(gè)數(shù)據(jù)報(bào)以片段形式到達(dá),那么在它的所有片段均到達(dá)且重組出改數(shù)據(jù)報(bào)之前,不傳遞任何分組到原始套接字。

4、NAPI

為了處理驅(qū)動(dòng)程序使用NAPI接口的設(shè)備,有四個(gè)新字段添加到net_device數(shù)據(jù)結(jié)構(gòu)中以供軟中斷使用。

  • poll
    這個(gè)虛函數(shù)可用于把緩沖區(qū)從設(shè)備的輸入隊(duì)列中退出,此隊(duì)列時(shí)使用NAPI設(shè)備的私有隊(duì)列。而softnet_data->input_pkt_queue供其他設(shè)備使用。

  • poll_list
    這是設(shè)備列表,列表中的設(shè)備就是在入口隊(duì)列中有等待被處理的新幀的設(shè)備。這些設(shè)備就是所謂的處于輪詢狀態(tài)

  • quota
    配額是一個(gè)整數(shù),代表的是poll虛函數(shù)一次可以從隊(duì)列推出緩沖區(qū)的最大數(shù)目。配額越低,表示潛在的延時(shí)越低,因此讓其他設(shè)備餓死的風(fēng)險(xiǎn)就越低。另一方面,低配額會(huì)增加設(shè)備間的切換量,因此整體的耗費(fèi)會(huì)增加。

  • weight
    quota值的增加以weight為單位,用于在不同設(shè)備之間施加某種公平性。

5、

5.1 netif_rx

當(dāng)新的輸入幀正等待處理時(shí),設(shè)備驅(qū)動(dòng)程序通常是調(diào)用定義在net/core/dev.c中的netif_rx函數(shù)。此函數(shù)的工作是為短暫執(zhí)行的軟IRQ(把幀退出隊(duì)列然后予以處理)調(diào)度

netif_rx通常是在中斷環(huán)境下被驅(qū)動(dòng)程序調(diào)用,netif_rx在其啟動(dòng)時(shí)會(huì)關(guān)閉本地CPU的中斷事件,然后當(dāng)其完成工作時(shí)回再予以重新開(kāi)啟

5.2 softnet_data

每個(gè)CPU都有其隊(duì)列,用來(lái)接收進(jìn)來(lái)的幀,因?yàn)槊總€(gè)CPU都有其數(shù)據(jù)結(jié)構(gòu)處理入口和出口流量,因此,不同CPU之間沒(méi)有必要使用上鎖機(jī)制。此隊(duì)列的數(shù)據(jù)結(jié)構(gòu)softnet_data就定義在include/linux/netdevice.h

struct softnet_data
{
    int                  throttle;
    int                  cng_level;
    struct sk_buff_head  input_pkt_queue;
    struct list_head     poll_list;
    struct net_device    *output_queue;
    struct sk_buff       *completion_queue;
    struct net_device    backlog_dev;
}

此結(jié)構(gòu)的字段可用于接收和傳輸,換而言之,NET_RX_SOFTIRQNET_TX_SOFTIRQ軟IRQ都引用此數(shù)據(jù)結(jié)構(gòu)。入口幀會(huì)排入input_pkt_queue ,而出口隊(duì)列會(huì)放入由流量控制(QoS層)所處理的特殊隊(duì)列(而不是由軟IRQ和softnet_data結(jié)構(gòu)所處理)

  • throttle
    布爾變量,超負(fù)載為真,否則為假。其值依賴于input_pkt_queue中幀的數(shù)目,當(dāng)其置位時(shí),此CPU接收到的數(shù)據(jù)幀全部丟棄
  • avg_blog
    代表input_pkt_queue隊(duì)列長(zhǎng)度加權(quán)后的平均值,可用于計(jì)算cng_level
  • cng_level
    代表?yè)砣燃?jí)

這三個(gè)參數(shù)有擁塞管理算法使用,默認(rèn)每接收一個(gè)幀這三個(gè)字段都會(huì)被更新。與CPU相關(guān),因此可用于非NAPI設(shè)備,共享每個(gè)CPU所用的隊(duì)列input_pkt_queue

  • input_pkt_queue
    這個(gè)隊(duì)列(在net_dev_init中初始化)用來(lái)保存進(jìn)來(lái)的幀(被驅(qū)動(dòng)程序處理前)

  • backlog_dev
    是一個(gè)完整的嵌入式數(shù)據(jù)結(jié)構(gòu),不只是一個(gè)指針,類型為net_device,代表著一個(gè)設(shè)備已在相關(guān)聯(lián)的CPU上為net_rx_action調(diào)度以準(zhǔn)備執(zhí)行。這個(gè)字段是由非NAPI驅(qū)動(dòng)程序使用,稱為積壓設(shè)備。

  • poll_list
    一個(gè)雙向列表,其中的設(shè)備都帶有輸入幀等著被處理

  • output_queue / completion_queue
    output_queue是設(shè)備列表,其中的設(shè)備有數(shù)據(jù)要傳輸。completion_queue是緩沖區(qū)列表,其中的緩沖區(qū)已經(jīng)成功傳輸,因此可以釋放掉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容