设备模块中数据包接收的两个队列

驱动层程序通过netif_rx或者netif_rx_ni将接收的数据包传入到设备层,设备模块分成两个阶段处理数据包。第一阶段将数据包添加到接收队列(input_pkt_queue)末尾,接收处理完成;第二阶段将接收队列的数据包移动到处理队列(process_queue)中。两个阶段的操作都是链表操作,不涉及到skb数据包的拷贝。

在第一阶段中,仅是添加到接收队列即返回,以便驱动程序可以接收下一个数据包。第二阶段在中断下半部中执行,首先处理process_queue队列中已有的数据包,之后将第一阶段添加到input_pkt_queue队列中的数据包,拼接到process_queue队列中,等待下次处理。

队列初始化

网络设备模块在初始化时,为每一个CPU初始化接收和处理队列。

DEFINE_PER_CPU_ALIGNED(struct softnet_data, softnet_data);

数据包接收队列

函数enqueue_to_backlog负责将数据包添加到接收队列中。首先取出对应cpu的input_pkt_queue队列长度,如果其值已经超过netdev_max_backlog(默认为1000,可通过proc文件/proc/sys/net/core/netdev_max_backlog修改)的最大值,此数据包skb将会被丢弃。否则添加到接收队列的末尾。

由于每个CPU具有单独的input_pkt_queue队列,在对其操作的时候,我们只需要关闭当前CPU的中断即可。

static int enqueue_to_backlog(struct sk_buff *skb, int cpu, unsigned int *qtail)
{
    sd = &per_cpu(softnet_data, cpu);

    local_irq_save(flags);
    qlen = skb_queue_len(&sd->input_pkt_queue);
    if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
            __skb_queue_tail(&sd->input_pkt_queue, skb);
            local_irq_restore(flags);
            return NET_RX_SUCCESS;
    }
}

数据包处理队列

内核在下半部中调用process_backlog函数处理数据包。由其实现可见,对于process_queue队列中数据包,调用网络核心接收函数__netif_receive_skb处理,知道配额quota用完为止。之后,判读如果接收队列input_pkt_queue不为空,将接收队列拼接到处理队列上。接收队列清空。继续处理添加到process_queue队列的数据包。

在处理接收队列input_pkt_queue时,关闭本地CPU的中断,确保队列操作的完整性。

static int process_backlog(struct napi_struct *napi, int quota)
{
    while (again) {
        while ((skb = __skb_dequeue(&sd->process_queue))) {
            __netif_receive_skb(skb);
            if (++work >= quota)  return work;
        }

        local_irq_disable();
        if (skb_queue_empty(&sd->input_pkt_queue)) {
            again = false;
        } else {
            skb_queue_splice_tail_init(&sd->input_pkt_queue,
                           &sd->process_queue);
        }
        local_irq_enable();
    }
}

由函数process_backlog可见,process_queue处理队列的存在,使在处理数据包时不必关闭本地CPU的中断。而对input_pkt_queue队列的处理又做到了精简,提高了效率,关闭中断的时间缩短了。


内核版本

Linux-4.15
 

猜你喜欢

转载自blog.csdn.net/sinat_20184565/article/details/81541507