Linux網卡驅動分析--(結合cs8900)

Linux網絡設備驅動程序概述

Linux網絡驅動程序遵循通用的接口。設計時採用的是面向對象的方法。一個設備就是一個對象(net_device結構),它內部有自己的數據和方法。一個網絡設備最基本的方法有初始化,發送和接收。
Linux網絡驅動程序的體系結構可以劃分爲四層:
網絡協議接口,網絡設備接口,設備驅動功能,網絡設備和網絡媒介層網絡驅動程序,最主要的工作就是完成設備驅動功能層。在Linux中所有網絡設備都抽象爲一個接口,這個接口提供了對所有網絡設備的操作集合。由數據結構struct net_device來表示網絡設備在內核中的運行情況,即網絡設備接口。它既包括純軟件網絡設備接口,如環路(Loopback),也包括硬件網絡設備接口,如以太網卡。而由以dev_base爲頭指針的設備鏈表來集體管理所有網絡設備,該設備鏈表中的每個元素代表一個網絡設備接口。數據結構net_device中有很多供系統訪問和協議層調用的設備方法,包括初始化,打開和關閉網絡設備的open和stop函數,處理數據包發送的hard_start_xmit函數,以及中斷處理函數等。
網絡設備在Linux裏做專門的處理。Linux的網絡系統主要是基於BSDunix的socket機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。

網絡架構

Linux網絡設備

網絡設備,又叫網絡接口是Linux第三類標準設備
• 網絡設備和塊設備類似,在內核的特定數據結構中註冊自己
• 當發生網絡數據交換時,網絡設備驅動程序註冊的方法將被內核調用
• 網絡設備不會在/dev下存在一個設備入口,它使用保留的內部設備名
•網絡設備異步的接收外來的數據包,有別於其他設備
• 網絡設備同時要執行大量的管理任務
– 設置地址
– 修改傳輸參數
– 維護流量和流量控制
– 錯誤統計和報告
• 網絡子系統是完全與協議無關的,網絡驅動程序與內核其餘部分之間的每次交互處理的都是一個網絡數據包

網絡設備驅動體系架構



以太網幀結構

以太網(IEEE 802.3)協議其驅動相關的幀結構:


 網卡驅動也不必關心所有內容,如其中前導序列、幀起始位、CRC 校驗由硬件自動添加/刪除,與驅動軟件無關


socket 簡介

客戶端與服務器都圍繞着通信端點(commnication endpoint)的概念,即套接字。
• 一個套接字通過使用socket()函數惟一確定了一個端點。最終,客戶端定義的端點將與服務器定義的端點進行連接和通信。
• 使用UDP和TCP時,端點就是本地或遠程IP地址與端口的組合。Socket廣泛用在網絡編程中,使用socket有固定的流程。

服務器:

– 調用socket
– 調用bind連接網絡地址
– 啓動監聽listen
– 等待連接accept
– recv,send交換數據
– close socket

• 客戶端:

– 調用socket
– 調用connet連接遠程主機
– recv,send交換數據
– close socket


socket編程相關數據類型定義
兩個結構:用來保存socket信息
  struct sockaddr{
       unsigned short sa family;   /*地址族,AF XXX*/
       char sa_data[14];       /*14字節的協議地址*/
  };
  struct sockaddr_in{
       short int sin_family;              /*地址族*/
       unsigned shortint sin_port;    /*端口號*/
       struct in_addr sin_addr;         /*IP地址*/
       unsigned char sin_zero[8];    /*填充0以保持與struct sockaddr同樣大小*/
   };
Bind()函數:將socket與本機上的一個端口相關聯,即“綁定”
connect()函數:用來與遠端服務器建立一個TCP連接
listen()函數:用來監聽是否有服務請求
accept() 函數:連接端口的服務請求
send()和recv() 函數:數據的發送和接收
sendto()和recvfrom() 函數:利用數據報方式進行數據傳輸
close()和shutdown() 函數:結束數據傳輸
DNS:域名服務相關函數,gethostbyname()完成域名和IP地址的轉換

sk_buff結構

套接字緩衝區(sk_buff)結構是Linux內核網絡子系統的核心內容,在<linux/skbuff.h>中被定義
• sk_buff結構中的重要字段:
– struct net_device *input_dev;
– struct net_device *dev;
分別爲接收和發送緩衝區的設備
– union { /* ... */ } h;
– union { /* . . . */ } nh;
– union { /*... */} mac;
指向數據包中各個層的數據包頭。h指向傳輸層包頭,nh指向網絡層包頭,mac指向鏈路層包頭


– unsigned char *head;
– unsigned char *data;
– unsigned char *tail;
– unsigned char *end;
用來尋址數據包中的數據指針。head指向已分配空間開頭,data指向有效的octet開頭,tail指向有效的octet結尾,而end指向tail可以到達的最大地址
– unsigned long len;描述數據長度
– unsigned char ip_summed;描述該數據包的校驗和策略
– unsigned char pkt_type;描述該數據包的類型,可以是PACKET_HOST ,PACKET_BROADCAST, PACKET_MULTICAST,PACKET_OTHERHOST

操作sk_buff 的函數

 struct sk_buff *alloc_skb(unsigned int len, int priority);
• struct sk_buff *dev_alloc_skb(unsigned int len);用來分配套接字緩衝區
• void kfree_skb(struct sk_buff *skb);
• void dev_kfree_skb(struct sk_buff *skb);用來釋放套接字緩衝區
• unsigned char *skb_put(struct sk_buff *skb, int len);
• unsigned char *skb_push(struct sk_buff *skb, int len);這兩個函數用來在緩衝區末尾添加數據,前一個函數會進行檢查
• unsigned char *skb_push(struct sk_buff *skb, int len);
• unsigned char *_ _skb_push(struct sk_buff *skb, intlen);這兩個函數用來在緩衝區頭部添加數據
• int skb_tailroom(struct sk_buff *skb);該函數返回緩衝區後部可用空間總量
• int skb_headroom(struct sk_buff *skb);該函數返回緩衝區前面部分可用空間總量
• void skb_reserve(struct sk_buff *skb, int len);該函數在可填充緩衝區之前保留包頭空間
• unsigned char *skb_pull(struct sk_buff *skb, int len);該函數從數據包頭拿出數據,通常用來剝離數據包頭

net_device結構分析

• 該結構是網絡設備驅動的核心,它包含了許多成員。 可以參考<include/linux/netdevice.h>文件,閱讀它的完整定義。
• net_device結構可分爲全局成員、硬件相關成員、接口相關成員、設備方法成員和公用成員等五個部分。
• char name[IFNAMSIZ];
• unsigned long mem_end;
• unsigned long mem_start;這些字段描述了設備共享內存的起止地址。
• unsigned long base_addr;描述設備的I/O基地址
• unsigned char irq;描述設備中斷號
• unsigned char if_port;描述多端口設備的活動端口
• unsigned char dma;描述設備的DMA通道


• 網絡設備要實現一系列函數作爲調用方法的實現
• 基本方法包括
– int (*open)(struct net_device *dev);
打開接口。當ifconfig激活網絡設備時,接口被打開。通常我們在open方法裏面完成資源的分配,包括I/O映射、中斷註冊、DMA註冊等。同時激活硬件,並增加使用計數
– int (*stop)(struct net_device *dev);停止接口。在這個方法裏面,我們完成與open方法相反的,註銷操作
– int (*hard_start_xmit) (struct sk_buff *skb, struct net_device*dev);
該方法初始化數據包的傳輸。是網絡設備驅動中非常重要的一個方法。我們將完整的數據包放入一個套接字緩衝區sk_buff結構裏
– 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);該方法用來在傳輸數據包之前重建硬件頭
– 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 ifreq *ifr, int cmd);用來實現設備自定義的ioctl命令。如果不需要自定義命令,可以爲NULL
• void (*set_multicast_list)(struct net_device *dev);當設備的組播列表改變或設備標誌改變時,該方法被
調用
• int (*set_mac_address)(struct net_device *dev, void *addr);如果接口支持MAC地址改變,則可以實現該函數
• int (*change_mtu)(struct net_device *dev, int new_mtu);當接口的MTU改變時,該方法將被調用,負責做出相應的特定處理
• int (*header_cache) (struct neighbour *neigh, structhh_cache *hh);根據ARP查詢的結果填充hh_cache結構
• int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);在發生變化時,該方法更新hh_cache結構中的目的地址
• int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);從skb中包含的數據包中獲得源地址,並將其複製到位於haddr的緩衝區中

數據包傳送與接收

• 網絡接口最重要任務就是發送和接收數據
• 當內核要發送一個數據包時,它會調用hard_start_transmit方法來將數據放入發送隊列。
• 內核處理過的每個數據包位於一個套接字緩衝區(struct sk_buff)裏面。

傳輸超時

• 網絡傳輸需要面對可能不可靠的設備、傳輸介質
• 硬件驅動程序必須能夠處理硬件不能正確響應的情況
• 一般來說驅動程序採取超時方式處理這類異常情況,即:
– 如果某個操作在定時器到期時還未完成,則認爲出現異常
– 網絡驅動程序可以在net_device結構的watchdog_timeo字段中設置超時時間,以jiffies爲單位
– 如果當前的系統時間超過設備的trans_start時間至少一個超時週期,那麼網絡層將最終調用驅動程序的tx_timeout方法
– 這個方法完成解決超時問題的工作,並保證正在進行的任何傳輸能夠正常結束

數據包的接收

• 接收數據包首先通過設備中斷,由硬件通知驅動程序有數據包到達。
• 網絡例子驅動程序實現cs8900_receive函數,該函數在收到數據包後被調用。它獲得一個指向數據的指針以及數據包的長度,然後將數據以及附加信息發送到上層的網絡代碼。

網絡中斷處理

• 中斷處理程序可以檢查物理設備上的狀態寄存器,用來區分是數據包到達中斷還是傳輸完成中斷
• 傳輸結束時,中斷處理程序更新統計信息,並且調用dev_kfree_skb將不再使用的套接字緩衝區釋放給系統
• 同時在傳輸結束時,如果之前驅動程序停止了傳輸隊列,則調用netif_wake_queue重新啓動傳輸隊列
• 而在數據包到達中斷髮生時,中斷處理程序只是調用數據接收函數就夠了

網絡設備驅動的基本實現

• 一個網絡設備最基本的方法有初始化、打開、關閉、發送和接收。
• 初始化程序完成硬件的初始化、網絡設備中變量的初始化和系統資源的申請。發送程序是在驅動程序的上層協議層有數據要發送時自動調用的。
• 一般驅動程序中不對發送數據進行緩存,而是直接使用硬件的發送功能把數據發送出去。接收數據一般是通過硬件中斷來通知的。在中斷處理程序裏,把硬件幀信息填入一個sk_buff結構中,然後調用netif_rx()傳遞給上層處理。

初始化

• 設備探測工作在init方法中進行,一般調用一個稱之爲probe方法的函數
• 初始化的主要工作是檢測設備,配置和初始化硬件,最後向系統申請這些資源。此外,填充該設備的dev結構,我們可以調用內核提供的ether_setup方法來設置一些以太網默認的設置
• 當我們需要卸載網絡驅動程序時,我們應該釋放初始化時分配的資源,最後調用unregister_netdev來講自己從全局網絡設備鏈表中註銷

打開

• open這個方法在網絡設備驅動程序裏是網絡設備被激活的時候被調用(即設備狀態由down-->up)。
• 實際上很多在初始化中的工作可以放到這裏來做。比如資源的申請,硬件的激活。如果dev->open返回非0(error),則硬件的狀態還是down。
• 網絡驅動程序例子中由cs8900_start 函數實現open方法。

關閉

• stop方法做和open相反的工作。
• 可以釋放某些資源以減少系統負擔。
• stop是在設備狀態由up轉爲down時被調用的。
• 網絡驅動程序例子中由cs8900_stop函數實現stop方法。

發送

• 在系統調用驅動程序的hard_start_xmit時,發送的數據放在一個sk_buff結構中。一般的驅動程序把數據傳給硬件發出去。也有一些特殊的設備比如loopback把數據組成一個接收數據再回送給系統,或者dummy設備直接丟棄數據。
• 如果發送成功,hard_start_xmit方法裏釋放sk_buff,返回0(發送成功)。如果設備暫時無法處理,比如硬件忙,則返回1。網絡驅動程序例子中由cs8900_send_start函數實現發送

接收

• 一般設備收到數據後都會產生一箇中斷,在中斷處理程序中驅動程序申請一塊sk_buff(skb),從硬件讀出數據放置到申請好的緩衝區裏。
• 接下來填充sk_buff中的一些信息。網絡驅動程序例子中由cs8900_receive函數實現接收功能












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章