初始化報文接收軟中斷
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; }