Linu網(wǎng)絡(luò)設(shè)備分析_第1頁
Linu網(wǎng)絡(luò)設(shè)備分析_第2頁
Linu網(wǎng)絡(luò)設(shè)備分析_第3頁
Linu網(wǎng)絡(luò)設(shè)備分析_第4頁
Linu網(wǎng)絡(luò)設(shè)備分析_第5頁
已閱讀5頁,還剩112頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

117/117Linux網(wǎng)絡(luò)設(shè)備分析[摘要]在本文中,首先概括了網(wǎng)絡(luò)設(shè)備總體特征和工作原理,接著在分析了一個重要的數(shù)據(jù)結(jié)構(gòu)device后,重點剖析了網(wǎng)絡(luò)設(shè)備的整個初始化工作過程;簡單地分析了設(shè)備的打開和關(guān)閉的操作后,是有關(guān)數(shù)據(jù)包的傳輸和接收的分析;在最后,本文對寫網(wǎng)絡(luò)設(shè)備驅(qū)動程序做了一個總結(jié)。以上的每部分的分析,都是在NE2000以太網(wǎng)卡的基礎(chǔ)上進行的。在附錄中是一個虛擬的字符設(shè)備驅(qū)動程序以及寫這個程序的體會,該程序已成功使用過,它是在網(wǎng)絡(luò)設(shè)備分析之前本人做的一個小小的試驗。網(wǎng)絡(luò)設(shè)備概述在LINUX中,為了簡化對設(shè)備的管理,所有外圍的硬件設(shè)備被歸結(jié)為三類:字符設(shè)備(如鍵盤、鼠標(biāo)等)、塊設(shè)備(如硬盤、光驅(qū)、軟驅(qū)等)和網(wǎng)絡(luò)設(shè)備(也稱為網(wǎng)絡(luò)接口,networkinferface),如以太網(wǎng)卡。在本文中,我們將等效使用“網(wǎng)絡(luò)設(shè)備”和“網(wǎng)絡(luò)接口”這兩個概念,而對某個具體的網(wǎng)絡(luò)設(shè)備,我們將稱之為“物理設(shè)備”或“物理網(wǎng)絡(luò)設(shè)備”。為了屏蔽網(wǎng)絡(luò)環(huán)境中物理網(wǎng)絡(luò)設(shè)備的多樣性,LINUX對所有的物理設(shè)備進行抽象并定義了一個統(tǒng)一的概念,稱之為接口(Interface)。所有對網(wǎng)絡(luò)硬件的訪問都是通過接口進行的,接口提供了一個對所有類型的硬件一致化的操作集合來處理基本數(shù)據(jù)的發(fā)送和接收。一個網(wǎng)絡(luò)接口被看作是一個發(fā)送和接收數(shù)據(jù)包(packets)的實體。對于每個網(wǎng)絡(luò)接口,都用一個device的數(shù)據(jù)結(jié)構(gòu)表示,有關(guān)該數(shù)據(jù)結(jié)構(gòu)的具體內(nèi)容,將在本文的后面詳細介紹。通常,網(wǎng)絡(luò)設(shè)備是一個物理設(shè)備如以太網(wǎng)卡,但軟件也可以作為網(wǎng)絡(luò)設(shè)備,如回送設(shè)備(loopback)。在內(nèi)核啟動時,通過網(wǎng)絡(luò)設(shè)備驅(qū)動程序,將登記存在的網(wǎng)絡(luò)設(shè)備。設(shè)備用標(biāo)準(zhǔn)的支持網(wǎng)絡(luò)的機制來轉(zhuǎn)遞收到的數(shù)據(jù)到相應(yīng)的網(wǎng)絡(luò)層。所有被發(fā)送和接收的包都用數(shù)據(jù)結(jié)構(gòu)sk_buff表示。這是一個具有很好的靈活性的數(shù)據(jù)結(jié)構(gòu),可以很容易增加或刪除網(wǎng)絡(luò)協(xié)議數(shù)據(jù)包的首部。網(wǎng)絡(luò)設(shè)備作為其中的三類設(shè)備之一,它有其非常特殊的地方。它與字符設(shè)備及塊設(shè)備都有很大的不同:網(wǎng)絡(luò)接口不存在于Linux的文件系統(tǒng)中,而是在核心中用一個device數(shù)據(jù)結(jié)構(gòu)表示的。每一個字符設(shè)備或塊設(shè)備則在文件系統(tǒng)中都存在一個相應(yīng)的特殊設(shè)備文件來表示該設(shè)備,如/dev/hda1、/dev/sda1、/dev/tty1等。網(wǎng)絡(luò)設(shè)備在做數(shù)據(jù)包發(fā)送和接收時,直接通過接口訪問,不需要進行文件的操作;而對字符設(shè)備和塊設(shè)備的訪問都需通過文件操作界面。網(wǎng)絡(luò)接口是在系統(tǒng)初始化時實時生成的,對于核心支持的但不存在的物理網(wǎng)絡(luò)設(shè)備,將不可能有與之相對應(yīng)的device結(jié)構(gòu)。而對于字符設(shè)備和塊設(shè)備,即使該物理設(shè)備不存在,在/dev下也必定有相應(yīng)的特殊文件與之相對應(yīng)。且在系統(tǒng)初始化時,核心將會對所有內(nèi)核支持的字符設(shè)備和塊設(shè)備進行登記,初始化該設(shè)備的文件操作界面(structfile_operations),而不管該設(shè)備在物理上是否存在。以上兩點是網(wǎng)絡(luò)設(shè)備與其他設(shè)備之間存在的最主要的不同。然而,它們之間又有一些共同之處,如在系統(tǒng)中一個網(wǎng)絡(luò)設(shè)備的角色和一個安裝的塊設(shè)備相似。一個塊設(shè)備在blk_dev數(shù)組及核心其他的數(shù)據(jù)結(jié)構(gòu)中登記自己,然后根據(jù)請求,通過自己的request_function函數(shù)“發(fā)送”和“接收”數(shù)據(jù)塊。相似地,為了能與外面世界進行數(shù)據(jù)交流,一個網(wǎng)絡(luò)接口也必須在一個特殊的數(shù)據(jù)結(jié)構(gòu)中登記自己。在系統(tǒng)內(nèi)核中,存在字符設(shè)備管理表chardevs和塊設(shè)備管理表blkdevs,這兩張保存著指向file_operations結(jié)構(gòu)的指針的設(shè)備管理表,分別用來描述各種字符驅(qū)動程序和塊設(shè)備驅(qū)動程序。類似地,在內(nèi)核中也存在著一張網(wǎng)絡(luò)接口管理表dev_base,但與前兩張表不同,dev_base是指向device結(jié)構(gòu)的指針,因為網(wǎng)絡(luò)設(shè)備是通過device數(shù)據(jù)結(jié)構(gòu)來表示的。dev_base實際上是一條device結(jié)構(gòu)鏈表的表頭,在系統(tǒng)初始化完成以后,系統(tǒng)檢測到的網(wǎng)絡(luò)設(shè)備將自動地保存在這張鏈表中,其中每一個鏈表單元表示一個存在的物理網(wǎng)絡(luò)設(shè)備。當(dāng)要發(fā)送數(shù)據(jù)時,網(wǎng)絡(luò)子系統(tǒng)將根據(jù)系統(tǒng)路由表選擇相應(yīng)的網(wǎng)絡(luò)接口進行數(shù)據(jù)傳輸,而當(dāng)接收到數(shù)據(jù)包時,通過驅(qū)動程序登記的中斷服務(wù)程序進行數(shù)據(jù)的接收處理(軟件網(wǎng)絡(luò)接口除外)。以下是網(wǎng)絡(luò)設(shè)備工作原理圖:圖一Linux網(wǎng)絡(luò)設(shè)備工作原理圖每一個具體的網(wǎng)絡(luò)接口都應(yīng)該有一個名字,以在系統(tǒng)中能唯一標(biāo)識一個網(wǎng)絡(luò)接口。通常一個名字僅表明該接口的類型。Linux對網(wǎng)絡(luò)設(shè)備命名有以下約定:(其中N為一個非負整數(shù))ethN 以太網(wǎng)接口,包括10Mbps和100Mbps;trN 令牌環(huán)接口;slN SLIP網(wǎng)絡(luò)接口;pppN PPP網(wǎng)絡(luò)接口,包括同步和異步;plipN PLIP網(wǎng)絡(luò)接口,其中N與打印端口號相同;tunlN IPIP壓縮頻道網(wǎng)絡(luò)接口;nrN NetROM虛擬設(shè)備接口;isdnN ISDN網(wǎng)絡(luò)接口;dummyN 空設(shè)備;lo 回送網(wǎng)絡(luò)接口。重要數(shù)據(jù)結(jié)構(gòu)——structdevice結(jié)構(gòu)device存儲一個網(wǎng)絡(luò)接口的重要信息,是網(wǎng)絡(luò)驅(qū)動程序的核心。在邏輯上,它可以分割為兩個部分:可見部分和隱藏部分??梢姴糠质怯赏獠抠x值;隱藏部分的域段僅面向系統(tǒng)內(nèi)部,它們可以隨時被改變。下面我們將對之進行詳細的分析和解剖。/*frominclude/linux/netdevice.h*/structdevice{屬性char *name;設(shè)備的名字。如果第一字符為NULL(即’\0’),register_netdev(drivers/net/net_init.c)將會賦給它一個n最小的可用網(wǎng)絡(luò)設(shè)備名ethn。unsignedlong rmem_end; /*shmem"recv"end */unsignedlong rmem_start; /*shmem"recv"start */unsignedlong mem_end; /*sharedmemend */unsignedlong mem_start; /*sharedmemstart */這些域段標(biāo)識被設(shè)備使用的共享內(nèi)存的首地址及尾地址。如果設(shè)備用來接收和發(fā)送的內(nèi)存塊不同,則mem域段用來標(biāo)識發(fā)送的內(nèi)存位置,rmem用來標(biāo)識接收的內(nèi)存位置。mem_start和mem_end可在系統(tǒng)啟動時用內(nèi)核的命令行指定,用ifconfig可以查看它們的值。rmem域段從來不被驅(qū)動程序以外的程序所引用。unsignedlong base_addr; /*deviceI/Oaddress */unsignedchar irq; /*deviceIRQnumber */I/O基地址和中斷號。它們都是在設(shè)備檢測期間被賦值的,但也可以在系統(tǒng)啟動時指定傳入(如傳給LILO)。ifconfig命令可顯示及修改他們的當(dāng)前值。volatileunsignedchar start; /*startanoperation */volatileunsignedchar interrupt; /*interruptarrived */這是兩個二值的低層狀態(tài)標(biāo)志。通常在設(shè)備打開時置start標(biāo)志,在設(shè)備關(guān)閉時清start標(biāo)志。當(dāng)interrupt置位時,表示有一個中斷已到達且正在進行中斷服務(wù)程序理。unsignedlong tbusy; /*transmitterbusymustbelongforbitops*/標(biāo)識“發(fā)送忙”。在驅(qū)動程序不能接受一個新的需傳輸?shù)陌鼤r,該域段應(yīng)該為非零。structdevice *next;指向下一個網(wǎng)絡(luò)設(shè)備,用于維護鏈表。unsignedchar if_port;記錄哪個硬件I/O端口正在被接口所用,如BNC,AUI,TP等(drivers/net/de4x5.h)。unsignedchar dma; 設(shè)備用的DMA通道。一些設(shè)備可能需要以上兩個域段,但非必需的。unsignedlong trans_start; /*Time(injiffies)oflastTx */上次傳輸?shù)臅r間點(injiffies)unsignedlong last_rx; /*TimeoflastRx */上次接收的時間點(injiffies)。如trans_start可用來幫助內(nèi)核檢測數(shù)據(jù)傳輸?shù)乃梨i(lockup)。unsignedshort flags; /*interfaceflags(alaBSD) */該域描述了網(wǎng)絡(luò)設(shè)備的能力和特性。它包括以下flags:(include/linux/if.h)IFF_UP表示接口在運行中。當(dāng)接口被激活時,內(nèi)核將置該標(biāo)志位。IFF_BROADCAST表示設(shè)備中的廣播地址時有效的。以太網(wǎng)支持廣播。IFF_DEBUG調(diào)試模式,表示設(shè)備調(diào)試打開。當(dāng)想控制printk及其他一些基于調(diào)試目的的信息顯示時,可利用這個標(biāo)志位。雖然當(dāng)前沒有正式的驅(qū)動程序使用它,但它可以在程序中通過ioctl來設(shè)置從而使用它。IFF_LOOPBACK表示這是一個回送(loopback)設(shè)備,回送接口應(yīng)該置該標(biāo)志位。核心是通過檢查此標(biāo)志位來判斷設(shè)備是否是回送設(shè)備的,而不是看設(shè)備的名字是否是lo。IFF_POINTTOPOINT表示這是一個點對點鏈接(SLIPandPPP),點對點接口必須置該標(biāo)志位。Ifconfig也可以置此標(biāo)志位及清除它。若置上該標(biāo)志位,則dev->dstaddr應(yīng)也相應(yīng)的置為鏈接對方的地址。IFF_MASTER /*masterofaloadbalancer */IFF_SLAVE /*slaveofaloadbalancer */此兩個標(biāo)志位在裝入平等化中要用到。IFF_NOARP表示不支持ARP協(xié)議。通常的網(wǎng)絡(luò)接口能傳輸ARP包,如果想讓接口不執(zhí)行ARP,可置上該標(biāo)志位。如點對點接口不需要運行ARP。IFF_PROMISC全局接受模式。在該模式下,設(shè)備將接受所有的包,而不關(guān)這些包是發(fā)給誰的。在缺省情況下,以太網(wǎng)接口會使用硬件過濾,以保證只接受廣播包及發(fā)給本網(wǎng)絡(luò)接口的包。Sniff的原理就是通過設(shè)置網(wǎng)絡(luò)接口為全局接受模式,接受所有到達本接口媒介的包,來“偷聽”本子網(wǎng)的“秘密”。IFF_MULTICAST能接收多點傳送的IP包,具有多點傳輸?shù)哪芰?。ether_setup缺省是置該標(biāo)志位的,故若不想支持多點傳送,必須在初始化時清除該標(biāo)志位。IFF_ALLMULTI接收所有多點傳送的IP包。IFF_NOTRAILERS /*無網(wǎng)絡(luò)TRAILER*/IFF_RUNNING /*資源被分配*/此標(biāo)志在Linux中沒什么用,只是為了與BSD兼容。unsignedshort family; /*addressfamilyID(AF_INET) */該域段標(biāo)識本設(shè)備支持的協(xié)議地址簇。大部分為AF_INET(英特網(wǎng)IP協(xié)議),接口通常不需要用這個域段或賦值給它。unsignedshort metric; /*routingmetric(notused) */unsignedshort mtu; 不包括數(shù)據(jù)鏈路層幀首幀尾的最大傳輸單位(MaximumTransferUnit)。網(wǎng)絡(luò)層在包傳輸時要用到。對以太網(wǎng)而言,該域段為1500,不包括MAC幀的幀首和幀尾(MAC幀格式稍后所示)。unsignedshort type; /*interfacehardwaretype */接口的硬件類型,描述了與該網(wǎng)絡(luò)接口綁在一起的媒介類型。Linux網(wǎng)絡(luò)設(shè)備支持許多不同種類的媒介,如以太網(wǎng),X.25,令牌環(huán),SLIP,PPP,AppleLocaltalk等。ARP在判定接口支持哪種類型的物理地址時要用到該域段。若是以太網(wǎng)接口,則在ether_setup中將之設(shè)為ARPHRD_ETHER(Ethernet10Mbps)。unsignedshort hard_header_len; /*hardwarehdrlength */在被傳送的包中IP頭之前的字節(jié)數(shù)。對于以太網(wǎng)接口,該域段為14(ETH_HLEN,include\linux\if_ether.h),這個值可由MAC幀的格式得出:MAC幀格式:目的地址(6字節(jié))+源地址(6字節(jié))+數(shù)據(jù)長度(2字節(jié))+數(shù)據(jù)(46~~1500)+FCSvoid *priv; /*pointertoprivatedata */該指針指向私有數(shù)據(jù),通常該數(shù)據(jù)結(jié)構(gòu)中包括structenet_statistics。類似于structfile的private_data指針,但priv指針是在設(shè)備初始化時被分配內(nèi)存空間的(而不是在設(shè)備打開時),因為該指針指向的內(nèi)容包括設(shè)備接口的統(tǒng)計數(shù)據(jù),而這些數(shù)據(jù)即使在接口卸下(down)時也應(yīng)可以得到的,如用戶通過ifconfig查看。unsignedchar pad; /*makedev_addralignedto8bytes*/unsignedchar broadcast[MAX_ADDR_LEN]; /*hwbcastadd*/廣播地址由六個0xff構(gòu)成,即表示255.255.255.255。memset(dev->broadcast,0xFF,ETH_ALEN);(drivers/net/net_init.c)unsignedchar dev_addr[MAX_ADDR_LEN]; /*hwaddress */設(shè)備的物理地址。當(dāng)包傳送給驅(qū)動程序傳輸時,要用物理地址來產(chǎn)生正確的幀首。unsignedchar addr_len; /*hardwareaddresslength */物理地址的長度。以太網(wǎng)網(wǎng)卡的物理地址為6字節(jié)(ETH_ALEN)。unsignedlong pa_addr; /*protocoladdress */unsignedlong pa_brdaddr; /*protocolbroadcastaddr */unsignedlong pa_mask; /*protocolnetmask */該三個域段分別描述接口的協(xié)議地址、協(xié)議廣播地址和協(xié)議的網(wǎng)絡(luò)掩碼。若dev->family為AF_INET,則它們即為IP地址。這些域段可用ifconfig賦值。unsignedshort pa_alen; /*protocoladdresslength */協(xié)議地址的長度。AF_INET的為4。unsignedlong pa_dstaddr; /*protocolP-Pothersideaddr */點對點協(xié)議接口(如SLIP、PPP)用這個域記錄連接另一邊的IP值。structdev_mc_list *mc_list; /*Multicastmacaddresses */int mc_count; /*Numberofinstalledmcasts */structip_mc_list *ip_mc_list; /*IPmulticastfilterchain*/這三個域段用于處理多點傳輸。其中mc_count表示mc_list中的項目數(shù)。__u32 tx_queue_len; /*Maxframesperqueueallowed*/一個設(shè)備的傳輸隊列能容納的最大的幀數(shù)。對以太網(wǎng),缺省為100;而plip則為節(jié)省系統(tǒng)資源,僅設(shè)為10。/*Forloadbalancingdriverpairsupport*/unsignedlong pkt_queue; /*Packetsqueued*/structdevice *slave; /*Slavedevice*/structnet_alias_info *alias_info; /*maindevaliasinfo*/structnet_alias *my_alias; /*aliasdevs*/structsk_buff_head buffs[DEV_NUMBUFFS];指向網(wǎng)絡(luò)接口緩沖區(qū)的指針。服務(wù)處理程序以下是一些對網(wǎng)絡(luò)接口的操作,類似與字符設(shè)備和塊設(shè)備。網(wǎng)絡(luò)接口操作可以分為兩部分,一部分為基本操作,即每個網(wǎng)絡(luò)接口都必須有的操作;另一部分是可選操作。/*基本操作*/int (*init)(structdevice*dev);/*Calledonlyonce.*/初始化函數(shù)的指針,僅被調(diào)用一次。當(dāng)?shù)怯浺粋€設(shè)備時,核心一般會讓驅(qū)動程序初始化該設(shè)備。初始化函數(shù)功能包括以下內(nèi)容:檢測設(shè)備是否存在;自動檢測該設(shè)備的I/O端口和中斷號;填寫該設(shè)備device結(jié)構(gòu)的大部分域段;用kmalloc分配所需的內(nèi)存空間等。若初始化失敗,該設(shè)備的device結(jié)構(gòu)就不會被鏈接到全局的網(wǎng)絡(luò)設(shè)備表上。在系統(tǒng)啟動時,每個驅(qū)動程序都試圖登記自己,當(dāng)只有那些實際存在的設(shè)備才會登記成功。這與用主設(shè)備號及次設(shè)備號索引的字符設(shè)備和塊設(shè)備不同。int (*open)(structdevice*dev);打開網(wǎng)絡(luò)接口。每當(dāng)接口被ifconfig激活時,網(wǎng)絡(luò)接口都要被打開。Open操作做以下工作:登記一些需要的系統(tǒng)資源,如IRQ、DMA、I/O端口等;打開硬件;將module使用計數(shù)器加一。int (*stop)(structdevice*dev);停止網(wǎng)絡(luò)接口。操作內(nèi)容與open相逆。int (*hard_start_xmit)(structsk_buff*skb,structdevice*dev);硬件開始傳輸。這個操作請求對一個包的傳輸,這個包原保存在一個socket緩沖區(qū)結(jié)構(gòu)中(sk_buff)。int (*hard_header)(structsk_buff*skb,structdevice*dev,unsignedshorttype,void*daddr, void*saddr,unsignedlen);這個函數(shù)可根據(jù)先前得到的源物理地址和目的物理地址建立硬件頭(hardwareheader)。以太網(wǎng)接口的缺省函數(shù)是eth_header。int (*rebuild_header)(void*eth,structdevice*dev,unsignedlongraddr,structsk_buff*skb);在一個包被發(fā)送之前重建硬件頭。對于以太網(wǎng)設(shè)備,若有未知的信息,缺省函數(shù)將使用ARP填寫。structenet_statistics* (*get_stats)(structdevice*dev);當(dāng)一個應(yīng)用程序需要知道網(wǎng)絡(luò)接口的一些統(tǒng)計數(shù)據(jù)時,可調(diào)用該函數(shù),如ifconfig、netstat等。/*可選操作*/void (*set_multicast_list)(structdevice*dev);設(shè)置多點傳輸?shù)牡刂锋湵恚?mc_list)。int (*set_mac_address)(structdevice*dev,void*addr);改變硬件的物理地址。如果網(wǎng)絡(luò)接口支持改變它的硬件物理地址,就可用這個操作。許多硬件不支持該功能。int (*do_ioctl)(structdevice*dev,structifreq*ifr,intcmd);執(zhí)行依賴接口的ioctl命令。int (*set_config)(structdevice*dev,structifmap*map);改變接口配置。設(shè)備的I/O地址和中斷號可以通過該函數(shù)進行實時修改。void (*header_cache_bind)(structhh_cache**hhp,structdevice*dev,unsignedshorthtype,__u32daddr);void (*header_cache_update)(structhh_cache*hh,structdevice*dev,unsignedchar*haddr);int (*change_mtu)(structdevice*dev,intnew_mtu);這個函數(shù)負責(zé)使接口MTU改變后生效。如果當(dāng)MTU改變時驅(qū)動程序要作一些特殊的事情,就應(yīng)該寫這個函數(shù)。structiw_statistics* (*get_wireless_stats)(structdevice*dev);};網(wǎng)絡(luò)設(shè)備的初始化網(wǎng)絡(luò)設(shè)備的初始化主要工作是檢測設(shè)備的存在、初始化設(shè)備的device結(jié)構(gòu)及在系統(tǒng)中登記該設(shè)備。類似于字符設(shè)備和快塊設(shè)備,系統(tǒng)內(nèi)核中也存在著一張網(wǎng)絡(luò)接口管理表dev_base,但與dev_base是指向device結(jié)構(gòu)的,因為網(wǎng)絡(luò)設(shè)備是通過device數(shù)據(jù)結(jié)構(gòu)來表示的。dev_base實際上是一條device結(jié)構(gòu)鏈表的表頭,在系統(tǒng)初始化完成以后,系統(tǒng)檢測到的網(wǎng)絡(luò)設(shè)備將自動地保存在這張鏈表中,其中每一個鏈表單元表示一個存在的物理網(wǎng)絡(luò)設(shè)備。登記成功的網(wǎng)絡(luò)設(shè)備必定可在dev_base鏈表中找到。網(wǎng)絡(luò)設(shè)備的初始化從觸發(fā)角度看可分為兩類:一類是由shell命令insmod觸發(fā)的模塊化驅(qū)動程序(module),只有模塊化的網(wǎng)絡(luò)設(shè)備驅(qū)動程序才能用這種方式對設(shè)備進行初始化,稱為“模塊初始化模式”;另一類是系統(tǒng)驅(qū)動時由核心自動檢測網(wǎng)絡(luò)設(shè)備并進行初始化,我們稱為“啟動初始化模式”。顯然,這兩種初始化模式存在許多不同之處,以下我們對兩者分別進行分析。“模塊初始化模式”的分析概述insmod命令將調(diào)用相應(yīng)模塊的init_module(),裝載模塊。init_module函數(shù)在初始化dev->init函數(shù)指針后,將調(diào)用register_netdev()在系統(tǒng)登記該設(shè)備。若登記成功,則模塊裝載成功,否則返回出錯信息。register_netdev首先檢查設(shè)備名是否已確定,若沒賦值則給它一個缺省的值ethN,N為最小的可用以太網(wǎng)設(shè)備號注在2.0.34版本的內(nèi)核中,只有以太網(wǎng)設(shè)備的缺省名是在register_netdev中賦值的。對于其他網(wǎng)絡(luò)設(shè)備,一般在其他地方就賦以缺省值,而無需register_netdev處理。如PLIP,在plip.c中就預(yù)定了3個PLIP設(shè)備plip0、plip1注在2.0.34版本的內(nèi)核中,只有以太網(wǎng)設(shè)備的缺省名是在register_netdev中賦值的。對于其他網(wǎng)絡(luò)設(shè)備,一般在其他地方就賦以缺省值,而無需register_netdev處理。如PLIP,在plip.c中就預(yù)定了3個PLIP設(shè)備plip0、plip1和plip2。若啟動時或裝載模塊時若無指定參數(shù)傳入,則會依次對三個設(shè)備試圖進行初始化: for(i=0;i<3;i++){/*fromdrivers/net/plip.c*/ if(register_netdev(&dev_plip[i])==0) devices++; }圖二“模塊初始化模式”的函數(shù)調(diào)用關(guān)系圖init_moduleinit_module模塊初始化函數(shù),當(dāng)裝載模塊時,核心將自動調(diào)用該函數(shù)。在次此函數(shù)中一般處理以下內(nèi)容:處理用戶可能傳入的參數(shù)name、ports及irq的值。若有,則賦給相應(yīng)的接口(注意:未登記);對dev->init函數(shù)指針進行賦值,對于任何網(wǎng)絡(luò)設(shè)備這一步必不可少!!因為在register_netdev中要用到該函數(shù)指針;調(diào)用register_netdev,完成檢測、初始化及設(shè)備登記等工作。/*fromdrivers/net/ne.c*/init_module(void){ intthis_dev,found=0;/*對所有可能存在的以太網(wǎng)接口進行檢測并試圖去登記,MAX_NE_CARDS為4,*即最多可以使用4塊NE2000兼容網(wǎng)卡。*/ for(this_dev=0;this_dev<MAX_NE_CARDS;this_dev++){ structdevice*dev=&dev_ne[this_dev]; /*可能有用戶傳入的參數(shù):指定的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的檢測和初始化函數(shù)*/ dev->mem_end=bad[this_dev]; if(register_netdev(dev)==0){/*試圖登記該設(shè)備*/ found++; continue; /*設(shè)備登記成功,繼續(xù)登記下一個設(shè)備*/ } /*第一次發(fā)生登記不成功事件*/ if(found!=0) /*在這之前沒有成功登記NE2000接口,返回*/ return0; /*顯示出錯信息*/ if(io[this_dev]!=0) printk(KERN_WARNING"ne.c:NoNE*000cardfoundati/o=%#x\n",io[this_dev]); else printk(KERN_NOTICE"ne.c:NoPCIcardsfound.Use\"io=0xNNN\"value(s)for…………register_netdev該函數(shù)實現(xiàn)對網(wǎng)絡(luò)接口的登記功能。其實現(xiàn)步驟如下:首先檢查設(shè)備名是否已確定,若沒賦值則以以太網(wǎng)設(shè)備待之并給它一個缺省的值ethN,N為最小的可用以太網(wǎng)設(shè)備號;然后,網(wǎng)絡(luò)設(shè)備自己的init_function,即剛在init_module中賦值的dev->init,將被調(diào)用,用來實現(xiàn)對網(wǎng)絡(luò)接口的實際的初始化工作。若初始化成功,則將該網(wǎng)絡(luò)接口加到網(wǎng)絡(luò)設(shè)備管理表dev_base的尾部/*fromdrivers/net/net_init.c*/intregister_netdev(structdevice*dev){structdevice*d=dev_base;/*取得網(wǎng)絡(luò)設(shè)備管理表的表頭指針*/…………if(dev&&dev->init){ /*若設(shè)備名字沒確定,則將之看作是以太網(wǎng)設(shè)備!!*/ if(dev->name&& ((dev->name[0]=='\0')||(dev->name[0]==''))){ /*找到下一個最小的空閑可用以太網(wǎng)設(shè)備名字*/ for(i=0;i<MAX_ETH_CARDS;++i) if(ethdev_index[i]==NULL){ sprintf(dev->name,"eth%d",i); printk("loadingdevice'%s'...\n",dev->name); ethdev_index[i]=dev; break; } }…………/*調(diào)用初始化函數(shù)進行設(shè)備的初始化*/ if(dev->init(dev)!=0){………… /*將設(shè)備加到網(wǎng)絡(luò)設(shè)備管理表中,加在最后*/ if(dev_base){ /*找到鏈表尾部*/ while(d->next) d=d->next; d->next=dev; } else dev_base=dev; dev->next=NULL;…………init_function函數(shù)原型:intinit_function(structdevice*dev);當(dāng)系統(tǒng)登記一個網(wǎng)絡(luò)設(shè)備時,核心一般會請求該設(shè)備的驅(qū)動程序初始化自己。初始化函數(shù)功能包括以下內(nèi)容:1.檢測設(shè)備是否存在,一般和第二步一起作;2.自動檢測該設(shè)備的I/O地址和中斷號;對于可以與其他共享中斷號的設(shè)備,我們應(yīng)盡量避免在初始化函數(shù)中登記I/O地址和中斷號,I/O地址和中斷號的登記最好在設(shè)備被打開的時候,因為中斷號有可能被其他設(shè)備所共享。若不準(zhǔn)備和其他設(shè)備共享,則可在此調(diào)用request_irq和request_region馬上向系統(tǒng)登記。3.填寫傳入的該設(shè)備device結(jié)構(gòu)的大部分域段;對于以太網(wǎng)接口,device結(jié)構(gòu)中許多有關(guān)網(wǎng)絡(luò)接口信息都是通過調(diào)用ether_setup函數(shù)(driver/net/net_init.c)統(tǒng)一來設(shè)置的,因為以太網(wǎng)卡有很好的共性。對于非以太網(wǎng)接口,也有一些類似于ether_setup的函數(shù),如tr_setup(令牌網(wǎng)),fddi_setup。若添加的網(wǎng)絡(luò)設(shè)備都不屬于這些類型,就需要自己填寫device結(jié)構(gòu)的各個分量。4.kmalloc需要的內(nèi)存空間。若初始化失敗,該設(shè)備的device結(jié)構(gòu)就不會被鏈接到全局的網(wǎng)絡(luò)設(shè)備表上。在系統(tǒng)啟動時,每個驅(qū)動程序都試圖登記自己,當(dāng)只有那些實際存在的設(shè)備才會登記成功。這與用主設(shè)備號及次設(shè)備號索引的字符設(shè)備和塊設(shè)備不同。物理設(shè)備NE2000兼容網(wǎng)卡的初始化函數(shù)是由ne_probe和ne_probe1及ethdev_init共同實現(xiàn)。/*fromdrivers/net/ne.c*/intne_probe(structdevice*dev){…………intbase_addr=dev?dev->base_addr:0;/*I/O地址.Userknowsbest.<cough>*/if(base_addr>0x1ff) /*I/O地址有指定值*/returnne_probe1(dev,base_addr);/*這個函數(shù)在下面分析*/elseif(base_addr!=0) /*不自動檢測I/O*/returnENXIO;…………/*base_addr=0,自動檢測,若有第二塊ISA網(wǎng)卡則是一個冒險!*對所有NE2000可能的I/O地址都進行檢測,可能的I/O地址在存在*netcard_portlist數(shù)組中:*staticunsignedintnetcard_portlist[]={0x300,0x280,0x320,0x340,0x360,0};*/for(i=0;netcard_portlist[i];i++){intioaddr=netcard_portlist[i];if(check_region(ioaddr,NE_IO_EXTENT))continue;/*檢測到一個I/O端口地址*/if(ne_probe1(dev,ioaddr)==0)return0;…………/*fromdrivers/net/ne.c*/staticintne_probe1(structdevice*dev,intioaddr){…………/*檢測、確認I/O地址;初始化8390*/…………/*自動檢測中斷號,非常巧妙??!*/if(dev->irq<2){autoirq_setup(0);/*自動檢測準(zhǔn)備*/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);/*觸發(fā)中斷*/outb_p(0x00,ioaddr+EN0_IMR); /*屏蔽中斷*/dev->irq=autoirq_report(0); /*獲得剛才產(chǎn)生的中斷號*/……/*登記中斷號,中斷服務(wù)程序為ei_interrupt。*因為ISA網(wǎng)卡不能和其他設(shè)備共享中斷。*/intirqval=request_irq(dev->irq,ei_interrupt,pci_irq_line?SA_SHIRQ:0,name,dev);if(irqval){printk("unabletogetIRQ%d(irqval=%d).\n",dev->irq,irqval);returnEAGAIN;}dev->base_addr=ioaddr; /*設(shè)置I/O地址——已經(jīng)過確認*//*調(diào)用ethdev_init初始化dev結(jié)構(gòu)*/if(ethdev_init(dev)){/*該函數(shù)下面將分析*/printk("unabletogetmemoryfordev->priv.\n");free_irq(dev->irq,NULL);/*初始化不成功,釋放登記的中斷號!*/return-ENOMEM;}/*向系統(tǒng)登記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:%sfoundat%#x,usingIRQ%d.\n",dev->name,name,ioaddr,dev->irq);…………/*向dev結(jié)構(gòu)登記設(shè)備打開和關(guān)閉函數(shù)*/dev->open=&ne_open;dev->stop=&ne_close;…………/*fromdrivers/net/8390.c*/intethdev_init(structdevice*dev){…………if(dev->priv==NULL){structei_device*ei_local;/*申請私有數(shù)據(jù)結(jié)構(gòu)空間,用于記錄設(shè)備的狀態(tài)等*/dev->priv=kmalloc(sizeof(structei_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_setupether_setup是一個通用于以太網(wǎng)接口的網(wǎng)絡(luò)接口設(shè)置函數(shù)。由于以太網(wǎng)卡有很好的共性,device結(jié)構(gòu)中許多有關(guān)的網(wǎng)絡(luò)接口信息都是通過調(diào)用ether_setup函數(shù)統(tǒng)一來設(shè)置。那么讓我們看看它到底會缺省設(shè)哪些域段及設(shè)為什么值。若你滿意這些缺省設(shè)置,那么在寫驅(qū)動程序時只要調(diào)用一下這個函數(shù)就可以將這些域段的設(shè)置工作“置之不理了”,否則,也可在調(diào)用該函數(shù)之后再改過。/*fromdrivers/net/net_init.c*/voidether_setup(structdevice*dev){inti;/*初始化緩沖隊列鏈表,這是一個雙向鏈表*/for(i=0;i<DEV_NUMBUFFS;i++) /*DEV_NUMBUFFS=3*/skb_queue_head_init(&dev->buffs[i]);…………/*一些處理函數(shù)的初始化,驅(qū)動程序可以不寫這些函數(shù)了*/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;/*Ethernet10Mbps*/dev->hard_header_len=ETH_HLEN; /*MAC層協(xié)議頭的大小14*/dev->mtu =1500; /*最大傳輸單位*/dev->addr_len =ETH_ALEN; /*協(xié)議地址長度4*/dev->tx_queue_len =100; /*傳輸隊列的長度*/memset(dev->broadcast,0xFF,ETH_ALEN);/*物理地址長度6*/ /*廣播地址有效及支持多點傳輸*/dev->flags =IFF_BROADCAST|IFF_MULTICAST;dev->family =AF_INET; /*英特網(wǎng)IP協(xié)議簇*/dev->pa_addr =0; /*以后用ifconfig命令設(shè)置*/dev->pa_brdaddr=0; /*以后用ifconfig命令設(shè)置*/dev->pa_mask =0; /*以后用ifconfig命令設(shè)置*/dev->pa_alen =4; /*協(xié)議地址長度4*/}至此模塊化網(wǎng)絡(luò)設(shè)備的初始化就完成了。“啟動初始化模式”的分析初始化策略“啟動初始化模式”與“模塊初始化模式”不同,前者要對所有內(nèi)核支持的網(wǎng)絡(luò)設(shè)備進行檢測和初始化,而后者僅需檢測和初始化被裝載模塊的網(wǎng)絡(luò)設(shè)備。為了實現(xiàn)在啟動時對所有可能存在的設(shè)備進行初始化,系統(tǒng)在啟動之前將所有內(nèi)核支持的網(wǎng)絡(luò)設(shè)備的名字及相應(yīng)的初始化函數(shù)都掛在網(wǎng)絡(luò)設(shè)備管理表(dev_base)上。啟動后,net_dev_int()將依次對網(wǎng)絡(luò)設(shè)備管理表dev_base中的每個設(shè)備,調(diào)用該設(shè)備本身的init_function進行初始化。若init_function失敗,即該設(shè)備不存在或I/O、IRQ不能獲得,則將該設(shè)備從dev_base去掉。這樣,最后網(wǎng)絡(luò)設(shè)備管理表中剩下的網(wǎng)絡(luò)接口都是存在的,顯然也已是被初始化過的。我們看一下dev_base的初始化情況。 圖三網(wǎng)絡(luò)設(shè)備表的初始化后的示意圖/*網(wǎng)絡(luò)設(shè)備管理表的初始化*//*fromdrivers/net/Space.c*/…………staticstructdeviceeth7_dev={"eth7",0,0,0,0,0xffe0/*I/Obase*/,0,0,0,0,NEXT_DEV,ethif_probe};staticstructdeviceeth6_dev={"eth6",0,0,0,0,0xffe0/*I/Obase*/,0,0,0,0,ð7_dev,ethif_probe};…………staticstructdeviceeth0_dev={"eth0",0,0,0,0,ETH0_ADDR,ETH0_IRQ,0,0,0,ð1_dev,ethif_probe};/*在八個eth接口中,只有eth0將I/O設(shè)為0,讓其進行自動檢測,其他*eth接口的I/O都設(shè)為0xffe0,不進行檢測。Linux缺省的內(nèi)核在啟動時*只能自動檢測到一塊eth網(wǎng)卡,就是這個原因*/#undefNEXT_DEV#defineNEXT_DEV (ð0_dev)#ifdefined(PLIP)||defined(CONFIG_PLIP)externintplip_init(structdevice*);staticstructdeviceplip2_dev={ "plip2",0,0,0,0,0x278,2,0,0,0,NEXT_DEV,plip_init,};…………staticstructdeviceplip0_dev={"plip0",0,0,0,0,0x3BC,5,0,0,0,&plip1_dev,plip_init,};…………externintloopback_init(structdevice*dev);structdeviceloopback_dev={"lo",0x0,0x0,0x0,0x0,0,0,0,0,0,NEXT_DEV,loopback_init};structdevicedev_base=&loopback_dev; /*關(guān)鍵的一個語句*/函數(shù)調(diào)用關(guān)系系統(tǒng)轉(zhuǎn)入核心后,start_kernel將會創(chuàng)建一個init進程,該init進程則會通過系統(tǒng)調(diào)用sys_steup進行所有尚未初始化的設(shè)備(有一些設(shè)備如內(nèi)存、PCI等系統(tǒng)已先于此進行了初始化)。device_setup不僅要初始化內(nèi)核支持的字符設(shè)備、塊設(shè)備,也調(diào)用net_dev_init初始化所有內(nèi)核支持的且實際存在的網(wǎng)絡(luò)設(shè)備。net_dev_init會對每個內(nèi)核支持的網(wǎng)絡(luò)設(shè)備調(diào)用該設(shè)備的init_functions進行具體的物理設(shè)備的初始化工作。整個函數(shù)調(diào)用關(guān)系圖如下:圖四“啟動初始化模式”的函數(shù)調(diào)用關(guān)系圖具體流程LINUX啟動時,完成了實模式下的系統(tǒng)初始化(arch/i386/boot/setup.S)與保護模式下的核心初始化包括初始化寄存器和數(shù)據(jù)區(qū)(arch/i386/boot/compressed/head.S)、核心代碼解壓縮、頁表初始化(arch/i386/kernel/head.S)、初始化idt、gdt和ldt等工作后,系統(tǒng)轉(zhuǎn)入了核心。調(diào)用函數(shù)start_kernel啟動核心(init/main.c)后,將繼續(xù)各方面的初始化工作,其中與網(wǎng)絡(luò)子系統(tǒng)有關(guān)的部分為:調(diào)用sock_init()(net/socket.c)初始化網(wǎng)絡(luò)模塊。sock_init()函數(shù)主要做以下動作:初始狀態(tài)設(shè)為不支持任何網(wǎng)絡(luò)協(xié)議:staticstructproto_ops*pops[NPROTO];//#defineNPROTO16……for(i=0;i<NPROTO;++i)pops[i]=NULL;調(diào)用init_netlink(),登記一個網(wǎng)絡(luò)字符設(shè)備(即把netlink看作一種字符設(shè)備),主設(shè)備號為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;}其中staticint(*netlink_handler[MAX_LINKS])(structsk_buff*skb);MAX_LINKS為次設(shè)備數(shù),定義為11。調(diào)用netlink_attach(),在netlink_handler登記路由設(shè)備的回調(diào)函數(shù):(net/netlink.c)netlink_attach(NETLINK_ROUTE,netlink_donothing);其中netlink_attach為:{active_map|=(1<<unit);netlink_handler[unit]=function;}調(diào)用fwchain_init(),初始化防火墻,設(shè)為FW_ACCEPT。調(diào)用proto_init(),執(zhí)行核心支持的各種網(wǎng)絡(luò)協(xié)議的初始化函數(shù):voidproto_init(void){/*核心支持的網(wǎng)絡(luò)協(xié)議在net/protocols.c中定義*/externstructnet_protoprotocols[];structnet_proto*pro;pro=protocols;while(pro->name!=NULL){(*pro->init_func)(pro);pro++;}}調(diào)用export_net_symbols(),向系統(tǒng)登記發(fā)布networksymbols,以供系統(tǒng)的模塊(modules)使用,因為module中的函數(shù)所調(diào)用的外部函數(shù)只能是已分布登記(export)的函數(shù):/*fromnet/netsyms.c*/voidexport_net_symbols(void){/*該函數(shù)為宏,定義在(include/linux/module.h)*/register_symtab(&net_syms);}其中net_syms靜態(tài)結(jié)構(gòu)變量初始化為:(net/netsyms.c)staticstructsymbol_tablenet_syms={#include<linux/symtab_begin.h>/*Socketlayerregistration*/X(sock_register),X(sock_unregister),X(sock_alloc),X(sock_release),……#include<linux/symtab_end.h>};至此,系統(tǒng)完成了一些有關(guān)網(wǎng)絡(luò)模塊的初始化工作。但請注意,目前系統(tǒng)還沒涉及任何有關(guān)網(wǎng)絡(luò)設(shè)備的初始化內(nèi)容(別急)。有些特別:網(wǎng)絡(luò)協(xié)議的初始化竟是在網(wǎng)絡(luò)設(shè)備的初始化之前!!由于本文主要著重于網(wǎng)絡(luò)設(shè)備的分析,故對整個網(wǎng)絡(luò)子系統(tǒng)的組成和結(jié)構(gòu),及網(wǎng)絡(luò)上層協(xié)議(如TCP/IP、SOCKET等)和網(wǎng)絡(luò)設(shè)備之間的縫合未作深入的代碼級分析,望能有后來者彌補這方面的工作(?)。題外話了,言歸正傳。start_kernel最后將調(diào)用kernel_thread(init,NULL,0),創(chuàng)建init進程進行系統(tǒng)配置(其中包括所有設(shè)備的初始化工作)。/*frominit/main.c*/staticintinit(void*unused){…………/*創(chuàng)建后臺進程bdflush,以不斷循環(huán)寫出文件系統(tǒng)緩沖區(qū)中“臟”的內(nèi)容*/kernel_thread(bdflush,NULL,0);/*創(chuàng)建后臺進程kswapd,專門處理頁面換出工作*/kswapd_setup();kernel_thread(kswapd,NULL,0);…………/*哈哈,原來在這里!終于逮到了!!*該setup()函數(shù)即系統(tǒng)調(diào)用sys_setup()*關(guān)于這一點,作者已作過以下試驗:在setup()調(diào)用以前和調(diào)用之后,*以及在sys_setup()函數(shù)內(nèi)部的開始和結(jié)束加入printk語句(以使系統(tǒng)*啟動時能輸出信息)后,重新編譯內(nèi)核。發(fā)現(xiàn)用新的內(nèi)核后,通過*啟動時的信息顯示,發(fā)現(xiàn)setup()函數(shù)的確就是系統(tǒng)調(diào)用sys_setup()。*至于為什么是這樣,尚待分析。(?)*/setup();…………}由于是調(diào)用sys_setup,那么讓我們來看一下sys_setup。在此函數(shù)中,將調(diào)用device_setup對所有的設(shè)備進行初始化工作。/*fromfs/filesystems.c*/asmlinkageintsys_setup(void){staticintcallable=1;if(!callable)return-1;callable=0; /*通過靜態(tài)變量,限制該函數(shù)最多只能被調(diào)用一次*/device_setup(); /*調(diào)用device_setup(),初始化所有設(shè)備*/…………}在device_setup中將對字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備、SCSI設(shè)備等進行初始化/*fromdrivers/block/genhd.c*/voiddevice_setup(void){…………chr_dev_init(); /*字符設(shè)備的初始化drivers/cha/mem.c*/blk_dev_init(); /*塊設(shè)備的初始化drivers/block/ll_rw_blk.c*/sti();#ifdefCONFIG_SCSIscsi_dev_init(); /*SCSI設(shè)備的初始化drivers/scsi/scsi.c*/#endif#ifdefCONFIG_INETnet_dev_init(); /*網(wǎng)絡(luò)設(shè)備的初始化net/core/dev.c*/#endif…………}/*fromnet/core/dev.c*/intnet_dev_init(void){…………/*初始化數(shù)據(jù)包接收隊列*/skb_queue_head_init(&backlog);/*網(wǎng)橋必須在其他設(shè)備之前初始化*/#ifdefCONFIG_BRIDGE br_init();#endif…………/*以下進行網(wǎng)絡(luò)設(shè)備初始化檢測,如果dev->init初始化失敗(大多數(shù)是*因為設(shè)備不存在),則從網(wǎng)絡(luò)設(shè)備鏈dev_base上除去該設(shè)備。*//*dev_base是drivers/net/Space.c中的一個指向device結(jié)構(gòu)的靜態(tài)指針變量,*并已初始化為核心支持的所有網(wǎng)絡(luò)設(shè)備的鏈表,其中包括每個設(shè)備的probe*函數(shù)指針。*/dp=&dev_base;while((dev=*dp)!=NULL){inti;for(i=0;i<DEV_NUMBUFFS;i++){/*#defineDEV_NUMBUFFS3*/skb_queue_head_init(dev->buffs+i);}if(dev->init&&dev->init(dev))/*初始化失敗,將該設(shè)備從鏈表中刪除,并準(zhǔn)備初始化下一個*/*dp=dev->next;elsedp=&dev->next; /*成功,準(zhǔn)備初始化下一個*/}…………網(wǎng)絡(luò)設(shè)備的打開和關(guān)閉至此,通過分析我們知道了一個網(wǎng)絡(luò)接口是如何在模塊裝載或內(nèi)核啟動時被檢測和初始化的。為了使用網(wǎng)絡(luò)設(shè)備,下一步就是解決如何激活(打開)初始化好了的接口這個問題了。打開和關(guān)閉一個接口是由shell命令ifconfig調(diào)用的,而ifconfig則要調(diào)用一個通用的設(shè)備打開函數(shù)dev_open(net/core/dev.c),相應(yīng)地還有一個dev_close函數(shù)。這兩個函數(shù)提供了獨立于設(shè)備的操作接口的打開和關(guān)閉的功能。顯然,這二個提供獨立于設(shè)備界面的接口操作函數(shù),一定也需要調(diào)用網(wǎng)絡(luò)接口dev的open、stop函數(shù),同時它們還需置上dev->flags的IFF_UP標(biāo)志。下面是dev_open的源代碼,dev_close函數(shù)與以下的類似,但它另外還要釋放接口所有的sk_buff空間。/*fromnet/core/dev.c*/intdev_open(structdevice*dev){ intret=-ENODEV; if(dev->open) ret=dev->open(dev); /*調(diào)用接口的open函數(shù)*/ if(ret==0) /*接口打開成功*/ { dev->flags|=(IFF_UP|IFF_RUNNING);/*置標(biāo)志位*/ /*初始化有關(guān)多點傳輸?shù)囊恍顟B(tài)*/ dev_mc_upload(dev); notifier_call_chain(&netdev_chain,NETDEV_UP,dev); } return(ret);}對于網(wǎng)絡(luò)接口自己的dev->open函數(shù)一般包括以下幾方面內(nèi)容:若沒有在初始化函數(shù)中登記中斷號和I/O地址,則在設(shè)備打開時要進行登記分別用request_irqrequest_region這兩個函數(shù)進行登記。若要分配DMA通道,則用request_dma進行分配登記;將該設(shè)備掛到irq2dev_map中。若是使用基于中斷的接收數(shù)據(jù)方式,以后就可以通過中斷號直接索引到相應(yīng)的設(shè)備了;初始化物理設(shè)備的寄存器的狀態(tài);設(shè)置接口相應(yīng)dev的私有數(shù)據(jù)結(jié)構(gòu)(dev->priv)中的一些域段;設(shè)置dev中的tbusy、interrupt、start等域段;在返回之前嵌入宏MOD_INC_USE_COUNT。dev->stop函數(shù)則與以上動作恰好相逆,如第六步要改為MOD_DEC_USE_COUNT。以下我們同樣以NE2000作為例子,由上一節(jié)可知,NE2000的dev->open和dev->stop分別對應(yīng)ne_open和ne_close。由于NE2000驅(qū)動程序是在初始化就登記IRQ和I/O地址的,故在這里就不需要登記了。/*fromdrivers/net/ne.c*/staticintne_open(structdevice*dev){ei_open(dev); /*下面將分析*/MOD_INC_USE_COUNT; /*對應(yīng)于第7項內(nèi)容*/return0;}staticintne_close(structdevice*dev){if(ei_debug>1)printk("%s:Shuttingdownethercard.\n",dev->name);ei_close(dev); /*下面將分析*/MOD_DEC_USE_COUNT; /*對應(yīng)于第7項內(nèi)容*/return0;}由上容易看到,ne_close幾乎就是ne_open在鏡中的像。/*fromdrivers/net/8390.c*/intei_open(structdevice*dev){structei_device*ei_local=(structei_device*)dev->priv;if(ei_local==NULL){ /*只有沒調(diào)用ethdev_init(),才會出現(xiàn)以下的錯誤*/printk(KERN_EMERG"%s:ei_openpassedanon-existentdevice!\n",dev->name);return-ENXIO;}irq2dev_map[dev->irq]=dev; /*對應(yīng)于上面所列內(nèi)容的第3項*/NS8390_init(dev,1); /*下面將分析*/dev->start=1; /*對應(yīng)于第6項,表示接口UP*/ei_local->irqlock=0; /*對應(yīng)于第5項*/return0;}intei_close(structdevice*dev){NS8390_init(dev,0);dev->start=0; /*對應(yīng)于第6項內(nèi)容,表示接口DOWN*/return0;}voidNS8390_init(structdevice*dev,intstartp){…………/*設(shè)置8390的各種寄存器的狀態(tài)*/…………dev->tbusy=0;dev->interrupt=0; /*對應(yīng)于第6項內(nèi)容*/ei_local->tx1=ei_local->tx2=0;ei_local->txing=0; /*對應(yīng)于第5項內(nèi)容*/…………}另外,文件net/core/dev.c還提供一系列界面獨立于具體網(wǎng)絡(luò)設(shè)備的操作函數(shù),如dev_ifsioc(void*arg,unsignedintgetset)它可以處理許多ioctlSIGNAL:讀取和修改接口網(wǎng)絡(luò)地址(對TCP/IP就是IP地址)、讀取和修改接口的dev->flags、讀取和設(shè)置MTU、讀取和設(shè)置廣播地址等等。ifconfig的功能大部分是通過該文件提供的函數(shù)實現(xiàn)的。數(shù)據(jù)包的傳輸和接收當(dāng)物理網(wǎng)絡(luò)設(shè)備接收到數(shù)據(jù)時,系統(tǒng)是如何知道并讀取數(shù)據(jù)的呢?當(dāng)前可通過兩種途徑解決這個問題。一種方法是輪詢方式,系統(tǒng)每隔一定的時間間隔就去檢查一次物理設(shè)備,若設(shè)備“報告”說有數(shù)據(jù)到達,就調(diào)用讀取數(shù)據(jù)的程序。在Linux中,輪詢方式可通過定時器實現(xiàn),但該方法存在一個明顯的缺點:不管設(shè)備是否有數(shù)據(jù),系統(tǒng)總是要固定地花CPU時間去查看設(shè)備,且可能延遲對一些緊急數(shù)據(jù)的處理,因為網(wǎng)絡(luò)設(shè)備有數(shù)據(jù)時可能不能馬上得到CPU的響應(yīng)。在這種方式下,設(shè)備完全處于一種被動的狀態(tài),而CPU又負擔(dān)過重。無論從資源的利用率上還是從效率上看,這種方法都不是最優(yōu)的。另一種方法是中斷方式,中斷方式利用硬件體系結(jié)構(gòu)的中斷機制實現(xiàn)設(shè)備和系統(tǒng)的應(yīng)答對話,即當(dāng)物理設(shè)備需要CPU處理數(shù)據(jù)時,設(shè)備就發(fā)一個中斷信號給系統(tǒng),系統(tǒng)則在收到信號后調(diào)用相應(yīng)的中斷服務(wù)程序響應(yīng)對設(shè)備中斷的處理。中斷方式有效地解決了設(shè)備與CPU的對話交流問題,并將CPU從繁重的設(shè)備輪詢中解脫出來,大大提高了CPU的利用率。當(dāng)前不管是Linux平臺還是Windows平臺,它們的網(wǎng)絡(luò)設(shè)備驅(qū)動程序幾乎都是使用中斷方式的。故在此我們主要討論基于中斷方式的網(wǎng)絡(luò)設(shè)備驅(qū)動程序。網(wǎng)絡(luò)分層引起的一個問題是,每層的協(xié)議在發(fā)送數(shù)據(jù)包時要加協(xié)議頭和協(xié)議尾到原數(shù)據(jù)中,在收到數(shù)據(jù)包時則要將本層的協(xié)議頭和協(xié)議尾從數(shù)據(jù)包中去掉。這使得在不同層協(xié)議間傳輸時,每層都需要知道自己這一層的協(xié)議頭和協(xié)議尾在數(shù)據(jù)包的哪里。一種解決方法是在每層都復(fù)制緩沖區(qū),但顯然效率太低。Linux的做法是用一種數(shù)據(jù)結(jié)構(gòu)sk_buff在不同協(xié)議層及網(wǎng)絡(luò)設(shè)備驅(qū)動程序之間傳送數(shù)據(jù)。sk_buff包括指針和長度域段,允許每個協(xié)議層通過標(biāo)準(zhǔn)的函數(shù)操作傳送的數(shù)據(jù)包。該數(shù)據(jù)結(jié)構(gòu)在整個Linux的網(wǎng)絡(luò)子系統(tǒng)包括網(wǎng)絡(luò)設(shè)備中扮演了一個十分重要的角色,故我們在分析數(shù)據(jù)包的傳輸和接收之前,首先來看看sk_buff這個數(shù)據(jù)結(jié)構(gòu)的內(nèi)容及系統(tǒng)提供的相關(guān)操作。因為對該數(shù)據(jù)結(jié)構(gòu)的了解將大大有助于對Linux整個網(wǎng)絡(luò)子系統(tǒng)的理解。Socket緩沖區(qū)及相關(guān)操作與塊設(shè)備的緩沖區(qū)處理方式不同,網(wǎng)絡(luò)設(shè)備發(fā)送與接收數(shù)據(jù)包用的緩沖區(qū)是一個統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)sk_buff(include/linux/skbuff.h)。對該數(shù)據(jù)結(jié)構(gòu),核心提供了一系列低層的操作函數(shù),從而使該數(shù)據(jù)結(jié)構(gòu)具有網(wǎng)絡(luò)協(xié)議傳輸需要的通常的緩沖功能和流控制能力,并可方便、靈活地處理數(shù)據(jù)包首尾的增加和刪除。右圖是sk_buff結(jié)構(gòu)的一個示意圖,其中每個sk_buff都帶有一塊數(shù)據(jù)區(qū),并有四個數(shù)據(jù)指針指向相應(yīng)的位置:unsignedchar*head;指向被分配的內(nèi)存空間的首地址;unsignedchar*data;指向當(dāng)前數(shù)據(jù)包的首地址;unsignedchar*tail;指向當(dāng)前數(shù)據(jù)包的末地址;unsignedchar*end;指向被分配的內(nèi)存空間的末地址;unsignedlonglen;當(dāng)前數(shù)據(jù)包的大小。len=skb->tail–skb->data;unsignedlongtruesize分配到的內(nèi)存空間大小。len=skb->end–skb->head;由于數(shù)據(jù)包的大小會隨著自己在不同協(xié)議層間的傳送而會不斷地變化,故data和tail指針也將會不斷地改變,即依賴于skb當(dāng)前所在的協(xié)議層;head和end指針則在內(nèi)存空間分配后就固定不變。對緩沖區(qū)的操作,核心提供了一個比較完整的函數(shù)界面,下面將列出用的最多的幾個函數(shù)并作分析說明。/*fromnet/core/skbuff.c*/structsk_buff*alloc_skb(unsignedintlen,intpriority);structsk_buff*dev_alloc_skb(unsignedintlen);申請一個sk_buff緩沖區(qū)。alloc_skb函數(shù)分配一個緩沖區(qū)并將skb->data和skb->tail初始化為skb->head;dev_alloc_skb函數(shù)是alloc_skb函數(shù)的一個快捷方式,它用priority=GFP_ATOMIC調(diào)用alloc_skb并在skb->data和skb->head之間保留16字節(jié)的空間。這16字節(jié)也用來填寫硬件頭(hardwareheader)。voidkfree_skb(structsk_buff*skb,intrw);voiddev_kfree_skb(structsk_buff*skb,intrw);釋放一個sk_buff緩沖區(qū)。kfree_skb供核心內(nèi)部調(diào)用,驅(qū)動程序應(yīng)該用dev_kfree_skb,因為它能正確處理緩沖區(qū)加鎖。參數(shù)rw可用FREE_READ或FREE_WRITE。用于發(fā)送的緩沖區(qū)應(yīng)該用FREE_WRITE,用于接收的則用FREE_READ。unsignedchar*skb_put(structsk_buff*skb,intlen);當(dāng)有數(shù)據(jù)要加到緩沖區(qū)的尾部時,用于增加skb->tail和skb->len。返回值是修改之前的skb->tail指針。unsignedchar*skb_push(structsk_buff*skb,intlen);當(dāng)有數(shù)據(jù)要加到緩沖區(qū)的首部時,用于減少skb->data及增大skb->len。返回值是修改之后的skb->data。intskb_tailroom(structsk_buff*skb);該函數(shù)返回在sk_buff中可用于put的空間大小(尾部空余空間)。若緩沖區(qū)被正確分配到空間,驅(qū)動程序通常不需要檢查緩沖區(qū)中空余空間的大小。由于驅(qū)動程序在申請空間之前可得到數(shù)據(jù)包的大小,故只有嚴(yán)重出錯的驅(qū)動程序才會put太多的數(shù)據(jù)到緩沖區(qū)中。intskb_headroom(structsk_buff*skb);類似于skb_tailroom,該函數(shù)返回可用的push的空間大小,即首部空余空間。voidskb_reserve(structsk_buff*skb,intlen);該函數(shù)既增加skb->data又增加skb->tail,即在首部留出len大小的空間。在填充緩沖區(qū)之前,可用該函數(shù)保留一部分首部空間。許多以太網(wǎng)卡在首部保留2字節(jié)空間,這樣在14字節(jié)的以太網(wǎng)頭的后面,IP頭就能以16字節(jié)對齊了。unsignedchar*skb_pull(structsk_buff*skb,intlen);從數(shù)據(jù)包的頭部剔除數(shù)據(jù)。它減少skb->len并增加skb->data。以太網(wǎng)的頭就是這樣從接收到的數(shù)據(jù)包中被剔除的。voidskb_trim(structsk_buff*skb,intlen)從數(shù)據(jù)包的尾部剔除數(shù)據(jù)。它將skb->len設(shè)為len,并改變skb->tail。數(shù)據(jù)包的傳輸由于網(wǎng)絡(luò)分層的原因,當(dāng)用戶要傳輸數(shù)據(jù)時,數(shù)據(jù)包是沿著網(wǎng)絡(luò)協(xié)議由上往下逐層下傳的。如本文開頭的網(wǎng)絡(luò)設(shè)備工作原理圖所示,最后,數(shù)據(jù)包將通過dev_queue_xmit()[net/core/dev.c]函數(shù)傳送給網(wǎng)絡(luò)接口。網(wǎng)

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論