数据接收
网卡驱动的数据接收,实际上是一个生产者/消费者模型。核心是输入队列(全局的,或者网卡私有的)。网卡收到数据时,触发中断。在中断执行例程中,把skb挂入输入队列,并出发软中断。稍后的某个时刻,当软中断执行时,再从该队列中把skb取下来,投递给上层协议。
一软中断
2.6版中的数据传输是通过软中断softirq来实现的。它是以前bh的一个替代品。原理上和bh差不多,都是设置一个全局的向量softirq_vec,并在内核初始化时,调用net_dev_init:
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
把传输和发送数据的软中断中断函数设置为net_tx_action和net_rx_action。需要发送和接收数据时,只要触发相应的软中断,如__raise_softirq_irqoff(NET_RX_SOFTIRQ)。内核就会在适当时机执行do_softirq来处理pending的软中断。
关于softirq细节,可参考” Linux内核的Softirq机制”。关键点是谁发起,谁执行。
二softnet_data结构
为了发挥多cpu的优势,引入了softnet_data结构。
struct softnet_data
{
intthrottle;
intcng_level;
intavg_blog;
struct sk_buff_headinput_pkt_queue;
struct list_headpoll_list;
struct net_device*output_queue;
struct sk_buff*completion_queue;
struct net_devicebacklog_dev;
}
每个cpu都有自己的softnet_data结构。
前三个变量用于拥塞控制。
input_pkt_queue当驱动不适用NAPI时,收到数据后,会把skb直接挂到该队列。该队列是所有网卡共享的。如果使用NAPI的话,就使用自己的队列。
poll_list有数据要接收的设备队列。如果使用non NAPI,则被挂上去的是blacklog_dev。
output_queue有数据要发送的设备队列。
backlog_dev non NAPI方式下,使用的默认设备。其实主要是使用它的poll方法。该结构是在内核启动为每个cpu初始化softnet_data结构时,调用net_dev_init进行初始化的:
set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
queue->backlog_dev.weight = weight_p;
queue->backlog_dev.poll = process_backlog;
其中process_backlog就相当于驱动中的poll方法。只是它针对是所有non NAPI网卡驱动。于是,传入并挂到poll_list中的是process_backlog结构。而NAPI传入和挂载的是该驱动对应的net_device结构。这个和下面提到的netif_rx实现有关系。
三Non NAPI数据接收
1.skb入队
对于使用non NAPI的驱动,如pcnet32(也可配置为NAPI),数据的接收起始于中断处理函数。
pcnet32_interrupt->pcnet32_rx
在quota允许的范围内,调用pcnet32_rx_entry处理每个收到的包。
pcnet32_interrupt->pcnet32_rx->pcnet32_rx_entry
该函数获取skb,并初始化skb的protocol skb->protocol = eth_type_trans(skb, dev);然后调用函数把skb挂到接收队列,以便在软中断中处理skb。注意,这儿是non NAPI和NAPI的分界点:
#ifdef CONFIG_PCNET32_NAPI
netif_receive_skb(skb);
#else
netif_rx(skb);
pcnet32_interrupt->pcnet32_rx->pcnet32_rx_entry-> netif_rx
该函数主要把skb放入softnet_data的input_pkt_queue
__skb_queue_tail(&queue->input_pkt_queue, skb);
然后把backlog_dev设备挂入poll_list;netif_rx_schedule(&queue->backlog_dev);
netif_rx_schedule
该函数先调用netif_rx_schedule_prep设置net_device.state。注意,该变量是数据收发的重点。它组成了一个状态机。然后调用__netif_rx_schedule把net_device(backlog_dev)加入poll_list。并触发一个软中断
list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
到此,在中断中,对于一个skb的处理完成。接下来,对数据包的处理,以及向协议上层传递,将在软中断处理例程中进行。
2.skb出队
内核会在适当时机检查pending的softirq,并调用do_softirq来执行相应得软中断。
do_softirq-> __do_softirq
h = softirq_vec;
do {
if (pending & 1) {
h->action(h);// net_rx_action
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
由于我们在网卡中断处理程序中出发了一个软中断,所以这儿会执行net_rx_action,来处理队列中的skb。
net_rx_action
该函数中,循环取出poll_list中的接收数据的net_device,然后执行它的poll方法。
dev = list_entry(queue->poll_list.next,
struct net_device, poll_list);
if (dev->quota <= 0 || dev->poll(dev, &budget)) {
netpoll_poll_unlock(have);
local_irq_disable();
list_move_tail(&dev->poll_list, &queue->poll_list);//如果配额用完,但还有数据,则把它加入poll_list对尾,等待下次处理。
if (dev->quota < 0)
dev->quota += dev->weight;
else
dev->quota = dev->weight;
}
注意,不论是non NAPI还是NAPI处理软中断都会调用该方法。只是由于由于在skb入队时,挂入的net_device不同,调用了不同的poll方法。NAPI调用的是自己的net_device.poll,而non NAPI调用的是backlog_dev.poll,即process_backlog,该方法中,只是dequeue softnet_data. input_pkt_queue。
在dev->poll中,无论是non NAPI还是NAPI,都会调用netif_receive_skb(skb)往上层协议发送数据。
四NAPI数据接收(e100)
1.skb入队
e100_intr
在e100_intr中断处理例程中,直接把当前net_device挂入poll_list,等待在软中断处理历程中,调用该net_device中的poll方法来处理接收队列。注意,由于现在不是共享input_pkt_queue,所以,驱动程序必须为net_device提供一个priv结构,来实现缓冲队列。如e100,该环形缓冲就是由nic->rxs指向,它相当于input_pkt_queue,不过这个queue,是e100网卡维护的。关于e100的ring的实现,参考后面的”e100 ring buffer”
2.skb出队
e100_poll
该函数中,把rx队列中的skb取下来投递给上层协议。
e100_poll->e100_rx_clean
对每个rx调用e100_rx_indicate。如果e100_rx_indicate返回错误,则停止循环处理rx。错误情况有两种,其中如果为EAGIN,则说明该net_device配额已经用完,需要等待下次处理。注意,此处不像non NAPI,无须把net_device重新入队。
e100_poll->e100_rx_clean->e100_rx_indicate
根据收到的数据,设置skb_buff_header。并把skb送往上层协议netif_receive_skb。
主要执行流程:
e100_rx_clean-> e100_rx_indicate-> netif_receive_skb
和process_backlog最大不同点是,它处理的是自己的输入队列rx。
附e100 ring buffer
E100采用了共享内存的架构,在共享内存中维护了一个ring buffer。
E100中有个系统控制块scb,它的general pointer指向环形缓冲的第一个buffer。该buffer是以RFD结构来描述。同过RFD的link域组成环形队列。该RFD其实是skb data的一部分,是直接加在数据帧上的。上面还记录了队列的相关信息。