我这里描述的只是2层的处理。
首先,我们来看softnet_data这个结构,每个cpu都有这样的一个队列,它主要是用来存储incoming frame。由于他是每个cpu都有一个队列,因此在不同的cpu之间我们就不要任何锁来控制并发的处理这个帧队列。我们在操作系统层要取得帧数据,都是通过这个数据来读取。
-
-
-
-
-
struct softnet_data
-
{
-
-
struct Qdisc *output_queue;
-
-
struct sk_buff_head input_pkt_queue;
-
-
struct list_head poll_list;
-
-
struct sk_buff *completion_queue;
-
-
struct napi_struct backlog;
-
#ifdef CONFIG_NET_DMA
-
struct dma_chan *net_dma;
-
#endif
-
};
这张图很好的表示了coy的softnet_data结构和网络设备的关系:
接下来我们来看它的初始化:
-
static int __init net_dev_init(void)
-
{
-
..............................
-
-
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;
-
}
-
...............................................
-
return rc;
-
}
当一个新的帧的到达之后,内核处理的函数有两种(其实也就是2层的处理,当处理完后,就将帧扔到3层):
1 老的netif_rx函数
2 新的napi接口。
首先来介绍napi。
简单来说就是,当内核还在处理一个帧的时候,有另外的帧到来,这时napi不需要再执行中断,而是保持轮询设备的输入队列,从而取得新到的帧,当队列为空时,退出轮询。重新打开中断。
napi的数据结构:
-
struct napi_struct {
-
-
-
-
-
-
-
-
struct list_head poll_list;
-
-
unsigned long state;
-
-
int weight;
-
-
int (*poll)(struct napi_struct *, int);
-
#ifdef CONFIG_NETPOLL
-
spinlock_t poll_lock;
-
int poll_owner;
-
struct net_device *dev;
-
struct list_head dev_list;
-
#endif
-
};
下面的这张图可以看出napi和net_rx_action(软中断处理函数)的关系:
接下来我们来看一下内核如何把老的驱动模型和napi统一到一起,关键的数据结构就是我们上面讲的softnet_data的backlog结构。
先来看下napi和非napi驱动的区别:
这里很清楚的看到在飞napi的驱动中,要调用netif_rx函数,然后再调用netif_rx_schedule来把所需处理的帧交给软中断处理链表。
我们来看它的实现:
-
int netif_rx(struct sk_buff *skb)
-
{
-
struct softnet_data *queue;
-
unsigned long flags;
-
-
-
if (netpoll_rx(skb))
-
return NET_RX_DROP;
-
-
if (!skb->tstamp.tv64)
-
net_timestamp(skb);
-
-
-
-
-
-
-
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;
-
}
然后我们来看网络代码最核心的一个函数net_rx_action 也就是软中断(NET_RX_SOFTIRQ)的处理函数:
-
static void net_rx_action(struct softirq_action *h)
-
{
-
-
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
-
-
unsigned long start_time = jiffies;
-
-
int budget = netdev_budget;
-
void *have;
-
-
local_irq_disable();
-
-
-
while (!list_empty(list)) {
-
struct napi_struct *n;
-
int work, weight;
-
-
if (unlikely(budget <= 0 || jiffies != start_time))
-
goto softnet_break;
-
-
local_irq_enable();
-
-
-
n = list_entry(list->next, struct napi_struct, poll_list);
-
-
have = netpoll_poll_lock(n);
-
-
weight = n->weight;
-
-
work = 0;
-
-
if (test_bit(NAPI_STATE_SCHED, &n->state))
-
work = n->poll(n, weight);
-
-
WARN_ON_ONCE(work > weight);
-
-
budget -= work;
-
-
local_irq_disable();
-
-
-
if (unlikely(work == weight)) {
-
if (unlikely(napi_disable_pending(n)))
-
__napi_complete(n);
-
else
-
-
list_move_tail(&n->poll_list, list);
-
}
-
-
netpoll_poll_unlock(have);
-
}
-
out:
-
local_irq_enable();
-
......................................
-
-
return;
-
-
softnet_break:
-
__get_cpu_var(netdev_rx_stat).time_squeeze++;
-
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
-
goto out;
-
}
下来我们来看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;
-
}