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_#:
struct softnet_#
{
       
        int                        throttle;
       
        int                        cng_level;
        int                        avg_blog;
       
        struct sk_buff_head        input_pkt_queue;
       
        struct list_head        poll_list;
        struct net_device        *output_queue;
        struct sk_buff                *completion_queue;

        struct net_device        backlog_dev;       
};



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

net/core/dev.c

CODE:
DECLARE_PER_CPU(struct softnet_#,softnet_#);


static int __init net_dev_init(void)
{
        int i, rc = -ENOMEM;

        BUG_ON(!dev_boot_phase);

        net_random_init();

        if (dev_proc_init())               
                goto out;

        if (netdev_sysfs_init())       
                goto out;

       
        INIT_LIST_HEAD(&ptype_all);
        for (i = 0; i < 16; i++)
                INIT_LIST_HEAD(&ptype_base[i]);

        for (i = 0; i < ARRAY_SIZE(dev_name_head); i++)
                INIT_HLIST_HEAD(&dev_name_head[i]);

        for (i = 0; i < ARRAY_SIZE(dev_index_head); i++)
                INIT_HLIST_HEAD(&dev_index_head[i]);

       

       
        for (i = 0; i < NR_CPUS; i++) {
                struct softnet_# *queue;
               
               
                queue = &per_cpu(softnet_#, i);
               
                skb_queue_head_init(&queue->input_pkt_queue);
                queue->throttle = 0;
                queue->cng_level = 0;
                queue->avg_blog = 10;
                queue->completion_queue = NULL;
                INIT_LIST_HEAD(&queue->poll_list);
                set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
                queue->backlog_dev.weight = weight_p;
               
                queue->backlog_dev.poll = process_backlog;
                atomic_set(&queue->backlog_dev.refcnt, 1);
        }

#ifdef OFFLINE_SAMPLE
        samp_timer.expires = jiffies + (10 * HZ);
        add_timer(&samp_timer);
#endif

        dev_boot_phase = 0;
       
       
        open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
        open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

        hotcpu_notifier(dev_cpu_callback, 0);
        dst_init();
        dev_mcast_init();
        rc = 0;
out:
        return rc;
}

这样,初始化完成后,在驱动程序中,在中断处理函数中,会调用netif_rx将数据交上来,这与采用轮询技术,有本质的不同:
int netif_rx(struct sk_buff *skb)
{
        int this_cpu;
        struct softnet_# *queue;
        unsigned long flags;

       
        if (netpoll_rx(skb))
                return NET_RX_DROP;

       
        if (!skb->stamp.tv_sec)
                net_timestamp(&skb->stamp);

       
        local_irq_save(flags);
       
        this_cpu = smp_processor_id();
        queue = &__get_cpu_var(softnet_#);

       
        __get_cpu_var(netdev_rx_stat).total++;
       
       
        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);                       
                        __skb_queue_tail(&queue->input_pkt_queue, skb);               
#ifndef OFFLINE_SAMPLE
                        get_sample_stats(this_cpu);
#endif
                        local_irq_restore(flags);
                        return queue->cng_level;
                }

               
                if (queue->throttle)
                        queue->throttle = 0;

               
                netif_rx_schedule(&queue->backlog_dev);
                goto enqueue;
        }

       
        if (!queue->throttle) {
                queue->throttle = 1;
                __get_cpu_var(netdev_rx_stat).throttled++;
        }


drop:
        __get_cpu_var(netdev_rx_stat).dropped++;
        local_irq_restore(flags);

        kfree_skb(skb);
        return NET_RX_DROP;
}

从这段代码的分析中,我们可以看到,当数据被接收后,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:

static inline void netif_rx_schedule(struct net_device *dev)
{
        if (netif_rx_schedule_prep(dev))
                __netif_rx_schedule(dev);
}



static inline void __netif_rx_schedule(struct net_device *dev)
{
        unsigned long flags;

        local_irq_save(flags);
        dev_hold(dev);
       
        list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_#).poll_list);
        if (dev->quota < 0)
                dev->quota += dev->weight;
        else
                dev->quota = dev->weight;
       
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        local_irq_restore(flags);
}

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

static void net_rx_action(struct softirq_action *h)
{
        struct softnet_# *queue = &__get_cpu_var(softnet_#);
        unsigned long start_time = jiffies;
        int budget = netdev_max_backlog;

       
        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 = list_entry(queue->poll_list.next,
                                 struct net_device, poll_list);
                netpoll_poll_lock(dev);

               
                if (dev->quota <= 0 || dev->poll(dev, &budget)) {
                        netpoll_poll_unlock(dev);
                       
                       
                        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 {
                        netpoll_poll_unlock(dev);
                        dev_put(dev);
                        local_irq_disable();
                }
        }
out:
        local_irq_enable();
        return;

softnet_break:
        __get_cpu_var(netdev_rx_stat).time_squeeze++;
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        goto out;
}

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

static int process_backlog(struct net_device *backlog_dev, int *budget)
{
        int work = 0;
        int quota = min(backlog_dev->quota, *budget);
        struct softnet_# *queue = &__get_cpu_var(softnet_#);
        unsigned long start_time = jiffies;

        backlog_dev->weight = weight_p;
       
       
        for (;;) {
                struct sk_buff *skb;
                struct net_device *dev;

                local_irq_disable();
                skb = __skb_dequeue(&queue->input_pkt_queue);
                if (!skb)
                        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;

        }

        backlog_dev->quota -= work;
        *budget -= work;
        return -1;


job_done:
        backlog_dev->quota -= work;
        *budget -= work;

        list_del(&backlog_dev->poll_list);
        smp_mb__before_clear_bit();
        netif_poll_enable(backlog_dev);

        if (queue->throttle)
                queue->throttle = 0;
        local_irq_enable();
        return 0;
}

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值