1. 程式人生 > >Linux 網路裝置驅動開發(二) —— Linux 網路棧剖析

Linux 網路裝置驅動開發(二) —— Linux 網路棧剖析

一、協議簡介

    雖然對於網路的正式介紹一般都參考了 OSI(Open Systems Interconnection)模型,但是本文對 Linux 中基本網路棧的介紹分為四層的 Internet 模型(如圖 1 所示)。

圖 1. 網路棧的 Internet 模型
網路棧的 Internet 模型

    這個棧的最底部是鏈路層。鏈路層是指提供對物理層訪問的裝置驅動程式,這可以是各種介質,例如串列埠鏈路或乙太網裝置。鏈路層上面是網路層,它負責將報文定向到目標位置。再上一層稱為傳輸層,負責端到端的通訊(例如,在一臺主機內部)。儘管網路層負責管理主機之間的通訊,但是傳輸層需要負責管理主機內部各端之間的通訊。最後一層是應用層

,它通常是一個語義層,能夠理解要傳輸的資料。例如,超文字傳輸協議(HTTP)就負責傳輸伺服器和客戶機之間對 Web 內容的請求與響應。

   實際來說,網路棧的各個層次有一些更為人所熟知的名字。在鏈路層上,可以找到乙太網,這是最常用的一種高速介質。更早的鏈路層協議包括一些串列埠協議,例如 SLIP(Serial Line Internet Protocol)、CSLIP(Compressed SLIP)和PPP(Point-to-Point Protocol)。最常見的網路層協議是 IP(Internet Protocol),但是網路層中還存在一些滿足其他需求的協議,例如 ICMP(Internet Control Message Protocol)和ARP( Address Resolution Protocol)。在傳輸層上是 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)。最後,應用層中包含很多大家都非常熟悉的協議,包括標準的 Web 協議 HTTP 和電子郵件協議 SMTP(Simple Mail Transfer Protocol)。

二、核心網路架構

現在繼續瞭解 Linux 網路棧的架構以及如何實現這種 Internet 模型。圖 2 提供了 Linux 網路棧的高階檢視。最上面是使用者空間層,或稱為應用層,其中定義了網路棧的使用者。底部是物理裝置,提供了對網路的連線能力(串列埠或諸如乙太網之類的高速網路)。中間是核心空間,即網路子系統,也是本文介紹的重點。流經網路棧內部的是 socket 緩衝區(sk_buffs),它負責在源和匯點之間傳遞報文資料。您很快就將看到sk_buff 的結構。

圖 2. Linux 高階網路棧架構
Linux 高階網路棧架構

     首先,讓我們來快速瀏覽一下 Linux 網路子系統的核心元素,後續章節中會更詳細進行介紹。頂部(請參閱圖 2)是系統呼叫介面。它簡單地為使用者空間的應用程式提供了一種訪問核心網路子系統的方法。位於其下面的是一個協議無關層,它提供了一種通用方法來使用底層傳輸層協議。然後是實際協議,在 Linux 中包括內嵌的協議 TCP、UDP,當然還有 IP。然後是另外一個協議無關層,提供了與各個裝置驅動程式通訊的通用介面,最下面是裝置驅動程式本身。

三、系統呼叫介面

   系統呼叫介面可以從兩個角度進行描述。使用者發起網路呼叫時,通過系統呼叫介面進入核心的過程應該是多路的。最後呼叫 ./net/socket.c 中的sys_socketcall 結束該過程,然後進一步將呼叫分路傳送到指定目標。系統呼叫介面的另一種描述是使用普通檔案操作作為網路 I/O。例如,典型的讀寫操作可以在網路 socket 上執行(socket 使用一個檔案描述符表示,與一個普通檔案一樣)。因此,儘管有很多操作是網路專用的(使用 socket 呼叫建立一個 socket,使用 connect 呼叫連線一個收信方,等等),但是也有一些標準的檔案操作可以應用於網路物件,就像操作普通檔案一樣。最後,系統呼叫介面提供了在使用者空間應用程式和核心之間轉移控制的方法。

四、協議無關介面

     socket 層是一個協議無關介面,它提供了一組通用函式來支援各種不同協議。socket 層不但可以支援典型的 TCP 和 UDP 協議,而且還可以支援 IP、裸乙太網和其他傳輸協議,例如 SCTP(Stream Control Transmission Protocol)。

     通過網路棧進行的通訊都需要對 socket 進行操作。Linux 中的 socket 結構是 struct sock,這個結構是在 linux/include/net/sock.h 中定義的。這個巨大的結構中包含了特定 socket 所需要的所有狀態資訊,其中包括 socket 所使用的特定協議和在 socket 上可以執行的一些操作。

     網路子系統可以通過一個定義了自己功能的特殊結構來了解可用協議。每個協議都維護了一個名為 proto 的結構(可以在 linux/include/net/sock.h 中找到)。這個結構定義了可以在從 socket 層到傳輸層中執行特定的 socket 操作(例如,如何建立一個 socket,如何使用 socket 建立一個連線,如何關閉一個 socket 等等)。

五、網路協議

     網路協議這一節對一些可用的特定網路協議作出了定義(例如 TCP、UDP 等)。它們都是在 linux/net/ipv4/af_inet.c 檔案中一個名為inet_init 的函式中進行初始化的(因為 TCP 和 UDP 都是 inet 簇協議的一部分)。 inet_init 函式使用 proto_register 函式來註冊每個內嵌協議。這個函式是在 linux/net/core/sock.c 中定義的,除了可以將這個協議新增到活動協議列表中之外,如果需要,該函式還可以選擇分配一到多個 slab 快取。

    通過 linux/net/ipv4/ 目錄中 udp.c 和 raw.c 檔案中的 proto 介面,您可以瞭解各個協議是如何標識自己的。這些協議介面每個都按照型別和協議對映到 inetsw_array,該陣列將內嵌協議與操作對映到一起。inetsw_array 結構及其關係如圖 3 所示。最初,會呼叫 inet_init 中的inet_register_protosw 將這個陣列中的每個協議都初始化為 inetsw。函式 inet_init 也會對各個 inet 模組進行初始化,例如 ARP、ICMP 和 IP 模組,以及 TCP 和 UDP 模組。

圖 3. Internet 協議陣列結構
Internet 協議陣列結構

Socket 協議的相互關係

回想以下在建立 socket 時,需要指定型別和協議,例如my_sock = socket( AF_INET, SOCK_STREAM, 0 )AF_INET 表示一個 Internet 地址簇,它使用的是一個流 socket,定義為 SOCK_STREAM(如此處的inetsw_array 所示)。

       注意在 圖 3 中,proto 結構定義了傳輸特有的方法,而 proto_ops 結構則定義了通用的 socket 方法。可以通過呼叫inet_register_protosw 將其他協議加入到 inetsw 協議中。例如,SCTP 就是通過呼叫 linux/net/sctp/protocol.c 中的 sctp_init 加入其中的。有關 SCTP 的更多資訊,請參閱 參考資料 一節的內容。

      socket 中的資料移動是使用一個所謂的 socket 緩衝區(sk_buff)的核心結構實現的。sk_buff 中包含了報文資料,以及涉及協議棧中多個層次的狀態資料。所傳送或接收的每個報文都是使用一個 sk_buff 表示的。sk_buff 結構是在 linux/include/linux/skbuff.h 中定義的,如圖 4 所示。

圖 4. Socket 緩衝區及其與其他結構的關係
Socket 緩衝區及其與其他結構的關係

      如圖所示,多個 sk_buff 可以針對某個給定連線連結在一起。每個 sk_buff 都在裝置結構(net_device)中標識報文傳送的目的地,或者接收報文的來源地。由於每個報文都是使用一個 sk_buff 表示的,因此報文頭都可以通過一組指標(thiph 和 mac[用於 Media Access Control 或者 MAC 頭])方便地進行定位。由於 sk_buff 是 socket 資料管理的中心,因此建立了很多支援函式來對它們進行管理。其中有些函式用於建立和銷燬 sk_buff 結構,或對它進行克隆或排隊管理。

     針對給定的 socket,Socket 緩衝區可以連結在一起,這樣可以包含眾多資訊,包括到協議頭的連結、時間戳(報文是何時傳送或接收的),以及與這個報文相關的裝置。

六、裝置無關介面

      協議層下面是另外一個無關介面層,它將協議與具有很多各種不同功能的硬體裝置連線在一起。這一層提供了一組通用函式供底層網路裝置驅動程式使用,讓它們可以對高層協議棧進行操作。

     首先,裝置驅動程式可能會通過呼叫 register_netdevice 或 unregister_netdevice 在核心中進行註冊或登出。呼叫者首先填寫net_device 結構,然後傳遞這個結構進行註冊。核心呼叫它的 init 函式(如果定義了這種函式),然後執行一組健全性檢查,並建立一個sysfs 條目,然後將新裝置新增到裝置列表中(核心中的活動裝置連結串列)。在 linux/include/linux/netdevice.h 中可以找到這個 net_device 結構。這些函式都是在 linux/net/core/dev.c 中實現的。

     要從協議層向裝置中傳送 sk_buff,就需要使用 dev_queue_xmit 函式。這個函式可以對 sk_buff 進行排隊,從而由底層裝置驅動程式進行最終傳輸(使用 sk_buff 中引用的 net_device 或 sk_buff->dev 所定義的網路裝置)。dev 結構中包含了一個名為hard_start_xmit 的方法,其中儲存有發起 sk_buff 傳輸所使用的驅動程式函式。

     報文的接收通常是使用 netif_rx 執行的。當底層裝置驅動程式接收一個報文(包含在所分配的 sk_buff 中)時,就會通過呼叫 netif_rx將 sk_buff 上傳至網路層。然後,這個函式通過 netif_rx_schedule 將 sk_buff 在上層協議佇列中進行排隊,供以後進行處理。可以在 linux/net/core/dev.c 中找到 dev_queue_xmit 和 netif_rx 函式。

    最近,核心中引入了一種新的應用程式程式設計介面(NAPI),該介面允許驅動程式與裝置無關層(dev)進行互動。有些驅動程式使用的是 NAPI,但是大多數驅動程式仍然在使用老式的幀接收介面(比例大約是 6 比 1)。NAPI 在高負載的情況下可以產生更好的效能,它避免了為每個傳入的幀都產生中斷。

七、裝置驅動程式

      網路棧底部是負責管理物理網路裝置的裝置驅動程式。例如,包串列埠使用的 SLIP 驅動程式以及乙太網裝置使用的乙太網驅動程式都是這一層的裝置。

     在進行初始化時,裝置驅動程式會分配一個 net_device 結構,然後使用必須的程式對其進行初始化。這些程式中有一個是 dev->hard_start_xmit,它定義了上層應該如何對 sk_buff 排隊進行傳輸。這個程式的引數為 sk_buff。這個函式的操作取決於底層硬體,但是通常 sk_buff 所描述的報文都會被移動到硬體環或佇列中。就像是裝置無關層中所描述的一樣,對於 NAPI 相容的網路驅動程式來說,幀的接收使用了 netif_rx 和 netif_receive_skb 介面。NAPI 驅動程式會對底層硬體的能力進行一些限制。

    裝置驅動程式在 dev 結構中配置好自己的介面之後,呼叫 register_netdevice 便可以使用該配置。在 linux/drivers/net 中可以找出網路裝置專用的驅動程式。