初始化報文接收軟中斷
static int __init net_dev_init(void)
{
......
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
......
}
報文接收軟中斷的處理函數net_rx_action詳解:
static void net_rx_action(struct softirq_action *h)
{
/*取得本地cpu 的softnet_data 的poll_list 鏈表*/
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
/*設置軟中斷處理程序一次允許的最大執行時間爲2個jiffies*/
unsigned long time_limit = jiffies + 2;
/*設置軟中斷接收函數一次最多處理的報文個數爲 300 */
int budget = netdev_budget;
/*關閉本地cpu的中斷,下面判斷list是否爲空時防止硬中斷搶佔*/
local_irq_disable();
/*循環處理pool_list 鏈表上的等待處理的napi*/
while (!list_empty(list))
{
struct napi_struct *n;
int work, weight;
/*如果處理報文超出一次處理最大的個數
或允許時間超過最大時間就停止執行,
跳到softnet_break 處*/
if (unlikely(budget <= 0 || time_after(jiffies, time_limit)))
{
goto softnet_break;
}
/*使能本地中斷,上面判斷list爲空已完成,下面調用NAPI
的輪詢函數是在硬中斷開啓的情況下執行*/
local_irq_enable();
/* 取得softnet_data pool_list 鏈表上的一個napi,
即使現在硬中斷搶佔軟中斷,會把一個napi掛到pool_list的尾端
軟中斷只會從pool_list 頭部移除一個pool_list,這樣不存在臨界區*/
n = list_entry(list->next, struct napi_struct, poll_list);
/*用weighe 記錄napi 一次輪詢允許處理的最大報文數*/
weight = n->weight;
/* work 記錄一個napi總共處理的報文數*/
work = 0;
/*如果取得的napi狀態是被調度的,就執行napi的輪詢處理函數*/
if (test_bit(NAPI_STATE_SCHED, &n->state))
{
work = n->poll(n, weight);
}
WARN_ON_ONCE(work > weight);
/*預算減去已經處理的報文數*/
budget -= work;
/*禁止本地CPU 的中斷,下面會有把沒執行完的NAPI掛到softnet_data
尾部的操作,和硬中斷存在臨界區。同時while循環時判斷list是否
爲空時也要禁止硬中斷搶佔*/
local_irq_disable();
/*如果napi 一次輪詢處理的報文數正好等於允許處理的最大數,
說明一次輪詢沒處理完全部需要處理的報文*/
if (unlikely(work == weight))
{
/*如果napi已經被禁用,就把napi 從 softnet_data 的pool_list 上移除*/
if (unlikely(napi_disable_pending(n)))
{
local_irq_enable();
napi_complete(n);
local_irq_disable();
}
else
{
/*否則,把napi 移到 pool_list 的尾端*/
list_move_tail(&n->poll_list, list);
}
}
}
out:
local_irq_enable();
return;
/*如果處理時間超時,或處理的報文數到了最多允許處理的個數,
說明還有napi 上有報文需要處理,調度軟中斷。
否則,說明這次軟中斷處理完全部的napi上的需要處理的報文,不再需要
調度軟中斷了*/
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
虛擬NAPI backlog 的輪詢函數process_backlog():
參數:
napi : 本地cpu上softnet_data 的backlog .
quota : 一次輪詢可以處理的最多報文數。
函數詳解:
static int process_backlog(struct napi_struct *napi, int quota)
{
int work = 0;
/*取得本地CPU上的softnet_data 數據*/
struct softnet_data *queue = &__get_cpu_var(softnet_data);
/*開始計時,一旦允許時間到,就退出輪詢*/
unsigned long start_time = jiffies;
napi->weight = weight_p;
/*循環從softnet_data 的輸入隊列取報文並處理,直到隊列中沒有報文了,
或處理的報文數大於了允許的上限值了,
或輪詢函數執行時間大於一個jiffies 了
*/
do
{
struct sk_buff *skb;
/*禁用本地中斷,要存隊列中取skb,防止搶佔*/
local_irq_disable();
/*從softnet_data 的輸入隊列中取得一個skb*/
skb = __skb_dequeue(&queue->input_pkt_queue);
/*如果隊列中沒有skb,則使能中斷並退出輪詢*/
if (!skb)
{
/*把napi 從 softnet_data 的 pool_list 鏈表上摘除*/
__napi_complete(napi);
/*使能本地CPU的中斷*/
local_irq_enable();
break;
}
/*skb 已經摘下來了,使能中斷*/
local_irq_enable();
/*把skb送到協議棧相關協議模塊進行處理,詳細處理見後續章節*/
netif_receive_skb(skb);
} while (++work < quota && jiffies == start_time);
/*返回處理報文個數*/
return work;
}
linux舊的收包方式提供給驅動的接口netif_rx():
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
/*如果接收skb的時間戳沒設定,設定接收時間戳*/
if (!skb->tstamp.tv64)
{
net_timestamp(skb);
}
/*禁止本地cpu的中斷*/
local_irq_save(flags);
/*取得本地cpu的softnet_data*/
queue = &__get_cpu_var(softnet_data);
/*每個CPU都有一個統計數據,增加統計數據*/
__get_cpu_var(netdev_rx_stat).total++;
/*如果本地CPU的輸入隊列中的skb 個數小於允許的最多的個數*/
if (queue->input_pkt_queue.qlen <= netdev_max_backlog)
{
/*如果本地cpu的輸入隊列長度不爲0,表示輸入隊列已經有skb了,
並且特殊的napi backlog 已經掛入了softnet_data 的
pool_list上了*/
if (queue->input_pkt_queue.qlen)
{
enqueue:
/*把skb 放入CPU的輸入隊列 input_pkt_queue*/
__skb_queue_tail(&queue->input_pkt_queue, skb);
/*使能中斷 並 返回*/
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
/*如果輸入隊列爲空,則把 特殊的napi backlog 掛到softnet_data
的 pool_list 上 並返回把skb放入輸入隊列並返回*/
napi_schedule(&queue->backlog);
goto enqueue;
}
/*如果本地cpu的輸入隊列已經滿了,則丟棄報文,
並增加丟包計數並返回*/
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
本文出自 “耀洋的博客” 博客,請務必保留此出處http://yaoyang.blog.51cto.com/7657153/1263842