Chinaunix首页 | 论坛 | 博客
  • 博客访问: 805913
  • 博文数量: 127
  • 博客积分: 2669
  • 博客等级: 少校
  • 技术积分: 1680
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-23 11:39
文章分类

全部博文(127)

文章存档

2014年(5)

2013年(19)

2012年(25)

2011年(9)

2010年(25)

2009年(44)

分类: LINUX

2009-11-06 16:10:46

数据接收

网卡驱动的数据接收,实际上是一个生产者/消费者模型。核心是输入队列(全局的,或者网卡私有的)。网卡收到数据时,触发中断。在中断执行例程中,把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_actionnet_rx_action。需要发送和接收数据时,只要触发相应的软中断,如__raise_softirq_irqoff(NET_RX_SOFTIRQ)。内核就会在适当时机执行do_softirq来处理pending的软中断。

       关于softirq细节,可参考” Linux内核的Softirq机制。关键点是谁发起,谁执行。

 

softnet_data结构

       为了发挥多cpu的优势,引入了softnet_data结构。

struct softnet_data

{

    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;

}

每个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,并初始化skbprotocol 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_datainput_pkt_queue

__skb_queue_tail(&queue->input_pkt_queue, skb);

然后把backlog_dev设备挂入poll_listnetif_rx_schedule(&queue->backlog_dev);

netif_rx_schedule

该函数先调用netif_rx_schedule_prep设置net_device.state。注意,该变量是数据收发的重点。它组成了一个状态机。然后调用__netif_rx_schedulenet_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出队

内核会在适当时机检查pendingsoftirq,并调用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网卡维护的。关于e100ring的实现,参考后面的”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结构来描述。同过RFDlink域组成环形队列。该RFD其实是skb data的一部分,是直接加在数据帧上的。上面还记录了队列的相关信息。

 

阅读(3147) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~