歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux 網絡設備驅動開發(三) —— 網絡設備驅動基本原理和框架

Linux 網絡設備驅動開發(三) —— 網絡設備驅動基本原理和框架

日期:2017/3/1 12:09:49   编辑:關於Linux

一、協議棧層次對比
\

二、Linux網絡子系統

Linux網絡子系統的頂部是系統調用接口層。它為用戶空間提供的應用程序提供了一種訪問內核網絡子系統的方法(socket)。位於其下面是一個協議無關層,它提供一種通用的方法來使用傳輸層協議。然後是具體協議的實現,在Linux中包括內核的協議TCP,UDP,當然還有IP。然後是設備無關層,它提供了協議與設備驅動通信的通用接口,最下面是設備的驅動程序。


\

設備無關接口將協議與各種網絡驅動連接在一起,這一層提供一組通用函數供底層網絡設備驅動使用,讓它們可以對高層協議棧進行操作。需要從協議層向設備發生數據,需要調用dev_queue_xmit函數,這個函數對數據進行列隊,然後交由底層驅動程序的hard_start_xmit方法最終完成傳輸。接收通常是使用netif_rx執行的。當底層設備程序接收到一個報文(發生中斷)時,就會調用netif_rx將數據上傳至設備無關層。


三、設備無關層到驅動層的體系結構

下圖為設備無關層到驅動層的體系結構
\

1)、網絡協議接口層向網絡層協議提供提供統一的數據包收發接口,不論上層協議為ARP還是IP,都通過dev_queue_xmit()函數發送數據,並通過netif_rx()函數接受數據。這一層的存在使得上層協議獨立於具體的設備。
2)、網絡設備接口層向協議接口層提供統一的用於描述具體網絡設備屬性和操作的結構體net_device,該結構體是設備驅動功能層中各函數的容器。實際上,網絡設備接口層從宏觀上規劃了具體操作硬件的設備驅動功能層的結構。
3)、設備驅動功能層各函數是網絡設備接口層net_device數據結構的具體成員,是驅使網絡設備硬件完成相應動作的程序,他通過hard_start_xmit()函數啟動發送操作,並通過網絡設備上的中斷觸發接受操作。
4)、網絡設備與媒介層是完成數據包發送和接受的物理實體,包括網絡適配器和具體的傳輸媒介,網絡適配器被驅動功能層中的函數物理上驅動。對於Linux系統而言,網絡設備和媒介都可以是虛擬的。

1、網絡協議接口層:

這裡主要進行數據包的收發,使用函數原型為:

dev_queue_xmit(struct sk_buff *skb);int netif_rx(struct sk_buff *skb);  
這裡使用了一個skb_buff結構體,定義於include/linux/skbuff.h中,它的含義為“套接字緩沖區”,用於在Linux網絡子系統各層間傳輸數據。他是一個雙向鏈表,在老的內核中會有一個list域指向sk_buff_head也就是鏈表頭,但是在我研究的linux2.6.30.4內核中已經不存在了,如下圖:
\

sk_buff中重要的數據成員

struct device *dev;正在處理該包的設備

__u32 sadd;r//IP元地址

__u32 daddr;//IP目的地址

__u32 raddr;//IP路由器地址

unsigned char *head;//分配空間的開始

unsigned char *data;//有效數據的開始

unsigned char *tail;//有效數據的結束

unsigned char *end;//分配空間的結束

unsigned long len;//有效數據的長度


sk_buff操作
a -- 分配:分配一個sk_buff結構,供協議棧代碼使用

struct sk_buff *alloc_skb(unsigned int len, int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);  

分配一個緩沖區。alloc_skb函數分配一個緩沖區並初始化skb->data和skb->tail為skb->head。參數len為數據緩沖區的空間大小,通常以L1_CACHE_BYTES字節(對ARM為32)對齊,參數priority為內存分配的優先級。dev_alloc_skb()函數以GFP_ATOMIC優先級進行skb的分配。

b -- 釋放:

void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb); 

Linux內核內部使用kfree_skb()函數,而網絡設備驅動程序中則最好使用dev_kfree_skb()。

sk_buff中比較重要的成員是指向數據包中數據的指針,如下圖所示:

\
用於尋址數據包中數據的指針,head指向已分配空間開頭,data指向有效的octet開頭,tail指向有效的octet結尾,而end指向tail可以到達的最大地址。如果不這樣做而分配一個大小固定的緩沖區,如果buffer不夠用,則要申請一個更大的buffer,拷貝進去再增加,這樣降低了性能。


3)變更

unsigned char *skb_put(struct sk_buff *skb, int len);將taill指針向後移動len長度,並返回tail移動之前的值。用於向skb有效數據區域末尾添加數據。
unsigned char *skb_push(struct sk_buff *skb, int len);將data指針向前移動len長度。並返回移動之後的值。用於向skb有效數據區域前端添加數據(包頭)。
unsigned char *skb_pull(struct sk_buff *skb, int len);
void skb_reserve(struct sk_buff ×skb, int len); 
下圖分別對應了這四個函數,看了這張圖應該對這4個函數的作用了然於胸。
\

2、網絡設備接口層:

網絡設備接口層的主要功能是為千變萬化的網絡設備定義了統一,抽象的數據結構net_device結構體,以不變應萬變,實現多種硬件在軟件層次上的統一。

每一個網絡設備都由struct net_device來描述,該結構可使用如下內核函數進行動態分配

struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void(*setup)(struct net_device *))

sizeof_priv是私有數據區大小;mask是設備名,setup是初始化函數,在注冊該設備時,該函數被調用。也就是net_deivce的init成員。

struct net_device *alloc_etherdev(intsizeof_priv)
這個函數和上面的函數不同之處在於內核知道會將該設備做一個以太網設備看待並做一些相關的初始化。

net_device結構可分為全局成員、硬件相關成員、接口相關成員、設備方法成員和公用成員等五個部分

a -- 主要全局成員

char name[INFAMSIZ]    設備名,如:eh%d
unsigned long state  設備狀態
unsigned long base_addr  I/O基地址
unsigned int irq   中斷號

b -- 主要設備方法

//首先看打開和關閉網絡設備的函數:

int (*open)(struct net_device *dev);
//打開接口。ifconfig激活時,接口將被打開

int (*stop)(struct net_device *dev);  
//停止接口,ifconfig eth% down時調用
//要注意的是ifconfig是interface config的縮寫,通常我們在用戶空間輸入:
//ifconfig eth0 up  會調用這裡的open函數。
//在用戶空間輸入:
//ifconfig eth0 down  會調用這裡的stop函數。
//在使用ifconfig向接口賦予地址時,要執行兩個任務。首先,它通過ioctl(SIOCSIFADDR)(Socket I/O Control Set Interface Address)賦予地址,然後通過ioctl(SIOCSIFFLAGS)(Socket I/O Control Set Interface Flags)設置dev->flag中的IFF_UP標志以打開接口。這個調用會使得設備的open方法得到調用。類似的,在接口關閉時,ifconfig使用ioctl(SIOCSIFFLAGS)來清理IFF_UP標志,然後調用stop函數。

int  (*init)(struct  net_device *dev)
//初始化函數,該函數在register_netdev時被調用來完成對net_device結構的初始化

int (*hard_start_xmit)(struct sk_buf*skb,struct net_device *dev)
//數據發送函數

int (*hard_header)(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len); 
//該方法根據先前檢索到的源和目的硬件地址建立硬件頭

int (*rebuild_header)(struct sk_buff *skb);
//以太網的mac地址是固定的,為了高效,第一個包去詢問mac地址,得到對應的mac地址後就會作為cache把mac地址保存起來。以後每次發包不用詢問了,直接把包的地址拷貝出來。

void (*tx_timeout)(struct net_device *dev);  
//如果數據包發送在超時時間內失敗,這時該方法被調用,這個方法應該解決失敗的問題,並重新開始發送數據。

struct net_device_stats *(*get_stats)(struct net_device *dev);  
//當應用程序需要獲得接口的統計信息時,這個方法被調用。

int (*set_config)(struct net_device *dev, struct ifmap *map);  
//改變接口的配置,比如改變I/O端口和中斷號等,現在的驅動程序通常無需該方法。

int (*do_ioctl)(struct net_device *dev, struct ifmap *map);  
//用來實現自定義的ioctl命令,如果不需要可以為NULL。

void (*set_multicast_list)(struct net_device *dev);  
//當設備的組播列表改變或設備標志改變時,該方法被調用。

int (*set_mac_address)(struct net_device *dev, void *addr);  
//如果接口支持mac地址改變,則可以實現該函數。

3、設備驅動接口層:

net_device結構體的成員(屬性和函數指針)需要被設備驅動功能層的具體數值和函數賦予。對具體的設置xxx,工程師應該編寫設備驅動功能層的函數,這些函數型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。

4、網絡設備與媒介層:

網絡設備與媒介層直接對應於實際的硬件設備。

網絡設備的注冊

網絡設備注冊方式與字符驅動不同之處在於它沒有主次設備號,並使用下面的函數注冊

int register_netdev(struct net_deivce*dev)
網絡設備的注銷
void unregister_netdev(struct net_device*dev)

四、驅動的實現

1)初始化(init)

設備探測工作在init方法中進行,一般調用一個稱之為probe方法的函數

初始化的主要工作時檢測設備,配置和初始化硬件,最後向系統申請這些資源。此外填充該設備的dev結構,我們調用內核提供的ether_setup方法來設置一些以太網默認的設置。

2)打開(open)

open這個方法在網絡設備驅動程序裡是網絡設備被激活時被調用(即設備狀態由down變成up)

實際上很多在初始化的工作可以放到這裡來做。比如說資源的申請,硬件的激活。如果dev->open返回非0,則硬件狀態還是down,
注冊中斷、DMA等;設置寄存器,啟動設備;啟動發送隊列

一般注冊中斷都在init中做,但在網卡驅動程序中,注冊中斷大部分都是放在open中注冊,因為要經常關閉和重啟網卡

3)關閉(stop)

stop方法做和open相反的工作

可以釋放某些資源以減少系統負擔

stop是在設備狀態由up轉為down時被調用

4)發送(hard_start_xmit)

在系統調用的驅動程序的hard_start_xmit時,發送的數據放在一個sk_buff結構中。一般的驅動程序傳給硬件發出去。也有一些特殊的設備比如說loopback把數據組成一個接收數據在傳送給系統或者dummy設備直接丟棄數據。

如果發送成功,hard_start_xmit方法釋放sk_buff。如果設備暫時無法處理,比如硬件忙,則返回1。

5)接收

驅動程序並存在一個接受方法。當有數據收到時驅動程序調用netif_rx函數將skb交交給設備無關層。

一般設備收到數據後都會產生一個中斷,在中斷處理程序中驅動程序申請一塊sk_buff(skb)從硬件中讀取數據位置到申請號的緩沖區裡。

接下來填充sk_buff中的一些信息。

中斷有可能是收到數據產生也可能是發送完成產生,中斷處理程序要對中斷類型進行判斷,如果是收到數據中斷則開始接收數據,如果是發送完成中斷,則處理發送完成後的一些操作,比如說重啟發送隊列。
接收流程:
1、分配skb=dev_alloc_skb(pkt->datalen+2)
2、從硬件中讀取數據到skb
3、調用netif_rx將數據交給協議棧

中斷處理

網絡接口通常支持3種類型的中斷:新報文到達中斷、報文發送完成中斷和出錯中斷。中斷處理程序可通過查看網卡的中斷狀態寄存器,來分辨出中斷類型。

Copyright © Linux教程網 All Rights Reserved