f !supportLists]-->內核默認軟中斷機制分析(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.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函數并且進一步向上層協議遞交和處理。至此,內核的軟中斷的主要過程已經結束了。
電子發燒友App






















評論