Linux 網(wǎng)卡數(shù)據(jù)收發(fā)過(guò)程分析
一般來(lái)說(shuō),網(wǎng)卡主要有兩個(gè)重要的功能:接收數(shù)據(jù) 和 發(fā)送數(shù)據(jù)。
所以,當(dāng)網(wǎng)卡接收到數(shù)據(jù)包后,要通知 Linux 內(nèi)核有數(shù)據(jù)需要處理。另外,網(wǎng)卡驅(qū)動(dòng)應(yīng)該提供讓 Linux 內(nèi)核把數(shù)據(jù)把發(fā)送出去的接口。
net_device 結(jié)構(gòu)是 Linux 為了適配不同類型的網(wǎng)卡設(shè)備而抽象出來(lái)的對(duì)象,不同的網(wǎng)卡驅(qū)動(dòng)只需要按 Linux 的規(guī)范來(lái)填充 net_device 結(jié)構(gòu)的各個(gè)成員變量,Linux 內(nèi)核就能夠識(shí)別出網(wǎng)卡,并工作起來(lái)。
下面我們將分析網(wǎng)卡設(shè)備接收和發(fā)送數(shù)據(jù)包的實(shí)現(xiàn)原理。
net_device 結(jié)構(gòu)
net_device 結(jié)構(gòu)是 Linux 內(nèi)核對(duì)網(wǎng)卡設(shè)備的抽象,但由于歷史原因,net_device 結(jié)構(gòu)的定義十分復(fù)雜。
不過(guò)本文主要分析網(wǎng)卡設(shè)備收發(fā)數(shù)據(jù)的實(shí)現(xiàn),所以不會(huì)分析 net_device 結(jié)構(gòu)的所有成員。下面主要列出收發(fā)數(shù)據(jù)相關(guān)的成員,如下:
struct net_device{char name[IFNAMSIZ]; // 設(shè)備名字...unsigned int irq; // 中斷號(hào)...int (*init)(struct net_device *dev); // 設(shè)備初始化設(shè)備的接口...int (*open)(struct net_device *dev); // 打開(kāi)設(shè)備時(shí)調(diào)用的接口int (*stop)(struct net_device *dev); // 關(guān)閉設(shè)備時(shí)調(diào)用的接口// 發(fā)送數(shù)據(jù)接口int (*hard_start_xmit)(struct sk_buff *skb,struct net_device *dev);...};
下面介紹一下各個(gè)成員的作用:
name:設(shè)備的名字。用于在終端顯示設(shè)備的名字或者通過(guò)設(shè)備名字來(lái)搜索設(shè)備。irq:中斷號(hào)。當(dāng)網(wǎng)卡從網(wǎng)絡(luò)接收到數(shù)據(jù)包后,需要產(chǎn)生一個(gè)中斷來(lái)通知 Linux 內(nèi)核有數(shù)據(jù)包需要處理,而irq就是網(wǎng)卡驅(qū)動(dòng)注冊(cè)到內(nèi)核中斷服務(wù)的中斷號(hào)。init、open、stop:分別為設(shè)備的初始化接口,打開(kāi)接口和關(guān)閉接口。hard_start_xmit:當(dāng)需要通過(guò)網(wǎng)卡設(shè)備發(fā)送數(shù)據(jù)時(shí),可以調(diào)用這個(gè)接口來(lái)發(fā)送數(shù)據(jù)。
所以,一個(gè)網(wǎng)卡驅(qū)動(dòng)必須完成以下兩個(gè)工作:
通過(guò)實(shí)現(xiàn)
net_device結(jié)構(gòu)的hard_start_xmit方法來(lái)提供發(fā)送數(shù)據(jù)的功能。通過(guò)向內(nèi)核注冊(cè)硬件中斷服務(wù),來(lái)通知內(nèi)核處理網(wǎng)卡設(shè)備接收到的數(shù)據(jù)包。
也就是說(shuō),發(fā)送數(shù)據(jù)的功能是由 net_device 結(jié)構(gòu)的 hard_statr_xmit 方法提供,而通知內(nèi)核處理接收到的數(shù)據(jù)包的功能是由網(wǎng)卡的硬件中斷提供的。
圖1 展示了網(wǎng)卡接收和發(fā)送數(shù)據(jù)的過(guò)程:

圖1 網(wǎng)卡接收和發(fā)送數(shù)據(jù)過(guò)程
上圖展示的是 NS8390網(wǎng)卡 接收和發(fā)送數(shù)據(jù)的過(guò)程(紅色括號(hào)為接收過(guò)程,藍(lán)色括號(hào)為發(fā)送過(guò)程),從上圖可以發(fā)現(xiàn),NS8390網(wǎng)卡驅(qū)動(dòng) 完成了兩件事情:
將
net_device結(jié)構(gòu)的hard_start_xmit方法設(shè)置為ei_start_xmit。向 Linux 內(nèi)核注冊(cè)了
ei_interrupt硬件中斷服務(wù)。
所以,當(dāng)網(wǎng)卡接收到數(shù)據(jù)包時(shí),會(huì)觸發(fā) ei_interrupt 中斷服務(wù)來(lái)通知內(nèi)核有數(shù)據(jù)包需要處理。而當(dāng)需要通過(guò)網(wǎng)卡發(fā)送數(shù)據(jù)時(shí),將會(huì)調(diào)用 ei_start_xmit 方法把數(shù)據(jù)發(fā)送出去。
接收數(shù)據(jù)過(guò)程
當(dāng)網(wǎng)卡從網(wǎng)絡(luò)中接收到數(shù)據(jù)包后,會(huì)觸發(fā) ei_interrupt 中斷服務(wù),我們來(lái)看看 ei_interrupt 中斷服務(wù)的實(shí)現(xiàn):
void ei_interrupt(int irq, void *dev_id, struct pt_regs *regs){struct net_device *dev = dev_id;long e8390_base;int interrupts, nr_serviced = 0;struct ei_device *ei_local;e8390_base = dev->base_addr;ei_local = (struct ei_device *)dev->priv;spin_lock(&ei_local->page_lock);...// (1) 通過(guò)讀取網(wǎng)卡的中斷類型來(lái)進(jìn)行相應(yīng)的操作while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0&& ++nr_serviced < MAX_SERVICE){...// (2) 如果中斷類型為接收到數(shù)據(jù)包if (interrupts & (ENISR_RX + ENISR_RX_ERR)) {ei_receive(dev); // (3) 則從網(wǎng)卡讀取數(shù)據(jù)}...}...spin_unlock(&ei_local->page_lock);return;}
上面的代碼刪除了很多硬件相關(guān)的操作,因?yàn)楸疚牟⒉皇欠治鼍W(wǎng)卡驅(qū)動(dòng)的實(shí)現(xiàn)。
ei_interrupt 中斷服務(wù)首先讀取中斷的類型,保存到 interrupts 變量中。然后判斷中斷類型是否為接收到數(shù)據(jù)包,如果是就調(diào)用 ei_receive 函數(shù)從網(wǎng)卡處讀取數(shù)據(jù)。
我們繼續(xù)分析 ei_receive 函數(shù)的實(shí)現(xiàn):
static void ei_receive(struct net_device *dev){...while (++rx_pkt_count < 10){int pkt_len; // 數(shù)據(jù)包的長(zhǎng)度int pkt_stat; // 數(shù)據(jù)包的狀態(tài)...if ((pkt_stat & 0x0F) == ENRSR_RXOK) { // 如果數(shù)據(jù)包狀態(tài)是合法的struct sk_buff *skb;skb = dev_alloc_skb(pkt_len + 2); // 申請(qǐng)一個(gè)數(shù)據(jù)包對(duì)象if (skb) {skb_reserve(skb, 2);skb->dev = dev; // 設(shè)置接收數(shù)據(jù)包的設(shè)備skb_put(skb, pkt_len); // 增加數(shù)據(jù)的長(zhǎng)度// 從網(wǎng)卡中讀取數(shù)據(jù)(由網(wǎng)卡驅(qū)動(dòng)實(shí)現(xiàn)), 并將數(shù)據(jù)保存到skb中ei_block_input(dev, pkt_len, skb, current_offset+sizeof(rx_frame));skb->protocol = eth_type_trans(skb, dev); // 從以太網(wǎng)頭部中獲取網(wǎng)絡(luò)層協(xié)議類型netif_rx(skb); // 將數(shù)據(jù)包上送給內(nèi)核網(wǎng)絡(luò)協(xié)議棧...}}...}...return;}
ei_receive 函數(shù)主要完成以下幾個(gè)工作:
申請(qǐng)一個(gè)
sk_buff數(shù)據(jù)包對(duì)象,并且設(shè)置其dev字段為接收數(shù)據(jù)包的設(shè)備。通過(guò)調(diào)用
ei_block_input函數(shù)從網(wǎng)卡中讀取接收到的數(shù)據(jù),并保存到剛申請(qǐng)的sk_buff數(shù)據(jù)包對(duì)象中。ei_block_input函數(shù)是由網(wǎng)卡驅(qū)動(dòng)實(shí)現(xiàn)的,所以這里不作詳細(xì)分析。通過(guò)調(diào)用
eth_type_trans函數(shù)從數(shù)據(jù)包的以太網(wǎng)頭部中獲取網(wǎng)絡(luò)層協(xié)議類型。調(diào)用
netif_rx函數(shù)將數(shù)據(jù)包上送給內(nèi)核網(wǎng)絡(luò)協(xié)議棧。
當(dāng)把數(shù)據(jù)包上送給內(nèi)核網(wǎng)絡(luò)協(xié)議棧后,數(shù)據(jù)包的處理就由內(nèi)核接管。一般來(lái)說(shuō),內(nèi)核網(wǎng)絡(luò)協(xié)議棧會(huì)通過(guò)網(wǎng)絡(luò)層的 IP協(xié)議 和傳輸層的 TCP協(xié)議 或者 UDP協(xié)議 來(lái)對(duì)數(shù)據(jù)包進(jìn)行處理,處理完后就會(huì)把數(shù)據(jù)提交給應(yīng)用層的進(jìn)程進(jìn)行處理。
發(fā)送數(shù)據(jù)過(guò)程
當(dāng)網(wǎng)絡(luò)協(xié)議棧需要通過(guò)網(wǎng)卡設(shè)備發(fā)送數(shù)據(jù)時(shí),會(huì)調(diào)用 net_device 結(jié)構(gòu)的 hard_start_xmit 方法,而對(duì)于 NS8390網(wǎng)卡 來(lái)說(shuō),hard_start_xmit 方法會(huì)被設(shè)置為 ei_start_xmit 函數(shù)。
也就是說(shuō),使用 NS8390網(wǎng)卡 發(fā)送數(shù)據(jù)時(shí),最終會(huì)調(diào)用 ei_start_xmit 函數(shù)將數(shù)據(jù)發(fā)送出去。我們來(lái)看看 ei_start_xmit 函數(shù)的實(shí)現(xiàn):
static int ei_start_xmit(struct sk_buff *skb, struct net_device *dev){...length = skb->len; // 數(shù)據(jù)包的長(zhǎng)度...disable_irq_nosync(dev->irq); // 關(guān)閉硬件中斷spin_lock(&ei_local->page_lock); // 對(duì)設(shè)備進(jìn)行上鎖, 避免多核CPU對(duì)設(shè)備的使用...// 使用網(wǎng)卡驅(qū)動(dòng)的發(fā)送接口把數(shù)據(jù)發(fā)送出去,skb->data 為數(shù)據(jù)包的數(shù)據(jù)部分ei_block_output(dev, length, skb->data, ei_local->tx_start_page);...spin_unlock(&ei_local->page_lock); // 對(duì)設(shè)備進(jìn)行解鎖enable_irq(dev->irq); // 打開(kāi)硬件中斷...return 0;}
刪減了硬件相關(guān)的操作后,ei_start_xmit 函數(shù)的實(shí)現(xiàn)就非常簡(jiǎn)單:
首先關(guān)閉網(wǎng)卡的硬件中斷,防止發(fā)送過(guò)程中受到硬件中斷的干擾。
調(diào)用
ei_block_output函數(shù)把數(shù)據(jù)包的數(shù)據(jù)發(fā)送出去,此函數(shù)由網(wǎng)卡驅(qū)動(dòng)實(shí)現(xiàn),這里不作詳細(xì)分析。打開(kāi)網(wǎng)卡的硬件中斷,讓網(wǎng)卡能夠繼續(xù)通知內(nèi)核。
總結(jié)
本文主要簡(jiǎn)單的介紹了網(wǎng)卡設(shè)備接收和發(fā)送數(shù)據(jù)包的過(guò)程,而網(wǎng)卡設(shè)備的初始化過(guò)程并沒(méi)有涉及。當(dāng)然網(wǎng)卡設(shè)備的初始化過(guò)程也非常重要,可能會(huì)在后面的文章繼續(xù)分析。
