Linux網卡驅動框架及制作虛擬網卡
1.概述
網卡驅動與硬件相關,主要負責收發網絡的數據包,將上層協議傳遞下來的數據包以特定的媒介訪問控制方式進行發送,並將接收到的數據包傳遞給上層協議。
網卡設備與字符設備,塊設備不同,網絡設備驅動程序不依賴與 /dev 或 /sys 來與用戶空間通信,應用程序是通過網絡接口(如作為第一個網絡接口的eth0)與網卡驅動程序互相操作的。
網卡設備存放在 /sys/class/net 目錄下
2.Linux 系統網絡協議的處理框架
網絡設備驅動的框架層次分為四層:
1)網絡設備與媒介層:
用來負責完成數據包發送和接收的物理實體, 設備驅動功能層的函數都在這物理上驅動的,即DM9000網絡處理芯片。
2)設備驅動功能層:
用來負責驅動網絡設備硬件來完成各個功能, 如通過hard_start_xmit() 函數啟動發送操作, 並通過網絡設備上的中斷觸發接收操作,即DM9000網卡驅動,實現文件在 linux/driver/net
3)網絡設備接口層:
是整個網絡接口的關鍵部位,它為網絡協議提供統一的發送接口,屏蔽各種物理介質,同時又負責把來自下層的包向合適的協議配送。
通過net_device結構體來描述一個具體的網絡設備的信息,實現不同的硬件的統一。實現文件在linux/net/core 下,其中dev.c 為主要實現文件。
4)網絡協議接口層:
此部分實現了各種具體協議,Linux支持 TCP/IP, IPX, X.25, AppleTalk 等協議,實現源碼在Linux / net 目錄下有對應名稱。
此處主要討論 TCP/IP (IPv4) 協議,實現源碼在 linux/net/ipv4 下,其中 af_inet.c為主要實現文件。
實現統一的數據包收發的協議,該層主要負責調用dev_queue_xmit()函數發送數據, netif_rx()函數接收數據
-------------------------
在網絡協議接口層之上還有一層為 網絡接口socket層, 主要為用戶提供網絡服務的編程接口,方便用戶進行網絡應用程序開發,源碼在 linux/net/socket.c
--------------------------
了解網絡設備驅動的框架層次後,下面來分析網卡驅動的初始化,數據發送和接收的處理流程。
3.網卡驅動初始化
此次的網卡驅動程序,只需要編寫網絡設備接口層,填充net_device數據結構的內容並將net_device註冊入內核,設置硬件相關操作,使能中斷處理等
3.1其中net_device結構體的重要成員:
1 struct net_device 2 { 3 char name[IFNAMSIZ]; //網卡設備名稱 4 unsigned long mem_end; //該設備的內存結束地址 5 unsigned long mem_start; //該設備的內存起始地址 6 unsigned long base_addr; //該設備的內存I/O基地址 7 unsigned int irq; //該設備的中斷號 8 unsigned char if_port; //多端口設備使用的端口類型 9 unsigned char dma; //該設備的DMA通道 10 unsigned long state; //網絡設備和網絡適配器的狀態信息 11 ...... 12 struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統計信息 13 //運行ifconfig便會調用該成員函數,並返回一個net_device_stats結構體獲取信息 14 15 struct net_device_stats stats; //用來保存統計信息的net_device_stats結構體 16 17 18 unsigned long features; //接口特征, 19 unsigned int flags; //flags指網絡接口標誌,以IFF_(Interface Flags)開頭 20 //當flags =IFF_UP( 當設備被激活並可以開始發送數據包時, 內核設置該標誌)、 IFF_AUTOMEDIA(設置設備可在多種媒介間切換)、 21 //IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調試模式, 可用於控制printk調用的詳細程度) 、 IFF_LOOPBACK( 回環)、 22 //IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 接口不能執行ARP,點對點接口就不需要運行 ARP) 和IFF_POINTOPOINT( 接口連接到點到點鏈路) 等。 23 24 unsigned mtu; //最大傳輸單元,也叫最大數據包 25 26 unsigned short type; //接口的硬件類型 27 28 unsigned short hard_header_len; //硬件幀頭長度,一般被賦為ETH_HLEN,即14 29 30 unsigned char dev_addr[MAX_ADDR_LEN]; //存放設備的MAC地址 31 32 unsigned long last_rx; //接收數據包的時間戳,調用netif_rx()後賦上jiffies即可 33 34 unsigned long trans_start; //發送數據包的時間戳,當要發送的時候賦上jiffies即可 35 36 unsigned char dev_addr[MAX_ADDR_LEN]; //MAC地址 37 38 int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev); 39 //數據包發送函數, sk_buff就是用來收發數據包的結構體 40 41 42 void (*tx_timeout) (struct net_device *dev); //發包超時處理函數 43 ... ... 44 }
上面的統計信息net_device_stats結構體,其中重要成員如下所示:
1 struct net_device_stats 2 { 3 unsigned long rx_packets; /*收到的數據包數*/ 4 unsigned long tx_packets; /*發送的數據包數 */ 5 unsigned long rx_bytes; /*收到的字節數,可以通過sk_buff結構體的成員len來獲取*/ 6 unsigned long tx_bytes; /*發送的字節數,可以通過sk_buff結構體的成員len來獲取*/ 7 unsigned long rx_errors; /*收到的錯誤數據包數*/ 8 unsigned long tx_errors; /*發送的錯誤數據包數*/ 9 ... ... 10 }
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結構體是一個雙向鏈表,其中重要成員如下所示:
1 struct sk_buff { 2 /* These two members must be first. */ 3 struct sk_buff *next; //指向下一個sk_buff結構體 4 struct sk_buff *prev; //指向前一個sk_buff結構體 5 ... ... 6 unsigned int len, //數據包的總長度,包括線性數據和非線性數據 7 data_len, //非線性的數據長度 8 mac_len; //mac包頭長度 9 10 __u32 priority; //該sk_buff結構體的優先級 12 __be16 protocol; //存放上層的協議類型,可以通過eth_type_trans()來獲取 13 ... ... 14 15 sk_buff_data_t transport_header; //傳輸層頭部的偏移值 16 sk_buff_data_t network_header; //網絡層頭部的偏移值 17 sk_buff_data_t mac_header; //MAC數據鏈路層頭部的偏移值 18 19 sk_buff_data_t tail; //指向緩沖區的數據包末尾 20 sk_buff_data_t end; //指向緩沖區的末尾 21 unsigned char *head, //指向緩沖區的協議頭開始位置 22 *data; //指向緩沖區的數據包開始位置 23 ... ... 24 }
其中sk_buff結構體的空間,如下圖所示:
其中sk_buff-> data數據包格式如下圖所示:
4.2 所以,hard_start_xmit()發包函數處理步驟如下所示:
1)把數據包發出去之前,需要使用netif_stop_queue()來停止上層傳下來的數據包,
2)設置寄存器,通過網絡設備硬件,來發送數據
3)當數據包發出去後, 再調用dev_kfree_skb()函數來釋放sk_buff,該函數原型如下:
void dev_kfree_skb(struct sk_buff *skb);
4)當數據包發出成功,就會進入TX中斷函數,然後更新統計信息,調用netif_wake_queue()來喚醒,啟動上層繼續發包下來.
5)若數據包發出去超時,一直進不到TX中斷函數,就會調用net_device結構體的(*tx_timeout)超時成員函數,在該函數中更新統計信息, 調用netif_wake_queue()來喚醒
其中netif_wake_queue()和netif_stop_queue()函數原型如下所示:
1 void netif_wake_queue(struct net_device *dev); //喚醒被阻塞的上層,啟動繼續向網絡設備驅動層發送數據包 2 3 void netif_stop_queue(struct net_device *dev); //阻止上層向網絡設備驅動層發送數據包
5.網卡驅動收包過程
接收數據包主要是通過中斷函數處理,來判斷中斷類型,如果等於 ISQ_RECEIVER_EVENT, 表示為接收中斷,然後進入接收數據函數,通過 netif_rx() 將數據上交給上層
例如下圖所示,參考的內核中自帶的網卡驅動: /drivers/net/cs89x0.c
1 static irqreturn_t net_interrupt(int irq, void *dev_id) 2 { 3 struct net_device *dev = dev_id; 4 struct net_local *lp; 5 int ioaddr, status; 6 int handled = 0; 7 8 ioaddr = dev->base_addr; 9 lp = netdev_priv(dev); 10 11 /* we MUST read all the events out of the ISQ, otherwise we‘ll never 12 get interrupted again. As a consequence, we can‘t have any limit 13 on the number of times we loop in the interrupt handler. The 14 hardware guarantees that eventually we‘ll run out of events. Of 15 course, if you‘re on a slow machine, and packets are arriving 16 faster than you can read them off, you‘re screwed. Hasta la 17 vista, baby! */ 18 while ((status = readword(dev->base_addr, ISQ_PORT))) { 19 if (net_debug > 4)printk("%s: event=%04x\n", dev->name, status); 20 handled = 1; 21 switch(status & ISQ_EVENT_MASK) { 22 case ISQ_RECEIVER_EVENT: /* 判斷是否為接收中斷 */ 23 /* Got a packet(s). */ 24 net_rx(dev); /* 進入net_rx(dev)函數,將接收的數據交給上層 */ 25 break; 26 case ISQ_TRANSMITTER_EVENT: /* 判斷是否為發送中斷 */ 27 lp->stats.tx_packets++; 28 netif_wake_queue(dev); /* Inform upper layers. */ 29 if ((status & ( TX_OK | 30 TX_LOST_CRS | 31 TX_SQE_ERROR |
.......................................................
如上圖所示,通過獲取的status標誌來判斷是什麽中斷,如果是接收中斷,就進入net_rx()
5.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.驅動具體代碼如下:
1 /*
2 *參考linux-2.6.22.6\drivers\net\Cs89x0.c
3 */
4
5 #include <linux/module.h>
6 #include <linux/errno.h>
7 #include <linux/netdevice.h>
8 #include <linux/etherdevice.h>
9 #include <linux/kernel.h>
10 #include <linux/types.h>
11 #include <linux/fcntl.h>
12 #include <linux/interrupt.h>
13 #include <linux/ioport.h>
14 #include <linux/in.h>
15 #include <linux/skbuff.h>
16 #include <linux/slab.h>
17 #include <linux/spinlock.h>
18 #include <linux/string.h>
19 #include <linux/init.h>
20 #include <linux/bitops.h>
21 #include <linux/delay.h>
22 #include <linux/ip.h>
23
24
25 #include <asm/system.h>
26 #include <asm/io.h>
27 #include <asm/irq.h>
28
29
30 static struct net_device *vnet_dev;
31
32 static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
33 {
34 /* 參考LDD3 */
35 unsigned char *type;
36 struct iphdr *ih;
37 __be32 *saddr, *daddr, tmp;
38 unsigned char tmp_dev_addr[ETH_ALEN];
39 struct ethhdr *ethhdr;
40
41 struct sk_buff *rx_skb;
42
43 // 從硬件讀出/保存數據
44 /* 對調"源/目的"的mac地址 */
45 ethhdr = (struct ethhdr *)skb->data;
46 memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
47 memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
48 memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
49
50 /* 對調"源/目的"的ip地址 */
51 ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
52 saddr = &ih->saddr;
53 daddr = &ih->daddr;
54
55 tmp = *saddr;
56 *saddr = *daddr;
57 *daddr = tmp;
58
59 //((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
60 //((u8 *)daddr)[2] ^= 1;
61 type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
62 //printk("tx package type = %02x\n", *type);
63 // 修改類型, 原來0x8表示ping
64 *type = 0; /* 0表示reply */
65
66 ih->check = 0; /* and rebuild the checksum (ip needs it) */
67 ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
68
69 // 構造一個sk_buff
70 rx_skb = dev_alloc_skb(skb->len + 2);
71 skb_reserve(rx_skb, 2); /* align IP on 16B boundary */
72 memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
73
74 /* Write metadata, and then pass to the receive level */
75 rx_skb->dev = dev;
76 rx_skb->protocol = eth_type_trans(rx_skb, dev);
77 rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don‘t check it */
78 dev->stats.rx_packets++;
79 dev->stats.rx_bytes += skb->len;
80
81 // 提交sk_buff
82 netif_rx(rx_skb);
83 }
84
85 static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
86 {
87 static int cnt = 0;
88 printk("virt_net_send_packet = %d\n", ++cnt);
89
90 /*對於真實的網卡,會把skb裏的數據發送出去*/
91 netif_stop_queue(dev); /* 停止該網卡的隊列 */
92 /* -------------- */ /* 把skb的數據寫入網卡 */
93
94 /* 構造一個假的sk_buff,上報 */
95 emulator_rx_packet(skb, dev);
96
97 dev_kfree_skb (skb); /* 釋放skb */
98 netif_wake_queue(dev); /* 數據全部發送出去後,中斷喚醒隊列 */
99 /*更新統計信息*/
100 dev->stats.tx_packets++;
101 dev->stats.tx_bytes += skb->len;
102
103 /*構造一個假的sk_buff上報*/
104 emulator_rx_packet(skb, dev);
105
106 return 0;
107 }
108
109
110 static int virt_net_init(void)
111 {
112 /* 1.分配一個net_device結構體 */
113 vnet_dev = alloc_netdev(0, "vnet%d", ether_setup); /*alloc_etherdev*/
114
115 /* 2.設置 */
116 vnet_dev->hard_start_xmit = virt_net_send_packet;
117
118 vnet_dev->dev_addr[0] = 0x08;
119 vnet_dev->dev_addr[1] = 0x08;
120 vnet_dev->dev_addr[2] = 0x89;
121 vnet_dev->dev_addr[3] = 0x08;
122 vnet_dev->dev_addr[4] = 0x08;
123 vnet_dev->dev_addr[5] = 0x11;
124
125 /* 設置一下兩項才能ping通 */
126 vnet_dev->flags |= IFF_NOARP;
127 vnet_dev->features |= NETIF_F_NO_CSUM;
128
129 /* 3.註冊 */
130 register_netdev(vnet_dev);
131
132 return 0;
133 }
134
135 static void virt_net_exit(void)
136 {
137 unregister_netdev(vnet_dev);
138 free_netdev(vnet_dev);
139
140 }
141
142 module_init(virt_net_init);
143 module_exit(virt_net_exit);
144 MODULE_LICENSE("GPL");
virt_net.c
以上內容基本摘自:
Linux網卡驅動框架及制作虛擬網卡