一、調(diào)研要求
二、?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ò)部分劃分為五層:
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)的所有信息,這些信息中包括:
其示意圖如下所示: 圖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傳輸控制塊,即 ops字段指向特定傳輸協(xié)議的操作集接口, 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í):
當(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_bufsk_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示意圖 各字段含義如下:
操作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)了以下主要功能。
每一個(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)核中的表示。
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)信息。
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; }
/* 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); } 這里我們看到, 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)核線程 ? ? 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ù) 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(); } 在 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。所以 這里需要注意一個(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ù) 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 - 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); ... } 在讀取操作中, 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); }
//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); }
//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; ...... } 在 3、網(wǎng)絡(luò)協(xié)議棧處理
? ? ?圖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; } } } 在 //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); }
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); } 這里 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); } 跟蹤 //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; } } 所以回到 /* Input packet from network to transport. */ static inline int dst_input(struct sk_buff *skb) { return skb_dst(skb)->input(skb); }
//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ù)是 //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); }
//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ì)列中。如果有,那就通過
四、send分析1、傳輸層分析send的定義如下所示:
當(dāng)在調(diào)用 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; }
繼續(xù)追蹤 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, ... 而 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); } 在
struct sock{ ... struct sk_buff_head sk_write_queue;/*指向skb隊(duì)列的第一個(gè)元素*/ ... struct sk_buff *sk_send_head;/*指向隊(duì)列第一個(gè)還沒有發(fā)送的元素*/ }
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)用了
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); } 在 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; ......
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ā)送接口 ...... }
傳輸層時(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)用 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; }
網(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)用 int dev_queue_xmit(struct sk_buff *skb) { return __dev_queue_xmit(skb, NULL); } 直接調(diào)用
數(shù)據(jù)鏈路層時(shí)序圖如下所示。 ? ? 圖18 數(shù)據(jù)鏈路層時(shí)序圖 GDB調(diào)試結(jié)果。 ? ? 圖19 GDB調(diào)試結(jié)果 五、recv分析1、數(shù)據(jù)鏈路層分析在數(shù)據(jù)鏈路層接受數(shù)據(jù)并傳遞給上層的步驟如下所示:
數(shù)據(jù)鏈路層的時(shí)序圖如下所示。 ? 圖20 數(shù)據(jù)鏈路層時(shí)序圖 GDB調(diào)試如下。 ? ? ?圖21 GDB調(diào)試 2、網(wǎng)絡(luò)層分析ip層的入口在 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); }
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)用
網(wǎng)絡(luò)層時(shí)序圖如下圖所示。 ? ? 圖22 網(wǎng)絡(luò)層時(shí)序圖 GDB調(diào)試如下圖所示。 ? ? 圖23 GDB調(diào)試 3、傳輸層分析對(duì)于 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); ..... }
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, ...
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ì)在 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 |
|