网络子系统在链路层的收发过程剖析(二)

4),数据包的接收

 

* Incoming packets are placed on per-cpu queues so that

 * no locking is needed.

 */

struct softnet_data

{

       struct net_device    *output_queue;

       struct sk_buff_head       input_pkt_queue;

       struct list_head       poll_list;

       struct sk_buff        *completion_queue;

 

       struct net_device    backlog_dev;  /* Sorry. 8) */

#ifdef CONFIG_NET_DMA

       struct dma_chan           *net_dma;

#endif

};

 

这个数据结构同时用于接收与发送数据包,它为per_CPU结构,这样每个CPU有自己独立的信息,这样在SMP之间就避免了加锁操作,从而大大提高了信息处理的并行性。

 

struct net_device    *output_queue;     

struct sk_buff        *completion_queue;

这两个域用于发送数据,将在下一节中描述。

 

struct sk_buff_head      input_pkt_queue;

struct list_head       poll_list;

struct net_device    backlog_dev;

这三个域用于接收数据,其中input_pkt_queuebacklog_dev仅用于non-NAPINICinput_pkt_queue是接收到的数据队列头,它用于netif_rx()中,并最终由虚拟的poll函数process_backlog()处理这个SKB队列。

poll_list则是有数据包等待处理的NIC设备队列。对于non-NAPI驱动来说,它始终是backlog_dev

 

接收过程:

   当一个数据包到来时,NIC会产生一个中断,这时,它会执行中断处理全程。

 

(1), NON-NAPI方式:

3c59x中的vortex_interrupt(),它会判断寄存器的值作出相应的动作:

 

              if (status & RxComplete)

                     vortex_rx(dev);

如上,当中断指示,有数据包在等待接收,这时,中断例程会调用接收函数vortex_rx(dev)接收新到来的包(如下,只保留核心部分):

 

int pkt_len = rx_status & 0x1fff;

                     struct sk_buff *skb;

 

                     skb = dev_alloc_skb(pkt_len + 5);

             

                     if (skb != NULL) {

                            skb->dev = dev;

                            skb_reserve(skb, 2);      /* Align IP on 16 byte boundaries */

                            /* 'skb_put()' points to the start of sk_buff data area. */

                            if (vp->bus_master &&

                                   ! (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)) {

                                   dma_addr_t dma = pci_map_single(VORTEX_PCI(vp), skb_put(skb, pkt_len),

                                                                  pkt_len, PCI_DMA_FROMDEVICE);

                                   iowrite32(dma, ioaddr + Wn7_MasterAddr);

                                   iowrite16((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen);

                                   iowrite16(StartDMAUp, ioaddr + EL3_CMD);

                                   while (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)

                                          ;

                                   pci_unmap_single(VORTEX_PCI(vp), dma, pkt_len, PCI_DMA_FROMDEVICE);

                    

                            }

                            iowrite16(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */

                            skb->protocol = eth_type_trans(skb, dev);

                            netif_rx(skb);

                    

它首先为新到来的数据包分配一个skb结构及pkt_len+5大小的数据长度,然后便将接收到的数据从网卡复制到(DMA)这个SKB的数据部分中。最后,调用netif_rx(skb)进一步处理数据:

 

int netif_rx(struct sk_buff *skb)

{

       struct softnet_data *queue;

       unsigned long flags;

 

       /*

        * The code is rearranged so that the path is the most

        * short when CPU is congested, but is still operating.

        */

       local_irq_save(flags);

       queue = &__get_cpu_var(softnet_data);

 

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

              if (queue->input_pkt_queue.qlen) {

enqueue:

                     dev_hold(skb->dev);

                     __skb_queue_tail(&queue->input_pkt_queue, skb);

                     local_irq_restore(flags);

                     return NET_RX_SUCCESS;

              }

 

              netif_rx_schedule(&queue->backlog_dev);

              goto enqueue;

       }

}

 

这段代码关键是,将这个SKB加入到相应的input_pkt_queue队列中,并调用netif_rx_schedule(),而对于NAPI方式,它没有使用input_pkt_queue队列,而是使用私有的队列,所以它没有这一个步骤。至此,中断的上半部已经完成,以下的工作则交由中断的下半部来实现。

 

void __netif_rx_schedule(struct net_device *dev)

{

       unsigned long flags;

 

       local_irq_save(flags);

       dev_hold(dev);

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

       if (dev->quota < 0)

              dev->quota += dev->weight;

       else

              dev->quota = dev->weight;

       __raise_softirq_irqoff(NET_RX_SOFTIRQ);

       local_irq_restore(flags);

}

 

netif_rx_schedule()就是将有等待接收数据包的NIC链入softnet_datapoll_list队列,然后触发软中断,让后半部去完成数据的处理工作。

 

注意:这里是否调用netif_rx_schedule()是有条件的,即当queue->input_pkt_queue.qlen==0时才会调用,否则由于这个队列的长度不为0,这个中断下半部的执行已由先前的中断触发,它会断续处理余下来的数据包的接收,所以,这里就不必要再次触发它的执行了。

 

总之,NONNAPI的中断上半部接收过程可以简单的描述为,它首先为新到来的数据帧分配合适长度的SKB,再将接收到的数据从NIC中拷贝过来,然后将这个SKB链入当前CPUsoftnet_data中的链表中,最后进一步触发中断下半部发继续处理。

 

 

(2), NAPI方式:

 

static irqreturn_t e100_intr(int irq, void *dev_id)

{

 

       if(likely(netif_rx_schedule_prep(netdev))) {

              e100_disable_irq(nic);

              __netif_rx_schedule(netdev);

       }

 

       return IRQ_HANDLED;

}

      

可以看到,两种方式的不同之处在于,NAPI方式直接调用__netif_rx_schedule(),而非NAPI方式则要通过辅助函数netif_rx()设置好接收队列再调用netif_rx_schedule(),再者,在非NAPI方式中,提交的是netif_rx_schedule(&queue->backlog_dev),而NAPI中,提交的是__netif_rx_schedule(netdev),即是设备驱动的net_device结构,而不是queue中的backlog_dev

 

 

3),net_rx_action()

       netif_rx_schedule()触发中断下半部的执行,这个下半部将执行net_rx_action()

 

static void net_rx_action(struct softirq_action *h)

{

       struct softnet_data *queue = &__get_cpu_var(softnet_data);

       unsigned long start_time = jiffies;

 

       local_irq_disable();

 

       while (!list_empty(&queue->poll_list)) {

              struct net_device *dev;

 

              local_irq_enable();

 

              dev = list_entry(queue->poll_list.next,

                             struct net_device, poll_list);

 

              if (dev->quota <= 0 || dev->poll(dev, &budget)) {

                     … //出错处理

              } else {

                     netpoll_poll_unlock(have);

                     dev_put(dev);

                     local_irq_disable();

              }

}

 

由上可以看到,下半部的主要工作是遍历有数据帧等待接收的设备链表,对于每个设备,执行它相应的poll函数。

你可能感兴趣的:(网络,list,struct,input,action,locking)