26.Linux-網卡驅動(詳解)

来源:http://www.cnblogs.com/lifexy/archive/2017/10/31/7763352.html
-Advertisement-
Play Games

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網卡驅動


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在使用ORACLE的過程中,會出現各種各樣的問題,各種各樣的錯誤,其中ORA-12899就是前段時間我在將數據導入到我本地機器上的時候一直出現的問題.不過還好已經解決了這個問題,現在分享一下,解決方案;出現ORA-12899,是字元集引起的,中文在UTF-8中占3個位元組,ZHS16GBK中占2個位元組 ...
  • 原文鏈接: http://www.aichengxu.com/database/8499581.htm 一.同一主機下位置的轉移 在mysql安裝完成後,要修改資料庫存儲的位置,比如從安裝目錄下的C:\Program Files\MySQL\MySQL Server 5.0\Data文件夾轉移到D: ...
  • [20171031]rman xxx Failure.txt--//簡單測試 List Failure, Advise Failure and Repair Failure命令在11g下,也許以後工作需要.--//雖然我自己很少使用這個命令,感覺這個有點傻瓜化.1.環境:SYS@book> @ &r ...
  • [20171031]markhot.txt--//昨天看了https://jonathanlewis.wordpress.com/2017/10/02/markhot/,測試看看這樣時候可以減少爭用.1.環境:SCOTT@book> @ &r/ver1PORT_STRING VERSION BANN ...
  • 出現問題的可能性 1、可能是/opt/mysql/data/數據目錄mysql用戶沒有許可權(修改數據目錄的許可權) 解決方法 :給予許可權,執行 "chown -R mysql.mysql /opt/mysql/data" 然後重新啟動mysqld 2、可能進程里已經存在mysql進程 解決方法:用命令 ...
  • Windows 10客戶端及Windows server 2016 伺服器可以使用powershell 命令獲得系統支持的密碼套件列表,禁用啟用相應的密碼套件。 Windows server 2016之前版本微軟並沒有給出相應的powershell 命令來獲取密碼套件列表,但在msdn上給出了c++ ...
  • 1、簡介 GCC程式編譯可分成四個階段: 預處理(Pre-Preocessing) 編譯(Compiling) 彙編(Assembling) 鏈接(Linking) 2、GCC基本用法 基本用法:gcc [options][filename] 常用options選型及其用法介紹如下: -o outp ...
  • 一、Nginx簡介 1.1Nginx特性 模塊化設計,較好的擴展性 高可靠性 支持熱部署:不停機更新配置文件,升級版本,更換日誌文件 低記憶體消耗:10000個keep alive連接模式下的非活動連接,僅 需要2.5M記憶體event driven,aio,mmap,sendfile 1.2Nginx ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...