1. 程式人生 > >(原創)網路處理的軟中斷機制分析

(原創)網路處理的軟中斷機制分析

核心預設軟中斷機制分析(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產生一個軟中斷,而較老的核心版本當中,插入佇列以後立刻呼叫這個函式產生軟中斷。

另外,如果佇列長度為

0,則需要呼叫netif_rx_schedule及後續的__netif_rx_schedule函式將當前網路裝置加入softnet_data的輪詢列表(poll_list)當中,這個列表維護所有網路裝置的列表,當系統軟中斷處理函式執行時,逐個檢索處於poll_list中的裝置,然後呼叫裝置的dev->poll方法處理輸入資料包佇列中的資料。

將裝置加入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.cdo_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;(系統每次從佇列中取300sk_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_backlogpoll函式執行成功返回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層以上的網路協議棧是如何進一步處理資料的,詳細的說明文件見《》。