Linux 網(wǎng)絡(luò)中斷下半部處理
在 上一篇文章 中,我們介紹了網(wǎng)卡接收和發(fā)過(guò)數(shù)據(jù)在 Linux 內(nèi)核中的處理過(guò)程,我們先來(lái)回顧一下網(wǎng)卡接收和發(fā)送數(shù)據(jù)的過(guò)程,如 圖1 所示:

圖1 網(wǎng)卡接收和發(fā)送數(shù)據(jù)過(guò)程
如上圖所示,當(dāng)網(wǎng)卡接收到從網(wǎng)絡(luò)中發(fā)送過(guò)來(lái)的數(shù)據(jù)后,網(wǎng)卡會(huì)向 CPU 發(fā)起一個(gè)硬件中斷。當(dāng) CPU 接收到網(wǎng)卡的硬件中斷后,便會(huì)調(diào)用網(wǎng)卡驅(qū)動(dòng)向內(nèi)核注冊(cè)的中斷處理服務(wù),如 NS8390網(wǎng)卡驅(qū)動(dòng) 會(huì)向內(nèi)核注冊(cè) ei_interrupt 中斷服務(wù)。
由于在處理硬件中斷服務(wù)時(shí)會(huì)關(guān)閉硬件中斷,所以在處理硬件中斷服務(wù)的過(guò)程中,如果發(fā)生了其他的硬件中斷,也不能得到有效的處理,從而導(dǎo)致硬件中斷丟失的情況。
為了避免這種情況出現(xiàn),Linux 內(nèi)核把中斷處理分為:中斷上半部 和 中斷下半部,上半部在關(guān)閉中斷的情況下進(jìn)行,而下半部在打開(kāi)中斷的情況下進(jìn)行。
由于中斷上半部在關(guān)閉中斷的情況下進(jìn)行,所以必須要快速完成,從而避免中斷丟失的情況。而中斷下半部處理是在打開(kāi)中斷的情況下進(jìn)行的,所以可以慢慢進(jìn)行。
一般來(lái)說(shuō),網(wǎng)卡驅(qū)動(dòng)向內(nèi)核注冊(cè)的中斷處理服務(wù)屬于 中斷上半部,如前面介紹的 NS8390網(wǎng)卡驅(qū)動(dòng) 注冊(cè)的 ei_interrupt 中斷處理服務(wù),而本文主要分析網(wǎng)卡 中斷下半部 的處理。
數(shù)據(jù)包上送
在上一篇文章中,我們介紹過(guò) ei_interrupt 中斷處理服務(wù)首先會(huì)創(chuàng)建一個(gè) sk_buff 數(shù)據(jù)包對(duì)象保存從網(wǎng)卡中接收到的數(shù)據(jù),然后調(diào)用 netif_rx 函數(shù)將數(shù)據(jù)包上送給網(wǎng)絡(luò)協(xié)議棧處理。
我們先來(lái)分析一下 netif_rx 函數(shù)的實(shí)現(xiàn):
int netif_rx(struct sk_buff *skb){int this_cpu = smp_processor_id(); // 獲取當(dāng)前運(yùn)行的CPUstruct softnet_data *queue;unsigned long flags;...queue = &softnet_data[this_cpu]; // 獲取當(dāng)前CPU的待處理的數(shù)據(jù)包隊(duì)列local_irq_save(flags); // 關(guān)閉本地硬件中斷// 如果待處理隊(duì)列的數(shù)據(jù)包數(shù)量沒(méi)超出限制if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {if (queue->input_pkt_queue.qlen) {...enqueue:dev_hold(skb->dev); // 增加網(wǎng)卡設(shè)備的引用計(jì)數(shù)器__skb_queue_tail(&queue->input_pkt_queue, skb); // 將數(shù)據(jù)包添加到待處理隊(duì)列中__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); // 啟動(dòng)網(wǎng)絡(luò)中斷下半部處理local_irq_restore(flags);return softnet_data[this_cpu].cng_level;}...goto enqueue;}...drop:local_irq_restore(flags); // 打開(kāi)本地硬件中斷kfree_skb(skb); // 釋放數(shù)據(jù)包對(duì)象return NET_RX_DROP;}
netif_rx 函數(shù)的參數(shù)就是要上送給網(wǎng)絡(luò)協(xié)議棧的數(shù)據(jù)包,netif_rx 函數(shù)主要完成以下幾個(gè)工作:
獲取當(dāng)前 CPU 的待處理的數(shù)據(jù)包隊(duì)列,在 Linux 內(nèi)核初始化時(shí),會(huì)為每個(gè) CPU 創(chuàng)建一個(gè)待處理數(shù)據(jù)包隊(duì)列,用于存放從網(wǎng)卡中讀取到網(wǎng)絡(luò)數(shù)據(jù)包。
如果待處理隊(duì)列的數(shù)據(jù)包數(shù)量沒(méi)超出
netdev_max_backlog設(shè)置的限制,那么調(diào)用__skb_queue_tail函數(shù)把數(shù)據(jù)包添加到待處理隊(duì)列中,并且調(diào)用__cpu_raise_softirq函數(shù)啟動(dòng)網(wǎng)絡(luò)中斷下半部處理。如果待處理隊(duì)列的數(shù)據(jù)包數(shù)量超出
netdev_max_backlog設(shè)置的限制,那么就把數(shù)據(jù)包釋放。
netif_rx 函數(shù)的處理過(guò)程如 圖2 所示:

圖2 netif_rx 函數(shù)的處理過(guò)程
所以,netif_rx 函數(shù)的主要工作就是把接收到的數(shù)據(jù)包添加到待處理隊(duì)列中,并且啟動(dòng)網(wǎng)絡(luò)中斷下半部處理。
對(duì)于 Linux 內(nèi)核的中斷處理機(jī)制可以參考我們之前的文章 Linux中斷處理,這里就不詳細(xì)介紹了。在本文中,我們只需要知道網(wǎng)絡(luò)中斷下半部處理例程為 net_rx_action 函數(shù)即可。
網(wǎng)絡(luò)中斷下半部處理
上面說(shuō)了,網(wǎng)絡(luò)中斷下半部處理例程為 net_rx_action 函數(shù),所以我們主要分析 net_rx_action 函數(shù)的實(shí)現(xiàn):
static void net_rx_action(struct softirq_action *h){int this_cpu = smp_processor_id(); // 當(dāng)前運(yùn)行的CPUstruct softnet_data *queue = &softnet_data[this_cpu]; // 當(dāng)前CPU對(duì)于的待處理數(shù)據(jù)包隊(duì)列...for (;;) {struct sk_buff *skb;local_irq_disable();skb = __skb_dequeue(&queue->input_pkt_queue); // 從待處理數(shù)據(jù)包隊(duì)列中獲取一個(gè)數(shù)據(jù)包local_irq_enable();if (skb == NULL)break;...{struct packet_type *ptype, *pt_prev;unsigned short type = skb->protocol; // 網(wǎng)絡(luò)層協(xié)議類(lèi)型pt_prev = NULL;...// 使用網(wǎng)絡(luò)層協(xié)議處理接口處理數(shù)據(jù)包for (ptype = ptype_base[ntohs(type)&15]; ptype; ptype = ptype->next) {if (ptype->type == type&& (!ptype->dev || ptype->dev == skb->dev)){if (pt_prev) {atomic_inc(&skb->users);// 如處理IP協(xié)議數(shù)據(jù)包的 ip_rcv() 函數(shù)pt_prev->func(skb, skb->dev, pt_prev);}pt_prev = ptype;}}if (pt_prev) {// 如處理IP協(xié)議數(shù)據(jù)包的 ip_rcv() 函數(shù)pt_prev->func(skb, skb->dev, pt_prev);} elsekfree_skb(skb);}...}...return;}
net_rx_action 函數(shù)主要完成以下幾個(gè)工作:
從待處理數(shù)據(jù)包隊(duì)列中獲取一個(gè)數(shù)據(jù)包,如果數(shù)據(jù)包為空,那么就退出網(wǎng)絡(luò)中斷下半部。
如果獲取的數(shù)據(jù)包不為空,那么就從數(shù)據(jù)包的以太網(wǎng)頭部中獲取到網(wǎng)絡(luò)層協(xié)議的類(lèi)型。然后根據(jù)網(wǎng)絡(luò)層協(xié)議類(lèi)型從
ptype_base數(shù)組中獲取數(shù)據(jù)處理接口,再通過(guò)此數(shù)據(jù)處理接口來(lái)處理數(shù)據(jù)包。在內(nèi)核初始化時(shí),通過(guò)調(diào)用
dev_add_pack函數(shù)向ptype_base數(shù)組中注冊(cè)網(wǎng)絡(luò)層協(xié)議處理接口,如ip_init函數(shù):static struct packet_type ip_packet_type = {__constant_htons(ETH_P_IP),NULL,ip_rcv, // 處理IP協(xié)議數(shù)據(jù)包的接口(void*)1,NULL,};void __init ip_init(void){// 注冊(cè)網(wǎng)絡(luò)層協(xié)議處理接口dev_add_pack(&ip_packet_type);...}
所以,net_rx_action 函數(shù)主要從待處理隊(duì)列中獲取數(shù)據(jù)包,然后根據(jù)數(shù)據(jù)包的網(wǎng)絡(luò)層協(xié)議類(lèi)型,找到相應(yīng)的處理接口處理數(shù)據(jù)包。其過(guò)程如 圖3 所示:

從上圖可知,net_rx_action 函數(shù)將數(shù)據(jù)包交由網(wǎng)絡(luò)層協(xié)議處理接口后就不管了,而網(wǎng)絡(luò)層協(xié)議處理接口接管數(shù)據(jù)包后,會(huì)對(duì)數(shù)據(jù)包進(jìn)行進(jìn)一步處理,如判斷數(shù)據(jù)包的合法性(數(shù)據(jù)包是否損壞、數(shù)據(jù)包是否發(fā)送給本機(jī))。如果數(shù)據(jù)包是合法的,就會(huì)交由傳輸層協(xié)議處理接口處理。
總結(jié)
本文主要介紹了網(wǎng)絡(luò)中斷下半部的處理,從分析可知,網(wǎng)絡(luò)中斷下半部主要工作是從待處理隊(duì)列中獲取數(shù)據(jù)包,并且根據(jù)數(shù)據(jù)包的網(wǎng)絡(luò)層協(xié)議類(lèi)型來(lái)找到相應(yīng)的處理接口,然后把數(shù)據(jù)包交由網(wǎng)絡(luò)層協(xié)議處理接口進(jìn)行處理。
對(duì)于網(wǎng)絡(luò)層協(xié)議處理接口的相關(guān)過(guò)程,我們將會(huì)在后面的文章繼續(xù)分析。
