zoukankan      html  css  js  c++  java
  • Linux内核数据包处理流程-数据包接收(3)[转载]

    五、队列层

    1、软中断与下半部

    当用中断处理的时候,为了减少中断处理的工作量,比如,一般中断处理时,需要屏蔽其它中断,如果中断处理时间过长,那么其它中断
    有可能得不到及时处理,也以,有一种机制,就是把不必马上处理的工作,推迟一点,让它在中断处理后的某一个时刻得到处理。这就
    是下半部。

    下半部只是一个机制,它在Linux中,有多种实现方式,其中一种对时间要求最严格的实现方式,叫软中断,可以使用:

    open_softirq()

    来向内核注册一个软中断,
    然后,在合适的时候,调用

    raise_softirq_irqoff()

    触发它。

    如果采用中断方式接收数据(这一节就是在说中断方式接收,后面,就不用这种假设了),同样也需要软中断,可以调用

    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

    向内核注册一个名为NET_RX_SOFTIR的软中断,net_rx_action是软中断的处理函数。

    然后,在驱动中断处理完后的某一个时刻,调用

    raise_softirq_irqoff(NET_RX_SOFTIRQ)


    触发它,这样net_rx_action将得到执行。

    2、队列层

    什么是队列层?通常,在网卡收发数据的时候,需要维护一个缓冲区队列,来缓存可能存在的突发数据,类似于前面的DMA环形缓冲区。
    队列层中,包含了一个叫做struct softnet_#

    [cpp] view plain copy

    1.  struct softnet_#  

    2.  {  

    3.           

    4.          int                        throttle;  

    5.           

    6.          int                        cng_level;  

    7.          int                        avg_blog;  

    8.           

    9.          struct sk_buff_head        input_pkt_queue;  

    10.         

    11.        struct list_head        poll_list;  

    12.        struct net_device        *output_queue;  

    13.        struct sk_buff                *completion_queue;  

    14.  

    15.        struct net_device        backlog_dev;         

    16.};  


    内核使用了一个同名的变量softnet_#,它是一个Per-CPU变量,每个CPU都有一个。

    net/core/dev.c

    CODE:

    DECLARE_PER_CPU(struct softnet_#,softnet_#);

     

    [cpp] view plain copy

    1.  static int __init net_dev_init(void)  

    2.  {  

    3.          int i, rc = -ENOMEM;  

    4.    

    5.          BUG_ON(!dev_boot_phase);  

    6.    

    7.          net_random_init();  

    8.    

    9.          if (dev_proc_init())                 

    10.                goto out;  

    11.  

    12.        if (netdev_sysfs_init())         

    13.                goto out;  

    14.  

    15.         

    16.        INIT_LIST_HEAD(&ptype_all);  

    17.        for (i = 0; i < 16; i++)  

    18.                INIT_LIST_HEAD(&ptype_base[i]);  

    19.  

    20.        for (i = 0; i < ARRAY_SIZE(dev_name_head); i++)  

    21.                INIT_HLIST_HEAD(&dev_name_head[i]);  

    22.  

    23.        for (i = 0; i < ARRAY_SIZE(dev_index_head); i++)  

    24.                INIT_HLIST_HEAD(&dev_index_head[i]);  

    25.  

    26.         

    27.  

    28.         

    29.        for (i = 0; i < NR_CPUS; i++) {  

    30.                struct softnet_# *queue;  

    31.                 

    32.                 

    33.                queue = &per_cpu(softnet_#, i);  

    34.                 

    35.                skb_queue_head_init(&queue->input_pkt_queue);  

    36.                queue->throttle = 0;  

    37.                queue->cng_level = 0;  

    38.                queue->avg_blog = 10;  

    39.                queue->completion_queue = NULL;  

    40.                INIT_LIST_HEAD(&queue->poll_list);  

    41.                set_bit(__LINK_STATE_START, &queue->backlog_dev.state);  

    42.                queue->backlog_dev.weight = weight_p;  

    43.                 

    44.                queue->backlog_dev.poll = process_backlog;  

    45.                atomic_set(&queue->backlog_dev.refcnt, 1);  

    46.        }  

    47.  

    48.#ifdef OFFLINE_SAMPLE  

    49.        samp_timer.expires = jiffies + (10 * HZ);  

    50.        add_timer(&samp_timer);  

    51.#endif  

    52.  

    53.        dev_boot_phase = 0;  

    54.         

    55.         

    56.        open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);  

    57.        open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);  

    58.  

    59.        hotcpu_notifier(dev_cpu_callback, 0);  

    60.        dst_init();  

    61.        dev_mcast_init();  

    62.        rc = 0;  

    63.out:  

    64.        return rc;  

    65.}  


    这样,初始化完成后,在驱动程序中,在中断处理函数中,会调用netif_rx将数据交上来,这与采用轮询技术,有本质的不同:

    [cpp] view plain copy

    1.  int netif_rx(struct sk_buff *skb)  

    2.  {  

    3.          int this_cpu;  

    4.          struct softnet_# *queue;  

    5.          unsigned long flags;  

    6.    

    7.           

    8.          if (netpoll_rx(skb))  

    9.                  return NET_RX_DROP;  

    10.  

    11.         

    12.        if (!skb->stamp.tv_sec)  

    13.                net_timestamp(&skb->stamp);  

    14.  

    15.         

    16.        local_irq_save(flags);  

    17.         

    18.        this_cpu = smp_processor_id();  

    19.        queue = &__get_cpu_var(softnet_#);  

    20.  

    21.         

    22.        __get_cpu_var(netdev_rx_stat).total++;  

    23.         

    24.         

    25.        if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {  

    26.                if (queue->input_pkt_queue.qlen) {  

    27.                        if (queue->throttle)                         

    28.                                goto drop;  

    29.                         

    30.                         

    31.enqueue:  

    32.                        dev_hold(skb->dev);                         

    33.                        __skb_queue_tail(&queue->input_pkt_queue, skb);                 

    34.#ifndef OFFLINE_SAMPLE  

    35.                        get_sample_stats(this_cpu);  

    36.#endif  

    37.                        local_irq_restore(flags);  

    38.                        return queue->cng_level;  

    39.                }  

    40.  

    41.                 

    42.                if (queue->throttle)  

    43.                        queue->throttle = 0;  

    44.  

    45.                 

    46.                netif_rx_schedule(&queue->backlog_dev);  

    47.                goto enqueue;  

    48.        }  

    49.  

    50.         

    51.        if (!queue->throttle) {  

    52.                queue->throttle = 1;  

    53.                __get_cpu_var(netdev_rx_stat).throttled++;  

    54.        }  

    55.  

    56.  

    57.drop:  

    58.        __get_cpu_var(netdev_rx_stat).dropped++;  

    59.        local_irq_restore(flags);  

    60.  

    61.        kfree_skb(skb);  

    62.        return NET_RX_DROP;  

    63.}  


    从这段代码的分析中,我们可以看到,当数据被接收后,netif_rx的工作,就是取得当前CPU的队列,然后入队,然后返回,然后中断函数
    现调用它,它再把数据包入队……
    当队列接收完成后,netif_rx就调用netif_rx_schedule进一步处理数据包,我们注意到:
    1、前面讨论过,采用轮询技术时,同样地,也是调用netif_rx_schedule,把设备自己传递了过去;

    2、这里,采用中断方式,传递的是队列中的一个伪设备,并且,这个伪设备的poll函数指针,指向了一个叫做process_backlog的函数;


    netif_rx_schedule函数完成两件重要的工作:

    1、将bakclog_dev设备加入处理数据包的设备的链表当中;

    2、触发软中断函数,进行数据包接收处理;


    这样,我们可以猜想,在软中断函数中,不论是伪设备bakclog_dev,还是真实的设备(如前面讨论过的e100),都会被软中断函数以:
    dev->poll()
    的形式调用,对于e100来说,poll函数的接收过程已经分析了,而对于其它所有没有采用轮询技术的网络设备来说,它们将统统调用
    process_backlog函数(我觉得把它改名为pseudo-poll是否更合适一些^o^)。


    OK,我想分析到这里,关于中断处理与轮询技术的差异,已经基本分析开了……


    继续来看,netif_rx_schedule进一步调用__netif_rx_schedule

    [cpp] view plain copy

    1.  static inline void netif_rx_schedule(struct net_device *dev)  

    2.  {  

    3.          if (netif_rx_schedule_prep(dev))  

    4.                  __netif_rx_schedule(dev);  

    5.  }  

     

    [cpp] view plain copy

    1.  static inline void __netif_rx_schedule(struct net_device *dev)  

    2.  {  

    3.          unsigned long flags;  

    4.    

    5.          local_irq_save(flags);  

    6.          dev_hold(dev);  

    7.           

    8.          list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_#).poll_list);  

    9.          if (dev->quota < 0)  

    10.                dev->quota += dev->weight;  

    11.        else  

    12.                dev->quota = dev->weight;  

    13.         

    14.        __raise_softirq_irqoff(NET_RX_SOFTIRQ);  

    15.        local_irq_restore(flags);  

    16.}  


    软中断被触发,注册的net_rx_action函数将被调用

    [cpp] view plain copy

    1.  static void net_rx_action(struct softirq_action *h)  

    2.  {  

    3.          struct softnet_# *queue = &__get_cpu_var(softnet_#);  

    4.          unsigned long start_time = jiffies;  

    5.          int budget = netdev_max_backlog;  

    6.    

    7.           

    8.          local_irq_disable();  

    9.           

    10.         

    11.        while (!list_empty(&queue->poll_list)) {  

    12.                struct net_device *dev;  

    13.  

    14.                if (budget <= 0 || jiffies - start_time > 1)  

    15.                        goto softnet_break;  

    16.  

    17.                local_irq_enable();  

    18.                 

    19.                 

    20.                dev = list_entry(queue->poll_list.next,  

    21.                                 struct net_device, poll_list);  

    22.                netpoll_poll_lock(dev);  

    23.  

    24.                 

    25.                if (dev->quota <= 0 || dev->poll(dev, &budget)) {  

    26.                        netpoll_poll_unlock(dev);  

    27.                         

    28.                         

    29.                        local_irq_disable();  

    30.                        list_del(&dev->poll_list);  

    31.                        list_add_tail(&dev->poll_list, &queue->poll_list);  

    32.                        if (dev->quota < 0)  

    33.                                dev->quota += dev->weight;  

    34.                        else  

    35.                                dev->quota = dev->weight;  

    36.                } else {  

    37.                        netpoll_poll_unlock(dev);  

    38.                        dev_put(dev);  

    39.                        local_irq_disable();  

    40.                }  

    41.        }  

    42.out:  

    43.        local_irq_enable();  

    44.        return;  

    45.  

    46.softnet_break:  

    47.        __get_cpu_var(netdev_rx_stat).time_squeeze++;  

    48.        __raise_softirq_irqoff(NET_RX_SOFTIRQ);  

    49.        goto out;  

    50.}  


    对于dev->poll(dev,&budget)的调用,一个真实的poll函数的例子,我们已经分析过了,现在来看process_backlog

    [cpp] view plain copy

    1.  static int process_backlog(struct net_device *backlog_dev, int *budget)  

    2.  {  

    3.          int work = 0;  

    4.          int quota = min(backlog_dev->quota, *budget);  

    5.          struct softnet_# *queue = &__get_cpu_var(softnet_#);  

    6.          unsigned long start_time = jiffies;  

    7.    

    8.          backlog_dev->weight = weight_p;  

    9.           

    10.         

    11.        for (;;) {  

    12.                struct sk_buff *skb;  

    13.                struct net_device *dev;  

    14.  

    15.                local_irq_disable();  

    16.                skb = __skb_dequeue(&queue->input_pkt_queue);  

    17.                if (!skb)  

    18.                        goto job_done;  

    19.                local_irq_enable();  

    20.  

    21.                dev = skb->dev;  

    22.  

    23.                netif_receive_skb(skb);  

    24.  

    25.                dev_put(dev);  

    26.  

    27.                work++;  

    28.  

    29.                if (work >= quota || jiffies - start_time > 1)  

    30.                        break;  

    31.  

    32.        }  

    33.  

    34.        backlog_dev->quota -= work;  

    35.        *budget -= work;  

    36.        return -1;  

    37.  

    38.  

    39.job_done:  

    40.        backlog_dev->quota -= work;  

    41.        *budget -= work;  

    42.  

    43.        list_del(&backlog_dev->poll_list);  

    44.        smp_mb__before_clear_bit();  

    45.        netif_poll_enable(backlog_dev);  

    46.  

    47.        if (queue->throttle)  

    48.                queue->throttle = 0;  

    49.        local_irq_enable();  

    50.        return 0;  

    51.}  


    这个函数重要的工作,就是出队,然后调用netif_receive_skb()将数据包交给上层,这与上一节讨论的poll是一样的。这也是为什么,
    在网卡驱动的编写中,采用中断技术,要调用netif_rx,而采用轮询技术,要调用netif_receive_skb啦!

    到了这里,就处理完数据包与设备相关的部分了,数据包将进入上层协议栈……

  • 相关阅读:
    事件基础
    Event loop 事件的循环和为什么使用函数
    Promise 对象---异步调用
    新版博客园分类不能正常显示
    OpenCV学习
    c# 常用正则
    C#调用OpenCV——美图效果
    WPF中的Image控件Source的设置(包括直接读取图片文件的代码)
    图像处理的灰度化和二值化
    C#弹出U盘
  • 原文地址:https://www.cnblogs.com/listenerln/p/6393467.html
Copyright © 2011-2022 走看看