数据包接收系列 — 上半部实现(内核接口)

来源:互联网 发布:vb如何设置启动窗体 编辑:程序博客网 时间:2024/06/06 00:54

本文主要内容:网络数据包接收的上半部实现,主要分析内核接口相关部分。

内核版本:2.6.37

上半部的实现

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

el_interrupt() // 网卡驱动

    |--> el_receive() // 网卡驱动

                |--> netif_rx() // 内核接口

                           |--> enqueue_to_backlog() // 内核接口

 

我们已经分析了网卡驱动相关部分,现在来看下内核接口相关部分:)

netif_rx

netif_rx()是内核接收网络数据包的入口(目前多数网卡支持新的接口NAPI,后续文章会分析)。

netif_rx()主要调用enqueue_to_backlog()进行后续处理。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * netif_rx - post buffer to the network code 
  3.  * @skb: buffer to post 
  4.  * This function receives a packet from a device and queues it for the upper (protocol) 
  5.  * levels to process. It always succeeds. The buffer may be dropped during processing 
  6.  * for congestion control or by the protocol layers. 
  7.  * return values: 
  8.  * NET_RX_SUCCESS (no congestion) 
  9.  * NET_RX_DROP (packet was dropped) 
  10.  */  
  11.   
  12. int netif_rx(struct sk_buff *skb)  
  13. {  
  14.     int ret;  
  15.   
  16.     /* if netpoll wants it, pretend we never saw it */  
  17.     if (netpoll_rx(skb))  
  18.         return NET_RX_DROP;  
  19.   
  20.     /* 记录接收时间到skb->tstamp */  
  21.     if (netdev_tstamp_prequeue)  
  22.        net_timestamp_check(skb);  
  23.   
  24.    trace_netif_rx(skb);  
  25. #ifdef CONFIG_RPS  
  26.     /* 暂不考虑RPS,后续再分析 */  
  27.     ...  
  28. #else  
  29.     {  
  30.         unsigned int qtail;  
  31.         ret = enqueue_to_backlog(skb, get_cpu(), &qtail);  
  32.         put_cpu();  
  33.     }  
  34. #endif  
  35.     return ret;  
  36. }  

softnet_data

每个cpu都有一个softnet_data实例,用于收发数据包。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* Incoming packets are placed on per-cpu queues */  
  2.   
  3. struct softnet_data {  
  4.     struct Qdisc *output_queue; /* 输出包队列 */  
  5.     struct Qdisc **output_queue_tailp;  
  6.    
  7.      /* 其中设备是处于轮询状态的,即入口队列有新的帧等待处理 */  
  8.     struct list_head poll_list;  
  9.   
  10.     struct sk_buff *completion_queue; /* 成功传输的数据包队列 */  
  11.       
  12.     /* 处理队列,把input_pkt_queue接入 */  
  13.     struct sk_buff_head process_queue;  
  14.   
  15.     /* stats */  
  16.     unsigned int processed; /* 处理过的数据包个数 */  
  17.     unsigned int time_squeeze; /* poll受限于允许的时间或数据包个数 */  
  18.     unsigned int cpu_collision;  
  19.     unsigned int received_rps;  
  20.   
  21. #ifdef CONFIG_RPS  
  22.     /* 暂不研究RPS */  
  23.     ...  
  24. #endif  
  25.   
  26.     unsigned dropped; /* 因输入队列满而丢包的个数 */  
  27.   
  28.     /* 输入队列,保存接收到的数据包。 
  29.      * 非NAPI使用,支持NAPI的网卡驱动有自己的私有队列。 
  30.      */  
  31.     struct sk_buff_head input_pkt_queue;  
  32.     struct napi_struct backlog; /* 虚拟设备,非NAPI设备共用 */  
  33. };  

 

定义

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* Device drivers call our routines to queue packets here. 
  2.  * We empty the queue in the local softnet handler. 
  3.  */  
  4. DEFINE_PER_CPU_ALIGNED(struct softnet_data, softnet_data);  
  5. EXPORT_PER_CPU_SYMBOL(softnet_data);  

 

初始化

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* Initialize the DEV module. At boot time this walks the device list and 
  2.  * unhooks any devices that fail to initialise (normally hardware not present) 
  3.  * and leaves us with a valid list of present and active devices. 
  4.  * 
  5.  * This is called single threaded during boot, so no need to take the rtnl semaphore. 
  6.  */  
  7.   
  8. static int __init net_dev_init(void)  
  9. {  
  10.     ...  
  11.     /* Initialise the packet receive queues. 
  12.      * 初始化每个cpu的softnet_data实例。 
  13.      */  
  14.     for_each_possible_cpu(i) {  
  15.         struct softnet_data *sd = &per_cpu(softnet_data, i);  
  16.         memset(sd, 0, sizeof(*sd));  
  17.         skb_queue_head_init(&sd->input_pkt_queue);  
  18.         skb_queue_head_init(&sd->process_queue);  
  19.         sd->completion_queue = NULL;  
  20.         INIT_LIST_HEAD(&sd->poll_list);  
  21.         sd->output_queue = NULL;  
  22.         sd->output_queue_tailp = &sd->output_queue;  
  23.   
  24. #ifdef CONFIG_RPS  
  25.         ...  
  26. #endif  
  27.   
  28.         sd->backlog.poll = process_backlog; /* 非NAPI的默认轮询函数 */  
  29.         sd->backlog.weight = weight_p; /* 64,每次轮询处理数据包个数上限 */  
  30.         sd->backlog.gro_list = NULL;  
  31.         sd->backlog.gro_count = 0;  
  32.     }  
  33.     ...  
  34.     /* 注册软中断处理函数 */  
  35.     open_softirq(NET_TX_SOFTIRQ, net_tx_action);  
  36.     open_softirq(NET_RX_SOFTIRQ, net_rx_action);  
  37.     ...  
  38. }  

enqueue_to_backlog

netif_rx()调用enqueue_to_backlog()来处理。

首先获取当前cpu的softnet_data实例sd,然后:

1. 如果接收队列sd->input_pkt_queue不为空,说明已经有软中断在处理数据包了,

    则不需要再次触发软中断,直接将数据包添加到接收队列尾部即可。

2. 如果接收队列sd->input_pkt_queue为空,说明当前没有软中断在处理数据包,

    则把虚拟设备backlog添加到sd->poll_list中以便进行轮询,最后设置NET_RX_SOFTIRQ

    标志触发软中断。

3. 如果接收队列sd->input_pkt_queue满了,则直接丢弃数据包。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* queue an skb to a per CPU backlog queue (may be a remote CPU queue). */  
  2.   
  3. static int enqueue_to_backlog(struct sk_buff *skb, int cpu, unsigned int *qtail)  
  4. {  
  5.     struct softnet_data *sd;  
  6.     unsigned long flags;  
  7.   
  8.     sd = &per_cpu(softnet_data, cpu); /* 获取当前cpu上的softnet_data实例 */  
  9.   
  10.     local_irq_save(flags); /* 禁止本地中断 */  
  11.     rps_lock(sd);  
  12.   
  13.     if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {  
  14.   
  15.         /* 如果接收队列不为空,则说明已经有软中断在处理数据包了, 
  16.          * 则不需要再次触发软中断,直接将数据包添加到接收队列尾部即可。 
  17.          */  
  18.         if (skb_queue_len(&sd->input_pkt_queue)) {  
  19. enqueue:  
  20.             __skb_queue_tail(&sd->input_pkt_queue, skb); /* 添加到接收队列尾部 */  
  21.   
  22.             input_queue_tail_incr_save(sd, qtail);  
  23.             rps_unlock(sd);  
  24.   
  25.             local_irq_restore(flags); /* 恢复本地中断 */  
  26.             return NET_RX_SUCCESS;  
  27.         }  
  28.   
  29.         /* Schedule NAPI for backlog device. 
  30.          * 如果接收队列为空,说明当前没有软中断在处理数据包, 
  31.          * 把虚拟设备backlog添加到sd->poll_list中以便进行轮询, 
  32.          * 最后设置NET_RX_SOFTIRQ标志触发软中断。 
  33.          */  
  34.         if (! __test_and_set_bit(NAPT_STATE_SCHED, &sd->backlog.state)) {  
  35.             if (! rps_ipi_queued(sd))  
  36.                 ____napi_schedule(sd, &sd->backlog);  
  37.         }  
  38.         goto enqueue;  
  39.     }  
  40.   
  41.     sd->dropped++; /* 如果接收队列满了就直接丢弃 */  
  42.     rps_unlock(sd);  
  43.     local_irq_restore(flags); /* 恢复本地中断 */  
  44.     atomic_long_inc(&skb->dev->rx_dropped);  
  45.     kfree_skb(skb); /* 释放数据包 */  
  46.     return NET_RX_DROP;  
  47. }  
  48.   
  49. int netdev_tstamp_prequeue = 1/* 记录接收时间 */  
  50. int netdev_max_backlog = 1000/* 接收队列的最大长度 */  

 

napi_struct代表一个虚拟设备,用于兼容非NAPI的驱动。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* Structure for NAPI scheduling similar to tasklet but with weighting */  
  2.   
  3. struct napi_struct {  
  4.     /* The poll_list must only be managed by the entity which changes the 
  5.      * state of the NAPI_STATE_SCHED bit. This means whoever atomically 
  6.      * sets that bit can add this napi_struct to the per-cpu poll_list, and 
  7.      * whoever clears that bit can remove from the list right before clearing the bit. 
  8.      */  
  9.     struct list_head poll_list; /* 用于加入处于轮询状态的设备队列 */  
  10.     unsigned long state; /* 虚拟设备的状态 */  
  11.     int weight; /* 每次处理的最大数量,非NAPI为weight_p,默认为64 */  
  12.     int (*poll) (struct napi_struct *, int); /* 此设备的轮询方法,默认为process_backlog() */  
  13.   
  14. #ifdef CONFIG_NETPOLL  
  15.     ...  
  16. #endif  
  17.   
  18.     unsigned int gro_count;  
  19.     struct net_device *dev;  
  20.     struct list_head dev_list;  
  21.     struct sk_buff *gro_list;  
  22.     struct sk_buff *skb;  
  23. };  
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi)  
  2. {  
  3.     /* 把napi_struct添加到softnet_data的poll_list中 */  
  4.     list_add_tail(&napi->poll_list, &sd->poll_list);  
  5.     __raise_softirq_irqoff(NET_RX_SOFTIRQ); /* 设置软中断标志位 */  
  6. }  
0 0
原创粉丝点击