关闭

数据包接收系列 — 下半部实现(软中断)

265人阅读 评论(0) 收藏 举报

本文主要内容:下半部的实现,分析数据包从上半部结束后到L3的处理过程。

内核版本:2.6.37

Author:zhangskd @ csdn blog

 

下半部的实现

 

接收数据包的下半部处理流程为:

net_rx_action // 软中断

    |--> process_backlog() // 默认poll

               |--> __netif_receive_skb() // L2处理函数

                            |--> ip_rcv() // L3入口

 

net_rx_action

 

软中断(NET_RX_SOFTIRQ)的处理函数net_rx_action()主要做了:

遍历sd->poll_list,对于每个处于轮询状态的设备,调用它的poll()函数来处理数据包。

如果设备NAPI被禁止了,则把设备从sd->poll_list上删除,否则把设备移动到sd->poll_list的队尾。

每次软中断最多允许处理netdev_budget(300)个数据包,最长运行时间为2jiffies(2ms)。

每个设备一次最多允许处理weight_p(64)个数据包(非NAPI)。

如果在这次软中断中没处理玩,则再次设置NET_RX_SOFTIRQ标志触发软中断。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static void net_rx_action(struct softirq_action *h)  
  2. {  
  3.     struct softnet_data *sd = &__get_cpu_var(softnet_data); /* 当前CPU的softnet_data实例 */  
  4.     unsigned long time_limit = jiffies + 2/* 一次软中断的最长处理时间(2ms) */  
  5.     int budget = netdev_budget; /* 一次软中断最多能够处理的skb个数(300) */  
  6.     void *have;  
  7.   
  8.     local_irq_disable(); /* 禁止本地中断 */  
  9.   
  10.     /* 如果有处于轮询状态的设备 */  
  11.     while(! list_empty(&sd->poll_list)) {  
  12.   
  13.         struct napi_struct *n;  
  14.         int work, weight;  
  15.         
  16.         /* If softirq window is exhuasted then punt. 
  17.          * Allow this to run for 2 jiffies since which will allow an average 
  18.          * latency of 1.5/HZ. 
  19.          * 如果处理的数据包过多了,或者处理的时间过长了,则退出。 
  20.          */  
  21.         if (unlikely(budget <= 0 || time_after(jiffies, time_limit)))  
  22.             goto softnet_break;  
  23.   
  24.         local_irq_enable(); /* 开启本地中断 */  
  25.   
  26.         /* Even though interrupts have been re-enabled, this access is safe because 
  27.          * interrupts can only add new entries to the tail of this list, and only ->poll() 
  28.          * calls can remove this head entry from the list. 
  29.          */  
  30.   
  31.         /* 获取链表上的第一个napi_struct实例 */  
  32.         n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);  
  33.   
  34.         have = netpoll_poll_lock(n);  
  35.         weight = n->weight; /* 这个设备每次能poll的数据包上限 */  
  36.   
  37.         /* This NAPI_STATE_SCHED test is for avoiding a race with netpoll's 
  38.          * poll_napi(). Only the entity which obtains the lock and sees NAPI_STATE_SCHED 
  39.          * set will actually make the ->poll() call. Therefore we avoid accidently calling ->poll() 
  40.          * when NAPI is not scheduled. 
  41.          */  
  42.         work = 0;  
  43.   
  44.         if (test_bit(NAPI_STATE_SCHED, &n->state)) {  
  45.             /* 调用napi_struct的poll方法,返回处理的数据包个数 */  
  46.             work = n->poll(n, weight); /* 默认为process_backlog() */  
  47.   
  48.             trace_napi_poll(n);  
  49.         }  
  50.         WARN_ON_ONCE(work > weight);  
  51.   
  52.         budget -= work; /* 总预算减去本次处理的数据包数 */  
  53.   
  54.         local_irq_disable(); /* 禁止本地中断 */  
  55.   
  56.         if (unlikely(work == weight)) {  
  57.             /* 如果NAPI被禁止了,则把当前napi_struct从poll_list中删除 */  
  58.             if (unlikely(napi_disable_pending(n))) {  
  59.                 local_irq_enable();  
  60.                 napi_complete(n);  
  61.                 local_irq_disable();  
  62.   
  63.             } else  
  64.                 /* 把当前napi_struct移动到poll_list的队尾 */  
  65.                 list_move_tail(&n->poll_list, &sd->poll_list);  
  66.         }  
  67.         netpoll_poll_unlock(have);  
  68.     }  
  69.   
  70. out:  
  71.     net_rps_action_and_irq_enable(sd); /* 开启本地中断 */  
  72.   
  73. #ifdef CONFIG_NET_DMA  
  74.     ...  
  75. #endif  
  76.   
  77.     return;  
  78.   
  79. softnet_break:  
  80.     sd->time_squeeze++; /* 跑满2ms,或处理了300个包 */  
  81.     __raise_softirq_irqoff(NET_RX_SOFTIRQ); /* 因为没处理完,再次触发软中断 */  
  82.     goto out;  
  83. }  

当调用napi_struct的poll()来处理数据包时,本地中断是开启的,这意味着新的数据包可以继续添加到

输入队列中。

 

process_backlog

 

如果网卡驱动不支持NAPI,则默认的napi_struct->poll()函数为process_backlog()。

process_backlog()的主要工作:

1. 处理sd->process_queue中的数据包

    分别取出每个skb,从队列中删除。

    开本地中断,调用__netif_rx_skb()把skb从L2传递到L3,然后关本地中断。

    这说明在处理skb时,是允许网卡中断把数据包添加到接收队列(sd->input_pkt_queue)中的。

2. 如果处理完sd->process_queue中的数据包了,quota还没用完

     把接收队列添加到sd->process_queue处理队列的尾部后,初始化接收队列。

     接下来会继续处理sd->process_queue中的数据包。

3. 如果本次能处理完sd->process_queue和sd->input_pkt_queue中的所有数据包

    把napi_struct从sd->poll_list队列中删除掉,清除NAPI_STATE_SCHED标志。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int process_backlog(struct napi_struct *napi, int quota)  
  2. {  
  3.     int work = 0;  
  4.     struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);  
  5.   
  6. #ifdef CONFIG_RPS  
  7.     ...  
  8. #endif  
  9.   
  10.     napi->weight = weight_p; /* 每次处理的最大数据包数,默认为64 */  
  11.     local_irq_disable(); /* 禁止本地中断 */  
  12.       
  13.     while(work < quota) { /* 配额允许时 */  
  14.         struct sk_buff *skb;  
  15.         unsigned int qlen;  
  16.   
  17.         /* 从sd->process_queue队列取出第一个skb,并把它从队列中删除。 
  18.          * sd->process_queue用于存储即将处理的数据包。 
  19.          */  
  20.         while((skb = __skb_dequeue(&sd->process_queue))) {  
  21.             local_irq_enable(); /* 开启本地中断 */  
  22.   
  23.             __netif_receive_skb(skb); /* 进行二层处理后转发给网络层 */  
  24.   
  25.             local_irq_disable();  
  26.             input_queue_head_incr(sd);  
  27.   
  28.             if (++work >= quota) { /* 处理的数据包个数超过上限了,返回 */  
  29.                 local_irq_enable();  
  30.                 return work;  
  31.             }  
  32.         }  
  33.   
  34.         rps_lock(sd);  
  35.         qlen = skb_queue_len(&sd->input_pkt_queue); /* 接收队列的长度 */  
  36.         /* 把接收队列添加到sd->process_queue的尾部,然后初始化接收队列 */  
  37.         if (qlen)  
  38.             skb_queue_splice_tail_init(&sd->input_pkt_queue, &sd->process_queue);  
  39.   
  40.         /* 如果能在本次处理完接收队列的数据包 */  
  41.         if (qlen < quota - work) {  
  42.             /* 把napi_struct从sd->poll_list队列中删除,因为马上要全部处理完了 */  
  43.             list_del(&napi->poll_list);  
  44.   
  45.             napi->state = 0/* 清除掉NAPI_STATE_SCHED标志 */  
  46.             quota = work + qlen; /* 减小quota,使接下来处理完process_queue的qlen个包即退出 */  
  47.         }  
  48.         rps_unlock(sd);  
  49.     }      
  50.    
  51.     local_irq_enable();  
  52.     return work;  
  53. }  

从sk_buff_head队列中取出第一个skb,并把它从队列中删除。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * __skb_dequeue - remove from the head of the queue 
  3.  * @list: list to dequeue from 
  4.  * Remove the head of the list.  
  5.  * The head item is returned or %NULL if the list is empty. 
  6.  */  
  7. static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list)  
  8. {  
  9.     struct sk_buff *skb = skb_peek(list); /* 取出队列的第一个元素 */  
  10.     if (skb)  
  11.         __skb_unlink(skb, list); /* 把skb从sk_buff_head队列中删除 */  
  12.     return skb;  
  13. }  

把list添加到head的队尾,然后把list重新初始化。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * skb_queue_splice_tail - join two skb lists and reinitialise the emptied list 
  3.  * @list: the new list to add 
  4.  * @head: the place to add it in the first list 
  5.  * Each of the lists is a queue. 
  6.  * The list at @list is reinitialised 
  7.  */  
  8. static inline void skb_queue_splice_tail_init(struct sk_buff_head *list, struct sk_buff_head *head)  
  9. {  
  10.     if (! skb_queue_empty(list)) {  
  11.         __skb_queue_splice(list, head->prev, (struct sk_buff *)head);  
  12.         head->qlen += list->qlen;  
  13.         __skb_queue_head_init(list);  
  14.     }  
  15. }  

 

__netif_receive_skb

 

__netif_receive_skb()的主要工作为:

处理NETPOLL、网卡绑定、入口流量控制、桥接、VLAN。

遍历嗅探器(ETH_P_ALL)链表ptype_all。对于每个注册的sniffer,调用它的处理函数

packet_type->func(),例如tcpdump。

赋值skb->network_header,根据skb->protocol从三层协议哈希表ptype_base中找到对应的

三层协议。如果三层协议是ETH_P_IP,相应的packet_type为ip_packet_type, 协议处理函数为ip_rcv()。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int __netif_receive_skb(struct sk_buff *skb)  
  2. {  
  3.     struct packet_type *ptype, *pt_prev;  
  4.     rx_handler_func_t *rx_handler;  
  5.     struct net_device *orig_dev;  
  6.     struct net_device *master;  
  7.     struct net_device *null_or_orig;  
  8.     struct net_device *orig_or_bond;  
  9.     int ret = NET_RX_DROP;  
  10.     __be16 type;  
  11.   
  12.     if (! netdev_tstamp_prequeue)  
  13.         net_timestamp_check(skb); /* 记录接收时间到skb->tstamp */  
  14.     trace_netif_receive_skb(skb);  
  15.   
  16.     /* If we've gotten here through NAPI, check netpoll */  
  17.     if (netpoll_receive_skb(skb))  
  18.         return NET_RX_DROP;  
  19.    
  20.     if (! skb->skb_iif)  
  21.         skb->skb_iif = skb->dev->ifinex; /* 记录设备编号 */  
  22.   
  23.     /* 处理网卡绑定(bonding) */  
  24.     null_or_orig = NULL;  
  25.     orig_dev = skb->dev;  
  26.     master = ACCESS_ONCE(orig_dev->master);  
  27.   
  28.     if (skb->deliver_no_wcard)  
  29.         null_or_orig = orig_dev;  
  30.     else if (master) {  
  31.         if (skb_bond_should_drop(skb, master)) {  
  32.             skb->deliver_no_wcard = 1;  
  33.             null_or_orig = orig_dev; /* deliver only exact match */  
  34.         } else  
  35.             skb->dev = master;  
  36.     }  
  37.   
  38.     __this_cpu_inc(softnet_data.processed); /* 增加本cpu处理过的数据包个数 */  
  39.     skb_reset_network_header(skb); /* 赋值skb->network_header */  
  40.     skb_reset_network_header(skb); /* 赋值skb->transport_header */  
  41.     skb->mac_len = skb->network_header - skb->mac_header; /* MAC头的长度,一般为14 */  
  42.     pt_prev = NULL;  
  43.   
  44.     rcu_read_lock();  
  45.   
  46. /* 入口流量控制 */  
  47. #ifdef CONFIG_NET_CLS_ACT   
  48.     if (skb->tc_verd & TC_NCLS) {  
  49.         skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);  
  50.         goto ncls;  
  51.     }  
  52. #endif  
  53.   
  54.     /* 遍历嗅探器(ETH_P_ALL)链表ptype_all。对于每个注册的sniffer, 
  55.      * 调用它的处理函数packet_type->func(),例如tcpdump。 
  56.      */  
  57.     list_for_each_entry_rcu(ptype, &ptype_all, list) {  
  58.         if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||  
  59.             ptype->dev == orig_dev) {  
  60.             if (pt_prev)  
  61.                 ret = deliver_skb(skb, pt_prev, orig_dev); /* 嗅探器的处理函数 */  
  62.                 pt_prev = ptype;  
  63.         }  
  64.     }  
  65.   
  66. #ifdef CONFIG_NET_CLS_ACT  
  67.     skb = handle_ing(skb, &pt_prev, &ret, orig_dev);  
  68.     if (! skb)  
  69.         goto out;  
  70. ncls:  
  71. #endif  
  72.   
  73.     /* Handle special case of bridge or macvlan,接收的特殊过程 */  
  74.     rx_handler = rcu_dereference(skb->dev->rx_handler);  
  75.     if (rx_handler) {  
  76.         if (pt_prev) {  
  77.             ret = deliver_skb(skb, pt_prev, orig_dev);  
  78.             pt_prev = NULL;  
  79.         }  
  80.         skb = rx_handler(skb);  
  81.         if (! skb)  
  82.             goto out;  
  83.     }  
  84.   
  85.     /* VLAN虚拟局域网 */  
  86.     if (vlan_tx_tag_present(skb)) {  
  87.         if (pt_prev) {  
  88.             ret = deliver_skb(skb, pt_prev, orig_dev);  
  89.             pt_prev = NULL;  
  90.         }  
  91.   
  92.         if (vlan_hwaccel_do_receive(&skb)) {  
  93.             ret = __netif_receive_skb(skb);  
  94.             goto out;  
  95.         } else if (unlikely(! skb))  
  96.             goto out;  
  97.     }  
  98.   
  99.     /* Make sure frames received on VLAN interfaces stacked on bonding 
  100.      * interfaces still make their way to any base bonding device that may 
  101.      * have registered for a specific ptype. The handler may have to adjust 
  102.      * skb->dev and orig_dev. 
  103.      */  
  104.     orig_or_bond = orig_dev;  
  105.     if ((skb->dev->priv_flags & IFF_802_1Q_VLAN) &&  
  106.         (vlan_dev_real_dev(skb->dev)->priv_flags & IFF_BONDING)) {  
  107.         orig_or_bond = vlan_dev_real_dev(skb->dev);  
  108.     }  
  109.   
  110.     type = skb->protocol; /* 三层协议类型 */  
  111.   
  112.     list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {  
  113.         if (ptype->type == type && (ptype->dev == null_or_orig || ptype->dev == skb->dev  
  114.             || ptype->dev == orig_dev || ptype->dev == orig_or_bond)) {  
  115.   
  116.             /* 如果三层协议是ETH_P_IP,相应的packet_type为ip_packet_type, 
  117.              * 协议处理函数为ip_rcv()。 
  118.              */  
  119.             if (pt_prev)  
  120.                 ret = deliver_skb(skb, pt_prev, orig_dev);  
  121.             pt_prev = ptype;  
  122.         }  
  123.     }  
  124.   
  125.     if (pt_prev) {  
  126.         ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);  
  127.     } else { /* 说明没找到对应的三层协议 */  
  128.         atomic_long_inc(&skb->dev->rx_dropped);  
  129.         kfree_skb(skb);  
  130.         ret = NET_RX_DROP;  
  131.     }  
  132.   
  133. out:  
  134.     rcu_read_unlock();  
  135.     return ret;  
  136. }  
  137.    

 

L3协议处理函数

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #define PTYPE_HASH_SIZE (16)  
  2. #define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)  
  3. static DEFINE_SPINLOCK(ptype_lock);  
  4.   
  5. static struct list_head ptype_base[PTYPE_HASH_SIZE]; /* 协议哈希表 */  
  6. static struct list_head ptype_all; /* 嗅探器(ETH_P_ALL)的链表 */  

packet_type用于描述一个协议:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct packet_type {  
  2.     __be16 type; /* This is really htons(ether_type). 协议代码 */  
  3.     struct net_device *dev; /* NULL is wildcarded here */  
  4.   
  5.     /* 协议处理函数,如ip_rcv() */  
  6.     int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);  
  7.     ...  
  8.     struct list_head list;  
  9. }  

IP协议:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* IP protocol layer initialiser */  
  2. static struct packet_type ip_packet_type = {  
  3.     .type = cpu_to_be16(ETH_P_IP),  
  4.     .func = ip_rcv,  
  5.     ...  
  6. };  
  7. #define ETH_P_IP 0x0800 /* Internet Protocol packet */  
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:169718次
    • 积分:3218
    • 等级:
    • 排名:第11009名
    • 原创:119篇
    • 转载:147篇
    • 译文:0篇
    • 评论:8条
    博客专栏