ronliu的专栏

NAPI和传统收包方法的区别是:NAPI可以进一次中断收很多次的包,但是传统方法进一次中断后将包放到local cpu的softnet_data的input_queue,之后就退出中断。

一、传统方法

进入处理程序后,首先从物理设备中将数据分组拷贝到内存中,然后分组是否合法,之后分配一个skb组包,然后调用netif_rx(skb)将包放入到softnet_data的input_queue中。在下图所示的传统API收包过程中,收包的IRQ是不需要被禁用的。因为将包放入到cpu的等待队列不会耗时太长。这也从另外一个角度说明,传统API只能适用与低速设备。因此每进一次中断,只收一个包。

以3c501.c为例:irqreturn_t el_interrupt(int irq, void *dev_id) -> el_receive(dev) -> dev_alloc_skb(pkt_len+2); netif_rx(skb);

netif_rx函数不是特定与网络设备的,真实物理的驱动可以调用它,一些虚拟接口,如ppp接口在处理完本层的业务后,剥离ppp头部,调用该函数处理包,之后在netif_receive_skb中会进入到IP层处理该数据报文。

int netif_rx(struct sk_buff *skb)

{

struct softnet_data *queue;

unsigned long flags;

local_irq_save(flags);

queue = &__get_cpu_var(softnet_data);

__get_cpu_var(netdev_rx_stat).total++;

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

if (queue->input_pkt_queue.qlen) {

enqueue:

__skb_queue_tail(&queue->input_pkt_queue, skb);

local_irq_restore(flags);

return NET_RX_SUCCESS;

}

napi_schedule(&queue->backlog);

goto enqueue;

}

__get_cpu_var(netdev_rx_stat).dropped++;

local_irq_restore(flags);

kfree_skb(skb);

return NET_RX_DROP;

}

netif_rx有两个返回值:NET_RX_SUCCESS和NET_RX_DROP。input_pkt_queue的类型是sk_buff_head。注意到该数据结构并没有使用内核常用的list_head作为保存skb的链表。softnet_data使用该数据结构存储与收包队列相关的元素,如skb、队列大小、lock等。如果使用list_head,那么必然要将qlen和lock放到softnet_data中,不够整洁。所以如果qlen比netdev_max_backlog还要大的时候,就会直接丢弃该包。netdev_max_backlog的值默认为1000,可以在/proc/sys/net/core/ netdev_max_backlog中设置。

struct sk_buff_head {

struct sk_buff *next; /* These two members must be first */

struct sk_buff *prev;

__u32 qlen;

spinlock_t lock;

};

如果sd queue的qlen为0,那么可能queue->backlog已经从当前cpu sd的poll_list移除,因此需要调用napi_schedule(&queue->backlog)重新将queue加入到相应的poll_list上。然后再__skb_queue_tail将skb入队列。

input_pkt_queue中的skb何时被取出呢?当我们在netif_rx中调用napi_schedule(&queue->backlog)的时候,它首先检查这个napi_struct实例是否可用。然后调用__napi_schedule来做实际的调度工作。

void __napi_schedule(struct napi_struct *n)

{

unsigned long flags;

local_irq_save(flags);

list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);

__raise_softirq_irqoff(NET_RX_SOFTIRQ);

local_irq_restore(flags);

}

本地CPU的softnet_data中,有一个poll_list字段,用于保存所有待调度的napi_struct实例。在上面代码中,将&queue->backlog加入这个调度队列上。之后引发软中断NET_RX_SOFTIRQ。该软中断的处理函数为net_rx_action。可以想见,在该函数中必然会遍历当前softnet_data上的所有napi_struct,包括传统收包API netif_rx对应的napi_struct实例。

for_each_possible_cpu(i) {

struct softnet_data *queue;

queue = &per_cpu(softnet_data, i);

skb_queue_head_init(&queue->input_pkt_queue);

queue->completion_queue = NULL;

INIT_LIST_HEAD(&queue->poll_list);

queue->backlog.poll = process_backlog;

queue->backlog.weight = weight_p;

}

在net_dev_init中,初始化所有CPU上的softnet_data实例。包括初始化napi_struct实例的input_pkt_queue、poll_list,以便这个napi_struct能够在napi_schedule中挂载到softnet_data->poll_list下被调度。另外,设置这个napi_struct实例的weight为weight_p,一般地这个值64,也可以通过/proc/sys/net/core/dev_weight修改它的值。

下面是process_backlog的实现。通过这段代码,也可以了解NAPI的poll函数的实现方式。其他设备napi实例的poll函数与下面这个大同小异。

static int process_backlog(struct napi_struct *napi, int quota)

{

int work = 0;

struct softnet_data *queue = &__get_cpu_var(softnet_data);

unsigned long start_time = jiffies;

napi->weight = weight_p;

do {

struct sk_buff *skb;

local_irq_disable();

skb = __skb_dequeue(&queue->input_pkt_queue);

if (!skb) {

__napi_complete(napi);

local_irq_enable();

break;

}

local_irq_enable();

netif_receive_skb(skb);

} while (++work < quota && jiffies == start_time);

return work;

}

人格的完善是本,财富的确立是末。

ronliu的专栏

相关文章:

你感兴趣的文章:

标签云: