- 論壇徽章:
- 3
|
網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)和數(shù)據(jù)鏈路層的分析
當(dāng)物理網(wǎng)絡(luò)設(shè)備接收到數(shù)據(jù)時(shí),系統(tǒng)是如何知道并讀取數(shù)據(jù)的呢?當(dāng)前可通過(guò)兩種途徑解決這個(gè)問(wèn)題。一種方法是輪詢(xún)方式,系統(tǒng)每隔一定的時(shí)間間隔就去檢查一次物理設(shè)備,若設(shè)備“報(bào)告”說(shuō)有數(shù)據(jù)到達(dá),就調(diào)用讀取數(shù)據(jù)的程序。在Linux中,輪詢(xún)方式可通過(guò)定時(shí)器實(shí)現(xiàn),但該方法存在一個(gè)明顯的缺點(diǎn):不管設(shè)備是否有數(shù)據(jù),系統(tǒng)總是要固定地花CPU時(shí)間去查看設(shè)備,且可能延遲對(duì)一些緊急數(shù)據(jù)的處理,因?yàn)榫W(wǎng)絡(luò)設(shè)備有數(shù)據(jù)時(shí)可能不能馬上得到CPU的響應(yīng)。在這種方式下,設(shè)備完全處于一種被動(dòng)的狀態(tài),而CPU又負(fù)擔(dān)過(guò)重。無(wú)論從資源的利用率上還是從效率上看,這種方法都不是最優(yōu)的。另一種方法是中斷方式,中斷方式利用硬件體系結(jié)構(gòu)的中斷機(jī)制實(shí)現(xiàn)設(shè)備和系統(tǒng)的應(yīng)答對(duì)話,即當(dāng)物理設(shè)備需要CPU處理數(shù)據(jù)時(shí),設(shè)備就發(fā)一個(gè)中斷信號(hào)給系統(tǒng),系統(tǒng)則在收到信號(hào)后調(diào)用相應(yīng)的中斷服務(wù)程序響應(yīng)對(duì)設(shè)備中斷的處理。中斷方式有效地解決了設(shè)備與CPU的對(duì)話交流問(wèn)題,并將CPU從繁重的設(shè)備輪詢(xún)中解脫出來(lái),大大提高了CPU的利用率。當(dāng)前不管是Linux平臺(tái)還是Windows平臺(tái),它們的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序幾乎都是使用中斷方式的。故在此我們主要討論基于中斷方式的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序。
網(wǎng)絡(luò)分層引起的一個(gè)問(wèn)題是,每層的協(xié)議在發(fā)送數(shù)據(jù)包時(shí)要加協(xié)議頭和協(xié)議尾到原數(shù)據(jù)中,在收到數(shù)據(jù)包時(shí)則要將本層的協(xié)議頭和協(xié)議尾從數(shù)據(jù)包中去掉。這使得在不同層協(xié)議間傳輸時(shí),每層都需要知道自己這一層的協(xié)議頭和協(xié)議尾在數(shù)據(jù)包的哪里。一種解決方法是在每層都復(fù)制緩沖區(qū),但顯然效率太低。Linux的做法是用一種數(shù)據(jù)結(jié)構(gòu)sk_buff在不同協(xié)議層及網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序之間傳送數(shù)據(jù)。sk_buff 包括指針和長(zhǎng)度域段,允許每個(gè)協(xié)議層通過(guò)標(biāo)準(zhǔn)的函數(shù)操作傳送的數(shù)據(jù)包。該數(shù)據(jù)結(jié)構(gòu)在整個(gè)Linux的網(wǎng)絡(luò)子系統(tǒng)包括網(wǎng)絡(luò)設(shè)備中扮演了一個(gè)十分重要的角色,故我們?cè)诜治鰯?shù)據(jù)包的傳輸和接收之前,首先來(lái)看看sk_buff這個(gè)數(shù)據(jù)結(jié)構(gòu)的內(nèi)容及系統(tǒng)提供的相關(guān)操作。因?yàn)閷?duì)該數(shù)據(jù)結(jié)構(gòu)的了解將大大有助于對(duì)Linux整個(gè)網(wǎng)絡(luò)子系統(tǒng)的理解。Sk_buff.doc
由于使用了硬件中斷請(qǐng)求機(jī)制,當(dāng)物理網(wǎng)絡(luò)設(shè)備接收到新數(shù)據(jù)時(shí),它將發(fā)送一個(gè)硬件中斷請(qǐng)求給系統(tǒng)。系統(tǒng)在偵察到有物理設(shè)備發(fā)出中斷請(qǐng)求,就會(huì)調(diào)用相應(yīng)的中斷服務(wù)程序來(lái)處理中斷請(qǐng)求。在這里,系統(tǒng)首先要知道哪個(gè)中斷對(duì)應(yīng)哪個(gè)中斷服務(wù)程序。為了讓系統(tǒng)知道網(wǎng)絡(luò)設(shè)備的中斷服務(wù)程序,一般在網(wǎng)絡(luò)設(shè)備初始化的時(shí)候或設(shè)備被打開(kāi)時(shí),要向系統(tǒng)登記中斷號(hào)及相應(yīng)的中斷服務(wù)程序(用request_irq這個(gè)函數(shù)登記);谥袛喾绞降脑O(shè)備驅(qū)動(dòng)程序若在設(shè)備初始化和設(shè)備打開(kāi)時(shí)都沒(méi)向系統(tǒng)登記中斷服務(wù)程序,則該設(shè)備肯定不能正常工作。由上幾節(jié)的NE2000代碼分析知,NE2000的中斷號(hào)登記是在設(shè)備初始化的時(shí)候,并登記中斷服務(wù)程序?yàn)閑i_interrupt。
一個(gè)網(wǎng)絡(luò)接口的中斷服務(wù)程序的工作步驟一般有以下幾步:
1.確定發(fā)生中斷的具體網(wǎng)絡(luò)接口,是rq2dev_map[irq]還是 (struct device*) dev_id;
2.打開(kāi)標(biāo)志位dev->interrupt,表示本服務(wù)程序正在被使用;
3.讀取中斷狀態(tài)寄存器,根據(jù)寄存器判斷中斷發(fā)生的原因。有兩種可能:一種是有新數(shù)據(jù)包到達(dá);另一種是上次的數(shù)據(jù)傳輸已完成。
4.若是因?yàn)橛行聰?shù)據(jù)包到達(dá),則調(diào)用接收數(shù)據(jù)包的子函數(shù);
5.若中斷由上次傳輸引起,則通知協(xié)議的上一層、修改接口的統(tǒng)計(jì)信息、關(guān)閉標(biāo)志位tbusy為下次傳輸做準(zhǔn)備;
6.關(guān)閉標(biāo)志位interrupt。
當(dāng)中斷服務(wù)程序明確物理網(wǎng)絡(luò)設(shè)備有數(shù)據(jù)包收到時(shí),將調(diào)用數(shù)據(jù)接收子程序來(lái)完成實(shí)際的依賴(lài)于硬件的數(shù)據(jù)接收工作,并在接收完成后通過(guò)函數(shù)netif_rx()[net/core/dev.c]將收到的數(shù)據(jù)包往上層傳。數(shù)據(jù)接收子程序的內(nèi)容可以由以下四點(diǎn)來(lái)概括:
1.申請(qǐng)skb緩沖區(qū)給新的數(shù)據(jù)包存儲(chǔ);
2.從硬件中讀取新到達(dá)的數(shù)據(jù);
3.調(diào)用netif_rx(),將新的數(shù)據(jù)包往網(wǎng)絡(luò)協(xié)議的上一層傳送;
4.修改接口的統(tǒng)計(jì)數(shù)據(jù)。
有上幾節(jié)的網(wǎng)絡(luò)設(shè)備的初始化分析可知,NE2000中斷服務(wù)程序ei_interrupt(),同時(shí)我們還將發(fā)現(xiàn)NE2000的數(shù)據(jù)接收子程序是ei_receive()。下面我們將繼續(xù)以NE2000為例,對(duì)網(wǎng)絡(luò)接口的接收過(guò)程進(jìn)行分析。
Linux的網(wǎng)絡(luò)設(shè)備是如何置入內(nèi)核并初始化的 :一系列device數(shù)據(jù)結(jié)構(gòu)在dev_base 表中相互連接起來(lái),每個(gè)device 結(jié)構(gòu)描述了它的 設(shè)備,并提供回調(diào)例程,當(dāng)需要網(wǎng)絡(luò)驅(qū)動(dòng)來(lái)執(zhí)行工作時(shí),網(wǎng)絡(luò)協(xié)議層調(diào)用這些例程。這些函數(shù)與傳輸?shù)臄?shù)據(jù)及網(wǎng)絡(luò)設(shè)備地址緊密相關(guān).當(dāng)一個(gè)網(wǎng)絡(luò)設(shè)備從網(wǎng)上接收包時(shí)它 必須將接收的數(shù)據(jù)轉(zhuǎn)換成sk_buff 結(jié)構(gòu),這些sk_buff 則被網(wǎng)絡(luò)驅(qū)動(dòng)加入到了backlog 隊(duì)列中。如果backlog 隊(duì)列太長(zhǎng),則丟棄接收的sk_buff 。準(zhǔn)備好要運(yùn)行時(shí),網(wǎng)絡(luò)底 層將被設(shè)置標(biāo)志。當(dāng)網(wǎng)絡(luò)底層按計(jì)劃開(kāi)始運(yùn)行后,處理backlog 隊(duì)列之前任何等待著被 傳輸?shù)木W(wǎng)絡(luò)包都由它來(lái)處理sk_buff 決定哪些層處理被接收的包
網(wǎng)卡設(shè)備驅(qū)動(dòng)程序?qū)⒂布袛嘀薪邮盏綌?shù)據(jù)幀存入sk_buff結(jié)構(gòu)然后檢查硬件幀頭, 識(shí)別幀類(lèi)型, 放入接收隊(duì)列backlog(由net_rx完成), 激活接收軟中
斷作進(jìn)一步處理.接收軟中斷(net_bh)提取接收包(中斷的“底半操作”), 根據(jù)它所在
的設(shè)備和協(xié)議類(lèi)型傳遞給各自的包處理器. 包處理器用dev_add_pack()注冊(cè),如果注冊(cè)的
設(shè)備號(hào)是零則表明它接收所有設(shè)備的包, 如果注冊(cè)的包類(lèi)型是(ETH_P_ALL),則表示它接
收所有類(lèi)型的包. 如果系統(tǒng)注冊(cè)有(ETH_P_ALL)類(lèi)型的包處理器,系統(tǒng)會(huì)通過(guò)
dev_queue_xmit_nit()將每一發(fā)送包復(fù)制一份副本傳遞給它們.
驅(qū)動(dòng)程序 中一般都有以下的幾個(gè)函數(shù)(名字不一定相同)
net_open(struct device *dev);
net_rx(struct device *dev);
net_send_packet(struct sk_buff *skb, struct device *dev);
net_close(struct device *dev);
static void net_interrupt(int irq, void *dev_id, struct pt_regs * regs);
int init_module(void);
其實(shí)驅(qū)動(dòng)程序并不存在一個(gè)接收方法(后面做解釋?zhuān)瑔?wèn)題 2)。有數(shù)據(jù)收到應(yīng)該是驅(qū)動(dòng)
程序通知系統(tǒng)的。一般設(shè)備收到數(shù)據(jù)后都會(huì)產(chǎn)生一個(gè)中斷,在中斷處理程序中驅(qū)動(dòng)程序
申請(qǐng)一個(gè)sk_buff(skb),從硬件讀出數(shù)據(jù)放置到申請(qǐng)好的緩沖區(qū)里。接下來(lái)填充sk_buff
中的一些信息。skb->dev = dev,判斷收到幀的協(xié)議類(lèi)型,填入skb->protocol(多協(xié)議
的支持)。把指針skb->mac.raw指向硬件數(shù)據(jù)然后丟棄硬件幀頭(skb_pull)。還要設(shè)置
skb->pkt_type,標(biāo)明第二層(鏈路層)數(shù)據(jù)類(lèi)型?梢允且韵骂(lèi)型:
PACKET_BROADCAST : 鏈路層廣播
PACKET_MULTICAST : 鏈路層組播
PACKET_SELF : 發(fā)給自己的幀
PACKET_OTHERHOST : 發(fā)給別人的幀(監(jiān)聽(tīng)模式時(shí)會(huì)有這種幀)
函數(shù)分析:
1 網(wǎng)卡接收數(shù)據(jù)處理網(wǎng)卡驅(qū)動(dòng)程序以3c501.c(3com網(wǎng)卡比較通用)為例:
static void el_receive(struct net_device *dev) //此設(shè)備的包接收主處理函數(shù),前綴el是3c501的標(biāo)志
{
struct net_local *lp = (struct net_local *)dev->priv;//網(wǎng)卡的私有數(shù)據(jù)存儲(chǔ)網(wǎng)卡的狀態(tài)信息
int ioaddr = dev->base_addr;//網(wǎng)卡的內(nèi)存影像區(qū)基地址
int pkt_len;
struct sk_buff *skb;
pkt_len = inw(RX_LOW);//提取包的長(zhǎng)度
skb = dev_alloc_skb(pkt_len+2);//分配sk_buff結(jié)構(gòu)
skb_reserve(skb,2); /* Force 16 byte alignment */
skb->dev = dev;
insb(DATAPORT, skb_put(skb,pkt_len), pkt_len);//把數(shù)據(jù)從內(nèi)存影像區(qū)讀到skb中
skb->protocol=eth_type_trans(skb,dev);//去掉幀頭,識(shí)別幀類(lèi)型
Netif_rx(skb); //放入接收隊(duì)列, 激活接收軟中斷作進(jìn)一步處理
dev->last_rx = jiffies;
lp->stats.rx_packets++;
lp->stats.rx_bytes+=pkt_len;//設(shè)置網(wǎng)卡統(tǒng)計(jì)狀態(tài)
}
2 eth_type_trans unsigned short eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
struct ethhdr *eth;
unsigned char *rawp;
skb->mac.raw=skb->data;//指向所接收數(shù)據(jù)幀
skb_pull(skb,dev->hard_header_len);//退掉硬件頭地址區(qū)域,此時(shí)data指向上層協(xié)議數(shù)據(jù)
eth= skb->mac.ethernet;
if(*eth->h_dest&1) //??
{//測(cè)試目標(biāo)地址的最高位(接網(wǎng)絡(luò)比特順序,最高有效位先傳送
if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0)
skb->pkt_type=PACKET_BROADCAST;//接收到廣播包
else
skb->pkt_type=PACKET_MULTICAST;//接收到同播包
}
else if(1 /*dev->flags&IFF_PROMISC*/)//假設(shè)所有網(wǎng)卡都設(shè)置了混雜模式
{
if(memcmp(eth->h_dest,dev->dev_addr, ETH_ALEN))
skb->pkt_type=PACKET_OTHERHOST;//接收去往其它主機(jī)的包硬件地址不匹配
}
rawp = skb->data;
}
3 Netif_rxint netif_rx(struct sk_buff *skb)
{//其中在這段代碼中加入了擁塞控制代碼,不列出
int this_cpu = smp_processor_id();//取得當(dāng)前cpu號(hào)
struct softnet_data *queue;
unsigned long flags;
if (skb->stamp.tv_sec == 0)
get_fast_time(&skb->stamp);//蓋上時(shí)間戳標(biāo)記到達(dá)時(shí)間
/* The code is rearranged so that the path is the most
short when CPU is congested, but is still operating.
*/
queue = & softnet_data[this_cpu](接收包相關(guān)數(shù)據(jù)結(jié)構(gòu).doc);//每個(gè)cpu都有一個(gè)網(wǎng)絡(luò)到達(dá)包的處理隊(duì)列
local_irq_save(flags);//保存現(xiàn)場(chǎng)到flags
dev_hold(skb->dev);//上鎖
__skb_queue_tail(&queue->input_pkt_queue,skb);
/* 把此skb加入到softnet_data[cpu]的接收隊(duì)列中去 */
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
/* 標(biāo)記軟中斷位 irq_stat[cpu]. __softirq_active |=1<<2 */接收包相關(guān)數(shù)據(jù)結(jié)構(gòu).doc
local_irq_restore(flags); |
|