程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> linux網卡驅動

linux網卡驅動

編輯:關於C語言

Linux網卡驅動

  NE2000以太網卡的基礎上進行的。 只要看懂一塊網卡的驅動,那麼其他網卡的驅動是類似的,模塊的劃分也是一致的,只是具體的函數和芯片的操作有區別。 文檔中紅色的標注都是重點。 自己看代碼的時候避免依賴於硬件和芯片的代碼,而對整個網絡設備管理機制的學習,並關注一般網絡設備所共有的東西。    

一. 網絡設備驅動

網絡的物理設備,稱為接口Interface)。所有對網絡硬件的訪問都是通過接口進行的,接口提供了一個對所有類型的硬件一致化的操作集合來處理基本數據的發送和接收。 一個網絡接口被看作是一個發送和接收數據包packets)的實體。 對於每個網絡接口,都用一個device的數據結構表示,有關該數據結構的具體內容,將在本文的後面詳細介紹,周日的代碼中已經將主要的部分注釋了。 網絡設備是一個物理設備如以太網卡,但軟件也可以作為網絡設備,如回送設備loopback)。 在內核啟動時,通過網絡設備驅動程序,將登記存在的網絡設備。設備用標准的支持網絡的機制來轉遞收到的數據到相應的網絡層。 所有被發送和接收的包都用數據結構sk_buff表示。這是一個具有很好的靈活性的數據結構,可以很容易增加或刪除網絡協議數據包的首部,是和上層的接口,對於IP層的網絡來說,只能識別和區分skbuff。 網絡接口的核心用一個device數據結構表示的。網絡設備在做數據包發送和接收時,直接通過接口訪問。 網絡接口是在系統初始化時實時生成的,對於核心支持的但不存在的物理網絡設備,將不可能有與之相對應的device結構。 在內核中也存在著一張網絡接口管理表dev_base,但與前兩張表不同,dev_base是指向device結構的指針,因為網絡設備是通過device數據結構來表示的。dev_base實際上是一條device結構鏈表的表頭,在系統初始化完成以後,系統檢測到的網絡設備將自動地保存在這張鏈表中,其中每一個鏈表單元表示一個存在的物理網絡設備。當要發送數據時,網絡子系統將根據系統路由表選擇相應的網絡接口進行數據傳輸,而當接收到數據包時,通過驅動程序登記的中斷服務程序進行數據的接收處理。 網絡設備工作原理圖:   每一個具體的網絡接口都應該有一個名字,以在系統中能唯一標識一個網絡接口。通常一個名字僅表明該接口的類型。Linux對網絡設備命名有以下約定:(其中N為一個非負整數) ethN              以太網接口,包括10Mbps和100Mbps,對應到代碼裡的字符串“eth%d”; trN         令牌環接口; slN         SLIP網絡接口; pppN      PPP網絡接口,包括同步和異步; plipN      PLIP網絡接口,其中N與打印端口號相同; tunlN      IPIP壓縮頻道網絡接口; nrN               NetROM虛擬設備接口; isdnN      ISDN網絡接口; dummyN        空設備; lo           回送網絡接口。  

二. struct device

這裡是我從代碼裡拷回來的,和代碼裡的注釋是對應的,最好和代碼一起看; /*  from  include/linux/netdevice.h  */ struct device {

1. 屬性

  char                          *name; 設備的名字。如果第一字符為NULL即’\0’),register_netdev (drivers/net/net_init.c)將會賦給它一個n最小的可用網絡設備名ethn。     unsigned long            rmem_end;            /* shmem "recv" end     */   unsigned long            rmem_start;           /* shmem "recv" start    */   unsigned long            mem_end;             /* shared mem end */   unsigned long            mem_start;            /* shared mem start       */ 這些域段標識被設備使用的共享內存的首地址及尾地址。如果設備用來接收和發送的內存塊不同,則mem域段用來標識發送的內存位置,rmem用來標識接收的內存位置。mem_start和mem_end可在系統啟動時用內核的命令行指定,用ifconfig可以查看它們的值。rmem域段從來不被驅動程序以外的程序所引用。     unsigned long            base_addr;             /* device I/O address     */   unsigned char            irq;                /* device IRQ number    */ I/O基地址和中斷號。它們都是在設備檢測期間被賦值的,但也可以在系統啟動時指定傳入如傳給LILO)。ifconfig命令可顯示及修改他們的當前值。     volatile unsigned char        start;            /* start an operation       */   volatile unsigned char        interrupt;        /* interrupt arrived */ 這是兩個二值的低層狀態標志。通常在設備打開時置start標志,在設備關閉時清start標志。當interrupt置位時,表示有一個中斷已到達且正在進行中斷服務程序理。     unsigned long            tbusy;             /* transmitter busy must be long for bitops */ 標識“發送忙”。在驅動程序不能接受一個新的需傳輸的包時,該域段應該為非零。     struct device              *next; 指向下一個網絡設備,用於維護鏈表。     unsigned char            if_port; 記錄哪個硬件I/O端口正在被接口所用,如BNC,AUI,TP等drivers/net/de4x5.h)。   unsigned char            dma;       設備用的DMA通道。 一些設備可能需要以上兩個域段,但非必需的。     unsigned long            trans_start;      /* Time (in jiffies) of last Tx */ 上次傳輸的時間點in jiffies)   unsigned long            last_rx;    /* Time of last Rx         */ 上次接收的時間點in jiffies)。如trans_start可用來幫助內核檢測數據傳輸的死鎖lockup)。     unsigned short           flags;      /* interface flags (BSD) */ 該域描述了網絡設備的能力和特性。它包括以下flags:include/linux/if.h) IFF_UP 表示接口在運行中。當接口被激活時,內核將置該標志位。 IFF_BROADCAST 表示設備中的廣播地址時有效的。以太網支持廣播。 IFF_DEBUG 調試模式,表示設備調試打開。當想控制printk及其他一些基於調試目的的信息顯示時,可利用這個標志位。雖然當前沒有正式的驅動程序使用它,但它可以在程序中通過ioctl來設置從而使用它。 IFF_LOOPBACK 表示這是一個回送loopback)設備,回送接口應該置該標志位。核心是通過檢查此標志位來判斷設備是否是回送設備的,而不是看設備的名字是否是lo。 IFF_POINTTOPOINT 表示這是一個點對點鏈接SLIP and PPP),點對點接口必須置該標志位。Ifconfig也可以置此標志位及清除它。若置上該標志位,則dev->dstaddr應也相應的置為鏈接對方的地址。 IFF_MASTER              /* master of a load balancer */ IFF_SLAVE          /* slave of a load balancer     */ 此兩個標志位在裝入平等化中要用到。 IFF_NOARP 表示不支持ARP協議。通常的網絡接口能傳輸ARP包,如果想讓接口不執行ARP,可置上該標志位。如點對點接口不需要運行ARP。 IFF_PROMISC 全局接受模式。在該模式下,設備將接受所有的包,而不關這些包是發給誰的。在缺省情況下,以太網接口會使用硬件過濾,以保證只接受廣播包及發給本網絡接口的包。Sniff的原理就是通過設置網絡接口為全局接受模式,接受所有到達本接口媒介的包,來“偷聽”本子網的“秘密”。 IFF_MULTICAST 能接收多點傳送的IP包,具有多點傳輸的能力。ether_setup缺省是置該標志位的,故若不想支持多點傳送,必須在初始化時清除該標志位。 IFF_ALLMULTI 接收所有多點傳送的IP包。 IFF_NOTRAILERS       /*無網絡TRAILER*/ IFF_RUNNING            /*資源被分配*/     unsigned short           family;    /* address family ID (AF_INET)  */ 該域段標識本設備支持的協議地址簇。大部分為AF_INET英特網IP協議),接口通常不需要用這個域段或賦值給它。     unsigned short           metric;    /* routing metric (not used)   */   unsigned short           mtu; 不包括數據鏈路層幀首幀尾的最大傳輸單位Maximum Transfer Unit)。網絡層在包傳輸時要用到。對以太網而言,該域段為1500,不包括MAC幀的幀首和幀尾MAC幀格式稍後所示)。     unsigned short           type;              /* interface hardware type     */ 接口的硬件類型,描述了與該網絡接口綁在一起的媒介類型。Linux網絡設備支持許多不同種類的媒介,如以太網,X.25,令牌環,SLIP,PPP,Apple Localtalk等。ARP在判定接口支持哪種類型的物理地址時要用到該域段。若是以太網接口,則在ether_setup中將之設為ARPHRD_ETHEREthernet 10Mbps)。     unsigned short           hard_header_len;    /* hardware hdr length   */ 在被傳送的包中IP頭之前的字節數。對於以太網接口,該域段為14ETH_HLEN,include\linux\if_ether.h),這個值可由MAC幀的格式得出: MAC幀格式: 目的地址6字節)+ 源地址6字節)+ 數據長度2字節)+ 數據46~~1500)+FCS     void                         *priv;      /* pointer to private data       */ 該指針指向私有數據,通常該數據結構中包括struct enet_statistics。類似於struct file的private_data指針,但priv指針是在設備初始化時被分配內存空間的而不是在設備打開時),因為該指針指向的內容包括設備接口的統計數據,而這些數據即使在接口卸下down)時也應可以得到的,如用戶通過ifconfig查看。     unsigned char            pad;                      /* make dev_addr aligned to 8 bytes */   unsigned char            broadcast[MAX_ADDR_LEN];    /* hw bcast add */ 廣播地址由六個0xff構成,即表示255.255.255.255。 memset(dev->broadcast,0xFF, ETH_ALEN); drivers/net/net_init.c)     unsigned char            dev_addr[MAX_ADDR_LEN];     /* hw address  */ 設備的物理地址。當包傳送給驅動程序傳輸時,要用物理地址來產生正確的幀首。     unsigned char            addr_len;        /* hardware address length    */ 物理地址的長度。以太網網卡的物理地址為6字節ETH_ALEN)。     unsigned long            pa_addr;         /* protocol address        */   unsigned long            pa_brdaddr;    /* protocol broadcast addr     */   unsigned long            pa_mask;        /* protocol netmask       */ 該三個域段分別描述接口的協議地址、協議廣播地址和協議的網絡掩碼。若dev->family為AF_INET,則它們即為IP地址。這些域段可用ifconfig賦值。     unsigned short           pa_alen;         /* protocol address length      */ 協議地址的長度。AF_INET的為4。     unsigned long            pa_dstaddr;     /* protocol P-P other side addr      */ 點對點協議接口如SLIP、PPP)用這個域記錄連接另一邊的IP值。     struct dev_mc_list      *mc_list;        /* Multicast mac addresses     */   int                            mc_count;      /* Number of installed mcasts       */   struct ip_mc_list        *ip_mc_list;    /* IP multicast filter chain    */ 這三個域段用於處理多點傳輸。其中mc_count表示mc_list中的項目數。     __u32                       tx_queue_len; /* Max frames per queue allowed */ 一個設備的傳輸隊列能容納的最大的幀數。對以太網,缺省為100;而plip則為節省系統資源,僅設為10。       /* For load balancing driver pair support */   unsigned long            pkt_queue;      /* Packets queued */   struct device              *slave;           /* Slave device */   struct net_alias_info   *alias_info;     /* main dev alias info */   struct net_alias           *my_alias;      /* alias devs */     struct sk_buff_head    buffs[DEV_NUMBUFFS]; 指向網絡接口緩沖區的指針。

2. 結構中指向函數的指針,用來作為服務操作

網絡接口操作可以分為兩部分,一部分為基本操作,即每個網絡接口都必須有的操作;另一部分是可選操作。   /* 基本操作 */   int       (*init) (struct device *dev);  /* Called only once. */ 初始化函數的指針,僅被調用一次。當登記一個設備時,核心一般會讓驅動程序初始化該設備。初始化函數功能包括以下內容:檢測設備是否存在;自動檢測該設備的I/O端口和中斷號;填寫該設備device結構的大部分域段;用kmalloc分配所需的內存空間等。若初始化失敗,該設備的device結構就不會被鏈接到全局的網絡設備表上。在系統啟動時,每個驅動程序都試圖登記自己,當只有那些實際存在的設備才會登記成功。這與用主設備號及次設備號索引的字符設備和塊設備不同。     int       (*open) (struct device *dev); 打開網絡接口。每當接口被ifconfig激活時,網絡接口都要被打開。Open操作做以下工作:登記一些需要的系統資源,如IRQ、DMA、I/O端口等;打開硬件;將module使用計數器加一。     int       (*stop) (struct device *dev); 停止網絡接口。操作內容與open相逆。     int       (*hard_start_xmit) (struct sk_buff *skb,  struct device *dev); 硬件開始傳輸。這個操作請求對一個包的傳輸,這個包原保存在一個socket緩沖區結構中sk_buff)。     int       (*hard_header) (struct sk_buff *skb,  struct device *dev,  unsigned short type,   void *daddr,        void *saddr,  unsigned len); 這個函數可根據先前得到的源物理地址和目的物理地址建立硬件頭hardware header)。以太網接口的缺省函數是eth_header。     int       (*rebuild_header)(void *eth, struct device *dev,  unsigned long raddr, struct sk_buff *skb); 在一個包被發送之前重建硬件頭。對於以太網設備,若有未知的信息,缺省函數將使用ARP填寫。     struct enet_statistics*         (*get_stats)(struct device *dev); 當一個應用程序需要知道網絡接口的一些統計數據時,可調用該函數,如ifconfig、netstat等。   /* 可選操作 */   void    (*set_multicast_list)(struct device *dev); 設置多點傳輸的地址鏈表*mc_list)。     int       (*set_mac_address)(struct device *dev, void *addr); 改變硬件的物理地址。如果網絡接口支持改變它的硬件物理地址,就可用這個操作。許多硬件不支持該功能。     int       (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd); 執行依賴接口的ioctl命令。     int       (*set_config)(struct device *dev, struct ifmap *map); 改變接口配置。設備的I/O地址和中斷號可以通過該函數進行實時修改。     void    (*header_cache_bind)(struct hh_cache **hhp,  struct device *dev,    unsigned short htype,  __u32 daddr);   void    (*header_cache_update)(struct hh_cache *hh, struct device *dev, unsigned char * haddr);     int       (*change_mtu) (struct device *dev, int new_mtu); 這個函數負責使接口MTU改變後生效。如果當MTU改變時驅動程序要作一些特殊的事情,就應該寫這個函數。     struct iw_statistics*    (*get_wireless_stats) (struct device *dev); };  

三. 網卡的初始化

網絡設備的初始化主要工作是檢測設備的存在、初始化設備的device結構及在系統中登記該設備。系統內核中存在著一張網絡接口管理表dev_base,但與dev_base是指向device結構的,因為網絡設備是通過device數據結構來表示的。 dev_base實際上是一條device結構鏈表的表頭,在系統初始化完成以後,系統檢測到的網絡設備將自動地保存在這張鏈表中,其中每一個鏈表單元表示一個存在的物理網絡設備。登記成功的網絡設備必定可在dev_base鏈表中找到。   網絡設備的初始化從觸發角度看可分為兩類: 一類是由shell命令insmod觸發的模塊化驅動程序module),只有模塊化的網絡設備驅動程序才能用這種方式對設備進行初始化,稱為“模塊初始化模式”; 另一類是系統驅動時由核心自動檢測網絡設備並進行初始化,我們稱為“啟動初始化模式”。

1. “模塊初始化模式”的分析

insmod命令將調用相應模塊的init_module),裝載模塊。init_module函數在初始化dev->init函數指針後,將調用register_netdev)在系統登記該設備。 若登記成功,則模塊裝載成功,否則返回出錯信息。 register_netdev首先檢查設備名是否已確定,若沒賦值則給它一個缺省的值ethN,N為最小的可用以太網設備號注; 然後,網絡設備自己的init_function,即剛在init_module中賦值的dev->init,將被調用,用來實現對網絡接口的實際的初始化工作。 若初始化成功,則將該網絡接口加到網絡設備管理表dev_base的尾部。   NE2000網卡為例。NE2000網卡的主要驅動程序在文件drivers/net/ne.c中。    

init_module

init_module---模塊初始化函數,當裝載模塊時,核心將自動調用該函數。在次此函數中一般處理以下內容: 1.處理用戶可能傳入的參數name、ports及irq的值。若有,則賦給相應的接口   2.對dev->init函數指針進行賦值,對於任何網絡設備這一步必不可少!因為在register_netdev中要用到該函數指針; 3.調用register_netdev,完成檢測、初始化及設備登記等工作。   /* from  drivers/net/ne.c */ init_module(void) { int this_dev, found = 0;   /* 對所有可能存在的以太網接口進行檢測並試圖去登記,MAX_NE_CARDS為4,  * 即最多可以使用4塊NE2000兼容網卡。 */ for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++) {         struct device *dev = &dev_ne[this_dev];         /* 可能有用戶傳入的參數:指定的name、ports及irq的值 */         dev->name = namelist+(NAMELEN*this_dev);         dev->irq = irq[this_dev];         dev->base_addr = io[this_dev];         dev->init = ne_probe;           /* NE2000的檢測和初始化函數 */         dev->mem_end = bad[this_dev];         if (register_netdev(dev) == 0) { /* 試圖登記該設備 */                found++;                continue;                      /* 設備登記成功,繼續登記下一個設備 */         }         /* 第一次發生登記不成功事件 */         if (found != 0)             /* 在這之前沒有成功登記NE2000接口,返回 */                return 0;         /* 顯示出錯信息 */         if (io[this_dev] != 0)                printk(KERN_WARNING "ne.c: No NE*000 card found at i/o = %#x\n", io[this_dev]);         else                printk(KERN_NOTICE "ne.c: No PCI cards found. Use \"io=0xNNN\" value(s) for   …………  

register_netdev

該函數實現對網絡接口的登記功能。其實現步驟如下: 1.首先檢查設備名是否已確定,若沒賦值則以以太網設備待之並給它一個缺省的值ethN,N為最小的可用以太網設備號; 2.然後,網絡設備自己的init_function,即剛在init_module中賦值的dev->init,將被調用,用來實現對網絡接口的實際的初始化工作。 3.若初始化成功,則將該網絡接口加到網絡設備管理表dev_base的尾部   /* from  drivers/net/net_init.c  */ int register_netdev(struct device *dev) { struct device *d = dev_base; /* 取得網絡設備管理表的表頭指針 */ ………… if (dev && dev->init) {         /*若設備名字沒確定,則將之看作是以太網設備!!*/         if (dev->name &&                ((dev->name[0] == '\0') || (dev->name[0] == ' '))) {                /* 找到下一個最小的空閒可用以太網設備名字 */                for (i = 0; i < MAX_ETH_CARDS; ++i)                       if (ethdev_index[i] == NULL) {                              sprintf(dev->name, "eth%d", i);                              printk("loading device '%s'...\n", dev->name);                              ethdev_index[i] = dev;                              break;                       }         } ………… /* 調用初始化函數進行設備的初始化 */         if (dev->init(dev) != 0) { …………         /* 將設備加到網絡設備管理表中,加在最後 */         if (dev_base) {                /* 找到鏈表尾部 */                while (d->next)                       d = d->next;                d->next = dev;         }         else                dev_base = dev;         dev->next = NULL; …………  

init_function

函數原型:int init_function (struct device *dev); 當系統登記一個網絡設備時,核心一般會請求該設備的驅動程序初始化自己。初始化函數功能包括以下內容: 1.檢測設備是否存在,一般和第二步一起作; 2.自動檢測該設備的I/O地址和中斷號; 對於可以與其他共享中斷號的設備,我們應盡量避免在初始化函數中登記I/O地址和中斷號,I/O地址和中斷號的登記最好在設備被打開的時候,因為中斷號有可能被其他設備所共享。若不准備和其他設備共享,則可在此調用request_irq和request_region馬上向系統登記。 3.填寫傳入的該設備device結構的大部分域段; 對於以太網接口,device結構中許多有關網絡接口信息都是通過調用ether_setup函數driver/net/net_init.c)統一來設置的,因為以太網卡有很好的共性。對於非以太網接口,也有一些類似於ether_setup的函數,如tr_setup令牌網),fddi_setup。若添加的網絡設備都不屬於這些類型,就需要自己填寫device結構的各個分量。 4.kmalloc需要的內存空間。   若初始化失敗,該設備的device結構就不會被鏈接到全局的網絡設備表上。在系統啟動時,每個驅動程序都試圖登記自己,當只有那些實際存在的設備才會登記成功。這與用主設備號及次設備號索引的字符設備和塊設備不同。   在膠片的代碼裡物理設備NE2000網卡的初始化函數是由ne_probe和ne_probe1及ethdev_init共同實現。   /* from  drivers/net/ne.c */ int ne_probe(struct device *dev) {    …………    int base_addr = dev ? dev->base_addr : 0;    /* I/O地址. User knows best. <cough> */    if (base_addr > 0x1ff)             /* I/O地址有指定值 */       return ne_probe1(dev, base_addr);  /* 這個函數在下面分析 */    else if (base_addr != 0)     /* 不自動檢測I/O */       return ENXIO;    …………    /* base_addr=0,自動檢測,若有第二塊ISA網卡則是一個冒險!J    * 對所有NE2000可能的I/O地址都進行檢測,可能的I/O地址在存在    * netcard_portlist數組中:    * static unsigned int netcard_portlist[]={ 0x300, 0x280, 0x320, 0x340, 0x360, 0};    */    for (i = 0; netcard_portlist[i]; i++) {       int ioaddr = netcard_portlist[i];       if (check_region(ioaddr, NE_IO_EXTENT))          continue;       /* 檢測到一個I/O端口地址 */       if (ne_probe1(dev, ioaddr) == 0)          return 0; …………   /* from  drivers/net/ne.c */ static int ne_probe1(struct device *dev, int ioaddr) {    …………    /* 檢測、確認I/O地址;初始化8390 */    …………    /* 自動檢測中斷號,非常巧妙!! */    if (dev->irq < 2) {       autoirq_setup(0); /* 自動檢測准備 */       outb_p(0x50, ioaddr + EN0_IMR);           /* 中斷使能 */       outb_p(0x00, ioaddr + EN0_RCNTLO);       outb_p(0x00, ioaddr + EN0_RCNTHI);       outb_p(E8390_RREAD+E8390_START, ioaddr); /* 觸發中斷 */       outb_p(0x00, ioaddr + EN0_IMR);          /* 屏蔽中斷 */       dev->irq = autoirq_report(0);     /* 獲得剛才產生的中斷號 */       …………     …………   /* 登記中斷號,中斷服務程序為ei_interrupt。    * 因為ISA網卡不能和其他設備共享中斷。*/    int irqval = request_irq(dev->irq, ei_interrupt, pci_irq_line ? SA_SHIRQ : 0, name, dev);    if (irqval) {       printk (" unable to get IRQ %d (irqval=%d).\n", dev->irq, irqval);       return EAGAIN;    }    dev->base_addr = ioaddr;         /* 設置I/O地址——已經過確認 */      /* 調用ethdev_init初始化dev結構 */    if (ethdev_init(dev)) { /* 該函數下面將分析 */       printk (" unable to get memory for dev->priv.\n");       free_irq(dev->irq, NULL); /* 初始化不成功,釋放登記的中斷號! */       return -ENOMEM;    }    /* 向系統登記I/O地址 */    request_region(ioaddr, NE_IO_EXTENT, name);    /* 將硬件的物理地址賦給dev->dev_add */    for(i = 0; i < ETHER_ADDR_LEN; i++) {       printk(" %2.2x", SA_prom[i]);       dev->dev_addr[i] = SA_prom[i];    }    printk("\n%s: %s found at %#x, using IRQ %d.\n",                     dev->name, name, ioaddr, dev->irq);    …………    /* 向dev結構登記設備打開和關閉函數 */    dev->open = &ne_open;    dev->stop = &ne_close; …………   /* from  drivers/net/8390.c  */ int ethdev_init(struct device *dev) {    …………    if (dev->priv == NULL) {       struct ei_device *ei_local;       /* 申請私有數據結構空間,用於記錄設備的狀態等 */       dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL);     …………    dev->hard_start_xmit = &ei_start_xmit;    dev->get_stats = get_stats;    dev->set_multicast_list = &set_multicast_list;    ether_setup(dev); …………

ether_setup

ether_setup是一個通用於以太網接口的網絡接口設置函數。所有的以太網卡的device結構中許多有關的網絡接口信息都是通過調ether_setup函數統一來設置。   /* from  drivers/net/net_init.c */ void ether_setup(struct device *dev) {    int i;    /* 初始化緩沖隊列鏈表,這是一個雙向鏈表 */    for (i = 0; i < DEV_NUMBUFFS; i++)   /* DEV_NUMBUFFS=3 */       skb_queue_head_init(&dev->buffs[i]);    …………    /* 一些處理函數的初始化,驅動程序可以不寫這些函數了 */    dev->change_mtu= eth_change_mtu;    dev->hard_header= eth_header;    dev->rebuild_header = eth_rebuild_header;    dev->set_mac_address = eth_mac_addr;    dev->header_cache_bind = eth_header_cache_bind;    dev->header_cache_update= eth_header_cache_update;      dev->type                 = ARPHRD_ETHER; /* Ethernet 10Mbps */    dev->hard_header_len= ETH_HLEN;      /* MAC層協議頭的大小 14 */    dev->mtu                 = 1500;                /* 最大傳輸單位  */    dev->addr_len          = ETH_ALEN;      /* 協議地址長度 4 */    dev->tx_queue_len    = 100;                   /* 傳輸隊列的長度 */    memset(dev->broadcast,0xFF, ETH_ALEN);/* 物理地址長度 6 */                                                   /* 廣播地址有效及支持多點傳輸 */    dev->flags         = IFF_BROADCAST|IFF_MULTICAST;    dev->family      = AF_INET; /* 英特網IP協議簇 */    dev->pa_addr    = 0;               /* 以後用ifconfig命令設置 */    dev->pa_brdaddr= 0;               /* 以後用ifconfig命令設置 */    dev->pa_mask   = 0;               /* 以後用ifconfig命令設置 */    dev->pa_alen     = 4;               /* 協議地址長度 4  */ }  

2.  “啟動初始化模式”的分析

初始化策略

“啟動初始化模式”與“模塊初始化模式”不同,前者要對所有內核支持的網絡設備進行檢測和初始化,而後者僅需檢測和初始化被裝載模塊的網絡設備。 為了實現在啟動時對所有可能存在的設備進行初始化,系統在啟動之前將所有內核支持的網絡設備的名字及相應的初始化函數都掛在網絡設備管理表dev_base)上。 啟動後,net_dev_int)將依次對網絡設備管理表dev_base中的每個設備,調用該設備本身的init_function進行初始化。 若init_function失敗,即該設備不存在或I/O、IRQ不能獲得,則將該設備從dev_base去掉。 最後網絡設備管理表中剩下的網絡接口都是存在的,而且是被初始化過的。                                                             /* 網絡設備管理表的初始化 */ /* from  drivers/net/Space.c */ ………… static struct device eth7_dev = {     "eth7", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, NEXT_DEV, ethif_probe }; static struct device eth6_dev = {     "eth6", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, &eth7_dev, ethif_probe }; ………… static struct device eth0_dev = {     "eth0", 0, 0, 0, 0, ETH0_ADDR, ETH0_IRQ, 0, 0, 0, &eth1_dev, ethif_probe }; /* 在八個eth接口中,只有eth0將I/O設為0,讓其進行自動檢測,其他 * eth接口的I/O都設為0xffe0,不進行檢測。Linux缺省的內核在啟動時 * 只能自動檢測到一塊eth網卡,就是這個原因 */   #   undef NEXT_DEV #   define NEXT_DEV       (&eth0_dev) #if defined(PLIP) || defined(CONFIG_PLIP) extern int plip_init(struct device *); static struct device plip2_dev = {            "plip2", 0, 0, 0, 0, 0x278, 2, 0, 0, 0, NEXT_DEV, plip_init, }; ………… static struct device plip0_dev = {     "plip0", 0, 0, 0, 0, 0x3BC, 5, 0, 0, 0, &plip1_dev, plip_init, }; ………… extern int loopback_init(struct device *dev); struct device loopback_dev = {     "lo", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, loopback_init }; struct device dev_base = &loopback_dev; /* 關鍵 */  

函數調用關系

系統轉入核心後,start_kernel將會創建一個init進程,該init進程則會通過系統調用sys_steup進行所有尚未初始化的設備。D evice_setup不僅要初始化內核支持的字符設備、塊設備,也調用net_dev_init初始化所有內核支持的且實際存在的網絡設備。 net_dev_init會對每個內核支持的網絡設備調用該設備的init_functions進行具體的物理設備的初始化工作。整個函數調用關系圖如下:    

具體流程

LINUX啟動時,完成了實模式下的系統初始化arch/i386/boot/setup.S)與保護模式下的核心初始化包括初始化寄存器和數據區(arch/i386/boot/compressed/head.S)、核心代碼解壓縮、頁表初始化arch/i386/kernel/head.S)、初始化idt、gdt和ldt等工作後,系統轉入了核心。調用函數start_kernel啟動核心init/main.c)後,將繼續各方面的初始化工作,其中與網絡子系統有關的部分為:調用sock_init()net/socket.c)初始化網絡模塊。 sock_init)函數主要做以下動作:     初始狀態設為不支持任何網絡協議: static struct proto_ops *pops[NPROTO];   // #define NPROTO 16 …… for (i = 0; i < NPROTO; ++i) pops[i] = NULL;     調用init_netlink()登記一個網絡字符設備即把netlink看作一種字符設備),主設備號為NETLINK_MAJOR(36): net/netlink.c) register_chrdev(NETLINK_MAJOR,"netlink", &netlink_fops); for(ct=0;ct<MAX_LINKS;ct++){           skb_queue_head_init(&skb_queue_rd[ct]);           netlink_handler[ct]=netlink_err; } 其中static int (*netlink_handler[MAX_LINKS])(struct sk_buff *skb);MAX_LINKS為次設備數,定義為11。     調用netlink_attach(),在netlink_handler登記路由設備的回調函數:net/netlink.c) netlink_attach(NETLINK_ROUTE, netlink_donothing); 其中netlink_attach為:{ active_map|=(1<<unit); netlink_handler[unit]=function;}     調用fwchain_init(),初始化防火牆,設為FW_ACCEPT。     調用proto_init(),執行核心支持的各種網絡協議的初始化函數: void proto_init(void){ /* 核心支持的網絡協議在net/protocols.c中定義 */ extern struct net_proto protocols[]; struct net_proto *pro; pro = protocols; while (pro->name != NULL) { (*pro->init_func)(pro); pro++; } }     調用export_net_symbols(),向系統登記發布network  symbols,以供系統的模塊modules)使用,因為module中的函數所調用的外部函數只能是已分布登記export)的函數: /* from  net/netsyms.c   */ void export_net_symbols(void) { /* 該函數為宏,定義在include/linux/module.h)*/ register_symtab(&net_syms); } 其中net_syms靜態結構變量初始化為:net/netsyms.c) static struct symbol_table net_syms = { #include <linux/symtab_begin.h>   /* Socket layer registration */ X(sock_register), X(sock_unregister), X(sock_alloc), X(sock_release), ………… ………… #include <linux/symtab_end.h> }; 網絡協議的初始化竟是在網絡設備的初始化之前! start_kernel最後將調用kernel_thread (init, NULL, 0),創建init進程進行系統配置其中包括所有設備的初始化工作)。                                   /*  from  init/main.c  */ static int init(void * unused) { ………… /* 創建後台進程bdflush,以不斷循環寫出文件系統緩沖區中“髒”的內容 */ kernel_thread(bdflush, NULL, 0); /* 創建後台進程kswapd,專門處理頁面換出工作  */ kswapd_setup(); kernel_thread(kswapd, NULL, 0); ………… / *   *  該setup() 函數即系統調用sys_setup()  *  關於這一點,作者已作過以下試驗:在setup()調用以前和調用之後,  *  以及在sys_setup()函數內部的開始和結束加入printk語句以使系統  *  啟動時能輸出信息)後,重新編譯內核。發現用新的內核後,通過  *  啟動時的信息顯示,發現setup()函數的確就是系統調用sys_setup()。  *   */ setup(); ………… }   sys_setup函數調用device_setup對所有的設備進行初始化工作。 /* from  fs/filesystems.c  */ asmlinkage int sys_setup(void) { static int callable = 1; if (!callable) return -1; callable = 0;          /* 通過靜態變量,限制該函數最多只能被調用一次 */   device_setup();     /* 調用device_setup(),初始化所有設備 */ ………… } 在device_setup中將對字符設備、塊設備、網絡設備、SCSI設備等進行初始化 /* from  drivers/block/genhd.c  */ void device_setup(void) { ………… chr_dev_init();              /* 字符設備的初始化 drivers/cha/mem.c      */ blk_dev_init();              /* 塊設備的初始化   drivers/block/ll_rw_blk.c */ sti(); #ifdef CONFIG_SCSI scsi_dev_init();      /* SCSI設備的初始化 drivers/scsi/scsi.c */ #endif #ifdef CONFIG_INET net_dev_init();              /* 網絡設備的初始化 net/core/dev.c   */ #endif ………… }   /* from  net/core/dev.c   */ int net_dev_init(void) { ………… /* 初始化數據包接收隊列 */ skb_queue_head_init(&backlog); /* 網橋必須在其他設備之前初始化 */ #ifdef CONFIG_BRIDGE     br_init(); #endif ………… /* 以下進行網絡設備初始化檢測,如果dev->init初始化失敗大多數是  * 因為設備不存在),則從網絡設備鏈dev_base上除去該設備。*/   /* dev_base是drivers/net/Space.c中的一個指向device結構的靜態指針變量, * 並已初始化為核心支持的所有網絡設備的鏈表,其中包括每個設備的probe * 函數指針。 */ dp = &dev_base; while ((dev = *dp) != NULL) { int i; for (i = 0; i < DEV_NUMBUFFS; i++)  { /* #define DEV_NUMBUFFS 3 */ skb_queue_head_init(dev->buffs + i); } if (dev->init && dev->init(dev)) /* 初始化失敗,將該設備從鏈表中刪除,並准備初始化下一個 */ *dp = dev->next; else dp = &dev->next;  /* 成功,准備初始化下一個 */ } …………  

四. 網絡設備的打開和關閉

使用網絡設備,激活打開)初始化好了的接口。打開和關閉一個接口是由shell命令ifconfig調用的,而ifconfig 則要調用一個通用的設備打開函數dev_opennet/core/dev.c),相應地還有一個dev_close函數。這兩個函數提供了獨立於設備的操作接口的打開和關閉的功能。 需要調用網絡接口dev的open、stop函數,同時它們還需置上dev->flags的IFF_UP標志。   /* from net/core/dev.c */ int dev_open(struct device *dev) {        int ret = -ENODEV;        if (dev->open)               ret = dev->open(dev);   /* 調用接口的open函數 */        if (ret == 0)                        /* 接口打開成功 */        {               dev->flags |= (IFF_UP | IFF_RUNNING); /* 置標志位 */               /*初始化有關多點傳輸的一些狀態 */               dev_mc_upload(dev);               notifier_call_chain(&netdev_chain, NETDEV_UP, dev);        }        return(ret); }   對於網絡接口自己的dev->open函數一般包括以下幾方面內容: 1. 若沒有在初始化函數中登記中斷號和I/O地址,則在設備打開時要進行登記分別用request_irq request_region這兩個函數進行登記。 2. 若要分配DMA通道,則用request_dma進行分配登記; 3. 將該設備掛到irq2dev_map中。若是使用基於中斷的接收數據方式,以後就可以通過中斷號直接索引到相應的設備了; 4. 初始化物理設備的寄存器的狀態; 5. 設置接口相應dev的私有數據結構(dev->priv)中的一些域段; 6. 設置dev中的tbusy、interrupt、start等域段; 7. 在返回之前嵌入宏MOD_INC_USE_COUNT。 dev->stop函數則相反,如第六步要改為MOD_DEC_USE_COUNT。 我們看NE2000, NE2000的dev->open和dev->stop分別對應ne_open和ne_close。由於NE2000驅動程序是在初始化就登記IRQ和I/O地址的,在這裡就不需要登記了。 /* from  drivers/net/ne.c */ static int ne_open(struct device *dev) { ei_open(dev);               /* 下面將分析 */ MOD_INC_USE_COUNT;   /* 對應於第7項內容 */ return 0; } static int ne_close(struct device *dev) { if (ei_debug > 1) printk("%s: Shutting down ethercard.\n", dev->name); ei_close(dev);        /* 下面將分析 */ MOD_DEC_USE_COUNT;  /* 對應於第7項內容 */ return 0; }   /* from  drivers/net/8390.c  */ int ei_open(struct device *dev) { struct ei_device *ei_local = (struct ei_device *) dev->priv; if (ei_local == NULL){ /* 只有沒調用ethdev_init(),才會出現以下的錯誤 */ printk(KERN_EMERG "%s: ei_open passed a non-existent device!\n", dev->name); return -ENXIO; } irq2dev_map[dev->irq] = dev;      /* 對應於上面所列內容的第3項 */ NS8390_init(dev, 1);                  /* 下面將分析 */ dev->start = 1;                                   /* 對應於第6項,表示接口UP */ ei_local->irqlock = 0;                  /* 對應於第5項 */ return 0; } int ei_close(struct device *dev) { NS8390_init(dev, 0); dev->start = 0;                            /* 對應於第6項內容,表示接口DOWN */ return 0; }   void NS8390_init(struct device *dev, int startp) { ………… /* 設置8390的各種寄存器的狀態 */ ………… dev->tbusy = 0; dev->interrupt = 0;                      /* 對應於第6項內容 */ ei_local->tx1 = ei_local->tx2 = 0; ei_local->txing = 0;                            /* 對應於第5項內容 */ ………… }   另外,文件net/core/dev.c還提供獨立於具體網絡設備的操作函數,如 dev_ifsioc(void *arg, unsigned int getset) 它可以處理許多ioctl SIGNAL:讀取和修改接口網絡地址對TCP/IP就是IP地址)、讀取和修改接口的dev->flags、讀取和設置MTU、讀取和設置廣播地址等等。  

五. 數據包的傳輸和接收

當物理網絡設備接收到數據時,可通過兩種途徑解決這個問題。 一種方法是輪詢方式,系統每隔一定的時間間隔就去檢查一次物理設備,若設備“報告”說有數據到達,就調用讀取數據的程序。在Linux中,輪詢方式可通過定時器實現,但該方法存在一個明顯的缺點:不管設備是否有數據,系統總是要固定地花CPU時間去查看設備,且可能延遲對一些緊急數據的處理,因為網絡設備有數據時可能不能馬上得到CPU的響應。在這種方式下,設備完全處於一種被動的狀態,而CPU又負擔過重。無論從資源的利用率上還是從效率上看,這種方法都不是最優的。 另一種方法是中斷方式,中斷方式利用硬件體系結構的中斷機制實現設備和系統的應答對話,即當物理設備需要CPU處理數據時,設備就發一個中斷信號給系統,系統則在收到信號後調用相應的中斷服務程序響應對設備中斷的處理。中斷方式有效地解決了設備與CPU的對話交流問題,並將CPU從繁重的設備輪詢中解脫出來,大大提高了CPU的利用率。當前不管是Linux平台還是Windows平台,它們的網絡設備驅動程序幾乎都是使用中斷方式的。 網絡分層有一個問題是,每層的協議在發送數據包時要加協議頭和協議尾到原數據中,在收到數據包時則要將本層的協議頭和協議尾從數據包中去掉。這使得在不同層協議間傳輸時,每層都需要知道自己這一層的協議頭和協議尾在數據包的哪裡。 一種解決方法是在每層都復制緩沖區,但顯然效率太低。 Linux的做法是用一種數據結構sk_buff在不同協議層及網絡設備驅動程序之間傳送數據。sk_buff 包括指針和長度域段,允許每個協議層通過標准的函數操作傳送的數據包。 首先來分析sk_buff這個數據結構。

1. Socket緩沖區及相關操作

網絡設備發送與接收數據包用的緩沖區是一個統一的數據結構sk_buff include/linux/skbuff.h)。對該數據結構,核心提供了一系列低層的操作函數,從而使該數據結構具有網絡協議傳輸需要的通常的緩沖功能和流控制能力,並可方便、靈活地處理數據包首尾的增加和刪除。                                                         sk_buff結構的一個示意圖,其中每個sk_buff 都帶有一塊數據區,並有四個數據指針指向相應的位置: unsigned char  *head; 指向被分配的內存空間的首地址; unsigned char  *data; 指向當前數據包的首地址; unsigned char  *tail; 指向當前數據包的末地址; unsigned char  *end; 指向被分配的內存空間的末地址; unsigned long  len; 當前數據包的大小。 len=skb->tail – skb->data; unsigned long  truesize 分配到的內存空間大小。 len=skb->end – skb->head; 由於數據包的大小會隨著自己在不同協議層間的傳送而會不斷地變化,故data和tail指針也將會不斷地改變,即依賴於skb當前所在的協議層;head和end指針則在內存空間分配後就固定不變。 對緩沖區的操作,核心提供了一個比較完整的函數界面,下面將列出用的最多的幾個函數並作分析說明。 /* from  net/core/skbuff.c  */ struct sk_buff  *alloc_skb (unsigned int len, int priority); struct sk_buff  *dev_alloc_skb (unsigned int len); 申請一個sk_buff緩沖區。alloc_skb函數分配一個緩沖區並將skb->data和skb->tail初始化為skb->head;dev_alloc_skb函數是alloc_skb函數的一個快捷方式,它用priority= GFP_ATOMIC調用alloc_skb並在skb->data和skb->head之間保留16字節的空間。這16字節也用來填寫硬件頭hardware header)。 void kfree_skb (struct sk_buff *skb, int rw); void dev_kfree_skb (struct sk_buff *skb, int rw); 釋放一個sk_buff緩沖區。kfree_skb供核心內部調用,驅動程序應該用dev_kfree_skb,因為它能正確處理緩沖區加鎖。參數rw可用FREE_READ或FREE_WRITE。用於發送的緩沖區應該用FREE_WRITE,用於接收的則用FREE_READ。 unsigned char  *skb_put (struct sk_buff *skb, int len); 當有數據要加到緩沖區的尾部時,用於增加skb->tail和skb->len。返回值是修改之前的skb->tail指針。 unsigned char  *skb_push (struct sk_buff *skb, int len); 當有數據要加到緩沖區的首部時,用於減少skb->data及增大skb->len。返回值是修改之後的skb->data。 int skb_tailroom (struct sk_buff *skb); 該函數返回在sk_buff 中可用於put的空間大小尾部空余空間)。若緩沖區被正確分配到空間,驅動程序通常不需要檢查緩沖區中空余空間的大小。由於驅動程序在申請空間之前可得到數據包的大小,故只有嚴重出錯的驅動程序才會put太多的數據到緩沖區中。 int skb_headroom (struct sk_buff *skb); 類似於skb_tailroom,該函數返回可用的push的空間大小,即首部空余空間。 void skb_reserve (struct sk_buff *skb, int len); 該函數既增加skb->data又增加skb->tail,即在首部留出len大小的空間。在填充緩沖區之前,可用該函數保留一部分首部空間。許多以太網卡在首部保留2字節空間,這樣在14字節的以太網頭的後面,IP頭就能以16字節對齊了。 unsigned char  *skb_pull (struct sk_buff *skb, int len); 從數據包的頭部剔除數據。它減少skb->len並增加skb->data。以太網的頭就是這樣從接收到的數據包中被剔除的。 void skb_trim(struct sk_buff *skb, int len) 從數據包的尾部剔除數據。它將skb->len設為len,並改變skb->tail。  

2. 數據包的傳輸

用戶要傳輸數據時,數據包是沿著網絡協議由上往下逐層下傳的。數據包將通過dev_queue_xmit[net/core/dev.c]函數傳送給網絡接口。網絡接口的任務就是將數據包傳送給硬件,讓物理網絡設備完成最終的物理傳輸。從device結構中我們可以看到,每個網絡接口都應有一個叫dev->hard_start_xmit的硬件傳輸函數指針,通過這個函數指針來完成實際的數據傳輸的。 硬件傳輸函數hard_start_xmit函數的一般流程如下:   1.通過標志位tbusy判斷上次數據包的傳輸是否完成。若tbusy=0就做下一步;否則,看上次傳輸是否已超時,若未超時,就不成功返回,若已超時,則初始化芯片寄存器、置tbusy=0,然後繼續下一步; 2.將tbusy標志位打開; 3.將數據包傳給硬件發送; 4.釋放緩沖區skb; 5.修改接口的一些統計信息。   NE2000的傳輸函數dev->hard_start_xmit為ei_start_xmit   /* from drivers/net/8390.c drivers/net/8390.h */ /* 傳輸函數 */ static int ei_start_xmit(struct sk_buff *skb, struct device *dev) {        int e8390_base = dev->base_addr;        struct ei_device *ei_local = (struct ei_device *) dev->priv;        int length, send_length, output_page;               /* 若設備忙,就判斷上次傳輸是否已超時 */        if (dev->tbusy) {                  /* 讀取傳輸狀態寄存器的值 */               int txsr = inb(e8390_base+EN0_TSR), isr;   /* EN0_TSR 傳輸狀態寄存器讀)*/               int tickssofar = jiffies - dev->trans_start;                             /* #define TX_TIMEOUT (20*HZ/100) */               if (tickssofar < TX_TIMEOUT ||   (tickssofar < (TX_TIMEOUT+5) && ! (txsr & ENTSR_PTX))) {                      return 1;  /* 未超時,或超時一點點且發送時出錯 */               }               /* 已超時 */               …………               /* 重新初始化芯片寄存器 */               ei_reset_8390(dev);               NS8390_init(dev, 1);               /* 將開始傳輸域段置為的當前時間坐標點 */               dev->trans_start = jiffies;        }        …………        /* 屏蔽網絡設備的硬件中斷 */        outb_p(0x00, e8390_base + EN0_IMR);      /* EN0_IMR 中斷屏蔽寄存器WR)*/        if (dev->interrupt) {               /* 正在運行中斷服務程序 */               printk("%s: Tx request while isr active.\n",dev->name); /* 恢復中斷屏蔽,失敗返回; EN0_IMR 中斷屏蔽寄存器WR)*/               outb_p(ENISR_ALL, e8390_base + EN0_IMR);               return 1;        }        ei_local->irqlock = 1;        send_length = ETH_ZLEN < length ? length : ETH_ZLEN;        /* 硬件傳輸 */        ei_block_output(dev, length, skb->data, ei_local->tx_start_page);        ei_local->txing = 1;        NS8390_trigger_send(dev, send_length, ei_local->tx_start_page);        /* 設置開始傳輸時間坐標點,打開“忙”標志 */        dev->trans_start = jiffies;        dev->tbusy = 1;        …………        ei_local->irqlock = 0;        /* 恢復中斷屏蔽 */        outb_p(ENISR_ALL, e8390_base + EN0_IMR);/* EN0_IMR 中斷屏蔽寄存器WR)*/        /* 釋放緩沖區skb */           dev_kfree_skb (skb, FREE_WRITE);        return 0; }  

3. 數據包的接收

由於使用了硬件中斷請求機制,當物理網絡設備接收到新數據時,它將發送一個硬件中斷請求給系統。系統在偵察到有物理設備發出中斷請求,就會調用相應的中斷服務程序來處理中斷請求。在這裡,系統首先要知道哪個中斷對應哪個中斷服務程序。 為了讓系統知道網絡設備的中斷服務程序,一般在網絡設備初始化的時候或設備被打開時,要向系統登記中斷號及相應的中斷服務程序用request_irq這個函數登記) 基於中斷方式的設備驅動程序若在設備初始化和設備打開時都沒向系統登記中斷服務程序,則該設備肯定不能正常工作。 NE2000的中斷號登記是在設備初始化的時候,並登記中斷服務程序為ei_interrupt 一個網絡接口的中斷服務程序的工作步驟一般有以下幾步:   1.確定發生中斷的具體網絡接口,是rq2dev_map[irq]還是 (struct device*) dev_id; 2.打開標志位dev->interrupt,表示本服務程序正在被使用; 3.讀取中斷狀態寄存器,根據寄存器判斷中斷發生的原因。有兩種可能:一種是有新數據包到達;另一種是上次的數據傳輸已完成。 4.若是因為有新數據包到達,則調用接收數據包的子函數; 5.若中斷由上次傳輸引起,則通知協議的上一層、修改接口的統計信息、關閉標志位tbusy為下次傳輸做准備; 6.關閉標志位interrupt。   當中斷服務程序明確物理網絡設備有數據包收到時,將調用數據接收子程序來完成實際的依賴於硬件的數據接收工作,並在接收完成後通過函數netif_rx[net/core/dev.c]將收到的數據包往上層傳。數據接收子程序的內容可以由以下四點來概括:   1.申請skb緩沖區給新的數據包存儲; 2.從硬件中讀取新到達的數據; 3.調用netif_rx),將新的數據包往網絡協議的上一層傳送; 4.修改接口的統計數據。   NE2000中斷服務程序ei_interrupt),同時我們還將發現NE2000的數據接收子程序是ei_receive)。   /* from drivers/net/8390.c drivers/net/8390.h */ /* 中斷服務程序 */ void ei_interrupt(int irq, void *dev_id, struct pt_regs * regs) {        struct device *dev = (struct device *)(irq2dev_map[irq]);        …………              e8390_base = dev->base_addr;        ei_local = (struct ei_device *) dev->priv;        if (dev->interrupt || ei_local->irqlock) {               /* 有其他進程運行中斷服務程序 */               return;        }        /* 打開中斷處理標志,阻止再次進入 */        dev->interrupt = 1;        …………              /* 讀取中斷狀態寄存器RD WR)*/        while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0               && ++nr_serviced < MAX_SERVICE) {               …………                      if (interrupts & ENISR_OVER) { /* ENISR_OVER 接收overrun標志 */                      /* 接收超出物理設備的承受能力 */                      ei_rx_overrun(dev); /* 恢復設備的正確狀態 */               } else if (interrupts & (ENISR_RX+ENISR_RX_ERR)) {                      /* ENISR_RX 正確接收標志;ENISR_RX_ERR 接收有錯標志 */                      /* 接收到數據包,包括正確接收和接收出錯 */                      ei_receive(dev); /* 從物理設備的緩存中取出數據 */               }               if (interrupts & ENISR_TX) {                      /* 正確發送了數據包, */                      ei_tx_intr(dev); /* 為下一次傳輸做准備 */               } else if (interrupts & ENISR_TX_ERR) {                      /* 發送數據包的過程中出錯 */                      ei_tx_err(dev); /* 錯誤處理 */               }               …………        }               …………        /* 關閉中斷處理標志 */        dev->interrupt = 0;        return; }   /* 數據接收子程序,被中斷服務程序所調用 */ static void ei_receive(struct device *dev) {        …………        /* 申請物理空間--skb緩沖區,為讀取新的數據包作准備 */        skb = dev_alloc_skb(pkt_len+2);        …………        /* 由於MAC頭是14字節的,為了與 IP頭的16字節對齊規則 一致,  * 特意保留了2字節 */        skb_reserve(skb,2);        skb->dev = dev;        /* 騰出邏輯空間給新的數據包 */        skb_put(skb, pkt_len);   /* Make room */        /* 從硬件中讀取新到達的數據 */        ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame));        skb->protocol=eth_type_trans(skb,dev);        /* 通過netif_rx函數,將收到的數據包往網絡協議的上一層傳送 */        netif_rx(skb);        /* 修改接口的統計數據。*/        ei_local->stat.rx_packets++;        ………… }  

六. 網絡設備驅動程序

網絡設備或網絡接口)是通過一個數據結構struct device來表示的。在系統中,每一個實際存在的物理網絡設備都對應於一個device結構。而所有這些device結構聯成一張鏈表並由一個全局變量指針dev_base指向表頭,從而使系統能夠隨時得到每個網絡接口的信息。 一個最簡單的網絡設備驅動程序,應該包括:   1.該網絡設備的檢測及初始化函數,供核心啟動初始化時調用 2.該網絡設備的初始化函數,供register_netdev調用可以寫成與第1項的共用,即用同一個);若是寫成module兼容方式的,還需寫該設備的init_module和cleanup_module函數; 3.提供該網絡設備的打開和關閉操作。供設備被打開或被關閉時調用一般用shell命令ifconfig調用); 4.提供該網絡設備的數據傳輸函數,負責向硬件發送數據包。當上層協議需要傳輸數據時,供dev_queue_xmit調用; 5.提供該網絡設備的中斷服務程序,處理數據傳輸完畢的善後事宜和數據的接收。當物理網絡設備有新數據到達或數據傳輸完畢時,將向系統發送硬件中斷請求,該函數就是用來響應該中斷請求的。  

         }

本文出自 “aban3rd的酒壺” 博客,謝絕轉載!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved