描述
網(wǎng)卡的驅(qū)動還是與硬件有關(guān),注意是負(fù)責(zé)收發(fā)網(wǎng)絡(luò)的數(shù)據(jù)包,它將上層協(xié)議傳遞下來的數(shù)據(jù)包,以特定的媒介訪問控制方式進(jìn)行發(fā)送,并將接收到的數(shù)據(jù)包傳遞給上層協(xié)議。
網(wǎng)卡設(shè)備與字符設(shè)備和塊設(shè)備不同,網(wǎng)卡設(shè)備并不對應(yīng)與/dev目錄下的文件,不過會放在/sys/class/net目錄下。

linux系統(tǒng)對網(wǎng)絡(luò)設(shè)備驅(qū)動定義了4個層次,這四個層次為:
- 網(wǎng)絡(luò)協(xié)議接口層
實(shí)現(xiàn)統(tǒng)一的數(shù)據(jù)包收發(fā)的協(xié)議,該層主要負(fù)責(zé)調(diào)用dev_queue_xmit()函數(shù)發(fā)送數(shù)據(jù), netif_rx()函數(shù)接收數(shù)據(jù)。 - 網(wǎng)絡(luò)設(shè)備接口層
通過net_device結(jié)構(gòu)體來描述一個具體的網(wǎng)絡(luò)設(shè)備的信息,實(shí)現(xiàn)不同的硬件的統(tǒng)一。 - 設(shè)備驅(qū)動功能層
用來負(fù)責(zé)驅(qū)動網(wǎng)絡(luò)設(shè)備硬件來完成各個功能, 它通過hard_start_xmit() 函數(shù)啟動發(fā)送操作, 并通過網(wǎng)絡(luò)設(shè)備上的中斷觸發(fā)接收操作。 -
網(wǎng)絡(luò)設(shè)備與媒介層
用來負(fù)責(zé)完成數(shù)據(jù)包發(fā)送和接收的物理實(shí)體, 設(shè)備驅(qū)動功能層的函數(shù)都在這物理上驅(qū)動的。
網(wǎng)卡驅(qū)動初始化
網(wǎng)卡驅(qū)動程序,只需要編寫網(wǎng)絡(luò)設(shè)備接口層,填充net_device數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將net_device注冊入內(nèi)核,設(shè)置硬件相關(guān)操作,使能中斷處理等。
其中net_device結(jié)構(gòu)體的重要成員
struct net_device
{
char name[IFNAMSIZ]; //網(wǎng)卡設(shè)備名稱
unsigned long mem_end; //該設(shè)備的內(nèi)存結(jié)束地址
unsigned long mem_start; //該設(shè)備的內(nèi)存起始地址
unsigned long base_addr; //該設(shè)備的內(nèi)存I/O基地址
unsigned int irq; //該設(shè)備的中斷號
unsigned char if_port; //多端口設(shè)備使用的端口類型
unsigned char dma; //該設(shè)備的DMA通道
unsigned long state; //網(wǎng)絡(luò)設(shè)備和網(wǎng)絡(luò)適配器的狀態(tài)信息
struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統(tǒng)計信息
//運(yùn)行ifconfig便會調(diào)用該成員函數(shù),并返回一個net_device_stats結(jié)構(gòu)體獲取信息
struct net_device_stats stats; //用來保存統(tǒng)計信息的net_device_stats結(jié)構(gòu)體
unsigned long features; //接口特征,
unsigned int flags; //flags指網(wǎng)絡(luò)接口標(biāo)志,以IFF_(Interface Flags)開頭
//當(dāng)flags =IFF_UP( 當(dāng)設(shè)備被激活并可以開始發(fā)送數(shù)據(jù)包時, 內(nèi)核設(shè)置該標(biāo)志)、 IFF_AUTOMEDIA(設(shè)置設(shè)備可在多種媒介間切換)、
IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調(diào)試模式, 可用于控制printk調(diào)用的詳細(xì)程度) 、 IFF_LOOPBACK( 回環(huán))、
IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 接口不能執(zhí)行ARP,點(diǎn)對點(diǎn)接口就不需要運(yùn)行 ARP) 和IFF_POINTOPOINT( 接口連接到點(diǎn)到點(diǎn)鏈路) 等。
unsigned mtu; //最大傳輸單元,也叫最大數(shù)據(jù)包
unsigned short type; //接口的硬件類型
unsigned short hard_header_len; //硬件幀頭長度,一般被賦為ETH_HLEN,即14
unsigned char dev_addr[MAX_ADDR_LEN]; //存放設(shè)備的MAC地址
unsigned long last_rx; //接收數(shù)據(jù)包的時間戳,調(diào)用netif_rx()后賦上jiffies即可
unsigned long trans_start; //發(fā)送數(shù)據(jù)包的時間戳,當(dāng)要發(fā)送的時候賦上jiffies即可
unsigned char dev_addr[MAX_ADDR_LEN]; //MAC地址
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
//數(shù)據(jù)包發(fā)送函數(shù), sk_buff就是用來收發(fā)數(shù)據(jù)包的結(jié)
void (*tx_timeout) (struct net_device *dev); //發(fā)包超時處理函數(shù)
上面講到的統(tǒng)計信息net_device_stats結(jié)構(gòu)體,其中重要成員如下所示:
struct net_device_stats
{
unsigned long rx_packets; /*收到的數(shù)據(jù)包數(shù)*/
unsigned long tx_packets; /*發(fā)送的數(shù)據(jù)包數(shù) */
unsigned long rx_bytes; /*收到的字節(jié)數(shù),可以通過sk_buff結(jié)構(gòu)體的成員len來獲取*/
unsigned long tx_bytes; /*發(fā)送的字節(jié)數(shù),可以通過sk_buff結(jié)構(gòu)體的成員len來獲取*/
unsigned long rx_errors; /*收到的錯誤數(shù)據(jù)包數(shù)*/
unsigned long tx_errors; /*發(fā)送的錯誤數(shù)據(jù)包數(shù)*/
... ...
}
所以init函數(shù),初始化網(wǎng)卡步驟如下所示
- 使用alloc_netdev()來分配一個net_device結(jié)構(gòu)體
- 設(shè)置網(wǎng)卡硬件相關(guān)的寄存器
- 設(shè)置net_device結(jié)構(gòu)體的成員
- 使用register_netdev()來注冊net_device結(jié)構(gòu)體
網(wǎng)卡驅(qū)動發(fā)包過程
在內(nèi)核中,當(dāng)上層發(fā)送一個數(shù)據(jù)包時,就會調(diào)用網(wǎng)絡(luò)設(shè)備層里net_device數(shù)據(jù)結(jié)構(gòu)的成員hard_start_xmit()將數(shù)據(jù)包發(fā)送出去。
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
這個函數(shù)中需要涉及到sk_buff結(jié)構(gòu)體,含義為(socket buffer)套接字緩沖區(qū),用來網(wǎng)絡(luò)各個層次之間傳遞數(shù)據(jù)。
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next; //指向下一個sk_buff結(jié)構(gòu)體
struct sk_buff *prev; //指向前一個sk_buff結(jié)構(gòu)體
... ...
unsigned int len, //數(shù)據(jù)包的總長度,包括線性數(shù)據(jù)和非線性數(shù)據(jù)
data_len, //非線性的數(shù)據(jù)長度
mac_len; //mac包頭長度
__u32 priority; //該sk_buff結(jié)構(gòu)體的優(yōu)先級
__be16 protocol; //存放上層的協(xié)議類型,可以通過eth_type_trans()來獲取
... ...
sk_buff_data_t transport_header; //傳輸層頭部的偏移值
sk_buff_data_t network_header; //網(wǎng)絡(luò)層頭部的偏移值
sk_buff_data_t mac_header; //MAC數(shù)據(jù)鏈路層頭部的偏移值
sk_buff_data_t tail; //指向緩沖區(qū)的數(shù)據(jù)包末尾
sk_buff_data_t end; //指向緩沖區(qū)的末尾
unsigned char *head, //指向緩沖區(qū)的協(xié)議頭開始位置
*data; //指向緩沖區(qū)的數(shù)據(jù)包開始位置
... ...
其中sk_buff結(jié)構(gòu)體的空間,如下圖所示:

其中sk_buff-> data數(shù)據(jù)包格式如下圖所示:

所以,hard_start_xmit()發(fā)包函數(shù)處理步驟如下所示:
- 把數(shù)據(jù)包發(fā)出去之前,需要使用netif_stop_queue()來停止上層傳下來的數(shù)據(jù)包。
- 設(shè)置寄存器,通過網(wǎng)絡(luò)設(shè)備硬件,來發(fā)送數(shù)據(jù)。
- 當(dāng)數(shù)據(jù)包發(fā)出去后, 再調(diào)用dev_kfree_skb()函數(shù)來釋放sk_buff,該函數(shù)原型如下:
- 當(dāng)數(shù)據(jù)包發(fā)出成功,就會進(jìn)入TX中斷函數(shù),然后更新統(tǒng)計信息,調(diào)用netif_wake_queue()來喚醒,啟動上層繼續(xù)發(fā)包下來.
- 若數(shù)據(jù)包發(fā)出去超時,一直進(jìn)不到TX中斷函數(shù),就會調(diào)用net_device結(jié)構(gòu)體的(*tx_timeout)超時成員函數(shù),在該函數(shù)中更新統(tǒng)計信息, 調(diào)用netif_wake_queue()來喚醒
其中netif_wake_queue()和netif_stop_queue()函數(shù)原型如下所示:
void netif_wake_queue(struct net_device *dev); //喚醒被阻塞的上層,啟動繼續(xù)向網(wǎng)絡(luò)設(shè)備驅(qū)動層發(fā)送數(shù)據(jù)包
void netif_stop_queue(struct net_device *dev); //阻止上層向網(wǎng)絡(luò)設(shè)備驅(qū)動層發(fā)送數(shù)據(jù)包
網(wǎng)卡驅(qū)動收包過程
接收數(shù)據(jù)包主要通過中斷函數(shù)處理,來判斷中斷類型,如果等于ISQ_RECEIVER_EVENT,表示為接收中斷,然后進(jìn)入接收數(shù)據(jù)函數(shù),通過netif_xr()將數(shù)據(jù)上交給上層。
內(nèi)核中自帶的網(wǎng)卡驅(qū)動:/drivers/net/cs89x0.c

通過獲取的status標(biāo)志來判斷是什么中斷,如果是接收中斷,就進(jìn)入net_rx()
其中net_rx()收包函數(shù)處理步驟如下所示:
- 用dev_alloc_skb()來構(gòu)造一個新的sk_buff
- 使用skb_reserve(rx_skb, 2); 將sk_buff緩沖區(qū)里的數(shù)據(jù)包先后位移2字節(jié),來騰出sk_buff緩沖區(qū)里的頭部空間
- 讀取網(wǎng)絡(luò)設(shè)備硬件上接收到的數(shù)據(jù)
- 使用memcpy()將數(shù)據(jù)復(fù)制到新的sk_buff里的data成員指向的地址處,可以使用skb_put()來動態(tài)擴(kuò)大sk_buff結(jié)構(gòu)體里中的數(shù)據(jù)區(qū)
- 使用eth_type_trans()來獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里
-
然后更新統(tǒng)計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協(xié)議中
使用skb_put()函數(shù)后,其中sk_buff緩沖區(qū)變化如下圖:
寫虛擬網(wǎng)卡驅(qū)動
不需要硬件相關(guān)操作,所以就沒有中斷函數(shù),我們通過linux的ping命令來實(shí)現(xiàn)發(fā)包,然后在發(fā)包函數(shù)中偽造一個收的ping包函數(shù),實(shí)現(xiàn)能ping通任何ip地址.
在init初始函數(shù)中
- 使用alloc_netdev()來分配一個net_device結(jié)構(gòu)體
- 設(shè)置net_device結(jié)構(gòu)體的成員
- 使用register_netdev()來注冊net_device結(jié)構(gòu)體
在發(fā)包函數(shù)中:
- 使用netif_stop_queue()來阻止上層向網(wǎng)絡(luò)設(shè)備驅(qū)動層發(fā)送數(shù)據(jù)包
- 調(diào)用收包函數(shù),并代入發(fā)送的sk_buff緩沖區(qū), 里面來偽造一個收的ping包函數(shù)
- 使用dev_kfree_skb()函數(shù)來釋放發(fā)送的sk_buff緩存區(qū)
- 更新發(fā)送的統(tǒng)計信息
- 使用netif_wake_queue()來喚醒被阻塞的上層,
在收包函數(shù)中:
首先修改發(fā)送的sk_buff里數(shù)據(jù)包的數(shù)據(jù),使它變?yōu)橐粋€接收的sk_buff,其中數(shù)據(jù)包結(jié)構(gòu)如下圖所示:

- 需要對調(diào)上圖的ethhdr結(jié)構(gòu)體 ”源/目的”MAC地址
- 需要對調(diào)上圖的iphdr結(jié)構(gòu)體”源/目的” IP地址
- 使用ip_fast_csum()來重新獲取iphdr結(jié)構(gòu)體的校驗(yàn)碼
- 設(shè)置上圖數(shù)據(jù)包的數(shù)據(jù)類型,之前是發(fā)送ping包0x08,需要改為0x00,表示接收ping包
- 使用dev_alloc_skb()來構(gòu)造一個新的sk_buff
- 使用skb_reserve(rx_skb, 2);將sk_buff緩沖區(qū)里的數(shù)據(jù)包先后位移2字節(jié),來騰出sk_buff緩沖區(qū)里的頭部空間
- 使用memcpy()將之前修改好的sk_buff->data復(fù)制到新的sk_buff里的data成員指向的地址處:
- 設(shè)置新的sk_buff 其它成員
- 使用eth_type_trans()來獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里
- 然后更新接收統(tǒng)計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協(xié)議中
驅(qū)動具體代碼
#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) 對調(diào)ethhdr結(jié)構(gòu)體 "源/目的"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)對調(diào) iphdr結(jié)構(gòu)體"源/目的" 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結(jié)構(gòu)體的校驗(yàn)碼*/
ih->check = 0;
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
/*4)設(shè)置數(shù)據(jù)類型*/
type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
*type = 0; //之前是發(fā)送ping包0x08,需要改為0x00,表示接收ping包
/*5)使用dev_alloc_skb()來構(gòu)造一個新的sk_buff */
rx_skb = dev_alloc_skb(skb->len + 2);
/*6)使用skb_reserve()來騰出2字節(jié)頭部空間 */
skb_reserve(rx_skb, 2);
/*7)使用memcpy()將之前修改好的sk_buff->data復(fù)制到新的sk_buff里*/
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); // skb_put():來動態(tài)擴(kuò)大sk_buff結(jié)構(gòu)體里中的數(shù)據(jù)區(qū),避免溢出
/*8)設(shè)置新的sk_buff 其它成員*/
rx_skb->dev = dev;
rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
/*9)使用eth_type_trans()來獲取上層協(xié)議 */
rx_skb->protocol = eth_type_trans(rx_skb, dev);
/*10) 更新接收統(tǒng)計信息,并使用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()來阻止上層向網(wǎng)絡(luò)設(shè)備驅(qū)動層發(fā)送數(shù)據(jù)包*/
netif_stop_queue(dev);
//期間設(shè)置硬件發(fā)送數(shù)據(jù)包
/*2)調(diào)用收包函數(shù),里面來偽造一個收的ping包函數(shù)*/
virt_rs_packet(skb,dev);
/*3)使用dev_kfree_skb()函數(shù)來釋放發(fā)送的sk_buff緩存區(qū)*/
dev_kfree_skb(skb);
/*4)更新發(fā)送的統(tǒng)計信息*/
dev->stats.tx_packets++; //成功發(fā)送一個包
dev->stats.tx_bytes+=skb->len; //成功發(fā)送len長字節(jié)
dev->trans_start = jiffies; //發(fā)送時間戳
/*5)使用netif_wake_queue()來喚醒被阻塞的上層*/
netif_wake_queue(dev);
return 0;
}
static int virt_net_init(void)
{
/*1)使用alloc_netdev()來分配一個net_device結(jié)構(gòu)體*/
virt_net= alloc_netdev(sizeof(struct net_device), "virt_eth0", ether_setup);
/*2)設(shè)置net_device結(jié)構(gòu)體的成員 */
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結(jié)構(gòu)體 */
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");
測試運(yùn)行
掛載驅(qū)動,如下圖所示,可以看到net類下就有了這個網(wǎng)卡設(shè)備
insmod 16_th_virt_net.ko
ls /sys/class/net/
開始試驗(yàn),首先設(shè)置這個網(wǎng)卡設(shè)備的ip,然后去ping一下其它的ip,如下圖所示:

上圖的ping,之所以成功,是因?yàn)槲覀冊诎l(fā)包函數(shù)中,偽造了一個來收包,通過netif_rx()來將收包上傳給上層
使用ifconfig,可以看到這個網(wǎng)卡設(shè)備的統(tǒng)計信息共收發(fā)了6個包,以及收發(fā)的總數(shù)據(jù)



