TCP/IP協議棧在Linux內核中的運行時序分析


TCP/IP協議棧在Linux內核中的運行時序分析

目錄

  

1、Linux內核與網絡體系結構

在我們了解整個linux系統的網絡體系結構之前,我們需要對整個網絡體系調用,初始化和交互的位置,同時也是Linux操作系統中最為關鍵的一部分代碼-------內核,有一個初步的認知。

1.1 Linux內核的結構

首先,從功能上,我們將linux內核划分為五個不同的部分,分別是

(1)進程管理:主要負載CPU的訪問控制,對CPU進行調度管理;

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

(3)文件系統:將硬盤的扇區組織成文件系統,實現文件讀寫等操作;

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

(5)網洛:主要負責管理各種網絡設備,並實現各種網絡協議棧,最終

實現通過網絡連接其它系統的功能;

每個部分分別處理一項明確的功能,又向其它各個部分提供自己所完成的功能,相互協調,共同完成操作系統的任務。

Linux內核架構如下圖所示:

圖1 Linux內核架構圖

1.2 linux網絡子系統

內核的基本架構我們已經了解清楚了,接下來我們重點關注到內核中的網絡模塊,觀察在linux內核中,我們是如何實現及運用TCP/IP協議,並完成網絡的初始化及各個模塊調用調度。
我們將內核中的網絡部分抽出,通過對比TCP/IP分層協議,與Linux網絡實現體系相對比,深入的了解學習linux內核是怎樣具體的實現TCP/IP協議棧的。

Linux網絡體系與TCP/IP協議棧如下圖所示

    

                               圖2 linux網絡體系       圖3 TCP/IP協議棧和OSI參考模型對應關系

可以看到,在圖2中,linux為了抽象與實現相分離,將內核中的網絡部分划分為五層:

1、系統調用接口

系統調用接口是用戶空間的應用程序正常訪問內核的唯一途徑,系統調用一般以sys開頭。

2、協議無關接口

協議無關接口是由socket來實現的,它提供一組通用函數來支持各種不同的協議。Linuxsocket結構是struct sock,這個結構定義了socket所需要的所有狀態信息,包括socke所使用的協議以及可以在socket上執行的操作。

3、網絡協議

Linux支持多種協議,每一個協議都對應net_family[]數組中的一項,net_family[]的元素為一個結構體指針,指向一個包含注冊協議信息的結構體net_proto_family;

4、設備無關接口

設備無關接口net_device實現的,任何設備與上層通信都是通過net_device設備無關接口。它將設備與具有很多功能的不同硬件連接在一起,這一層提供一組通用函數供底層網絡設備驅動程序使用,讓它們可以對高層協議棧進行操作。

5、設備驅動程序

網絡體系結構的最底部是負責管理物理網絡設備的設備驅動程序層。

Linux網絡子系統通過這五層結構的相互交互,共同完成TCP/IP協議棧的運行。

2、幾個重要的數據結構

2.1 sk_buf

sk_buf是Linux網絡協議棧最重要的數據結構之一,該數據結構貫穿於整個數據包處理的流程。由於協議采用分層結構,上層向下層傳遞數據時需要增加包頭,下層向上層數據時又需要去掉包頭。sk_buff中保存了L2,L3,L4層的頭指針,這樣在層傳遞時只需要對數據緩沖區改變頭部信息,並調整sk_buff中的指針,而不需要拷貝數據,這樣大大減少了內存拷貝的需要。

sk_buf的示意圖如下:

4  sk_buf簡單示意圖

各字段含義如下:

head:指向分配給的線性數據內存首地址。

data:指向保存數據內容的首地址。

tail:指向數據的結尾。 

end:指向分配的內存塊的結尾。

len:數據的長度。

head room: 位於head至data之間的空間,用於存儲:protocol header,例如:TCP header, IP header, Ethernet header等。

user data: 位於data至tail之間的空間,用於存儲:應用層數據,一般系統調用時會使用到。 

tail room: 位於tail至end之間的空間,用於填充用戶數據未使用完的空間。

skb_shared_info: 位於end之后,用於存儲特殊數據結構skb_shared_info,該結構用於描述分片信息。 

sk_buf的常用操作函數如下:

alloc_skb:分配sk_buf。

skb_reserve:為sk_buff設置header空間。

skb_put:添加用戶層數據。

 skb_push:向header空間添加協議頭。

skb_pull:復位data至數據區。

操作sk_buf的簡單示意圖如下:

圖5 sk_buf操作前后示意圖

2.2 net_device

在網絡適配器硬件和軟件協議棧之間需要一個接口,共同完成操作系統內核中協議棧數據處理與異步收發的功能。在Linux網絡體系結構中,這個接口要滿足以下要求:

(1)抽象出網絡適配器的硬件特性。

(2)為協議棧提供統一的調用接口。

以上兩個要求在Linux內核的網絡體系結構中分別由兩個軟件(設備獨立接口文件dev.c和網絡設備驅動程序)和一個主要的數據結構struct net_device實現。

2.2.1 設備獨立接口文件dev.c

dev.c文件中實現了對上層協議的統一調用接口,dev.c文件中的函數實現了以下主要功能。

(1)協議調用與驅動程序函數對應;

dev.c文件中的函數查看數據包由哪個網絡設備(struct sk_buff結構中*dev數據域指明該數據包由哪個網絡設備net_device實例接收/發送)傳送,根據系統中注冊的設備實例,調用網絡設備驅動程序函數,實現硬件的收發。

(2)對struct net_device數據結構的數據域統一初始化;

dev.c提供了一些常規函數,來初始化 struct net_device結構中的這樣一些數據域:它們的值對所有類型的設備都一樣,驅動程序可以調用這些函數來設置其設備實例的默認值,也可以重寫由內核初始化的值。

2.2.2 網絡驅動程序

每一個網絡設備都必須有一個驅動程序,並提供一個初始化函數供內核啟動時調用,或在裝載網絡驅動程序模塊時調用。不管網絡設備內部有什么不同,有一件事是所有網絡設備驅動程序必須首先完成的任務:初始化一個structnet_device數據結構的實例作為網絡設備在內核中的實體,並將struct net_device數據結構實例的各數據域初始化為可工作的狀態,然后將設備實例注冊到內核中,為協議棧提供傳送服務。

2.2.3 struct net_device數據結構

struct net_device數據結構從以下兩個方面描述了網絡設備的硬件特性在內核中的表示。

(1)描述設備屬性

struct net_device數據結構實例是網絡設備在內核中的表示,它是每個網絡設備在內核中的基礎數據結構,它包含的信息不僅僅是網絡設備的硬件屬性(中斷、端口地址、驅動程序函數等),還包括網絡中與設備有關的上層協議棧的配置信息(如IP地址、子網掩碼等)。它跟蹤連接到 TCP/IP協議棧上的所有設備的狀態信息。

(2)實現設備驅動程序接口

struct net_device數據結構代表了上層的網絡協議和硬件之間的一個通用接口,使我們可以將網絡協議層的實現從具體的網絡硬件部件中抽象出來,獨立於硬件設備。為了有效地實現這種抽象,struct net_device中使用了大量函數指針,這樣相對於上層的協議棧,它們在做數據收發操作時調用的函數的名字是相同的,但具體的函數實現細節可以根據不同的網絡適配器而不同,由設備驅動程序提供,對網絡協議棧透明。

圖6 網絡設備抽象數據結構

3、內核啟動與網絡子系統初始化

在系統運行過程中,物理網絡設備在Linux內核代碼中的實體是struct net _device數據結構的實例。struct net_device數據結構的實例要成為能被內核識別、代表正常工作網絡設備的代碼描述,與內核代碼融為一體,需要經過實例創建、初始化、設備注冊等一系列過程。這個過程涉及以下幾個方面的問題:

(1)linux內核識別網絡設備的步驟

(2)網絡設備初始化的一般過程

作為Linux內核組件之一的網絡子系統,網絡協議棧和網絡設備驅動程序都運行在內核的地址空間,它們的初始化是整個內核初始化的一個部分,具有內核啟動、初始化過程的一般特點。

3.1 內核初始化

bootloader中的指令是系統上電后執行的第一條指令,完成系統基本的硬件初始化,然后將控制權交給Linux內核,並將命令行參數傳給內核,進入secondloader。

內核啟動過程從init/main.c文件中的start_kernel函數開始。start_kernel函數的首要任務是建立內核運行的基線,即基礎子系統的初始化,包括:初始化系統時鍾,初始化中斷系統,初始化內存,解析命令行參數,建立文件系統等。

在start_kernel函數結束處調用rest_init函數創建第一個內核進程,執行kernel_init函數,kernel_init函數執行do_basic_setup函數轉而調用do_initcalls 函數,按照常規子系統注冊初始化函數的優先級順序調用它們提供的初始化函數,完成整個內核的啟動、初始化過程。

具體流程如下:

圖7 內核初始化過程

函數調用棧如下圖所示:

圖8 內核初始化調用過程

3.2 struct net_device的初始化

網絡設備驅動程序的初始化任務需要完成以下幾方面的任務:

(1)創建設備的代碼實例,即 struct net_device數據結構類型變量。

主要調用alloc_netdev_mq申請內存空間,為網絡設備分配設備名,建立網絡設備發送隊列,並初始化部分數據域。

(2)初始化網絡設備實例,即struct net_device數據結構類型變量的數

據域。網絡設備實例的數據域,有的由內核代碼(xxx_setup函數)賦值,有的由網絡設備驅動程序賦值。

(3)將網絡設備實例注冊到內核中(調用register_dev)。

具體初始化及注冊/注銷流程如下:

圖9 net_device的創建設備實例與初始化

圖10 注冊net_device的函數調用棧

一旦一個網絡設備注冊到內核后,該網絡設備就可以使用了。但它還不能進行數據包的接收和傳送操作,直到用戶允許該網絡設備后,網絡設備才被激活,隨后才能開始數據收發操作。

激活網絡設備由dev_open函數完成,而禁止網絡設備則由dev_close完成。

網絡設備的struct net_device 數據結構實例在創建后會插入到一個全局鏈表dev_base_head和兩個哈希鏈表中,這些數據結構使內核查找設備更容易。

圖11 管理全局實例的dev_base_head

3.3 事件通知鏈

Linux網絡設備自身並不知道有哪些協議實例在使用它的服務,存儲了對它的引用。為了滿足讓協議棧實例獲取網絡設備狀態信息的需求,Linux內核中實現了事件通知鏈(notification chain)機制。

事件通知鏈是一個事件處理函數的列表,每個通知鏈都與某個或某些事件相關,當特定的事件發生時,列在通知鏈中的函數就依次被執行,通知事件處理函數所屬的子系統某個事件發生了,子系統接到通知后做相應的處理。每個通知鏈中都存在被通知方和通知方。

被通知方:內核中的某個子系統,提供事件處理的回調函數。

通知方:內核中發生事件的子系統,通知鏈的創建者,事件發生后通知方依次調用事件通知鏈上的事件處理函數,通知其他子系統某個事件發生,對方應做出相應處理。

通知方是事件通知鏈創建方,它定義事件通知鏈,需要獲取通知方事件消息的其他子系統編寫自己的事件處理函數,並向通知方的事件通知鏈中注冊函數。

事件通知鏈中是一個一個的struct notifier_block 數據結構類型的成員列表,struct notifier_block 數據結構描述了通知鏈的成員由哪些屬性組成,這樣當事件通知鏈中的成員接收到事件通知后,才能做出正確反應。struct notifier_block包括三個數據成員,分別是:notifier_call函數指針,指向事件處理函數,struct noyifier_block *next指針,將事件通知鏈中成員連接成鏈表的指針,和事件處理函數的優先級priority。

圖12 網絡子系統的事件通知鏈netdev_chain

網絡子系統共創建了3個事件的通知鏈:inetaddr_chain、inet6addr_chain和netdev_chain。其中 netdev_chain為網絡設備狀態發生變化時的事件通知鏈,該事件通知鏈定義在net/core/dev.c文件中。

向網絡子系統事件通知鏈注冊的步驟如下:

1、聲明struct notifier_block數據結構實例。

2、編寫事件處理回調函數。

3、將事件處理回調函數的地址賦給struct notifier_block 數據結構實例的*notifier_call 成員。

4、調用特定事件通知鏈的注冊函數,將struct notifier_block 數據結構實例注冊到通知鏈中。

擁有事件通知鏈的事件通知方在事件發生時,調用notifier_call_chain函數(定義在kernel/notifier.c文件中)通知其他子系統有事件發生。

notifier_call_chain函數會按照事件通知鏈上各成員的優先級順序調用注冊在通知鏈中的所有回調函數。

4、網絡接口收發數據

網絡接口最主要的任務是數據收發,數據發送相對於數據接收要簡單一些,我們先介紹數據包是如何由內核向網絡發送的。

4.1 發送網絡數據

發送數據是將數據包通過網絡連接線路送出。無論什么時候,內核准備好要發送的數據包后(數據包存放在Socket Buffer 中),都會調用驅動程序的ndo_start_xmit函數把數據包放到網絡設備的硬件數據緩沖區,並啟動硬件發送。ndo_start_xmit是一個函數指針,指向驅動程序實現的數據發送函數。傳給ndo_start_xmit的 Socket Buffer 包含了實際要傳輸的物理數據、協議棧的協議頭;接口無須關心數據包的具體內容,也無須修改數據包的值。struct sk_buff 中skb->data給出了要發送數據的起始地址。

如果ndo_start_xmit執行成功則返回0,這時負責發送該數據的驅動程序應盡最大努力保證數據包的發送成功,最后釋放存放發送完成數據包的Socket Buffer。如果ndo_start_xmit返回值非0,則說明這次發送不成功,內核過一段時間后會重發。這時驅動程序應停止發送隊列,直到發送失敗錯誤恢復。

當CPU需要發送數據給網絡設備時,CPU將數據寫到IO端口,並向網絡設備的命令寄存器寫發送控制命令;相反,當網絡設備需要發送數據給CPU時,它產生一個中斷,CPU執行中斷處理程序來為網絡設備服務。

4.2 接收網絡數據

與發送數據包相反,接收數據包是內核事先無法預見的事件。網絡設備接收數據包的過程與內核的操作是並行的,網絡設備驅動程序把數據包推送給內核,有兩種方式通知內核數據到達。

輪詢,即內核周期性地查詢網絡設備。這種方式的問題是內核需要多長時間詢問網絡設備一次。如果間隔太短,會無謂地浪費CPU時間;如果間隔太長,數據發送的延遲會增加,可能會丟失數據。

中斷,即內核執行中斷處理程序接收數據包,將其存放在CPU的接收隊列中,其后的處理可以等CPU空閑時再完成。net_rx是在接收中斷中調用的處理接收數據的函數。

net_rx 的任務是申請一個Socket Buffer,將硬件數據復制到Socket Buffer中,再將接收設備注冊到Socket Buffer的dev數據域,識別數據包的協議類型。接着由netif_rx()函數將接收到的數據包放入CPU的接收隊列,更新接收統計信息,這時接收處理程序繼續接收下一個數據包,或者結束返回。

5、數據鏈路層

在TCP/IP協議棧中,數據鏈路層的關鍵任務是:將由網絡設備驅動程序從設備硬件緩沖區復制到內核地址空間的網絡數據幀掛到CPU的輸入隊列,並通知上層協議有網絡數據幀到達,隨后上層協議就可以從CPU的輸入隊列中獲取網絡數據幀並處理。

另外,上層協議實例要向外發送的數據幀會由數據鏈路層放到設備輸出隊列,再由設備驅動程序的硬件發送函數 hard_start_xmit將設備輸出隊列中的數據幀復制到設備硬件緩沖區,實現對外發送。

圖13 數據包接收/發送流程

5.1 數據幀的接收處理

網絡設備收到的數據幀由網絡設備驅動程序推送到內核地址空間后,在數據鏈路層中,Linux內核網絡子系統實現了兩種機制,將數據幀放入CPU的輸入隊列中。

1、netif_rx

這是目前大多數網絡設備驅動程序將數據幀復制到Socket Buffer 后,調用的數據鏈路層方法。它通知內核接收到了網絡數據幀;標記網絡接收軟件中斷,執行接收數據幀的后續處理。這種機制每接收一個數據幀會產生一個接收中斷。

2、NAPI

這是內核實現的新接口,在一次中斷中可以接收多個網絡數據幀,減少了CPU響應中斷請求進行中斷服務程序與現行程序之間切換所花費的時間。

5.1.1 NAPI的實現

NAPI的核心概念是使用中斷與輪詢相結合的方式來代替純中斷模式:當網絡設備從網絡上收到數據幀后,向CPU 發出中斷請求,內核執行設備驅動程序的中斷服務程序接收數據幀;在內核處理完前面收到的數據幀前,如果設備又收到新的數據幀,這時設備不需產生新的中斷(設備中斷為關閉狀態),內核繼續讀入設備輸入緩沖區中的數據幀(通過調用驅動程序的poll函數來完成),直到設備輸入緩沖區為空,再重新打開設備中斷。這樣設備驅動程序同時具有了中斷與輪詢兩種工作模式的優點。

異步事件通知:當有數據幀到達時用中斷通知內核,內核無須不斷查詢設備的狀態。

如果設備隊列中仍有數據,無須浪費時間處理中斷通知、程序切換等。

使用NAPI工作模式需要對網絡驅動程序做以下的升級:

(1)新增新的數據結構struct napi_struct;

該實例描述了與NAPI相關的屬性與操作。

(2)實現poll 函數;

設備驅動程序要實現自己的poll 函數,來輪詢自己的設備,將網絡輸入數據幀復制到內核地址空間的Socket Buffer,再放入CPU 輸入隊列。

(3)對接收中斷處理程序進行修改;

執行中斷處理程序后,不是調用netif_rx函數將Socket Buffer放入CPU 輸入隊列,而是調用netif_rx_schedule函數。

圖14 softnet_data、napi_struct、net_device數據結構之間的關系

其中struct softnet_data是管理CPU隊列的數據結構。

NAPI接收機制的工作流程如下:

 

圖15 NAPI接收機制的工作流程

5.1.2 netif_rx的實現

netif_rx函數由常規網絡設備驅動程序在接收中斷中調用,它的任務就是把輸入數據幀放入CPU的輸入隊列中,隨后標記軟件中斷來處理后續上傳數據幀給TCP/IP協議棧功能。

netif_rx的函數調用流程如下:

圖16 netif_rx函數調用流程

圖17 netif_rx調用函數棧

napi_schedule函數是_napi_schedule函數的包裝函數,_napi_schedule完成的功能就是將struct napi_struct 數據結構實例放入CPU的poll_list隊列,掛起網絡接收軟件中斷NET_RX_SOFTIRQ。這樣推送數據幀給上層協議實例的處理函數,就會在內核調度的網絡接收軟件中斷處理程序的net_rx _action函數中被執行。

網絡接收軟件中斷(NET_RX_SOFTIRQ)的處理程序net_rx_action是接收網絡數據中斷的后半段。引起net_rx _action函數執行的是網絡設備產生的接收數據硬件中斷,它通知內核收到了網絡數據幀,觸發內核調度接收中斷的后半段。net_rx_action函數的任務就是將設備收到的數據幀上傳給TCP/IP 協議棧的上層協議處理。

5.2 數據幀的發送處理

在數據幀的處理過程中,發送和接收的許多步驟都有對稱性。大部分數據結構、處理函數與前面討論的接收過程的數據結構和處理函數都是成對出現,只是其處理過程相反。例如接收軟件中斷是 NET_RX_SOFTIRQ,相對應的發送數據有軟件中斷NET_TX_SOFTIRQ;它們的軟件中斷處理程序分別是net_rx_action和 net_tx_action。

相較於接收過程,可預期的發送過程更為簡單,主要流程如下:

(1)啟動設備發送數據

Linux設置了一系列的API來操縱檢查網絡發送隊列的狀態,常用如下:netif_start_queue/netif_stop_queue(啟動/禁止網絡發送隊列),netif_queue_stopped(返回網絡隊列當前發送狀態),netif_wake_queue(喚醒網絡發送隊列,重啟網絡發送過程)。

(2)調度設備發送數據幀

內核實現了dev_queue_xmit函數,該函數將上層協議發送來的數據幀放到網絡設備的發送隊列(針對有發送隊列的網絡設備),隨后流量控制系統按照內核配置的隊列管理策略,將網絡設備發送隊列中的數據幀依次發送出去。發送時,從網絡設備的輸出隊列上獲取一個數據幀,將數據幀發送給設備驅動程序的dev->netdev_ops->ndo_start_xmit方法。

如果獲取保護輸出隊列並發訪問的鎖失敗,內核實現了函數_netif_schedule來重新調度網絡設備發送數據幀,它將網絡設備放到CPU的發送隊列softnet_data->output_queue 上,隨后標識網絡發送軟件中斷NET_TX_SOFTIRQ,當發送軟件中斷被內核調度執行時,CPU輸出隊列output queue 中的設備會重新被調度來發送數據幀。(其作用就類似netif_rx_schedule函數處理輸入路徑的功能)。

圖18 dev_queue_xmit函數調用棧

6、網絡層

網絡層協議是TCP/IP協議棧與網絡設備驅動程序所管理的硬件發送的連接處,網絡數據包經過網絡層的處理后,已包含了所有向外發送需要的信息,如源地址、目標地址、協議頭等,可交由網絡設備發送。另一方面,接收到的網絡數據包在網絡層確定其進一步發送的路徑,是在本機向上傳遞,還是繼續向前發送。數據鏈路層的驅動程序只關心將上層交給的數據包向外發送,將接到的數據包向上投遞,而網絡層協議的關鍵任務就是決定數據包的去向。

在linux中相應的協議處理函數都會分兩個階段實現。

第一階段函數名通常為do_something (如 ip_rcv),do_something 函數只對數據包做必要的合法性檢查,或為數據包預留需要的內存空間。

第二階段函數名通常為do_something_finish(如 ip_rcv_finish),是實際完成接收/發這操作的函數。

6.1 輸入數據包在IP層的處理

Linux內核定義了ptype_base鏈表來實現數據鏈路層與網絡層之間接收數據包的接口,網絡層各協議將自己的接收數據包處理函數注冊到ptype_base鏈表中,數據鏈路層按接收數據包的skb->protocol值在ptype_base鏈表中找到匹配的協議實例,將數據包傳給注冊的處理函數。IP協議在PF_INET協議族初始化函數inet_init中調用dev_add _pack注冊的處理函數是ip_rcv。 ip_rcv函數是網絡層IP協議處理輸入數據包的入口函數,IP協議通過ip_rcv、ip_rcv_finish等函數,完成IP層對輸入數據包的處理。

1、ip_rcv函數

Ip_rcv函數主要都是在對skb 中的數據包做各種合法性檢查:協議頭長度、協議版本、數據包長度、校驗和等。傳給ip_rcv函數處理的數據包存放在skb中,ip_rcv函數需要從 skb管理結構中獲取各種信息,作為處理的依據,並將數據鏈路層與網絡層的處理關聯在一起。

主要步驟如下:

(1)檢查當前skb各數據域的狀態

(2)skb->pkt_type數據域的值,確定上交還是丟棄

(3)數據包共享的處理

(4)IP協議頭信息的正確性檢查

(5)數據包的正確性檢查

(6)清理工作,獲得一個干凈的數據包

(7)函數結束,回調ip_rcv_finish完成對數據包的處理

2、ip_rcv_finish函數

Ip_rcv_finish函數主要功能如下:

確定數據包是前送還是在本機協議棧中上傳,如果是前送,需要確定輸出網絡設備和下一個接收站點的地址。

解析和處理部分IP選項。

處理流程如下:

(1)獲取路由信息

(2)更新流量控制系統使用的統計信息

(3)處理路由選項

函數結束處理,調用dst_input確定對數據包的處理函數是哪一個,實際的調用存放在 skb->dst->input數據域,根據數據包的目標地址,skb->dst->input設置成ip_local_deliver或ip_forward。

 圖19 ip_rcv_finish函數調用棧

6.2 發送數據包在IP層的處理

在 ip_rcv_finish 函數結束處,如果數據包的目標地址不是本機,內核需要將數據包前送給適當的主機;反之,如果數據包的目標地址是本地主機,內核需要將數據包上傳給TCP/IP 協議棧網絡層以上的協議實例。處理函數的選擇由dst_input完成,它根據路由將處理函數設置為 ip_forward或ip_local_deliver。

6.2.1 數據包的前送(發送至下一主機)

與數據包接收處理過程類似,數據包前送也分為兩個階段完成:ip_forward和 ip_forward_finish。第二個函數在第一個函數執行結束時調用。

函數處理流程如下:

圖20 ip_forward 函數處理流程

當ip_forward_finish處理完前送數據包的所有IP選項后,交由dst_output函數送達目的主機。

dst_output 調用虛函數output,虛函數output根據數據包的目標地址類型來初始化。目標地址為某一主機地址時,output初始化成ip_output,如果目標地址是組發送地址,則它初始化成ip_mc_output,數據包的發送分3個階段處理,分別由ip_output,ip_finish_output,ip_finish_output2三個函數完成,對應如下:

ip_output函數初始化數據包的輸出網絡設備和傳輸協議,最后由網絡過濾子系統對數據包進行過濾,並調用ip_finish_output函數完成實際的發送操作。

ip_finish_output 的主要任務是根據網絡配置確定是否需要對數據包進行重路由,是否需要對數據包進行分割,最后調用ip_finish_output2與相鄰子系統接口,將數據包目標地址中的IP地址轉換成目標主機的MAC地址。

ip_finish_output2函數是與相鄰子系統接口的函數,該函數的主要任務是,在數據包中為數據鏈路層協議插入其協議頭,或為數據鏈路層協議頭預留分配空間,然后將數據包交給相鄰子系統發送函數dst->neighbour->output處理。

圖21 ip層發送過程函數調用棧

6.2.2 數據包上傳本地主機

當數據包的目標地址為地本機時,在 ip_rcv_finish 函數中會將skb->dst->input初始化為ip_local_deliver。本地發送功能也分兩個階段完成: ip_local_deliver和 ip_local_deliver_finish。

ip_local_deliver函數的主要任務就是重組數據包,重組數據包的功能通過調用ip_defrag函數完成。ip_defrag完成數據包重組后返回指向完整數據包的指針,如果這時還沒有收到數據包的所有分片,數據包還不完整,則它返回NULL。

數據包重組成功,調用網絡過濾子系統的回調函數來查看數據包的配置,並執行ip_local_deliver_finish完成數據包上傳功能。

ip_local_ deliver_finish函數完成后,數據包就離開網絡層上傳至TCP/IP協議棧的傳輸層。

圖22 ip層本地接收函數調用棧

ip層時序圖具體如下:

圖23 網絡層實現數據包接收/發送的API調用關系總圖

7、傳輸層

傳輸層的主要任務是保證數據能准確傳送到目的地。在TCP/IP 協議棧中這個功能由傳輸控制協議(TCP,Transmission Control Protocol)完成。同時傳輸層緊接在應用層下,它也是用戶地址空間的數據進入內核地址空間的接口。TCP/IP 協議棧在傳輸層提供兩個最常用的協議:TCP和UDP,這里我們研究TCP協議的實現。

圖24 套接字,TCP,IP層之間的接口函數關系

7.1 傳輸層的接收過程的實現

為了將輸入數據包傳送給傳輸層正確的協議處理函數,輸入數據包使用的傳輸層協議在IP層處理時已設定。在傳輸層,各協議輸入處理函數在協議初始化時注冊到內核TCP/IP協議棧接口,IP層通過調用ip_local_deliver函數在IP數據包協議頭 iphdr->protocol數據域中設定的值,在傳輸層與IP層之間管理協議處理接口的哈希鏈表inet_protocol中查詢,找到正確的傳輸層協議處理函數塊,並上傳數據包。對於TCP協議,它在inet _protocol結構中初始化的輸入數據包處理函數是tcp_v4_rcv。

1、tcp_v4_rcv函數

在 tcp_v4_rcv函數的起始部分同樣也需要對接收到的skb數據包做一系列的正確性檢查。tcp_v4_rcv函數主要功能包括以下兩個方面:

(1)數據包合法性檢查。

(2)確定數據包是“快速路徑”處理,還是“慢速路徑”處理。

Linux TCP/IP協議棧中,在TCP層有兩條路徑處理輸入數據包:“Fast Path”和“SlowPath”。“Fast Path”是內核優化TCP處理輸入數據包的方式。當TCP協議實例收到一個數據包后,它首先通過協議頭來預定向數據包的去處:“Fast Path”或“Slow Path”。

進入快速路徑的數據段會放入prequeue隊列中,這時用戶進程被喚醒,在 prequeue隊列中的數據段就由用戶層的進程來處理,這個過程省略很多“Slow Path”處理中的步驟,從而加大了數據吞吐量。而慢路徑放入backlog queue,需要調用tcp_v4_do_rcv進行處理。

 

25 tvp_v4_rcv函數處理流程

圖26 tcp_v4_rcv函數調用棧

7.2 傳輸層發送數據流程

TCP協議實現的傳送功能是指將從應用層通過打開的套接字寫入的數據移入內核,通過TCP/IP協議棧,最終通過網絡設備發送到遠端接收主機。一旦應用層打開一個SOCK_STREAM類型的套接字,並發出寫數據請求,就會調用TCP協議實現的傳送例程tcp_sendmsg來處理所有從打開的套接字上傳來的寫數據請求。

tcp_sendmsg 函數是TCP協議初始化時在協議函數塊中注冊發送函數。完成的功能為:

(1)將數據復制到Socket Buffer 中。

(2)把Socket Buffer放入發送隊列。

(3)設置TCP控制塊結構,用於構造TCP協議發送方的頭信息。

tcp_sendmsg將數據從用戶地址空間復制到內核空間,最終所有這些從用戶地址空傳來的數據包,都是通過tcp_write_xmit函數調用 tcp_transmit_skb函數向IP層傳送的。

tcp_transmit_skb函數發送過程主要如下:

(1)tcp_transmit_skb函數初始化

(2)確定TCP數據段協議頭包含的內容

(3)發送數據,調用實際傳送函數將該數據段傳送給IP層

27 tcp_sendmsg函數調用棧

傳輸層TCP發送接收時序圖:

圖28 傳輸層接收/發送路徑

8、套接字接口層

套接字接口最初是BSD操作系統的一部分,在應用層與TCP/IP協議棧之間接供了一套標准的獨立於協議的接口。從TCP/IP 協議棧的角度來看,傳輸層以上的都是應用程序的一部分。Linux與傳統的UNIX類似,TCP/IP協議棧駐留在內核中,與內核的其他組件共享內存。傳輸層以上執行的網絡功能都是在用戶地址空間完成的。Linux使用內核套接字概念與用戶空間套接字通信,這樣使實現和操作更簡單。Linux 提供了一套API和套接字數據結構,這些服務向下與內核接口,向上與用戶空間接口,應用程序使用這一套API訪問內核中的網絡功能。

8.1 套接字API系統調用的實現

系統調用完成將用戶程序的函數調用轉換成對內核功能服務調用,並獲取內核功能服務的返回值,然后傳遞給用戶程序。

Linux中實現系統調用主要分為下列三步:

(1)內核實現了一系列的系統調用.所有系統調用函數以sys_為前綴名(如sys_bind),對應用戶程序的函數調用。內核為每個系統調用分配一個索引號,索引號與系統調用函數的對應關系保存在系統調用表中。

(2)用戶程序調用參數在用戶地址空間,內核功能函數要訪問這些參數,需將其從用戶地址空間映射到內核地址空間。

(3)返回系統調用結果,成功返回0,失敗返回錯誤代碼。

圖29 系統調用示意圖

8.1.1 socketcall系統調用

在Linux內核中只有一個系統調用sys_socketcall 完成用戶程序對所有套接字操作的調用,這需要以不同的參數來決定如何處理用戶程序的調用。傳給sys_socketcall函數的第-一個參數是一個數字常數,sys_socketcall 以該常數為索引選擇需要調用的實際函數。

圖30 linux中socketcall系統調用

8.1.2 sys_socketcall 套接字分路器

sys_socketcall函數接收所有來自應用程序對套接字的20種操作的系統調用,sys_socketcall函數的功能並不復雜,它猶如一個分路器,將來自應用程序的系統調用分支到其他函數上,每個函數實現個小的系統調用功能。

應用程序調用應用層套接字的API函數時,會產生一個內核系統調用中斷,該中斷轉而調用sys_socketcall 函數。

sys_socketcall 函數主要完成兩個功能:

(1)將從用戶地址空間傳來的每一個地址映射到內核地址空間。該功能通過調用copy_from_user函數完成。

(2)將應用層套接字的API函數映射到內核實現函數上。

sys_socketcall函數首先檢查函數調用索引號是否正確,其后調用copy_from_user函數將用戶地址空間參數復制到內核地址空間。最后switch詞句根據系統調用函數索引號,實現套接字分路器的功能,將來自應用程序的系統調用轉到內核實現函數sys_xxx 上。

圖31 sys_socketcall函數分路邏輯 

8.1.3 sys_sendto,sys_recvfrom,sys_socket調用流程

1、創建套接字sys_socket

圖32 創建套接字函數調用流程

2、發送sys_sendto

圖33 放送系統函數調用流程

2、接收sys_recvfrom

 

圖34 接收系統調用流程

9、課程小結

本文從Linux操作系統實現入手,深入的分析了Linux操作系統對於TCP/IP棧的實現原理與具體過程,了解了Linux網絡子系統的具體構成及流程,通過這次調研,使我對TCP/IP協議的原理及具體實現有了極其深入的理解,使我受益匪淺。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM