1. 程式人生 > >net_device結構體

net_device結構體

Device type

MTU

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

Bonding,也被稱作EtherChannel(Cisco的術語)和trunking(Sun的術語),允許把一定數量的介面組合起來當作一個新的裝置。這個特性在系統需要把多個點對點裝置組合起來以獲取更高頻寬時有用。新裝置的速度可以成倍增加,一般來說, 新裝置的吞吐量是單個裝置吞吐量的總和。

VLAN代表虛擬區域網。VLAN的作用是在二層交換機上劃分不同的廣播域,從而把不同廣播域的流量隔離開。它通過在鏈路層上增加一個標記來實現這個功能。你可以在http://www.linuxjournal.com/article/7268

找到VLAN的簡介以及它在LINUX中的使用方法。

所有裝置的net_device結構都放在一個全域性連結串列中,連結串列的頭指標是dev_base。net_device結構的定義在include/linux/netdevice.h中。

與sk_buff類似,net_device結構比較大,而且包含了很多特性相關的引數,這些引數在不同的協議層中使用。出於這個原因,net_device結構的組織會有一些改變,用於優化協議棧的效能。

網路裝置可以分為不同的型別,比如乙太網卡和令牌環網絡卡。net_device結構中的某些變數對同一型別的裝置來說,取值是相同的;而某些變數在同一裝置的不同工作模式下,取值必須不同。因此,對幾乎所有型別的裝置,linux核心提供了一個通用的函式用於初始化那些在所有模式下取值相同的變數。每一個裝置驅動在呼叫這個函式的同時,還初始化那些在當前模式下取值不同的變數。裝置驅動同樣可以覆蓋那些由核心初始化的變數(例如,在優化裝置效能時)。

net_device結構中的變數可以分為以下幾類:

  • Configuration

  • Statistics

  • Device status

  • List management

  • Traffic management

  • Feature specific

  • Generic

  • Function pointers (or VFT)

1. 識別符號

net_device結構包括三個識別符號:

int ifindex
全域性唯一的裝置ID。在每個設備註冊時,呼叫dev_new_index生成。

int iflink
這個變數主要被(虛擬)隧道裝置使用,用於標識隧道的真實裝置。

unsigned short dev_id
IPV6中使用zSeries OSA網絡卡時用到這個變數。這個變數用於區分同一裝置的不同虛擬實體,這些虛擬實體可以在不同的虛擬作業系統中共享。詳細內容,請參閱net/ipv6/addrconf.c中的註釋。

2. Configuration

有些引數可以在核心初始化此類裝置時設定一個預設值,而有些引數就需要留給裝置驅動來填充。裝置驅動可以更改預設值,這個在前面已經說過。有些變數甚至可以在執行時通過命令如ifconfig或ip等來更改。實際上,有些變數,比如base_addr,if_port,dma和irq等通常都是由使用者在載入裝置驅動模組時設定的。但是,這些變數不能被虛擬裝置使用。

char name[IFNAMSIZ]
裝置名稱(比如,eth0)

unsigned long mem_start
unsigned long mem_end
這兩個變數描述裝置與核心通訊所用到的記憶體邊界。它們由裝置驅動初始化,並且只能被裝置驅動訪問;高層協議不需要關心這塊記憶體。

unsigned long base_addr
對映到裝置記憶體空間中I/O記憶體起始地址。

unsigned int irq
裝置中斷號。它可以被多個裝置共享。裝置驅動呼叫request_irq來分配這個值,並呼叫free_irq來釋放它。

unsigned char if_port
介面的埠型別。

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 short flags
unsigned short gflags
unsigned short priv_flags
flags變數中的某些位表示網路裝置所支援的功能(例如是否支援多播IFF_MULTICAST等),其他位表示裝置狀態的變化(例如IFF_UP或者 IFF_RUNNING)。你可以在include/linux/if.h中找到flags 的完整列表。裝置驅動通常在裝置初始化時設定裝置所支援的功能,而裝置狀態則由核心根據某些外部事件來設定。flags的值可以通過命令ifconfig 檢視:

bash# ifconfig lo
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:3924 Metric:1
RX packets:198 errors:0 dropped:0 overruns:0 frame:0
TX packets:198 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0

在這個例子中,UP LOOPBACK RUNNING等名詞分別對應標記IFF_UP, IFF_LOOPBACK,IFF_RUNNING。

priv_flags儲存一些對使用者空間程式不可見的標記。目前,這個變數被VLAN和橋虛擬裝置使用。
gflags幾乎不會被使用,保留它只是為了保持相容。標記可以通過dev_change_flags函式修改。

int features
另一個使用位元位來表示裝置所支援的功能的變數。這個變數與上述的三個變數並不重複。features變數向CPU報告裝置所支援的功能,例如裝置是否支援高階記憶體的DMA,是否支援用硬體計算包的校驗和等。所有可能支援的功能列表都在net_device結構中定義。這個變數由裝置驅動初始化。你可以在 net_device結構的定義中找到相應的NETIF_F_XXX列表,每個巨集都有很好的註釋。

unsigned mtu
MTU的意思是最大傳輸單元,它表示裝置可以處理幀的最大長度。表1列出了一些常見網路裝置所支援的MTU值。

Table 1. MTU values for different device types

PPP

296

SLIP

296

Ethernet

1,500

ISDN

1,500

PLIP

1,500 (ether_setup)

Wavelan

1,500 (ether_setup)

EtherChannel

2,024

FDDI

4,352

Token Ring 4 MB/s (IEEE 802.5)

4,464

Token Bus (IEEE 802.4)

8,182

Token Ring 16 MB/s (IBM)

17,914

Hyperchannel

65,535

在1998年,Alteon Networks(2000年被Nortel Networks收購)提議將乙太網幀的負載大小增加到9K位元組。這個提案後來成為IETF的因特網草案,但是IEEE卻沒有接受它。IEEE規範中超過 1500位元組的幀通常被成為jumbo幀,這種幀用在千兆乙太網中以增加通吐量。這是因為更大的幀意味著用更少的幀可以傳輸更多的資料,從而減少中斷的數量,減少CPU的使用量,並減少頭部資料的佔用率。關於增加乙太網的MTU可以帶來的好處,以及為什麼IEEE沒有接受這個擴充套件的原因可以在白皮書“在乙太網中使用擴充套件的幀大小”中找到。這篇文章可以在網上搜索, 或者使用這個連結:
http://www.ietf.org/proceedings/01aug/I-D/draft-ietf-isis-ext-eth-01.txt

unsigned short type
裝置型別(乙太網,幀中繼等)。在include/linux/if_arp.h中有完整的型別列表。

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

unsigned char broadcast[MAX_ADDR_LEN]
鏈路層廣播地址。

unsigned char dev_addr[MAX_ADDR_LEN]
unsigned char addr_len
dev_addr是裝置的鏈路層地址,不要把它和IP地址或者L3地址混淆了。鏈路層地址的長度是addr_len,以位元組為單位。

int promiscuity
混雜模式

2.1. Interface types and ports

有些裝置可以支援多種介面(最常見的組合是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);
break; }

2.2. 混雜模式

有些網路管理任務需要系統接收所有經過共享線纜的幀,而不僅是發給本機的幀;如果一個裝置可以接收共享線纜上的所有幀,那麼就可以說它工作在混雜模式下。如果一個應用程式需要測試本地網的網路效能或者檢查網路的安全漏洞時,就需要使用混雜模式。混雜模式同樣被網橋程式碼使用。當然,這對那些惡意的監聽者也很有用,所有,在本地網上傳輸的資料都是不安全的,除非資料已被加密。

net_device結構中包含一個promiscuity計數器來標識裝置是否工作在混雜模式。之所以使用計數器而不是一個標誌位的原因是:可能有多個使用者程式設定裝置工作在混雜模式下。因此,每次進入混雜模式,計數器加一;退出混雜模式,計數器減一。只有計數器為0 時,裝置才退出混雜模式。這個變數通常呼叫函式dev_set_promiscuity來設定。

如果promiscuity不為0(呼叫函式dev_set_promiscuity設定),那麼flags變數裡面的 IFF_PROMISC標誌位會被置位,這個標誌位會在配置介面屬性的函式中被檢查。下面是一段摘自drivers/net/3c59x.c的程式碼,它展示了根據flags中的標誌位來設定不同接收模式的過程:

static void set_rx_mode(struct net_device *dev)  
{
int ioaddr = dev->base_addr;
int new_mode;
if (dev->flags & IFF_PROMISC) {
if (corqscreq_debug > 3)
printk("%s: Setting promiscuous mode./n", dev->name);
new_mode = SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm;
} else if ((dev->mc_list) || (dev->flags & IFF_ALLMULTI)) {
new_mode = SetRxFilter | RxStation | RxMulticast | RxBroadcast;
} else
new_mode = SetRxFilter | RxStation | RxBroadcast;
outw(new_mode, ioaddr + EL3_CMD); }

如果設定了IFF_PROMISC標誌,new_mode變數會被初始化為接收發給本機的幀(RxStation),多播幀(RxMulticast),廣播幀(RxBroadcast),和其他所有的幀(RxProm)。EL3_CMD是距離裝置記憶體起始地址的偏移量,它是核心傳送給裝置的命令所儲存的地址。

3. Statistics

net_device結構裡面沒有包含描述各種統計資訊的變數,相反,它包含一個名為priv的指標,這個指標指向一個裝置私有的,描述裝置統計資訊的資料結構。這個私有資料結構中包括了諸如裝置發包數,裝置收包數,以及出錯包數等統計資訊的變數。

priv所指向的資料結構的定義與裝置型別和裝置的工作模式有關:因此,不同的乙太網卡可能擁有不同的私有資料結構。但是,幾乎所有的結構都包含一個型別是net_device_stats的變數(在include/linux/netdevice.h中定義),它包含了所有裝置都具有的常用統計資訊,這些資訊可以通過函式get_stats來獲取。

無線裝置的行為與有線裝置的行為不同,因此,在無線裝置裡沒有net_device_stats型別的變數。相反,無線裝置有一個型別是iw_statistics的變數,這個變數的值可以通過函式get_wireless_stats來獲取,這個函式會在後面描述。

priv所指向的資料結構的名稱有時會反映裝置的型別(例如,vortex_private代表Vortex和 Boomerang系列,有時也被稱作3c59x系列)。一般情況下,它的名字就是簡單的net_local。但是,net_local中的變數對不同的裝置來說,都是不同的。

私有資料結構的複雜度與裝置所支援的功能,以及裝置驅動作者希望支援多麼複雜的統計功能,或者多麼複雜的設計以提高裝置效能有關。例如,drivers/net/3c507.c中的3c507驅動所定義的net_local就比drivers/net/3c59x.c中的 3c59x驅動所定義的vortex_private簡單的多。當然,它們都包含一個型別是net_device_stats的變數。

4. Device Status

為了控制與裝置的互動,每個裝置驅動都會維護一些資訊,比如:時間戳,介面支援哪些功能的標記等。在SMP系統中,核心需要保證不同cpu對同一裝置的併發訪問能夠正確地執行。net_device結構中的以下幾個變數實現這些功能:

unsigned long 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);
}

enum {...} reg_state
裝置的註冊狀態。


unsigned long trans_start
最後一個幀開始傳送的時間(用jiffies度量)。裝置驅動在傳送之前設定這個變數。這個變數用來檢測網絡卡是否在給定的時間內把幀傳送了出去。太長的傳送時間意味著有錯誤發生,在這種情況下,裝置驅動會重置網絡卡。


unsigned long last_rx

接收到最後一個包的時間(用jiffies度量)。目前,這個變數沒有什麼特殊的用途,但是,將來有可能會用到。


struct net_device *master

有些協議允許多個裝置組合到一起當做一個裝置使用。這些協議包括EQL(序列網路的覆載均衡),Bonding(又被稱作EtherChannel和trunking),和TEQL(trueequalizer,它是流量控制子系統中的一個排隊策略)。在裝置組中,有一個裝置被選出來當作主裝置,它有特殊的作用。這個變數是一個指向組中主裝置的指標。如果裝置不是一個組的成員,這個指標被置為NULL。


spinlock_t xmit_lock


int xmit_lock_owner

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


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地址列表等。

5. List Management

net_device結構放在一個全域性的連結串列和兩個雜湊表中。以下變數用於組成這些連結串列和雜湊表:

struct net_device *next

指向全域性連結串列中的下一個節點

struct hlist_node name_hlist


struct hlist_node index_hlist

把net_device結構連結到兩個雜湊表中。

6. Link Layer Multicast

多播是一種將資料傳遞給多個接收者的技術。多播既可以在L3(如ip)上實現,也可以在L2(如乙太網)上實現。在這一節,我們關注的是後面一種實現。

鏈路層多播可以通過特殊的多播地址或者在鏈路層頭部填入特殊的控制資訊來實現(如果鏈路層協議不支援多播,可以採用模擬的方式實現)。乙太網本身就支援多播。多播地址通過一個特定的位與其他地址區分開。這就意味著,有50%的地址是多播地址,它的數量是2的48次方,這是一個巨大的數字。如果一個介面要加入一個多播組(一個多播地址就是一個多播組),它可以簡單地接收所有多播地址的幀,而不是維護一個多播地址列表,並且過濾那些不在列表中的地址。net_device結構中的flags變數中有一位來控制裝置是否監聽所有的地址。裝置驅動設定或清除這個標記的動作由 all_multi變數控制。

每個net_device結構都會包含一個dev_mc_list型別的變數,這個變數維護裝置監聽的多播地址表。新增和刪除多播地址可以分別呼叫函式dev_mc_add和dev_mc_delete完成。在net_device中,相關的變數還包括:

struct dev_mc_list *mc_list

Pointer to the head of this device's list of dev_mc_list structures.


int mc_count

The number of multicast addresses for this device, which is also the length of the list to which mc_list points.


int allmulti

如果是非零值,那麼裝置將監聽所有的多播地址。和promiscuity一樣,這個變數是一個計數器而不僅是一個布林值。這是因為多個裝置(比如VLAN和bonding裝置)可能獨立地要求監聽所有地址。如果這個變數的值從0變為非零,核心會呼叫函式 dev_set_allmulti通知裝置監聽所有的多播地址。如果這個值變為0,則停止監聽所有的多播地址。

7. Traffic Management

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用於避免併發的訪問ingress_lock用於保護接收佇列。


unsigned long tx_queue_len

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

Table 2. tx_queue_len values for different device types

Device type

tx_queue_len

Ethernet

1,000

Token Ring

100

EtherChannel

100

Fibre Channel

100

FDDI

100

TEQL (true link equalizer)

100

ISDN

30

HIPPI

25

PLIP

10

SLIP

10

AX25

10

EQL (Equalizer load balancer for serial network interfaces)

5

Generic PPP

3

Bonding

0

Loopback

0

Bridge

0

VLAN

0

a TEQL is one of the queuing disciplines you can configure with Traffic Control (the QoS layer).


用不用tx_queue_len與裝置的接收,傳送佇列的排隊策略有關。一般情況下都使用FIFO
佇列(先進先出)或者其他一些簡單的佇列。

虛擬裝置的佇列長度一般都是0:它們依賴於真實裝置來完成包的快取(loopback裝置是一個特例,它們不需要佇列,因為發給它們的包一般都是在核心中立即傳送的)。

8. Feature Specific

就像我們在sk_buff中看到的一樣,net_device中的有些變數是與特性相關的,只有核心包含了這個特性,這些變數才會有效。[]這些變數只有在核心包含某些特性是才有效,比如br_port

struct divert_blk *divert

Diverter功能允許你修改進入包的源地址和目的地址。它可以把特定包轉發到某個特定介面或主機。為了使用這個功能,核心必須包含其他一些功能,比如網橋。這個指標指向的資料結構中包含了這個功能所使用的變數。相應的核心選項是“Device drivers ->Networking support ->Networking options ->Frame Diverter”。

struct net_bridge_port *br_port

裝置被配置成網橋介面時所需要的附加資訊。相應的核心選項是“Device drivers ->Networking support ->Networking options->802.1d Ethernet Bridging”。


void (*vlan_rx_register)(...)


void (*vlan_rx_add_vid)(...)


void (*vlan_rx_kill_vid)(...)

這三個函式指標被VLAN裝置用來註冊裝置可以處理的VLAN標記(參見net/8021q/vlan.c),它可以向裝置增加一個VLAN或者從裝置上刪除一個VLAN。相應的核心選項是“Device drivers ->Networking support ->Networking options->802.1Q VLAN Support”


int netpoll_rx


void (*poll_controller)(...)

9. Generic

除了前面提到的維護裝置連結串列的變數外,還有一些變數用於維護net_device結構。需要注意的是,如果這些變數不再有什麼作用,就會被刪掉:

atomic_t refcnt

引用計數。如果計數不為0,裝置就不能被解除安裝


int watchdog_timeo


struct timer_list watchdog_timer

這些變數,包括前面提到的tx_timeout變數,用於實現 “Watchdog timer”


int (*poll)(...)


struct list_head poll_list


int quota


int weight


const struct iw_handler_def *wireless_handlers


struct iw_public_data *wireless_data

Additional parameters and function pointers used by wireless devices. See also get_wireless_stats.


struct list_head todo_list

註冊和解除安裝一個網路裝置需要兩個步驟,todo_list在第二個步驟中使用。


struct class_device class_dev

Used by the new generic kernel driver infrastructure.

10. Function Pointers

核心網路程式碼使用了很多函式指標。net_device結構中同樣包括了許多函式指標。這些函式指標主要完成以下幾個功能:

  • Transmit and receive a frame

  • Add or parse the link layer header on a buffer

  • Change a part of the configuration

  • Retrieve statistics

  • Interact with a specific feature

A few function pointers were already introduced in the previous sections when describing the fields used to accomplish a specific task. Here are the generic ones:

struct ethtool_ops *ethtool_ops

設定或獲取不同裝置引數的一組函式指標。


int (*init)(...)


void (*uninit)(...)


void (*destructor)(...)


int (*open)(...)


int (*stop)(...)

Used to initialize, clean up, destroy, enable, and disable a device. Not all of them are always used.


struct net_device_stats* (*get_stats)(...)


struct iw_statistics* (*get_wireless_stats)(...)

有些裝置統計資訊可以通過使用者空間程式顯示,比如ifconfig和ip;而其他統計資訊只能被核心使用。有兩個函式用於獲取裝置統計資訊。get_stats用於操作一個普通裝置,而get_wireless_stats
用於操作一個無線裝置。


int (*hard_start_xmit)(...)

Used to transmit a frame.


int (*hard_header)(...)


int (*rebuild_header)(...)


int (*hard_header_cache)(...)


void (*header_cache_update)(...)


int (*hard_header_parse)(...)


int (*neigh_setup)(...)

Used by the neighboring layer.


int (*do_ioctl)(...)

ioctl is the system call used to issue commands to devices . This method is called to process some of the ioctl commands .


void (*set_multicast_list)(...)

mc_list和mc_count用於維護L2的多播地址表。這個方法用於設定裝置驅動監聽哪些多播地址。通常情況下,它不會被直接呼叫,而是通過一個包裝函式,比如dev_mc_upload或者不加鎖版本__dev_mc_upload呼叫。如果一個裝置不能維護一個多播地址表,那麼可以簡單地設定它監聽所有的多播地址。


int (*set_mac_address)(...)

Changes the device MAC address. When the device does not provide this capability (as in the case of Bridge virtual devices), it is set to NULL.


int (*set_config)(...)

設定裝置引數,比如irq,io_addr,和if_port等。高層引數(比如協議地址)由do_ioctl設定。只有很少的裝置使用這個函式。新的裝置一般都採用自動探測的方式獲取這些引數。你可以在drivers/net/sis900.c中找到 sis900_set_config,這是一個很好的例子。


int (*change_mtu)(...)

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


void (*tx_timeout)(...)

在watchdog定時器超時後呼叫這個函式。它用來確定傳送一個幀的時間是否太長。如果這個函式沒有定義,watchdog定時器就不會被啟用。


int (*accept_fastpath)(...)

快速交換(又被稱作FASTROUTE)是核心的一個功能,它允許裝置驅動在中斷上下文中用一個小的快取來快速路由進入的包(跳過協議層軟體)。從2.6.8版開始, 核心已經不支援這個功能了。這個函式用於測試一個裝置是否支援快速交換功能。

     http://blog.chinaunix.net/u/26185/showart_455200.html