(原創)網路處理的軟中斷機制分析
核心預設軟中斷機制分析(process_backlog)
首先需要介紹的就是netif_rx(在net/core/dev.c中定義)函式,這個函式在網絡卡驅動程式與linux核心之間建立了一道橋樑,將網絡卡接收上來的資料包(sk_buff形式)插入核心維護的接收緩衝區隊列當中:
int netif_rx(struct sk_buff *skb)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue;
unsigned long flags;
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
/*
獲取當前處理CPU的接收資料包緩衝區佇列指標。系統為每一個CPU都維護一個獨立的列表,這樣可以避免共享訪問互斥問題。
*/
queue = &softnet_data[this_cpu];
local_irq_save(flags);
netdev_rx_stat[this_cpu].total++;
/*
這裡判斷當前輸入佇列的長度是否超過預定義的一個值,如果沒有超過,則向下執行。
如果當前佇列的長度大於0,則將sk_buff插入佇列,並且返回,注意這裡並沒有呼叫__cpu_raise_softirq產生一個軟中斷,而較老的核心版本當中,插入佇列以後立刻呼叫這個函式產生軟中斷。
另外,如果佇列長度為
將裝置加入poll_list列表當中後,返回enqueue標記處繼續將sk_buff加入輸入資料包佇列中,然後返回。
*/
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;
enqueue:
dev_hold(skb->dev);
/*
將當前sk_buff插入input_pkt_queue佇列的尾部,即刻返回。
*/
__skb_queue_tail(&queue->input_pkt_queue,skb);
local_irq_restore(flags);
return queue->cng_level;
}
if (queue->throttle) {
queue->throttle = 0;
}
netif_rx_schedule(&queue->blog_dev);
goto enqueue;
}
if (queue->throttle == 0) {
queue->throttle = 1;
netdev_rx_stat[this_cpu].throttled++;
}
drop:
netdev_rx_stat[this_cpu].dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
從上面的分析可以知道,netif_rx函式主要負責將資料包插入核心佇列中,並觸發軟中斷,這一點與較早的版本是不同的,那麼軟中斷是在什麼地方觸發的呢?
以前的章節介紹過,硬體中斷是在irq.c的do_IRQ函式中呼叫handle_IRQ_event函式,進而呼叫相應硬體驅動程式的中斷處理函式實現的。在do_IRQ函式執行完硬體處理函式以後,接著就會呼叫do_softirq函式執行軟中斷,並且根據軟中斷號在softirq_vec陣列中查詢相應中斷的action方法,對於NET_RX_SOFTIRQ型別的軟中斷來說,系統將其action註冊為net_rx_action,這樣我們就進入了net_rx_action函式當中:
static void net_rx_action(struct softirq_action *h)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue = &softnet_data[this_cpu];
unsigned long start_time = jiffies;
int budget = netdev_max_backlog;(系統每次從佇列中取300個sk_buff處理)
br_read_lock(BR_NETPROTO_LOCK);
local_irq_disable();
/*
在這裡迴圈判斷當前輪詢列表是否為空。如果不為空,則進入軟中斷處理過程。
*/
while (!list_empty(&queue->poll_list)) {
struct net_device *dev;
if (budget <= 0 || jiffies - start_time > 1)
goto softnet_break;
local_irq_enable();
/*
從輪詢列表中取出當前的裝置dev指標,接著為dev呼叫poll方法,這是裝置初始化過程中已經定義好的方法,如果裝置未能自己實現該函式,則系統預設註冊為process_backlog。poll函式執行成功返回0,失敗返回-1。這裡邏輯判斷是,dev->quota必須大於0,而poll函式執行成功,則可以繼續,直到所有的裝置都查詢一遍為止。
*/
dev = list_entry(queue->poll_list.next, struct net_device, poll_list);
if (dev->quota <= 0 || dev->poll(dev, &budget)) {
/*
這裡的poll函式是netdevice結構的一個函式指標,對於不同的網絡卡驅動程式,我們可以根據自己的情況定義poll方法的實現(e1000網絡卡驅動程式就自己實現了一個poll方法,詳情後面來分析),而系統預設提供一個方法。
*/
local_irq_disable();
list_del(&dev->poll_list);
list_add_tail(&dev->poll_list, &queue->poll_list);
if (dev->quota < 0)
dev->quota += dev->weight;
else
dev->quota = dev->weight;
} else {
dev_put(dev);
local_irq_disable();
}
}
local_irq_enable();
br_read_unlock(BR_NETPROTO_LOCK);
return;
softnet_break:
netdev_rx_stat[this_cpu].time_squeeze++;
/*
觸發軟中斷處理,等待下一次呼叫本函式。
*/
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
local_irq_enable();
br_read_unlock(BR_NETPROTO_LOCK);
}
軟中斷處理函式net_rx_action實際上就是呼叫各個網路裝置的poll方法處理資料包的,一般的講,poll預設為process_backlog(在net/core/dev..c中定義):
static int process_backlog(struct net_device *backlog_dev, int *budget)
{
int work = 0;
int quota = min(backlog_dev->quota, *budget);
int this_cpu = smp_processor_id();
struct softnet_data *queue = &softnet_data[this_cpu];
unsigned long start_time = jiffies;
for (;;) {
struct sk_buff *skb;
struct net_device *dev;
local_irq_disable();
/*
從輸入緩衝區佇列中取出sk_buff,呼叫netif_receive_skb函式將sk_buff交給上層協議進行處理。這裡就是迴圈呼叫__skb_dequeue取出skb,直到所有的skb處理完畢為止。
*/
skb = __skb_dequeue(&queue->input_pkt_queue);
if (skb == NULL)
goto job_done;
local_irq_enable();
dev = skb->dev;
netif_receive_skb(skb);
dev_put(dev);
work++;
if (work >= quota || jiffies - start_time > 1)
break;
}
}
接下來看一下sk_buff是如何被遞交到上層協議進行處理的,只是通過呼叫netif_receive_skb(在net/core/dev.c中定義)函式實現的:
int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
int ret = NET_RX_DROP;
unsigned short type = skb->protocol;
/*
給每個網路資料包打上時間戳。
*/
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
skb_bond(skb);
pt_prev = NULL;
/*
上層的每個協議在其初始化的過程中會呼叫dev_add_pack函式將自己的packet_type結構加入到ptye_all列表當中,其中packet_type結構中定義了該協議的處理方法,對於ip協議來說,func方法就註冊為ip_rcv。另外,一般協議packet_type結構的dev欄位設為NULL,所以下面的ptype->dev就為NULL。
另外,如果我們需要增加自己的協議,則需要建立一個packet_type結構,用我們自己的協議處理函式填充該結構的func方法,並且呼叫dev_add_pack函式將我們自己的協議加入ptype_all陣列當中。
*/
for (ptype = ptype_all; ptype; ptype = ptype->next) {
/*
這裡每一種協議在定義其packet_type結構時都設定接收這種
資料包協議型別的裝置指標,如果設定為NULL,則可以從
任何裝置接收資料包。
這裡針對協議型別為ETH_P_ALL的情況進行處理,對於IP
協議來說,型別定義為ETH_P_IP,因此不在這裡處理。
*/
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev) {
if (!pt_prev->data) {
ret = deliver_to_old_ones(pt_prev, skb, 0);
} else {
atomic_inc(&skb->users);
ret = pt_prev->func(skb, skb->dev, pt_prev);
}
}
pt_prev = ptype;
}
}
/*
這裡針對各種協議進行處理,IP包的型別為ETH_P_IP,因此在這裡處理。
*/
for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev) {
if (!pt_prev->data) {
ret = deliver_to_old_ones(pt_prev, skb, 0);
} else {
atomic_inc(&skb->users);
ret = pt_prev->func(skb, skb->dev, pt_prev);
}
}
pt_prev = ptype;
}
}
if (pt_prev) {
if (!pt_prev->data) {
ret = deliver_to_old_ones(pt_prev, skb, 1);
} else {
ret = pt_prev->func(skb, skb->dev, pt_prev);
}
} else {
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
return ret;
}
在軟中斷處理函式當中,我們根據資料包的型別,呼叫相應的底層資料處理函式,對於IP包來說,就是呼叫ip_rcv函式並且進一步向上層協議遞交和處理。至此,核心的軟中斷的主要過程已經結束了。
接下來我們分析一下IP層以上的網路協議棧是如何進一步處理資料的,詳細的說明文件見《》。