天天看點

Linux網絡協定棧(三)——網絡裝置(1)

網絡裝置(network device)是核心對網絡擴充卡(硬體)的抽象與封裝,并為各個協定執行個體提供統一的接口,它是硬體與核心的接口,它有兩個特征:

(1)    作為基于硬體的網絡擴充卡與基于軟體的協定之間的接口;

(2)    核心協定棧異步輸入輸出點。

記住:網絡裝置軟體對硬體的抽象

網絡裝置與協定和網絡擴充卡的關系如下:

1、    net_device接口(net_device Interface)

     網絡裝置是核心中除了字元裝置、塊裝置之外第三類主要裝置,它的主要特征之一就是在裝置檔案系統/dev/沒有相應的表示,即不存在/dev/eth0等,這就意味着不能通過簡單的讀寫操作來通路它們。

    net_device結構儲存與網絡裝置相關的所有資訊。每一個網絡裝置都對應一個這樣的結構,包括真實裝置(例如以太網卡)和虛拟裝置(比如 bonding 或 VLAN)。

所有裝置的 net_device 結構都放在一個全局連結清單中,連結清單的頭指針是 dev_base。net_device結構的定義在include/linux/netdevice.h中。與 sk_buff 類似,net_device 結構比較大,而且包含了很多特性相關的參數,這些參數在不同的協定層中使用。出于這個原因,net_device 結構的組織會有一些改變,用于優化協定棧的性能。 網絡裝置可以分為不同的類型,比如以太網卡和令牌環網卡。net_device 結構中的某些變量對同一類型的裝置來說, 取值是相同的; 而某些變量在同一裝置的不同工作模式下,取值必須不同。是以,對幾乎所有類型的裝置,linux核心提供了一個通用的函數用于初始化那些在所有模式下取值相同的變量。每一個裝置驅動在調用這個函數的同時,還初始化那些在目前模式下取值不同的變量。裝置驅動同樣可以覆寫那些由核心初始化的變量(例如,在優化裝置性能時)。

  net_device的定義:

Linux網絡協定棧(三)——網絡裝置(1)

Code

    net_device結構主要分為以下幾部分:

1.1、    通用字段

name:

網絡擴充卡的名稱,比如eth0。在注冊網絡裝置時可以為裝置配置設定一個名稱,便必須唯一。

next:

所有的網絡裝置組成一個由dev_base開頭的連結清單。

int ifindex :

全局唯一的裝置ID。在每個裝置注冊時,調用dev_new_index 生成。

int iflink:

這個變量主要被(虛拟)隧道裝置使用,用于辨別隧道的真實裝置。

state:

它包含一組被網絡隊列子系統使用的标記。這些标記的值是枚舉類型netdev_state_t中的索引值,這個類型的定義在 include/linux/netdevice.h 中,每個标記都是諸如__LINK_STATE_XOFF 這樣的常量。每一位都可以通過函數 set_bit 和 clear_bit 設定或清除,但通常情況下,都會有一個包裝函數來隐藏标記位的資訊。例如,在網絡隊列子系統停止一個裝置隊列時,它調用函數 netif_stop_queue,這個函數的定義如下: 

  static inline void netif_stop_queue(struct net_device *dev) 

        ... 

        set_bit(_ _LINK_STATE_XOFF, &dev->state); 

}

trans_start:

最後一個幀開始發送的時間(用jiffies度量)。裝置驅動在發送之前設定這個變量。這個變量用來檢測網卡是否在給定的時間内把幀發送了出去。 太長的發送時間意味

着有錯誤發生,在這種情況下,裝置驅動會重置網卡。

last_rx :

接收到最後一個包的時間(用jiffies度量)。

xmit_lock 和xmit_lock_owner :

xmit_lock 用來序列化對裝置驅動函數hard_start_xmit的調用。這意味着,每個cpu每次隻能調用裝置完成一次發送。xmit_lock_owner 是擁有鎖的 CPU 的 ID。在單cpu 系統上,這個值是 0;在多 cpu 系統中,如果鎖沒有被占用,這個值是-1。核心同樣允許不加鎖的發送,前提條件是裝置驅動必須支援這個功能。

struct hlist_node name_hlist 

struct hlist_node index_hlist 

  把net_device結構連結到兩個哈希表中。

1.2、    硬體相關

unsigned int irq 

  裝置中斷号。它可以被多個裝置共享。裝置驅動調用request_irq來配置設定這個值,并

調用free_irq來釋放它。 

unsigned char if_port 

  接口的端口類型。有些裝置可以支援多種接口(最常見的組合是 BNC+RJ45),使用者可以根據需要來選擇使用哪種接口。這個變量用來設定裝置的接口類型。如果配置指令沒有指定裝置的接口類型,裝置驅動就使用預設的類型。在某些情況下,一個裝置驅動可以處理多種接口類型;在這種情況下,裝置驅動可以按一定的順序來測試每個接口的類型。下面的代碼片斷展示了一個裝置驅動如何根據配置來設定接口的類型: 

  switch (dev->if_port) { 

                case    IF_PORT_10BASE2: 

                       writeb((readb(addr) & 0xf8) | 1, addr); 

                        break; 

                case    IF_PORT_10BASET: 

                       writeb((readb(addr) & 0xf8), addr); 

                } 

unsigned char dma 

  裝置所使用的 DMA 通道。為擷取和釋放一個 DMA 通道,核心在 kernel/dma.c 中定義了兩個函數request_dma和free_dma。為了在擷取dma通道後,啟用或者停止dma通道,核心定義了兩個函數enable_dma和disable_dma。這兩個函數的實作與

體系結構相關,是以在 include/asm-architecture 下有相關的檔案(例如include/asm-i386)。這些函數被 ISA 裝置使用;PCI 裝置不使用這些函數,它們使

用其他函數。并不是所有的裝置都可以使用dma,因為有些總線不支援dma。

 unsigned long mem_start 

unsigned long mem_end 

  這兩個變量描述裝置與核心通信所用到的記憶體邊界。它們由裝置驅動初始化,并且隻能被裝置驅動通路;高層協定不需要關心這塊記憶體。 

unsigned long base_addr 

  映射到裝置記憶體空間中I/O 記憶體起始位址。

1.3、    實體層相關

unsigned mtu 

  MTU 的意思是最大傳輸單元,它表示裝置可以處理幀的最大長度。不同裝置的MTU值:

unsigned short type 

    裝置類型(以太網,幀中繼等)。在include/linux/if_arp.h 中有完整的類型清單。 

unsigned short hard_header_len 

  以位元組為機關的幀頭部長度。例如,以太網幀的頭是 14 位元組。某種裝置所支援幀的頭部長度在相應的裝置頭檔案中定義。對以太網來說,ETH_HLEN 在

<include/linux/if_ether.h>中定義。 

unsigned char broadcast[MAX_ADDR_LEN] 

  鍊路層廣播位址。 

unsigned char dev_addr[MAX_ADDR_LEN] 

unsigned char addr_len 

  dev_addr是裝置的鍊路層位址,不要把它和IP 位址或者L3 位址混淆了。鍊路層位址的長度是 addr_len,以位元組為機關。addr_len 的大小與裝置類型有關。以太網位址的長度是8。 

int promiscuity

promiscuity計數器來辨別裝置是否工作在混雜模式。之是以使用計數器而不是一個标志位的原因是:可能有多個使用者程式設定裝置工作在混雜模式下。是以,每次進入混雜模式,計數器加一;退出混雜模式,計數器減一。隻有計數器為0 時,裝置才退出混雜模式。這個變量通常調用函數dev_set_promiscuity 來設定。

struct dev_mc_list *mc_list 

  指向dev_mc_list結構 

int mc_count 

  裝置多點傳播位址的數量,它同樣表示mc_list所指向連結清單的長度。 

int allmulti 

  如果是非零值,那麼裝置将監聽所有的多點傳播位址。和 promiscuity 一樣,這個變量是一個計數器而不僅僅是一個布爾值。這是因為多個裝置(比如VLAN和bonding

裝置)可能獨立地要求監聽所有位址。如果這個變量的值從0變為非零,核心會調用函數dev_set_allmulti通知裝置監聽所有的多點傳播位址。如果這個值變為0,則停止監聽所有的多點傳播位址。

1.4、    協定相關

void *atalk_ptr 

void *ip_ptr 

void *dn_ptr 

void *ip6_ptr 

void *ec_ptr 

void *ax25_ptr 

這六個變量指向特定協定的資料結構,每個資料結構都包含協定私有的參數。例如,ip_ptr 指向一個 in_device 類型的結構(盡管 ip_ptr 的類型是 void*),它包含 IPv4相關的參數,其中包括裝置的 IP 位址清單等。

1.5、    流量管理

Linux 流量控制子系統的功能已經非常強大,并且已經成為 Linux 核心中的一個重要元件。相關的核心選項是 “Device drivers ->Networking support ->Networking options ->QoS and/or fair queueing”。net_device中的相關變量包括: 

struct net_device *next_sched 

  被核心軟中斷使用。 

struct Qdisc *qdisc 

struct Qdisc *qdisc_sleeping 

struct Qdisc *qdisc_ingress 

struct list_head qdisc_list 

  這些變量管理裝置的接收,發送隊列,并且可以被不同的cpu通路。 

spinlock_t queue_lock 

spinlock_t ingress_lock 

  流量控制子系統為每個網絡裝置定義了一個私有的發送隊列。 queue_lock用于避免并發的通路(參見第11章)。ingress_lock 用于保護接收隊列。 

unsigned long tx_queue_len 

  裝置發送隊列的長度。如果核心中包含了流量控制子系統,這個變量可能沒有什麼用(隻有幾個排隊政策會使用它)。常見裝置的 tx_queue_len 值(這個值可以通過sysfs檔案系統修改(在/sys/class/net/device_name/目錄下)):

1.6、    裝置驅動程式相關

int (*init)(...) 

void (*uninit)(...) 

void (*destructor)(...) 

int (*open)(...) 

int (*stop)(...) 

用于初始化,清除,銷毀,啟用和停止一個裝置。這些函數并不是每個裝置都會用到。

int (*hard_start_xmit)(...) 

 發送一個幀。

int (*hard_header)(...)

 根據源和目标的第2層位址建立第2層封包頭。

int (*rebuild_header)(...)

 負責在傳送包之前重建第2導封包頭。

int (*set_mac_address)(...) 

  修改裝置的 MAC 位址。如果裝置不提供這個功能(比如網橋裝置),可以把這個指針設定為NULL。

int (*change_mtu)(...) 

  修改裝置的MTU,修改mtu 不會對裝置驅動有任何影響,它隻是讓協定棧軟體可以根據新的mtu 正确地處理分片。

繼續閱讀