1.描述 網卡的驅動其實很簡單,它還是與硬體相關,主要是負責收髮網絡的數據包,它將上層協議傳遞下來的數據包以特定的媒介訪問控制方式進行發送, 並將接收到的數據包傳遞給上層協議。 網卡設備與字元設備和塊設備不同, 網路設備並不對應於/dev目錄下的文件,不過會存放在/sys/class/net目錄下 ...
1.描述
網卡的驅動其實很簡單,它還是與硬體相關,主要是負責收髮網絡的數據包,它將上層協議傳遞下來的數據包以特定的媒介訪問控制方式進行發送, 並將接收到的數據包傳遞給上層協議。
網卡設備與字元設備和塊設備不同, 網路設備並不對應於/dev目錄下的文件,不過會存放在/sys/class/net目錄下
如下圖所示,我們通過ls /sys/class/net/ 命令,可以看到有兩個網卡:
2.Linux系統對網路設備驅動定義了4個層次, 這4個層次有到下分為:
1)網路協議介面層:
實現統一的數據包收發的協議,該層主要負責調用dev_queue_xmit()函數發送數據, netif_rx()函數接收數據
2)網路設備介面層:
通過net_device結構體來描述一個具體的網路設備的信息,實現不同的硬體的統一
3)設備驅動功能層:
用來負責驅動網路設備硬體來完成各個功能, 它通過hard_start_xmit() 函數啟動發送操作, 並通過網路設備上的中斷觸發接收操作,
4)網路設備與媒介層:
用來負責完成數據包發送和接收的物理實體, 設備驅動功能層的函數都在這物理上驅動的
層次結構如下圖所示:
3.網卡驅動初始化
而我們的網卡驅動程式,只需要編寫網路設備介面層,填充net_device數據結構的內容並將net_device註冊入內核,設置硬體相關操作,使能中斷處理等
3.1其中net_device結構體的重要成員,整理後如下所示:
struct net_device { char name[IFNAMSIZ]; //網卡設備名稱 unsigned long mem_end; //該設備的記憶體結束地址 unsigned long mem_start; //該設備的記憶體起始地址 unsigned long base_addr; //該設備的記憶體I/O基地址 unsigned int irq; //該設備的中斷號 unsigned char if_port; //多埠設備使用的埠類型 unsigned char dma; //該設備的DMA通道
unsigned long state; //網路設備和網路適配器的狀態信息 struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統計信息
//運行ifconfig便會調用該成員函數,並返回一個net_device_stats結構體獲取信息 struct net_device_stats stats; //用來保存統計信息的net_device_stats結構體 unsigned long features; //介面特征, unsigned int flags; //flags指網路介面標誌,以IFF_(Interface Flags)開頭 //當flags =IFF_UP( 當設備被激活並可以開始發送數據包時, 內核設置該標誌)、 IFF_AUTOMEDIA(設置設備可在多種媒介間切換)、
IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調試模式, 可用於控制printk調用的詳細程度) 、 IFF_LOOPBACK( 迴環)、
IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 介面不能執行ARP,點對點介面就不需要運行 ARP) 和IFF_POINTOPOINT( 介面連接到點到點鏈路) 等。 unsigned mtu; //最大傳輸單元,也叫最大數據包 unsigned short type; //介面的硬體類型 unsigned short hard_header_len; //硬體幀頭長度,一般被賦為ETH_HLEN,即14 unsigned char dev_addr[MAX_ADDR_LEN]; //存放設備的MAC地址 unsigned long last_rx; //接收數據包的時間戳,調用netif_rx()後賦上jiffies即可 unsigned long trans_start; //發送數據包的時間戳,當要發送的時候賦上jiffies即可 unsigned char dev_addr[MAX_ADDR_LEN]; //MAC地址 int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev); //數據包發送函數, sk_buff就是用來收發數據包的結構體 void (*tx_timeout) (struct net_device *dev); //發包超時處理函數 ... ... }
上面講到的統計信息net_device_stats結構體,其中重要成員如下所示:
struct net_device_stats { unsigned long rx_packets; /*收到的數據包數*/ unsigned long tx_packets; /*發送的數據包數 */ unsigned long rx_bytes; /*收到的位元組數,可以通過sk_buff結構體的成員len來獲取*/ unsigned long tx_bytes; /*發送的位元組數,可以通過sk_buff結構體的成員len來獲取*/ unsigned long rx_errors; /*收到的錯誤數據包數*/ unsigned long tx_errors; /*發送的錯誤數據包數*/ ... ... }
3.2 所以init函數,初始化網卡步驟如下所示:
- 1)使用alloc_netdev()來分配一個net_device結構體
- 2)設置網卡硬體相關的寄存器
- 3)設置net_device結構體的成員
- 4)使用register_netdev()來註冊net_device結構體
4.網卡驅動發包過程
在內核中,當上層要發送一個數據包時, 就會調用網路設備層里net_device數據結構的成員hard_start_xmit()將數據包發送出去。
hard_start_xmit()發包函數需要我們自己構建,該函數原型如下所示:
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
在這個函數中需要涉及到sk_buff結構體,含義為(socket buffer)套接字緩衝區,用來網路各個層次之間傳遞數據.
4.1 sk_buff結構體是一個雙向鏈表,其中重要成員如下所示:
struct sk_buff { /* These two members must be first. */ struct sk_buff *next; //指向下一個sk_buff結構體 struct sk_buff *prev; //指向前一個sk_buff結構體 ... ... unsigned int len, //數據包的總長度,包括線性數據和非線性數據 data_len, //非線性的數據長度 mac_len; //mac包頭長度 __u32 priority; //該sk_buff結構體的優先順序 __be16 protocol; //存放上層的協議類型,可以通過eth_type_trans()來獲取 ... ... sk_buff_data_t transport_header; //傳輸層頭部的偏移值 sk_buff_data_t network_header; //網路層頭部的偏移值 sk_buff_data_t mac_header; //MAC數據鏈路層頭部的偏移值 sk_buff_data_t tail; //指向緩衝區的數據包末尾 sk_buff_data_t end; //指向緩衝區的末尾 unsigned char *head, //指向緩衝區的協議頭開始位置 *data; //指向緩衝區的數據包開始位置 ... ... }
其中sk_buff結構體的空間,如下圖所示:
其中sk_buff-> data數據包格式如下圖所示:
4.2 所以,hard_start_xmit()發包函數處理步驟如下所示:
- 1)把數據包發出去之前,需要使用netif_stop_queue()來停止上層傳下來的數據包,
- 2)設置寄存器,通過網路設備硬體,來發送數據
- 2)當數據包發出去後, 再調用dev_kfree_skb()函數來釋放sk_buff,該函數原型如下:
void dev_kfree_skb(struct sk_buff *skb);
- 3)當數據包發出成功,就會進入TX中斷函數,然後更新統計信息,調用netif_wake_queue()來喚醒,啟動上層繼續發包下來.
- 4)若數據包發出去超時,一直進不到TX中斷函數,就會調用net_device結構體的(*tx_timeout)超時成員函數,在該函數中更新統計信息, 調用netif_wake_queue()來喚醒
其中netif_wake_queue()和netif_stop_queue()函數原型如下所示:
void netif_wake_queue(struct net_device *dev); //喚醒被阻塞的上層,啟動繼續向網路設備驅動層發送數據包 void netif_stop_queue(struct net_device *dev); //阻止上層向網路設備驅動層發送數據包
5.網卡驅動收包過程
而接收數據包主要是通過中斷函數處理,來判斷中斷類型,如果等於ISQ_RECEIVER_EVENT,表示為接收中斷,然後進入接收數據函數,通過netif_rx()將數據上交給上層
例如下圖所示,參考的內核中自帶的網卡驅動:/drivers/net/cs89x0.c
如上圖所示,通過獲取的status標誌來判斷是什麼中斷,如果是接收中斷,就進入net_rx()
4.1 其中net_rx()收包函數處理步驟如下所示:
- 1)使用dev_alloc_skb()來構造一個新的sk_buff
- 2)使用skb_reserve(rx_skb, 2); 將sk_buff緩衝區里的數據包先後位移2位元組,來騰出sk_buff緩衝區里的頭部空間
- 3)讀取網路設備硬體上接收到的數據
- 4)使用memcpy()將數據複製到新的sk_buff里的data成員指向的地址處,可以使用skb_put()來動態擴大sk_buff結構體里中的數據區
- 5)使用eth_type_trans()來獲取上層協議,將返回值賦給sk_buff的protocol成員里
- 6)然後更新統計信息,最後使用netif_rx( )來將sk_fuffer傳遞給上層協議中
其中skb_put()函數原型如下所示:
static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
//len:將數據區向下擴大len位元組
使用skb_put()函數後,其中sk_buff緩衝區變化如下圖:
6.寫虛擬網卡驅動
本節便開始來寫一個簡單的虛擬網卡驅動,也就是說不需要硬體相關操作,所以就沒有中斷函數,我們通過linux的ping命令來實現發包,然後在發包函數中偽造一個收的ping包函數,實現能ping通任何ip地址
在init初始函數中:
- 1)使用alloc_netdev()來分配一個net_device結構體
- 2)設置net_device結構體的成員
- 3)使用register_netdev()來註冊net_device結構體
在發包函數中:
- 1)使用netif_stop_queue()來阻止上層向網路設備驅動層發送數據包
- 2)調用收包函數,並代入發送的sk_buff緩衝區, 裡面來偽造一個收的ping包函數
- 3)使用dev_kfree_skb()函數來釋放發送的sk_buff緩存區
- 4)更新發送的統計信息
- 5)使用netif_wake_queue()來喚醒被阻塞的上層,
在收包函數中:
首先修改發送的sk_buff里數據包的數據,使它變為一個接收的sk_buff,其中數據包結構如下圖所示:
- 1)需要對調上圖的ethhdr結構體 ”源/目的”MAC地址
- 2)需要對調上圖的iphdr結構體”源/目的” IP地址
- 3)使用ip_fast_csum()來重新獲取iphdr結構體的校驗碼
- 4)設置上圖數據包的數據類型,之前是發送ping包0x08,需要改為0x00,表示接收ping包
- 5)使用dev_alloc_skb()來構造一個新的sk_buff
- 6)使用skb_reserve(rx_skb, 2);將sk_buff緩衝區里的數據包先後位移2位元組,來騰出sk_buff緩衝區里的頭部空間
- 7)使用memcpy()將之前修改好的sk_buff->data複製到新的sk_buff里的data成員指向的地址處:
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); // skb_put():來動態擴大sk_buff結構體里中的數據區,避免溢出
- 8)設置新的sk_buff 其它成員
- 9)使用eth_type_trans()來獲取上層協議,將返回值賦給sk_buff的protocol成員里
- 10)然後更新接收統計信息,最後使用netif_rx( )來將sk_fuffer傳遞給上層協議中
7.驅動具體代碼如下:
#include <linux/module.h> #include <linux/errno.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/interrupt.h> #include <linux/ioport.h> #include <linux/in.h> #include <linux/skbuff.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/string.h> #include <linux/init.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/ip.h> #include <asm/system.h> #include <asm/io.h> #include <asm/irq.h> static struct net_device *virt_net; static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev) { unsigned char *type; struct iphdr *ih; __be32 *saddr, *daddr, tmp; unsigned char tmp_dev_addr[ETH_ALEN]; struct ethhdr *ethhdr; struct sk_buff *rx_skb; /*1) 對調ethhdr結構體 "源/目的"MAC地址*/ ethhdr = (struct ethhdr *)skb->data; memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN); memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN); memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
/*2)對調 iphdr結構體"源/目的" IP地址*/ ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr)); saddr = &ih->saddr; daddr = &ih->daddr;
tmp = *saddr; *saddr = *daddr; *daddr = tmp; /*3)使用ip_fast_csum()來重新獲取iphdr結構體的校驗碼*/ ih->check = 0; ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl); /*4)設置數據類型*/ type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr); *type = 0; //之前是發送ping包0x08,需要改為0x00,表示接收ping包 /*5)使用dev_alloc_skb()來構造一個新的sk_buff */ rx_skb = dev_alloc_skb(skb->len + 2); /*6)使用skb_reserve()來騰出2位元組頭部空間 */ skb_reserve(rx_skb, 2); /*7)使用memcpy()將之前修改好的sk_buff->data複製到新的sk_buff里*/ memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); // skb_put():來動態擴大sk_buff結構體里中的數據區,避免溢出 /*8)設置新的sk_buff 其它成員*/ rx_skb->dev = dev; rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ /*9)使用eth_type_trans()來獲取上層協議 */ rx_skb->protocol = eth_type_trans(rx_skb, dev); /*10) 更新接收統計信息,並使用netif_rx( )來 傳遞sk_fuffer收包 */ dev->stats.rx_packets++; dev->stats.rx_bytes += skb->len; dev->last_rx= jiffies; //收包時間戳 netif_rx(rx_skb); }
static int virt_send_packet(struct sk_buff *skb, struct net_device *dev) { /*1)使用netif_stop_queue()來阻止上層向網路設備驅動層發送數據包*/ netif_stop_queue(dev); //期間設置硬體發送數據包 /*2)調用收包函數,裡面來偽造一個收的ping包函數*/ virt_rs_packet(skb,dev); /*3)使用dev_kfree_skb()函數來釋放發送的sk_buff緩存區*/ dev_kfree_skb(skb); /*4)更新發送的統計信息*/ dev->stats.tx_packets++; //成功發送一個包 dev->stats.tx_bytes+=skb->len; //成功發送len長位元組 dev->trans_start = jiffies; //發送時間戳 /*5)使用netif_wake_queue()來喚醒被阻塞的上層*/ netif_wake_queue(dev); return 0; } static int virt_net_init(void) { /*1)使用alloc_netdev()來分配一個net_device結構體*/ virt_net= alloc_netdev(sizeof(struct net_device), "virt_eth0", ether_setup); /*2)設置net_device結構體的成員 */ virt_net->hard_start_xmit = virt_send_packet; virt_net->dev_addr[0] = 0x08; virt_net->dev_addr[1] = 0x89; virt_net->dev_addr[2] = 0x89; virt_net->dev_addr[3] = 0x89; virt_net->dev_addr[4] = 0x89; virt_net->dev_addr[5] = 0x89; virt_net->flags |= IFF_NOARP; virt_net->features |= NETIF_F_NO_CSUM; /*3)使用register_netdev()來註冊net_device結構體 */ register_netdev(virt_net);
return 0; } static void virt_net_exit(void) { unregister_netdev(virt_net); free_netdev(virt_net); } module_init(virt_net_init); module_exit(virt_net_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("by:zhang");
8.測試運行
掛載驅動,如下圖所示,可以看到net類下就有了這個網卡設備
開始試驗,首先設置這個網卡設備的ip,然後去ping一下其它的ip,如下圖所示:
上圖的ping,之所以成功,是因為我們在發包函數中,偽造了一個來收包,通過netif_rx()來將收包上傳給上層
使用ifconfig,可以看到這個網卡設備的統計信息共收發了6個包,以及收發的總數據
下節便開始學習如何移植DM9000C網卡驅動