免费高清特黄a大片,九一h片在线免费看,a免费国产一级特黄aa大,国产精品国产主播在线观看,成人精品一区久久久久,一级特黄aa大片,俄罗斯无遮挡一级毛片

分享

TCP/IP協(xié)議棧在Linux內(nèi)核中的運(yùn)行時(shí)序分析

 印度阿三17 2021-01-28

一、調(diào)研要求

  • 在深入理解Linux內(nèi)核任務(wù)調(diào)度(中斷處理、softirg、tasklet、wq、內(nèi)核線程等)機(jī)制的基礎(chǔ)上,分析梳理send和recv過程中TCP/IP協(xié)議棧相關(guān)的運(yùn)行任務(wù)實(shí)體及相互協(xié)作的時(shí)序分析。

  • 編譯、部署、運(yùn)行、測(cè)評(píng)、原理、源代碼分析、跟蹤調(diào)試等

  • 應(yīng)該包括時(shí)序圖

二、?Linux內(nèi)核與網(wǎng)絡(luò)體系結(jié)構(gòu)

在我們了解整個(gè)linux系統(tǒng)的網(wǎng)絡(luò)體系結(jié)構(gòu)之前,我們需要對(duì)整個(gè)網(wǎng)絡(luò)體系調(diào)用,初始化和交互的位置,同時(shí)也是Linux操作系統(tǒng)中最為關(guān)鍵的一部分代碼-------內(nèi)核,有一個(gè)初步的認(rèn)知。

1、Linux內(nèi)核的結(jié)構(gòu)

首先,從功能上,我們將linux內(nèi)核劃分為五個(gè)不同的部分,分別是

(1)進(jìn)程管理:主要負(fù)載CPU的訪問控制,對(duì)CPU進(jìn)行調(diào)度管理;

(2)內(nèi)存管理:主要提供對(duì)內(nèi)存資源的訪問控制;

(3)文件系統(tǒng):將硬盤的扇區(qū)組織成文件系統(tǒng),實(shí)現(xiàn)文件讀寫等操作;

(4)設(shè)備管理:用于控制所有的外部設(shè)備及控制器;

(5)網(wǎng)洛:主要負(fù)責(zé)管理各種網(wǎng)絡(luò)設(shè)備,并實(shí)現(xiàn)各種網(wǎng)絡(luò)協(xié)議棧,最終實(shí)現(xiàn)通過網(wǎng)絡(luò)連接其它系統(tǒng)的功能;

每個(gè)部分分別處理一項(xiàng)明確的功能,又向其它各個(gè)部分提供自己所完成的功能,相互協(xié)調(diào),共同完成操作系統(tǒng)的任務(wù)。

Linux內(nèi)核架構(gòu)如下圖所示:

?

?

?圖1 Linux內(nèi)核架構(gòu)圖

2、Linux網(wǎng)絡(luò)子系統(tǒng)

內(nèi)核的基本架構(gòu)我們已經(jīng)了解清楚了,接下來(lái)我們重點(diǎn)關(guān)注到內(nèi)核中的網(wǎng)絡(luò)模塊,觀察在linux內(nèi)核中,我們是如何實(shí)現(xiàn)及運(yùn)用TCP/IP協(xié)議,并完成網(wǎng)絡(luò)的初始化及各個(gè)模塊調(diào)用調(diào)度。我們將內(nèi)核中的網(wǎng)絡(luò)部分抽出,通過對(duì)比TCP/IP分層協(xié)議,與Linux網(wǎng)絡(luò)實(shí)現(xiàn)體系相對(duì)比,深入的了解學(xué)習(xí)linux內(nèi)核是怎樣具體的實(shí)現(xiàn)TCP/IP協(xié)議棧的。

Linux網(wǎng)絡(luò)體系與TCP/IP協(xié)議棧如下圖所示。? ? ? ?

? ? ?? ? ??

?

?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖2 linux網(wǎng)絡(luò)體系? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖3 TCP/IP協(xié)議棧和OSI參考模型對(duì)應(yīng)關(guān)系

可以看到,在圖2中,linux為了抽象與實(shí)現(xiàn)相分離,將內(nèi)核中的網(wǎng)絡(luò)部分劃分為五層:

  • 系統(tǒng)調(diào)用接口:系統(tǒng)調(diào)用接口是用戶空間的應(yīng)用程序正常訪問內(nèi)核的唯一途徑,系統(tǒng)調(diào)用一般以sys開頭。
  • 協(xié)議無(wú)關(guān)接口:協(xié)議無(wú)關(guān)接口是由socket來(lái)實(shí)現(xiàn)的,它提供一組通用函數(shù)來(lái)支持各種不同的協(xié)議。Linux中socket結(jié)構(gòu)是struct sock,這個(gè)結(jié)構(gòu)定義了socket所需要的所? 有狀態(tài)信息,包括socke所使用的協(xié)議以及可以在socket上執(zhí)行的操作。
  • 網(wǎng)絡(luò)協(xié)議:Linux支持多種協(xié)議,每一個(gè)協(xié)議都對(duì)應(yīng)net_family[]數(shù)組中的一項(xiàng),net_family[]的元素為一個(gè)結(jié)構(gòu)體指針,指向一個(gè)包含注冊(cè)協(xié)議信息的結(jié)構(gòu)體? ? ? ? ? ? ? ? ? ? ? net_proto_family;
  • 設(shè)備無(wú)關(guān)接口:設(shè)備無(wú)關(guān)接口net_device實(shí)現(xiàn)的,任何設(shè)備與上層通信都是通過net_device設(shè)備無(wú)關(guān)接口。它將設(shè)備與具有很多功能的不同硬件連接在一起,這一層提供一組通用函數(shù)供底層網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序使用,讓它們可以對(duì)高層協(xié)議棧進(jìn)行操作。
  • 設(shè)備驅(qū)動(dòng)程序:網(wǎng)絡(luò)體系結(jié)構(gòu)的最底部是負(fù)責(zé)管理物理網(wǎng)絡(luò)設(shè)備的設(shè)備驅(qū)動(dòng)程序?qū)印?/li>

Linux網(wǎng)絡(luò)子系統(tǒng)通過這五層結(jié)構(gòu)的相互交互,共同完成TCP/IP協(xié)議棧的運(yùn)行。

3、TCP/IP協(xié)議棧

3.1 網(wǎng)絡(luò)架構(gòu)

Linux網(wǎng)絡(luò)協(xié)議棧的架構(gòu)如下圖所示。該圖展示了如何實(shí)現(xiàn)Internet模型,在最上面的是用戶空間中實(shí)現(xiàn)的應(yīng)用層,而中間為內(nèi)核空間中實(shí)現(xiàn)的網(wǎng)絡(luò)子系統(tǒng),底部為物理設(shè)備,提供了對(duì)網(wǎng)絡(luò)的連接能力。在網(wǎng)絡(luò)協(xié)議棧內(nèi)部流動(dòng)的是套接口緩沖區(qū)(SKB),用于在協(xié)議棧的底層、上層以及應(yīng)用層之間傳遞報(bào)文數(shù)據(jù)。

網(wǎng)絡(luò)協(xié)議棧頂部是系統(tǒng)調(diào)用接口,為用戶空間中的應(yīng)用程序提供一種訪問內(nèi)核網(wǎng)絡(luò)子系統(tǒng)的接口。下面是一個(gè)協(xié)議無(wú)關(guān)層,它提供了一種通用方法來(lái)使用傳輸層協(xié)議。然后是傳輸層的具體協(xié)議,包括TCP、UDP。在傳輸層下面是網(wǎng)絡(luò)層,之后是鄰居子系統(tǒng),再下面是網(wǎng)絡(luò)設(shè)備接口,提供了與各個(gè)設(shè)備驅(qū)動(dòng)通信的通用接口。最底層是設(shè)備驅(qū)動(dòng)程序。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

?

?

?圖4?Linux網(wǎng)絡(luò)協(xié)議棧的架構(gòu)

3.2 協(xié)議無(wú)關(guān)接口

通過網(wǎng)絡(luò)協(xié)議棧通信需要對(duì)套接口進(jìn)行操作,套接口是一個(gè)與協(xié)議無(wú)關(guān)的接口,它提供了一組接口來(lái)支持各種協(xié)議,套接口層不但可以支持典型的TCP和UDP協(xié)議,還可以支持RAW套接口、RAW以太網(wǎng)以及其他傳輸協(xié)議。

Linux中使用socket結(jié)構(gòu)描述套接口,代表一條通信鏈路的一端,用來(lái)存儲(chǔ)與該鏈路有關(guān)的所有信息,這些信息中包括:

  • 所使用的協(xié)議
  • 協(xié)議的狀態(tài)信息(包括源地址和目標(biāo)地址)
  • 到達(dá)的連接隊(duì)列
  • 數(shù)據(jù)緩存和可選標(biāo)志等等

其示意圖如下所示:

圖5?使用socket結(jié)構(gòu)描述套接口

其中最關(guān)鍵的成員是sk和ops,sk指向與該套接口相關(guān)的傳輸控制塊,ops指向特定的傳輸協(xié)議的操作集。

下圖詳細(xì)展示了socket結(jié)構(gòu)體中的sk和ops字段,以TCP為例。

?

圖6 TCP的socket結(jié)構(gòu)體中的sk和ops字段

?

?

sk字段指向與該套接口相關(guān)的傳輸控制塊,傳輸層使用傳輸控制塊來(lái)存放套接口所需的信息,在上圖中即為TCP傳輸控制塊,即tcp_sock結(jié)構(gòu)。

ops字段指向特定傳輸協(xié)議的操作集接口,proto_pos結(jié)構(gòu)中定義的接口函數(shù)是從套接口系統(tǒng)調(diào)用到傳輸層調(diào)用的入口,因此其成員與socket系統(tǒng)調(diào)用基本上是一一對(duì)應(yīng)的。整個(gè)proto_ops結(jié)構(gòu)就是一張?zhí)捉涌谙到y(tǒng)調(diào)用的跳轉(zhuǎn)表,TCP、UDP、RAW套接口的傳輸層操作集分別為inet_stream_ops、inet_dgram_ops、inet_sockraw_ops。

3.3?套接口緩存

如下圖所示,網(wǎng)絡(luò)子系統(tǒng)中用來(lái)存儲(chǔ)數(shù)據(jù)的緩沖區(qū)叫做套接口緩存,簡(jiǎn)稱為SKB,該緩存區(qū)能夠處理可變長(zhǎng)數(shù)據(jù),即能夠很容易地在數(shù)據(jù)區(qū)頭尾部添加和移除數(shù)據(jù),且盡量避免數(shù)據(jù)的復(fù)制,通常每個(gè)報(bào)文使用一個(gè)SKB表示,各協(xié)議棧報(bào)文頭通過一組指針進(jìn)行定位,由于SKB是網(wǎng)絡(luò)子系統(tǒng)中數(shù)據(jù)管理的核心,因此有很多管理函數(shù)是對(duì)它進(jìn)行操作的。

SKB主要用于在網(wǎng)絡(luò)驅(qū)動(dòng)程序和應(yīng)用程序之間傳遞、復(fù)制數(shù)據(jù)包。當(dāng)應(yīng)用程序要發(fā)送一個(gè)數(shù)據(jù)包時(shí):

  • 數(shù)據(jù)通過系統(tǒng)調(diào)用提交到內(nèi)核中
  • 系統(tǒng)會(huì)分配一個(gè)SKB來(lái)存儲(chǔ)數(shù)據(jù)
  • 之后向下層傳遞
  • 再傳遞給網(wǎng)絡(luò)驅(qū)動(dòng)后才將其釋放

當(dāng)網(wǎng)絡(luò)設(shè)備接收到數(shù)據(jù)包也要分配一個(gè)SKB來(lái)對(duì)數(shù)據(jù)進(jìn)行存儲(chǔ),之后再向上傳遞,最終將數(shù)據(jù)復(fù)制到應(yīng)用程序后進(jìn)行釋放。

?

圖7 SKB原理

3.4 重要的數(shù)據(jù)結(jié)構(gòu)

3.4.1 sk_buf

sk_buf是Linux網(wǎng)絡(luò)協(xié)議棧最重要的數(shù)據(jù)結(jié)構(gòu)之一,該數(shù)據(jù)結(jié)構(gòu)貫穿于整個(gè)數(shù)據(jù)包處理的流程。由于協(xié)議采用分層結(jié)構(gòu),上層向下層傳遞數(shù)據(jù)時(shí)需要增加包頭,下層向上層數(shù)據(jù)時(shí)又需要去掉包頭。sk_buff中保存了L2,L3,L4層的頭指針,這樣在層傳遞時(shí)只需要對(duì)數(shù)據(jù)緩沖區(qū)改變頭部信息,并調(diào)整sk_buff中的指針,而不需要拷貝數(shù)據(jù),這樣大大減少了內(nèi)存拷貝的需要。

sk_buf的示意圖如下:

?

圖8 sk_buf示意圖

各字段含義如下:

  • head:指向分配給的線性數(shù)據(jù)內(nèi)存首地址。
  • data:指向保存數(shù)據(jù)內(nèi)容的首地址。
  • tail:指向數(shù)據(jù)的結(jié)尾。?
  • end:指向分配的內(nèi)存塊的結(jié)尾。
  • len:數(shù)據(jù)的長(zhǎng)度。
  • head room: 位于head至data之間的空間,用于存儲(chǔ):protocol header,例如:TCP header, IP header, Ethernet header等。
  • user data: 位于data至tail之間的空間,用于存儲(chǔ):應(yīng)用層數(shù)據(jù),一般系統(tǒng)調(diào)用時(shí)會(huì)使用到。?
  • tail room: 位于tail至end之間的空間,用于填充用戶數(shù)據(jù)未使用完的空間。
  • skb_shared_info: 位于end之后,用于存儲(chǔ)特殊數(shù)據(jù)結(jié)構(gòu)skb_shared_info,該結(jié)構(gòu)用于描述分片信息。?
  • sk_buf的常用操作函數(shù)如下:
  • alloc_skb:分配sk_buf。
  • skb_reserve:為sk_buff設(shè)置header空間。
  • skb_put:添加用戶層數(shù)據(jù)。
  • ?skb_push:向header空間添加協(xié)議頭。
  • skb_pull:復(fù)位data至數(shù)據(jù)區(qū)。
  • 操作sk_buf的簡(jiǎn)單示意圖如下:

操作sk_buf的簡(jiǎn)單示意圖如下:

?

?

?

?圖9?sk_buf操作前后示意圖

3.4.2?net_device

在網(wǎng)絡(luò)適配器硬件和軟件協(xié)議棧之間需要一個(gè)接口,共同完成操作系統(tǒng)內(nèi)核中協(xié)議棧數(shù)據(jù)處理與異步收發(fā)的功能。在Linux網(wǎng)絡(luò)體系結(jié)構(gòu)中,這個(gè)接口要滿足以下要求:

 ?。?)抽象出網(wǎng)絡(luò)適配器的硬件特性。

 ?。?)為協(xié)議棧提供統(tǒng)一的調(diào)用接口。

以上兩個(gè)要求在Linux內(nèi)核的網(wǎng)絡(luò)體系結(jié)構(gòu)中分別由兩個(gè)軟件(設(shè)備獨(dú)立接口文件dev.c和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序)和一個(gè)主要的數(shù)據(jù)結(jié)構(gòu)net_device實(shí)現(xiàn)。

設(shè)備獨(dú)立接口文件dev.c中實(shí)現(xiàn)了對(duì)上層協(xié)議的統(tǒng)一調(diào)用接口,dev.c文件中的函數(shù)實(shí)現(xiàn)了以下主要功能。

  • 協(xié)議調(diào)用與驅(qū)動(dòng)程序函數(shù)對(duì)應(yīng):dev.c文件中的函數(shù)查看數(shù)據(jù)包由哪個(gè)網(wǎng)絡(luò)設(shè)備(由sk_buff結(jié)構(gòu)中*dev數(shù)據(jù)域指明該數(shù)據(jù)包由哪個(gè)網(wǎng)絡(luò)設(shè)備net_device實(shí)例接收/發(fā)送)傳送,根據(jù)系統(tǒng)中注冊(cè)的設(shè)備實(shí)例,調(diào)用網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序函數(shù),實(shí)現(xiàn)硬件的收發(fā)。
  • 對(duì)net_device數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)域統(tǒng)一初始化:dev.c提供了一些常規(guī)函數(shù),來(lái)初始化net_device結(jié)構(gòu)中的這樣一些數(shù)據(jù)域:它們的值對(duì)所有類型的設(shè)備都一樣,驅(qū)動(dòng)程序可以調(diào)用這些函數(shù)來(lái)設(shè)置其設(shè)備實(shí)例的默認(rèn)值,也可以重寫由內(nèi)核初始化的值。

每一個(gè)網(wǎng)絡(luò)設(shè)備都必須有一個(gè)驅(qū)動(dòng)程序,并提供一個(gè)初始化函數(shù)供內(nèi)核啟動(dòng)時(shí)調(diào)用,或在裝載網(wǎng)絡(luò)驅(qū)動(dòng)程序模塊時(shí)調(diào)用。不管網(wǎng)絡(luò)設(shè)備內(nèi)部有什么不同,有一件事是所有網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序必須首先完成的任務(wù):初始化一個(gè)net_device數(shù)據(jù)結(jié)構(gòu)的實(shí)例作為網(wǎng)絡(luò)設(shè)備在內(nèi)核中的實(shí)體,并將net_device數(shù)據(jù)結(jié)構(gòu)實(shí)例的各數(shù)據(jù)域初始化為可工作的狀態(tài),然后將設(shè)備實(shí)例注冊(cè)到內(nèi)核中,為協(xié)議棧提供傳送服務(wù)。

net_device數(shù)據(jù)結(jié)構(gòu)從以下兩個(gè)方面描述了網(wǎng)絡(luò)設(shè)備的硬件特性在內(nèi)核中的表示。

  • 描述設(shè)備屬性

net_device數(shù)據(jù)結(jié)構(gòu)實(shí)例是網(wǎng)絡(luò)設(shè)備在內(nèi)核中的表示,它是每個(gè)網(wǎng)絡(luò)設(shè)備在內(nèi)核中的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),它包含的信息不僅僅是網(wǎng)絡(luò)設(shè)備的硬件屬性(中斷、端口地址、驅(qū)動(dòng)程序函數(shù)等),還包括網(wǎng)絡(luò)中與設(shè)備有關(guān)的上層協(xié)議棧的配置信息(如IP地址、子網(wǎng)掩碼等)。它跟蹤連接到?TCP/IP協(xié)議棧上的所有設(shè)備的狀態(tài)信息。

  • 實(shí)現(xiàn)設(shè)備驅(qū)動(dòng)程序接口

net_device數(shù)據(jù)結(jié)構(gòu)代表了上層的網(wǎng)絡(luò)協(xié)議和硬件之間的一個(gè)通用接口,使我們可以將網(wǎng)絡(luò)協(xié)議層的實(shí)現(xiàn)從具體的網(wǎng)絡(luò)硬件部件中抽象出來(lái),獨(dú)立于硬件設(shè)備。為了有效地實(shí)現(xiàn)這種抽象,net_device中使用了大量函數(shù)指針,這樣相對(duì)于上層的協(xié)議棧,它們?cè)谧鰯?shù)據(jù)收發(fā)操作時(shí)調(diào)用的函數(shù)的名字是相同的,但具體的函數(shù)實(shí)現(xiàn)細(xì)節(jié)可以根據(jù)不同的網(wǎng)絡(luò)適配器而不同,由設(shè)備驅(qū)動(dòng)程序提供,對(duì)網(wǎng)絡(luò)協(xié)議棧透明。

?

?

?圖10 網(wǎng)絡(luò)設(shè)備抽象數(shù)據(jù)結(jié)構(gòu)

3.4.3 socket

內(nèi)核中的進(jìn)程可以通過socket結(jié)構(gòu)體來(lái)訪問linux內(nèi)核中的網(wǎng)絡(luò)系統(tǒng)中的傳輸層、網(wǎng)絡(luò)層以及數(shù)據(jù)鏈路層,也可以說(shuō)socket是內(nèi)核中的進(jìn)程與內(nèi)核中的網(wǎng)絡(luò)系統(tǒng)的橋梁。

我們知道在TCP層中使用兩個(gè)協(xié)議:tcp協(xié)議和udp協(xié)議。而在將TCP層中的數(shù)據(jù)往下傳輸時(shí),要使用網(wǎng)絡(luò)層的協(xié)議,而網(wǎng)絡(luò)層的協(xié)議很多,不同的網(wǎng)絡(luò)使用不同的網(wǎng)絡(luò)層協(xié)議。我們常用的因特網(wǎng)中,網(wǎng)絡(luò)層使用的是IPV4和IPV6協(xié)議。所以在內(nèi)核中的進(jìn)程在使用struct socket提取內(nèi)核網(wǎng)絡(luò)系統(tǒng)中的數(shù)據(jù)時(shí),不光要指明struct socket的類型(用于說(shuō)明是提取TCP層中tcp協(xié)議負(fù)載的數(shù)據(jù),還是udp層負(fù)載的數(shù)據(jù)),還要指明網(wǎng)絡(luò)層的協(xié)議類型(網(wǎng)絡(luò)層的協(xié)議用于負(fù)載TCP層中的數(shù)據(jù))。

linux內(nèi)核中的網(wǎng)絡(luò)系統(tǒng)中的網(wǎng)絡(luò)層的協(xié)議,在linux中被稱為address family(地址簇,通常以AF_XXX表示)或protocol family(協(xié)議簇,通常以PF_XXX表示)。

?

三、網(wǎng)絡(luò)信息處理流程

1、硬中斷處理

  首先當(dāng)數(shù)據(jù)幀從網(wǎng)線到達(dá)網(wǎng)卡上的時(shí)候,第一站是網(wǎng)卡的接收隊(duì)列。網(wǎng)卡在分配給自己的RingBuffer中尋找可用的內(nèi)存位置,找到后DMA引擎會(huì)把數(shù)據(jù)DMA到網(wǎng)卡之前關(guān)聯(lián)的內(nèi)存里,這個(gè)時(shí)候CPU都是無(wú)感的。當(dāng)DMA操作完成以后,網(wǎng)卡會(huì)像CPU發(fā)起一個(gè)硬中斷,通知CPU有數(shù)據(jù)到達(dá)。

?

?

?圖11 網(wǎng)卡數(shù)據(jù)硬中斷處理過程

注意,當(dāng)RingBuffer滿的時(shí)候,新來(lái)的數(shù)據(jù)包將給丟棄。ifconfig查看網(wǎng)卡的時(shí)候,可以里面有個(gè)overruns,表示因?yàn)榄h(huán)形隊(duì)列滿被丟棄的包。如果發(fā)現(xiàn)有丟包,可能需要通過ethtool命令來(lái)加大環(huán)形隊(duì)列的長(zhǎng)度。

網(wǎng)卡的硬中斷注冊(cè)的處理函數(shù)是igb_msix_ring。

//file: drivers/net/ethernet/intel/igb/igb_main.c
static irqreturn_t igb_msix_ring(int irq, void *data)
{
    struct igb_q_vector *q_vector = data;

    /* Write the ITR value calculated from the previous interrupt. */
    igb_write_itr(q_vector);

    napi_schedule(&q_vector->napi);

    return IRQ_HANDLED;
}

igb_write_itr只是記錄一下硬件中斷頻率(據(jù)說(shuō)目的是在減少對(duì)CPU的中斷頻率時(shí)用到)。順著napi_schedule調(diào)用一路跟蹤下去,__napi_schedule=>____napi_schedule。

/* Called with irq disabled */
static inline void ____napi_schedule(struct softnet_data *sd,
                     struct napi_struct *napi)
{
    list_add_tail(&napi->poll_list, &sd->poll_list);
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

這里我們看到,list_add_tail修改了CPU變量softnet_data里的poll_list,將驅(qū)動(dòng)napi_struct傳過來(lái)的poll_list添加了進(jìn)來(lái)。 其中softnet_data中的poll_list是一個(gè)雙向列表,其中的設(shè)備都帶有輸入幀等著被處理。緊接著__raise_softirq_irqoff觸發(fā)了一個(gè)軟中斷NET_RX_SOFTIRQ, 這個(gè)所謂的觸發(fā)過程只是對(duì)一個(gè)變量進(jìn)行了一次或運(yùn)算而已。

void __raise_softirq_irqoff(unsigned int nr)
{
    trace_softirq_raise(nr);
    or_softirq_pending(1UL << nr);
}
//file: include/linux/irq_cpustat.h
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

Linux在硬中斷里只完成簡(jiǎn)單必要的工作,剩下的大部分的處理都是轉(zhuǎn)交給軟中斷的。通過上面代碼可以看到,硬中斷處理過程真的是非常短。只是記錄了一個(gè)寄存器,修改了一下下CPU的poll_list,然后發(fā)出個(gè)軟中斷。就這么簡(jiǎn)單,硬中斷工作就算是完成了。

2、ksoftirqd內(nèi)核線程處理軟中斷

?

圖12 ksoftirqd內(nèi)核線程

?

?ksoftirqd_should_run代碼如下:

static int ksoftirqd_should_run(unsigned int cpu)
{
    return local_softirq_pending();
}

#define local_softirq_pending()     __IRQ_STAT(smp_processor_id(), __softirq_pending)

這里看到和硬中斷中調(diào)用了同一個(gè)函數(shù)local_softirq_pending。使用方式不同的是硬中斷位置是為了寫入標(biāo)記,這里僅僅只是讀取。如果硬中斷中設(shè)置了NET_RX_SOFTIRQ,這里自然能讀取的到。接下來(lái)會(huì)真正進(jìn)入線程函數(shù)中run_ksoftirqd處理:

static void run_ksoftirqd(unsigned int cpu)
{
    local_irq_disable();
    if (local_softirq_pending()) {
        __do_softirq();
        rcu_note_context_switch(cpu);
        local_irq_enable();
        cond_resched();
        return;
    }
    local_irq_enable();
}

__do_softirq中,判斷根據(jù)當(dāng)前CPU的軟中斷類型,調(diào)用其注冊(cè)的action方法。

asmlinkage void __do_softirq(void)
{
    do {
        if (pending & 1) {
            unsigned int vec_nr = h - softirq_vec;
            int prev_count = preempt_count();

            ...
            trace_softirq_entry(vec_nr);
            h->action(h);
            trace_softirq_exit(vec_nr);
            ...
        }
        h  ;
        pending >>= 1;
    } while (pending);
}  

在網(wǎng)絡(luò)子系統(tǒng)初始化小節(jié), 我們看到我們?yōu)镹ET_RX_SOFTIRQ注冊(cè)了處理函數(shù)net_rx_action。所以net_rx_action函數(shù)就會(huì)被執(zhí)行到了。

這里需要注意一個(gè)細(xì)節(jié),硬中斷中設(shè)置軟中斷標(biāo)記,和ksoftirq的判斷是否有軟中斷到達(dá),都是基于smp_processor_id()的。這意味著只要硬中斷在哪個(gè)CPU上被響應(yīng),那么軟中斷也是在這個(gè)CPU上處理的。所以說(shuō),如果你發(fā)現(xiàn)你的Linux軟中斷CPU消耗都集中在一個(gè)核上的話,做法是要把調(diào)整硬中斷的CPU親和性,來(lái)將硬中斷打散到不通的CPU核上去。

我們?cè)賮?lái)把精力集中到這個(gè)核心函數(shù)net_rx_action上來(lái)。

static void net_rx_action(struct softirq_action *h)
{
    struct softnet_data *sd = &__get_cpu_var(softnet_data);
    unsigned long time_limit = jiffies   2;
    int budget = netdev_budget;
    void *have;

    local_irq_disable();

    while (!list_empty(&sd->poll_list)) {
        ......
        n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

        work = 0;
        if (test_bit(NAPI_STATE_SCHED, &n->state)) {
            work = n->poll(n, weight);
            trace_napi_poll(n);
        }

        budget -= work;
    }
}

函數(shù)開頭的time_limit和budget是用來(lái)控制net_rx_action函數(shù)主動(dòng)退出的,目的是保證網(wǎng)絡(luò)包的接收不霸占CPU不放。 等下次網(wǎng)卡再有硬中斷過來(lái)的時(shí)候再處理剩下的接收數(shù)據(jù)包。其中budget可以通過內(nèi)核參數(shù)調(diào)整。 這個(gè)函數(shù)中剩下的核心邏輯是獲取到當(dāng)前CPU變量softnet_data,對(duì)其poll_list進(jìn)行遍歷, 然后執(zhí)行到網(wǎng)卡驅(qū)動(dòng)注冊(cè)到的poll函數(shù)。對(duì)于igb網(wǎng)卡來(lái)說(shuō),就是igb驅(qū)動(dòng)力的igb_poll函數(shù)了。

/**
 *  igb_poll - NAPI Rx polling callback
 *  @napi: napi polling structure
 *  @budget: count of how many packets we should handle
 **/
static int igb_poll(struct napi_struct *napi, int budget)
{
    ...
    if (q_vector->tx.ring)
        clean_complete = igb_clean_tx_irq(q_vector);

    if (q_vector->rx.ring)
        clean_complete &= igb_clean_rx_irq(q_vector, budget);
    ...
}

  在讀取操作中,igb_poll的重點(diǎn)工作是對(duì)igb_clean_rx_irq的調(diào)用。

static bool igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
{
    ...

    do {

        /* retrieve a buffer from the ring */
        skb = igb_fetch_rx_buffer(rx_ring, rx_desc, skb);

        /* fetch next buffer in frame if non-eop */
        if (igb_is_non_eop(rx_ring, rx_desc))
            continue;
        }

        /* verify the packet layout is correct */
        if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {
            skb = NULL;
            continue;
        }

        /* populate checksum, timestamp, VLAN, and protocol */
        igb_process_skb_fields(rx_ring, rx_desc, skb);

        napi_gro_receive(&q_vector->napi, skb);
}

igb_fetch_rx_bufferigb_is_non_eop的作用就是把數(shù)據(jù)幀從RingBuffer上取下來(lái)。為什么需要兩個(gè)函數(shù)呢?因?yàn)橛锌赡軒级喽鄠€(gè)RingBuffer,所以是在一個(gè)循環(huán)中獲取的,直到幀尾部。獲取下來(lái)的一個(gè)數(shù)據(jù)幀用一個(gè)sk_buff來(lái)表示。收取完數(shù)據(jù)以后,對(duì)其進(jìn)行一些校驗(yàn),然后開始設(shè)置sbk變量的timestamp, VLAN id, protocol等字段。接下來(lái)進(jìn)入到napi_gro_receive中:

//file: net/core/dev.c
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
    skb_gro_reset_offset(skb);

    return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}

dev_gro_receive這個(gè)函數(shù)代表的是網(wǎng)卡GRO特性,可以簡(jiǎn)單理解成把相關(guān)的小包合并成一個(gè)大包就行,目的是減少傳送給網(wǎng)絡(luò)棧的包數(shù),這有助于減少 CPU 的使用量。我們暫且忽略,直接看napi_skb_finish, 這個(gè)函數(shù)主要就是調(diào)用了netif_receive_skb。

//file: net/core/dev.c
static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
{
    switch (ret) {
    case GRO_NORMAL:
        if (netif_receive_skb(skb))
            ret = GRO_DROP;
        break;
    ......
}

netif_receive_skb中,數(shù)據(jù)包將被送到協(xié)議棧中。聲明,以下的3.3, 3.4, 3.5也都屬于軟中斷的處理過程,只不過由于篇幅太長(zhǎng),單獨(dú)拿出來(lái)成小節(jié)。

3、網(wǎng)絡(luò)協(xié)議棧處理

netif_receive_skb函數(shù)會(huì)根據(jù)包的協(xié)議,假如是udp包,會(huì)將包依次送到ip_rcv(),udp_rcv()協(xié)議處理函數(shù)中進(jìn)行處理。

?

?

?圖13 網(wǎng)絡(luò)協(xié)議棧處理

//file: net/core/dev.c
int netif_receive_skb(struct sk_buff *skb)
{
    //RPS處理邏輯,先忽略
    ......

    return __netif_receive_skb(skb);
}

static int __netif_receive_skb(struct sk_buff *skb)
{
    ......   
    ret = __netif_receive_skb_core(skb, false);
}

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
    ......

    //pcap邏輯,這里會(huì)將數(shù)據(jù)送入抓包點(diǎn)。tcpdump就是從這個(gè)入口獲取包的
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }

    ......

    list_for_each_entry_rcu(ptype,
            &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
        if (ptype->type == type &&
            (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
             ptype->dev == orig_dev)) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
}

__netif_receive_skb_core中,我看著原來(lái)經(jīng)常使用的tcpdump的抓包點(diǎn),很是激動(dòng),看來(lái)讀一遍源代碼時(shí)間真的沒白浪費(fèi)。接著__netif_receive_skb_core取出protocol,它會(huì)從數(shù)據(jù)包中取出協(xié)議信息,然后遍歷注冊(cè)在這個(gè)協(xié)議上的回調(diào)函數(shù)列表。ptype_base?是一個(gè) hash table,在協(xié)議注冊(cè)小節(jié)我們提到過。ip_rcv 函數(shù)地址就是存在這個(gè) hash table中的。

//file: net/core/dev.c
static inline int deliver_skb(struct sk_buff *skb,
                  struct packet_type *pt_prev,
                  struct net_device *orig_dev)
{
    ......
    return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

pt_prev->func這一行就調(diào)用到了協(xié)議層注冊(cè)的處理函數(shù)了。對(duì)于ip包來(lái)講,就會(huì)進(jìn)入到ip_rcv(如果是arp包的話,會(huì)進(jìn)入到arp_rcv)。

4、IP協(xié)議層處理

我們?cè)賮?lái)大致看一下linux在ip協(xié)議層都做了什么,包又是怎么樣進(jìn)一步被送到udp或tcp協(xié)議處理函數(shù)中的。

//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
    ......

    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
               ip_rcv_finish);
}

這里NF_HOOK是一個(gè)鉤子函數(shù),當(dāng)執(zhí)行完注冊(cè)的鉤子后就會(huì)執(zhí)行到最后一個(gè)參數(shù)指向的函數(shù)ip_rcv_finish。

static int ip_rcv_finish(struct sk_buff *skb)
{
    ......

    if (!skb_dst(skb)) {
        int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
                           iph->tos, skb->dev);
        ...
    }

    ......

    return dst_input(skb);
}

跟蹤ip_route_input_noref?后看到它又調(diào)用了?ip_route_input_mc。 在ip_route_input_mc中,函數(shù)ip_local_deliver被賦值給了dst.input, 如下:

//file: net/ipv4/route.c
static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,
                u8 tos, struct net_device *dev, int our)
{
    if (our) {
        rth->dst.input= ip_local_deliver;
        rth->rt_flags |= RTCF_LOCAL;
    }
}

所以回到ip_rcv_finish中的return dst_input(skb)。

/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{
    return skb_dst(skb)->input(skb);
}

skb_dst(skb)->input調(diào)用的input方法就是路由子系統(tǒng)賦的ip_local_deliver。

//file: net/ipv4/ip_input.c
int ip_local_deliver(struct sk_buff *skb)
{
    /*
     *  Reassemble IP fragments.
     */

    if (ip_is_fragment(ip_hdr(skb))) {
        if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
            return 0;
    }

    return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
               ip_local_deliver_finish);
}
static int ip_local_deliver_finish(struct sk_buff *skb)
{
    ......

    int protocol = ip_hdr(skb)->protocol;
    const struct net_protocol *ipprot;

    ipprot = rcu_dereference(inet_protos[protocol]);
    if (ipprot != NULL) {
        ret = ipprot->handler(skb);
    }
}

如協(xié)議注冊(cè)小節(jié)看到inet_protos中保存著tcp_rcv()和udp_rcv()的函數(shù)地址。這里將會(huì)根據(jù)包中的協(xié)議類型選擇進(jìn)行分發(fā),在這里skb包將會(huì)進(jìn)一步被派送到更上層的協(xié)議中,udp和tcp。

5、UDP協(xié)議層處理

udp協(xié)議的處理函數(shù)是udp_rcv。

//file: net/ipv4/udp.c
int udp_rcv(struct sk_buff *skb)
{
    return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}


int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
           int proto)
{
    sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);

    if (sk != NULL) {
        int ret = udp_queue_rcv_skb(sk, skb
    }

    icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
}

__udp4_lib_lookup_skb是根據(jù)skb來(lái)尋找對(duì)應(yīng)的socket,當(dāng)找到以后將數(shù)據(jù)包放到socket的緩存隊(duì)列里。如果沒有找到,則發(fā)送一個(gè)目標(biāo)不可達(dá)的icmp包。

//file: net/ipv4/udp.c
int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{   
    ......

    if (sk_rcvqueues_full(sk, skb, sk->sk_rcvbuf))
        goto drop;

        rc = 0;

    ipv4_pktinfo_prepare(skb);
    bh_lock_sock(sk);
    if (!sock_owned_by_user(sk))
        rc = __udp_queue_rcv_skb(sk, skb);
    else if (sk_add_backlog(sk, skb, sk->sk_rcvbuf)) {
        bh_unlock_sock(sk);
        goto drop;
    }
    bh_unlock_sock(sk);

    return rc;
}

sock_owned_by_user判斷的是用戶是不是正在這個(gè)socker上進(jìn)行系統(tǒng)調(diào)用(socket被占用),如果沒有,那就可以直接放到socket的接收隊(duì)列中。如果有,那就通過sk_add_backlog把數(shù)據(jù)包添加到backlog隊(duì)列。 當(dāng)用戶釋放的socket的時(shí)候,內(nèi)核會(huì)檢查backlog隊(duì)列,如果有數(shù)據(jù)再移動(dòng)到接收隊(duì)列中。

sk_rcvqueues_full接收隊(duì)列如果滿了的話,將直接把包丟棄。接收隊(duì)列大小受內(nèi)核參數(shù)net.core.rmem_max和net.core.rmem_default影響。

四、send分析

1、傳輸層分析

send的定義如下所示:

ssize_t send(int sockfd, const void *buf, size_t len, int flags)

當(dāng)在調(diào)用send函數(shù)的時(shí)候,內(nèi)核封裝send()sendto(),然后發(fā)起系統(tǒng)調(diào)用。其實(shí)也很好理解,send()就是sendto()的一種特殊情況,而sendto()在內(nèi)核的系統(tǒng)調(diào)用服務(wù)程序?yàn)?code>sys_sendto,sys_sendto的代碼如下所示:

int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
         struct sockaddr __user *addr,  int addr_len)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err;
    struct msghdr msg; //用來(lái)表示要發(fā)送的數(shù)據(jù)的一些屬性
    struct iovec iov;
    int fput_needed;
    err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);
    if (unlikely(err))
        return err;
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    msg.msg_name = NULL;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_namelen = 0;
    if (addr) {
        err = move_addr_to_kernel(addr, addr_len, &address);
        if (err < 0)
            goto out_put;
        msg.msg_name = (struct sockaddr *)&address;
        msg.msg_namelen = addr_len;
    }
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    msg.msg_flags = flags;
    err = sock_sendmsg(sock, &msg); //實(shí)際的發(fā)送函數(shù)

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

__sys_sendto函數(shù)其實(shí)做了3件事:

  1. 通過fd獲取了對(duì)應(yīng)的struct socket
  2. 創(chuàng)建了用來(lái)描述要發(fā)送的數(shù)據(jù)的結(jié)構(gòu)體struct msghdr
  3. 調(diào)用了sock_sendmsg來(lái)執(zhí)行實(shí)際的發(fā)送

繼續(xù)追蹤sock_sendmsg,發(fā)現(xiàn)其最終調(diào)用的是sock->ops->sendmsg(sock, msg, msg_data_left(msg)),即socet在初始化時(shí)賦值給結(jié)構(gòu)體struct proto tcp_prot的函數(shù)tcp_sendmsg,如下所示:

struct proto tcp_prot = {
    .name            = "TCP",
    .owner            = THIS_MODULE,
    .close            = tcp_close,
    .pre_connect        = tcp_v4_pre_connect,
    .connect        = tcp_v4_connect,
    .disconnect        = tcp_disconnect,
    .accept            = inet_csk_accept,
    .ioctl            = tcp_ioctl,
    .init            = tcp_v4_init_sock,
    .destroy        = tcp_v4_destroy_sock,
    .shutdown        = tcp_shutdown,
    .setsockopt        = tcp_setsockopt,
    .getsockopt        = tcp_getsockopt,
    .keepalive        = tcp_set_keepalive,
    .recvmsg        = tcp_recvmsg,
    .sendmsg        = tcp_sendmsg,
  ...

tcp_send函數(shù)實(shí)際調(diào)用的是tcp_sendmsg_locked函數(shù),該函數(shù)的定義如下所示:

int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
    struct tcp_sock *tp = tcp_sk(sk);/*進(jìn)行了強(qiáng)制類型轉(zhuǎn)換*/
    struct sk_buff *skb;
    flags = msg->msg_flags;
    ......
        if (copied)
            tcp_push(sk, flags & ~MSG_MORE, mss_now,
                 TCP_NAGLE_PUSH, size_goal);
}

tcp_sendmsg_locked中,完成的是將所有的數(shù)據(jù)組織成發(fā)送隊(duì)列,這個(gè)發(fā)送隊(duì)列是struct sock結(jié)構(gòu)中的一個(gè)域sk_write_queue,這個(gè)隊(duì)列的每一個(gè)元素是一個(gè)skb,里面存放的就是待發(fā)送的數(shù)據(jù)。在該函數(shù)中通過調(diào)用tcp_push()函數(shù)將數(shù)據(jù)加入到發(fā)送隊(duì)列中。

sock結(jié)構(gòu)體的部分代碼如下所示:

struct sock{
    ...
    struct sk_buff_head    sk_write_queue;/*指向skb隊(duì)列的第一個(gè)元素*/
    ...
    struct sk_buff    *sk_send_head;/*指向隊(duì)列第一個(gè)還沒有發(fā)送的元素*/
}

tcp_push的代碼如下所示:

static void tcp_push(struct sock *sk, int flags, int mss_now,
             int nonagle, int size_goal)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;

    skb = tcp_write_queue_tail(sk);
    if (!skb)
        return;
    if (!(flags & MSG_MORE) || forced_push(tp))
        tcp_mark_push(tp, skb);

    tcp_mark_urg(tp, flags);

    if (tcp_should_autocork(sk, skb, size_goal)) {

        /* avoid atomic op if TSQ_THROTTLED bit is already set */
        if (!test_bit(TSQ_THROTTLED, &sk->sk_tsq_flags)) {
            NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTOCORKING);
            set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags);
        }
        /* It is possible TX completion already happened
         * before we set TSQ_THROTTLED.
         */
        if (refcount_read(&sk->sk_wmem_alloc) > skb->truesize)
            return;
    }

    if (flags & MSG_MORE)
        nonagle = TCP_NAGLE_CORK;

    __tcp_push_pending_frames(sk, mss_now, nonagle); //最終通過調(diào)用該函數(shù)發(fā)送數(shù)據(jù)
}

在之后tcp_push調(diào)用了__tcp_push_pending_frames(sk, mss_now, nonagle);來(lái)發(fā)送數(shù)據(jù)

__tcp_push_pending_frames的代碼如下所示:

void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
                   int nonagle)
{

    if (tcp_write_xmit(sk, cur_mss, nonagle, 0,
               sk_gfp_mask(sk, GFP_ATOMIC))) //調(diào)用該函數(shù)發(fā)送數(shù)據(jù)
        tcp_check_probe_timer(sk);
}

__tcp_push_pending_frames又調(diào)用了tcp_write_xmit來(lái)發(fā)送數(shù)據(jù),代碼如下所示:

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
               int push_one, gfp_t gfp)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;
    unsigned int tso_segs, sent_pkts;
    int cwnd_quota;
    int result;
    bool is_cwnd_limited = false, is_rwnd_limited = false;
    u32 max_segs;
    /*統(tǒng)計(jì)已發(fā)送的報(bào)文總數(shù)*/
    sent_pkts = 0;
    ......

    /*若發(fā)送隊(duì)列未滿,則準(zhǔn)備發(fā)送報(bào)文*/
    while ((skb = tcp_send_head(sk))) {
        unsigned int limit;

        if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) {
            /* "skb_mstamp_ns" is used as a start point for the retransmit timer */
            skb->skb_mstamp_ns = tp->tcp_wstamp_ns = tp->tcp_clock_cache;
            list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);
            tcp_init_tso_segs(skb, mss_now);
            goto repair; /* Skip network transmission */
        }

        if (tcp_pacing_check(sk))
            break;

        tso_segs = tcp_init_tso_segs(skb, mss_now);
        BUG_ON(!tso_segs);
        /*檢查發(fā)送窗口的大小*/
        cwnd_quota = tcp_cwnd_test(tp, skb);
        if (!cwnd_quota) {
            if (push_one == 2)
                /* Force out a loss probe pkt. */
                cwnd_quota = 1;
            else
                break;
        }

        if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) {
            is_rwnd_limited = true;
            break;
        ......
        limit = mss_now;
        if (tso_segs > 1 && !tcp_urg_mode(tp))
            limit = tcp_mss_split_point(sk, skb, mss_now,
                            min_t(unsigned int,
                              cwnd_quota,
                              max_segs),
                            nonagle);

        if (skb->len > limit &&
            unlikely(tso_fragment(sk, TCP_FRAG_IN_WRITE_QUEUE,
                      skb, limit, mss_now, gfp)))
            break;

        if (tcp_small_queue_check(sk, skb, 0))
            break;

        if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp))) //調(diào)用該函數(shù)發(fā)送數(shù)據(jù)
            break;
    ......

tcp_write_xmit位于tcpoutput.c中,它實(shí)現(xiàn)了tcp的擁塞控制,然后調(diào)用了tcp_transmit_skb(sk, skb, 1, gfp)傳輸數(shù)據(jù),實(shí)際上調(diào)用的是__tcp_transmit_skb。

__tcp_transmit_skb的部分代碼如下所示:

static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
                  int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
    
    skb_push(skb, tcp_header_size);
    skb_reset_transport_header(skb);
    ......
    /* 構(gòu)建TCP頭部和校驗(yàn)和 */
    th = (struct tcphdr *)skb->data;
    th->source        = inet->inet_sport;
    th->dest        = inet->inet_dport;
    th->seq            = htonl(tcb->seq);
    th->ack_seq        = htonl(rcv_nxt);

    tcp_options_write((__be32 *)(th   1), tp, &opts);
    skb_shinfo(skb)->gso_type = sk->sk_gso_type;
    if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
        th->window      = htons(tcp_select_window(sk));
        tcp_ecn_send(sk, skb, th, tcp_header_size);
    } else {
        /* RFC1323: The window in SYN & SYN/ACK segments
         * is never scaled.
         */
        th->window    = htons(min(tp->rcv_wnd, 65535U));
    }
    ......
    icsk->icsk_af_ops->send_check(sk, skb);

    if (likely(tcb->tcp_flags & TCPHDR_ACK))
        tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);

    if (skb->len != tcp_header_size) {
        tcp_event_data_sent(tp, sk);
        tp->data_segs_out  = tcp_skb_pcount(skb);
        tp->bytes_sent  = skb->len - tcp_header_size;
    }

    if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
        TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
                  tcp_skb_pcount(skb));

    tp->segs_out  = tcp_skb_pcount(skb);
    /* OK, its time to fill skb_shinfo(skb)->gso_{segs|size} */
    skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
    skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);

    /* Leave earliest departure time in skb->tstamp (skb->skb_mstamp_ns) */

    /* Cleanup our debris for IP stacks */
    memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
                   sizeof(struct inet6_skb_parm)));

    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl); //調(diào)用網(wǎng)絡(luò)層的發(fā)送接口
    ......
}

__tcp_transmit_skb是位于傳輸層發(fā)送tcp數(shù)據(jù)的最后一步,這里首先對(duì)TCP數(shù)據(jù)段的頭部進(jìn)行了處理,然后調(diào)用了網(wǎng)絡(luò)層提供的發(fā)送接口:

icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);實(shí)現(xiàn)了數(shù)據(jù)的發(fā)送,自此,數(shù)據(jù)離開了傳輸層,傳輸層的任務(wù)也就結(jié)束了。

傳輸層時(shí)序圖如下圖所示:

?

圖14 傳輸層時(shí)序圖

?

?GDB調(diào)試如下所示。

?

?

圖15 GDB調(diào)試

2、網(wǎng)絡(luò)層分析

將TCP傳輸過來(lái)的數(shù)據(jù)包打包成IP數(shù)據(jù)報(bào),將數(shù)據(jù)打包成IP數(shù)據(jù)包之后,通過調(diào)用ip_local_out函數(shù),在該函數(shù)內(nèi)部調(diào)用了__ip_local_out,該函數(shù)返回了一個(gè)nf_hook函數(shù),在該函數(shù)內(nèi)部調(diào)用了dst_output

int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
    struct inet_sock *inet = inet_sk(sk);
    struct net *net = sock_net(sk);
    struct ip_options_rcu *inet_opt;
    struct flowi4 *fl4;
    struct rtable *rt;
    struct iphdr *iph;
    int res;

    /* Skip all of this if the packet is already routed,
     * f.e. by something like SCTP.
     */
    rcu_read_lock();
    /*
     * 如果待輸出的數(shù)據(jù)包已準(zhǔn)備好路由緩存,
     * 則無(wú)需再查找路由,直接跳轉(zhuǎn)到packet_routed
     * 處作處理。
     */
    inet_opt = rcu_dereference(inet->inet_opt);
    fl4 = &fl->u.ip4;
    rt = skb_rtable(skb);
    if (rt)
        goto packet_routed;

    /* Make sure we can route this packet. */
    /*
     * 如果輸出該數(shù)據(jù)包的傳輸控制塊中
     * 緩存了輸出路由緩存項(xiàng),則需檢測(cè)
     * 該路由緩存項(xiàng)是否過期。
     * 如果過期,重新通過輸出網(wǎng)絡(luò)設(shè)備、
     * 目的地址、源地址等信息查找輸出
     * 路由緩存項(xiàng)。如果查找到對(duì)應(yīng)的路
     * 由緩存項(xiàng),則將其緩存到傳輸控制
     * 塊中,否則丟棄該數(shù)據(jù)包。
     * 如果未過期,則直接使用緩存在
     * 傳輸控制塊中的路由緩存項(xiàng)。
     */
    rt = (struct rtable *)__sk_dst_check(sk, 0);
    if (!rt) { /* 緩存過期 */
        __be32 daddr;

        /* Use correct destination address if we have options. */
        daddr = inet->inet_daddr; /* 目的地址 */
        if (inet_opt && inet_opt->opt.srr)
            daddr = inet_opt->opt.faddr; /* 嚴(yán)格路由選項(xiàng) */

        /* If this fails, retransmit mechanism of transport layer will
         * keep trying until route appears or the connection times
         * itself out.
         */ /* 查找路由緩存 */
        rt = ip_route_output_ports(net, fl4, sk,
                       daddr, inet->inet_saddr,
                       inet->inet_dport,
                       inet->inet_sport,
                       sk->sk_protocol,
                       RT_CONN_FLAGS(sk),
                       sk->sk_bound_dev_if);
        if (IS_ERR(rt))
            goto no_route;
        sk_setup_caps(sk, &rt->dst);  /* 設(shè)置控制塊的路由緩存 */
    }
    skb_dst_set_noref(skb, &rt->dst);/* 將路由設(shè)置到skb中 */

packet_routed:
    if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
        goto no_route;

    /* OK, we know where to send it, allocate and build IP header. */
    /*
     * 設(shè)置IP首部中各字段的值。如果存在IP選項(xiàng),
     * 則在IP數(shù)據(jù)包首部中構(gòu)建IP選項(xiàng)。
     */
    skb_push(skb, sizeof(struct iphdr)   (inet_opt ? inet_opt->opt.optlen : 0));
    skb_reset_network_header(skb);
    iph = ip_hdr(skb);/* 構(gòu)造ip頭 */
    *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
    if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
        iph->frag_off = htons(IP_DF);
    else
        iph->frag_off = 0;
    iph->ttl      = ip_select_ttl(inet, &rt->dst);
    iph->protocol = sk->sk_protocol;
    ip_copy_addrs(iph, fl4);

    /* Transport layer set skb->h.foo itself. */
     /* 構(gòu)造ip選項(xiàng) */
    if (inet_opt && inet_opt->opt.optlen) {
        iph->ihl  = inet_opt->opt.optlen >> 2;
        ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
    }

    ip_select_ident_segs(net, skb, sk,
                 skb_shinfo(skb)->gso_segs ?: 1);

    /* TODO : should we use skb->sk here instead of sk ? */
    /*
     * 設(shè)置輸出數(shù)據(jù)包的QoS類型。
     */
    skb->priority = sk->sk_priority;
    skb->mark = sk->sk_mark;

    res = ip_local_out(net, sk, skb);  /* 輸出 */
    rcu_read_unlock();
    return res;

no_route:
    rcu_read_unlock();
    /*
     * 如果查找不到對(duì)應(yīng)的路由緩存項(xiàng),
     * 在此處理,將該數(shù)據(jù)包丟棄。
     */
    IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
    kfree_skb(skb);
    return -EHOSTUNREACH;
}

dst_output()實(shí)際調(diào)用skb_dst(skb)->output(skb),skb_dst(skb)就是skb所對(duì)應(yīng)的路由項(xiàng)。skb_dst(skb)指向的是路由項(xiàng)dst_entry,它的input在收到報(bào)文時(shí)賦值ip_local_deliver(),而output在發(fā)送報(bào)文時(shí)賦值ip_output(),該函數(shù)的作用是處理單播數(shù)據(jù)報(bào),設(shè)置數(shù)據(jù)報(bào)的輸出網(wǎng)絡(luò)設(shè)備以及網(wǎng)絡(luò)層協(xié)議類型參數(shù)。隨后調(diào)用ip_finish_output,觀察數(shù)據(jù)報(bào)長(zhǎng)度是否大于MTU,若大于,則調(diào)用ip_fragment分片,否則調(diào)用ip_finish_output2輸出。在ip_finish_output2函數(shù)中會(huì)檢測(cè)skb的前部空間是否還能存儲(chǔ)鏈路層首部。如果不夠,就會(huì)申請(qǐng)更大的存儲(chǔ)空間,最終會(huì)調(diào)用鄰居子系統(tǒng)的輸出函數(shù)neigh_output進(jìn)行輸出,輸出分為有二層頭緩存和沒有兩種情況,有緩存時(shí)調(diào)用neigh_hh_output進(jìn)行快速輸出,沒有緩存時(shí),則調(diào)用鄰居子系統(tǒng)的輸出回調(diào)函數(shù)進(jìn)行慢速輸出。

網(wǎng)絡(luò)層時(shí)序圖如下圖所示。

?

?

?圖16 網(wǎng)絡(luò)層時(shí)序圖

GDB調(diào)試結(jié)果如下。

?

?

圖17 GDB調(diào)試結(jié)果

3、數(shù)據(jù)鏈路層分析

網(wǎng)絡(luò)層最終會(huì)通過調(diào)用dev_queue_xmit來(lái)發(fā)送報(bào)文,在該函數(shù)中調(diào)用的是__dev_queue_xmit(skb, NULL);,如下所示:

int dev_queue_xmit(struct sk_buff *skb)
{
    return __dev_queue_xmit(skb, NULL);
}

直接調(diào)用__dev_queue_xmit傳入的參數(shù)是一個(gè)skb 數(shù)據(jù)包

__dev_queue_xmit函數(shù)會(huì)根據(jù)不同的情況會(huì)調(diào)用__dev_xmit_skb或者sch_direct_xmit函數(shù),最終會(huì)調(diào)用dev_hard_start_xmit函數(shù),該函數(shù)最終會(huì)調(diào)用xmit_one來(lái)發(fā)送一到多個(gè)數(shù)據(jù)包。

數(shù)據(jù)鏈路層時(shí)序圖如下所示。

?

?

圖18 數(shù)據(jù)鏈路層時(shí)序圖

GDB調(diào)試結(jié)果。

?

?

圖19 GDB調(diào)試結(jié)果

五、recv分析

1、數(shù)據(jù)鏈路層分析

在數(shù)據(jù)鏈路層接受數(shù)據(jù)并傳遞給上層的步驟如下所示:

  1. 一個(gè) package 到達(dá)機(jī)器的物理網(wǎng)絡(luò)適配器,當(dāng)它接收到數(shù)據(jù)幀時(shí),就會(huì)觸發(fā)一個(gè)中斷,并將通過 DMA 傳送到位于 linux kernel 內(nèi)存中的 rx_ring。
  2. 網(wǎng)卡發(fā)出中斷,通知 CPU 有個(gè) package 需要它處理。中斷處理程序主要進(jìn)行以下一些操作,包括分配?skb_buff?數(shù)據(jù)結(jié)構(gòu),并將接收到的數(shù)據(jù)幀從網(wǎng)絡(luò)適配器I/O端口拷貝到skb_buff?緩沖區(qū)中;從數(shù)據(jù)幀中提取出一些信息,并設(shè)置?skb_buff相應(yīng)的參數(shù),這些參數(shù)將被上層的網(wǎng)絡(luò)協(xié)議使用,例如skb->protocol;
  3. 終端處理程序經(jīng)過簡(jiǎn)單處理后,發(fā)出一個(gè)軟中斷(NET_RX_SOFTIRQ),通知內(nèi)核接收到新的數(shù)據(jù)幀。
  4. 內(nèi)核 2.5 中引入一組新的 API 來(lái)處理接收的數(shù)據(jù)幀,即 NAPI。所以,驅(qū)動(dòng)有兩種方式通知內(nèi)核:(1) 通過以前的函數(shù)netif_rx;(2)通過NAPI機(jī)制。該中斷處理程序調(diào)用 Network device的?netif_rx_schedule函數(shù),進(jìn)入軟中斷處理流程,再調(diào)用net_rx_action函數(shù)。
  5. 該函數(shù)關(guān)閉中斷,獲取每個(gè) Network device 的 rx_ring 中的所有 package,最終 pacakage 從 rx_ring 中被刪除,進(jìn)入netif _receive_skb處理流程。
  6. netif_receive_skb是鏈路層接收數(shù)據(jù)報(bào)的最后一站。它根據(jù)注冊(cè)在全局?jǐn)?shù)組 ptype_all 和 ptype_base 里的網(wǎng)絡(luò)層數(shù)據(jù)報(bào)類型,把數(shù)據(jù)報(bào)遞交給不同的網(wǎng)絡(luò)層協(xié)議的接收函數(shù)(INET域中主要是ip_rcv和arp_rcv)。該函數(shù)主要就是調(diào)用第三層協(xié)議的接收函數(shù)處理該skb包,進(jìn)入第三層網(wǎng)絡(luò)層處理。

數(shù)據(jù)鏈路層的時(shí)序圖如下所示。

?

圖20 數(shù)據(jù)鏈路層時(shí)序圖

GDB調(diào)試如下。

?

?

?圖21 GDB調(diào)試

2、網(wǎng)絡(luò)層分析

ip層的入口在ip_rcv函數(shù),該函數(shù)首先會(huì)做包括 package checksum 在內(nèi)的各種檢查,如果需要的話會(huì)做 IP defragment(將多個(gè)分片合并),然后 packet 調(diào)用已經(jīng)注冊(cè)的 Pre-routing netfilter hook ,完成后最終到達(dá)ip_rcv_finish函數(shù)。

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
       struct net_device *orig_dev)
{
    struct net *net = dev_net(dev);

    skb = ip_rcv_core(skb, net);
    if (skb == NULL)
        return NET_RX_DROP;

    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
               net, NULL, skb, dev, NULL,
               ip_rcv_finish);
}

ip_rcv_finish函數(shù)如下所示:

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    struct net_device *dev = skb->dev;
    int ret;

    /* if ingress device is enslaved to an L3 master device pass the
     * skb to its handler for processing
     */
    skb = l3mdev_ip_rcv(skb);
    if (!skb)
        return NET_RX_SUCCESS;

    ret = ip_rcv_finish_core(net, sk, skb, dev, NULL);
    if (ret != NET_RX_DROP)
        ret = dst_input(skb);
    return ret;
}

ip_rcv_finish 函數(shù)最終會(huì)調(diào)用ip_route_input函數(shù),進(jìn)入路由處理環(huán)節(jié)。它首先會(huì)調(diào)用 ip_route_input 來(lái)更新路由,然后查找 route,決定該 package 將會(huì)被發(fā)到本機(jī)還是會(huì)被轉(zhuǎn)發(fā)還是丟棄:

  1. 如果是發(fā)到本機(jī)的話,調(diào)用ip_local_deliver?函數(shù),可能會(huì)做 de-fragment(合并多個(gè) IP packet),然后調(diào)用ip_local_deliver函數(shù)。該函數(shù)根據(jù) package 的下一個(gè)處理層的 protocal number,調(diào)用下一層接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。對(duì)于 TCP 來(lái)說(shuō),函數(shù)?tcp_v4_rcv?函數(shù)會(huì)被調(diào)用,從而處理流程進(jìn)入 TCP 棧。
  2. 如果需要轉(zhuǎn)發(fā) (forward),則進(jìn)入轉(zhuǎn)發(fā)流程。該流程需要處理 TTL,再調(diào)用dst_input函數(shù)。該函數(shù)會(huì) (1)處理 Netfilter Hook (2)執(zhí)行 IP fragmentation (3)調(diào)用?dev_queue_xmit,進(jìn)入鏈路層處理流程。

網(wǎng)絡(luò)層時(shí)序圖如下圖所示。

?

?

圖22 網(wǎng)絡(luò)層時(shí)序圖

GDB調(diào)試如下圖所示。

?

?

圖23 GDB調(diào)試

3、傳輸層分析

對(duì)于recv函數(shù),與send函數(shù)類似,調(diào)用的系統(tǒng)調(diào)用是__sys_recvfrom,其代碼如下所示:

int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
           struct sockaddr __user *addr, int __user *addr_len)
{
    ......
    err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);
    if (unlikely(err))
        return err;
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    .....
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    /* Save some cycles and don't copy the address if not needed */
    msg.msg_name = addr ? (struct sockaddr *)&address : NULL;
    /* We assume all kernel code knows the size of sockaddr_storage */
    msg.msg_namelen = 0;
    msg.msg_iocb = NULL;
    msg.msg_flags = 0;
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    err = sock_recvmsg(sock, &msg, flags); //調(diào)用該函數(shù)接受數(shù)據(jù)

    if (err >= 0 && addr != NULL) {
        err2 = move_addr_to_user(&address,
                     msg.msg_namelen, addr, addr_len);
    .....
}

__sys_recvfrom通過調(diào)用sock_recvmsg來(lái)對(duì)數(shù)據(jù)進(jìn)行接收,該函數(shù)實(shí)際調(diào)用的是sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);?,同樣類似send函數(shù)中,調(diào)用的實(shí)際上是socket在初始化時(shí)賦值給結(jié)構(gòu)體struct proto tcp_prot的函數(shù)tcp_rcvmsg,如下所示:

struct proto tcp_prot = {
    .name            = "TCP",
    .owner            = THIS_MODULE,
    .close            = tcp_close,
    .pre_connect        = tcp_v4_pre_connect,
    .connect        = tcp_v4_connect,
    .disconnect        = tcp_disconnect,
    .accept            = inet_csk_accept,
    .ioctl            = tcp_ioctl,
    .init            = tcp_v4_init_sock,
    .destroy        = tcp_v4_destroy_sock,
    .shutdown        = tcp_shutdown,
    .setsockopt        = tcp_setsockopt,
    .getsockopt        = tcp_getsockopt,
    .keepalive        = tcp_set_keepalive,
    .recvmsg        = tcp_recvmsg,
    .sendmsg        = tcp_sendmsg,
  ...

tcp_rcvmsg的代碼如下所示:

int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
        int flags, int *addr_len)
{
    ......
    if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) &&
        (sk->sk_state == TCP_ESTABLISHED))
        sk_busy_loop(sk, nonblock); //如果接收隊(duì)列為空,則會(huì)在該函數(shù)內(nèi)循環(huán)等待

    lock_sock(sk);
    .....
        if (unlikely(tp->repair)) {
        err = -EPERM;
        if (!(flags & MSG_PEEK))
            goto out;

        if (tp->repair_queue == TCP_SEND_QUEUE) 
            goto recv_sndq;

        err = -EINVAL;
        if (tp->repair_queue == TCP_NO_QUEUE)
            goto out;
    ......
        last = skb_peek_tail(&sk->sk_receive_queue); 
        skb_queue_walk(&sk->sk_receive_queue, skb) {
            last = skb;
    ......
            if (!(flags & MSG_TRUNC)) {
            err = skb_copy_datagram_msg(skb, offset, msg, used); //將接收到的數(shù)據(jù)拷貝到用戶態(tài)
            if (err) {
                /* Exception. Bailout! */
                if (!copied)
                    copied = -EFAULT;
                break;
            }
        }

        *seq  = used;
        copied  = used;
        len -= used;

        tcp_rcv_space_adjust(sk);

在連接建立后,若沒有數(shù)據(jù)到來(lái),接收隊(duì)列為空,進(jìn)程會(huì)在sk_busy_loop函數(shù)內(nèi)循環(huán)等待,知道接收隊(duì)列不為空,并調(diào)用函數(shù)數(shù)skb_copy_datagram_msg將接收到的數(shù)據(jù)拷貝到用戶態(tài),該函數(shù)內(nèi)部實(shí)際調(diào)用的是__skb_datagram_iter,其代碼如下所示:

int __skb_datagram_iter(const struct sk_buff *skb, int offset,
            struct iov_iter *to, int len, bool fault_short,
            size_t (*cb)(const void *, size_t, void *, struct iov_iter *),
            void *data)
{
    int start = skb_headlen(skb);
    int i, copy = start - offset, start_off = offset, n;
    struct sk_buff *frag_iter;

    /* 拷貝tcp頭部 */
    if (copy > 0) {
        if (copy > len)
            copy = len;
        n = cb(skb->data   offset, copy, data, to);
        offset  = n;
        if (n != copy)
            goto short_copy;
        if ((len -= copy) == 0)
            return 0;
    }

    /* 拷貝數(shù)據(jù)部分 */
    for (i = 0; i < skb_shinfo(skb)->nr_frags; i  ) {
        int end;
        const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];

        WARN_ON(start > offset   len);

        end = start   skb_frag_size(frag);
        if ((copy = end - offset) > 0) {
            struct page *page = skb_frag_page(frag);
            u8 *vaddr = kmap(page);

            if (copy > len)
                copy = len;
            n = cb(vaddr   frag->page_offset  
                offset - start, copy, data, to);
            kunmap(page);
            offset  = n;
            if (n != copy)
                goto short_copy;
            if (!(len -= copy))
                return 0;
        }
        start = end;
    }

傳輸層時(shí)序圖如下圖所示。

?

?圖24 傳輸層時(shí)序圖

?GDB調(diào)試如下圖所示。

?

?圖25 GDB調(diào)試

六、小結(jié)

感謝孟寧老師的嚴(yán)謹(jǐn)授課,讓我受益匪淺!本文基于孟老師上課所講,從Linux操作系統(tǒng)實(shí)現(xiàn)入手,深入的分析了Linux操作系統(tǒng)對(duì)于TCP/IP棧的實(shí)現(xiàn)原理與具體過程,了解了Linux網(wǎng)絡(luò)子系統(tǒng)的具體構(gòu)成及流程,通過這次調(diào)研,使我對(duì)TCP/IP協(xié)議的原理及具體實(shí)現(xiàn)有了極其深入的理解。

int __skb_datagram_iter(const struct sk_buff *skb, int offset, struct iov_iter *to, int len, bool fault_short, size_t (*cb)(constvoid *, size_t, void *, struct iov_iter *), void *data) { int start = skb_headlen(skb); int i, copy = start - offset, start_off = offset, n; struct sk_buff *frag_iter;/* 拷貝tcp頭部 */if (copy > 0) { if (copy > len) copy = len; n = cb(skb->data offset, copy, data, to); offset = n; if (n != copy) goto short_copy; if ((len -= copy) == 0) return0; } /* 拷貝數(shù)據(jù)部分 */for (i = 0; i < skb_shinfo(skb)->nr_frags; i ) { int end; constskb_frag_t *frag = &skb_shinfo(skb)->frags[i]; WARN_ON(start > offset len); end = start skb_frag_size(frag); if ((copy = end - offset) > 0) { struct page *page = skb_frag_page(frag); u8 *vaddr = kmap(page); if (copy > len) copy = len; n = cb(vaddr frag->page_offset offset - start, copy, data, to); kunmap(page); offset = n; if (n != copy) goto short_copy; if (!(len -= copy)) return0; } start = end; }

來(lái)源:https://www./content-3-837801.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多