Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8049
  • 博文数量: 1
  • 博客积分: 90
  • 博客等级: 民兵
  • 技术积分: 31
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-02 11:35
文章分类
文章存档

2009年(1)

我的朋友

分类: 网络与安全

2009-04-02 11:43:38

在内核设备操作阅读当中已经介绍过net_rx_action()这个重要的网络接收软中断处理函数了,不过这里为了清楚的分析轮询机制,需要再次分析这段代码:
static void net_rx_action(struct softirq_action *h)
{
       int this_cpu = smp_processor_id();
       /*获取当前CPU的接收队列。*/
       struct softnet_data *queue = &softnet_data[this_cpu];
       unsigned long start_time = jiffies;
/*呵呵,这里先做个预算,限定我们只能处理这么多数据(300个)。*/
       int budget = netdev_max_backlog;
 
       br_read_lock(BR_NETPROTO_LOCK);
       local_irq_disable();
       /*
      进入一个循环,因为软中断处理函数与硬件中断并不是同步的,因此,我们此时并不知道数据包属于哪个设备,因此只能采取逐个查询的方式,遍历整个接收设备列表。
       */
       while (!list_empty(&queue->poll_list)) {
              struct net_device *dev;
              /*如果花费超过预算,或者处理时间超过1秒,立刻从软中断处理函数跳出,我想这可能是考虑效率和实时性,一次不能做过多的工作或者浪费过多的时间。*/
              if (budget <= 0 || jiffies - start_time > 1)
                     goto softnet_break;
 
              local_irq_enable();
/*从当前列表中取出一个接收设备。并根据其配额判断是否能够继续接收数据,如果配额不足(<=0),则立刻将该设备从设备列表中删除。并且再次插入队列当中,同时为该设备分配一定的配额,允许它继续处理数据包。
如果此时配额足够,则调用设备的poll方法,对于e1000网卡来说,如果采用中断方式处理数据,则调用系统默认poll方法process_backlog(),而对于采用来说,则是调用e1000_clean()函数了。记住这里第一次传递的预算是300 ^_^。*/
              dev = list_entry(queue->poll_list.next, struct net_device, poll_list);
 
              if (dev->quota <= 0 || dev->poll(dev, &budget)) {
                     local_irq_disable();
                     list_del(&dev->poll_list);
                     list_add_tail(&dev->poll_list, &queue->poll_list);
                     if (dev->quota < 0)
                            dev->quota += dev->weight;
                     else
                            dev->quota = dev->weight;
              } else {
                     dev_put(dev);
                     local_irq_disable();
              }
       }
 
       local_irq_enable();
       br_read_unlock(BR_NETPROTO_LOCK);
       return;
 
softnet_break:
       netdev_rx_stat[this_cpu].time_squeeze++;
       /*再次产生软中断,准备下一次数据包处理。*/
       __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
 
       local_irq_enable();
       br_read_unlock(BR_NETPROTO_LOCK);
}
下面介绍一下e1000网卡的轮询poll处理函数e1000_clean(),这个函数只有定义了NAPI宏的情况下才有效:
#ifdef CONFIG_E1000_NAPI
static int e1000_clean(struct net_device *netdev, int *budget)
{
       struct e1000_adapter *adapter = netdev->priv;
       /*计算一下我们要做的工作量,取系统给定预算(300)和我们网卡设备的配额之间的最小值,这样做同样是为了效率和实时性考虑,不能让一个设备在接收设备上占用太多的资源和时间。*/
       int work_to_do = min(*budget, netdev->quota);
       int work_done = 0;
       /*处理网卡向外发送的数据,这里我们暂时不讨论。*/
       e1000_clean_tx_irq(adapter);
       /*处理网卡中断收到的数据包,下面详细讨论这个函数的处理方法。*/
       e1000_clean_rx_irq(adapter, &work_done, work_to_do);
       /*从预算中减掉我们已经完成的任务,预算在被我们支出,^_^。同时设备的配额也不断的削减。*/
       *budget -= work_done;
       netdev->quota -= work_done;
       /*如果函数返回时,完成的工作没有达到预期的数量,表明接收的数据包并不多,很快就全部处理完成了,我们就彻底完成了这次轮询任务,调用netif_rx_complete(),把当前指定的设备从 POLL 队列中清除(注意如果在 POLL 队列处于工作状态的时候是不能把指定设备清除的,否则将会出错),然后使能网卡中断。*/
       if(work_done < work_to_do) {
              netif_rx_complete(netdev);
              e1000_irq_enable(adapter);
       }
       /*如果完成的工作大于预期要完成的工作,则表明存在问题,返回1,否则正常返回0。*/
       return (work_done >= work_to_do);
}
 
设备轮询接收机制中最重要的函数就是下面这个函数,当然它同时也可以为中断接收机制所用,只不过处理过程有一定的差别。
static boolean_t
#ifdef CONFIG_E1000_NAPI
e1000_clean_rx_irq(struct e1000_adapter *adapter, int *work_done,
                   int work_to_do)
#else
e1000_clean_rx_irq(struct e1000_adapter *adapter)
#endif
{
       /*这里很清楚,获取设备的环形缓冲区指针。*/
       struct e1000_desc_ring *rx_ring = &adapter->rx_ring;
       struct net_device *netdev = adapter->netdev;
       struct pci_dev *pdev = adapter->pdev;
       struct e1000_rx_desc *rx_desc;
       struct e1000_buffer *buffer_info;
       struct sk_buff *skb;
       unsigned long flags;
       uint32_t length;
       uint8_t last_byte;
       unsigned int i;
       boolean_t cleaned = FALSE;
       /*把i置为下一个要清除的描述符索引,因为在环形缓冲区队列当中,我们即使已经处理完一个缓冲区描述符,也不是将其删除,而是标记为已经处理,这样如果有新的数据需要使用缓冲区,只是将已经处理的缓冲区覆盖而已。*/
       i = rx_ring->next_to_clean;
       rx_desc = E1000_RX_DESC(*rx_ring, i);
       /*如果i对应的描述符状态是已经删除,则将这个缓冲区取出来给新的数据使用*/
       while(rx_desc->status & E1000_RXD_STAT_DD) {
              buffer_info = &rx_ring->buffer_info[i];
 
#ifdef CONFIG_E1000_NAPI
       /*在配置了NAPI的情况下,判断是否已经完成的工作?,因为是轮询机制,所以我们必须自己计算我们已经处理了多少数据。*/
              if(*work_done >= work_to_do)
                     break;
 
              (*work_done)++;
#endif
 
              cleaned = TRUE;
              /*这个是DMA函数,目的是解除与DMA缓冲区的映射关系,这样我们就可以访问这个缓冲区,获取通过DMA传输过来的数据包(skb)。驱动程序在分配环形缓冲区的时候就将缓冲区与DMA进行了映射。*/
              pci_unmap_single(pdev,
                               buffer_info->dma,
                               buffer_info->length,
                               PCI_DMA_FROMDEVICE);
 
              skb = buffer_info->skb;
              length = le16_to_cpu(rx_desc->length);
              /*对接收的数据包检查一下正确性。确认是一个正确的数据包以后,将skb的数据指针进行偏移。*/
              skb_put(skb, length - ETHERNET_FCS_SIZE);
 
              /* Receive Checksum Offload */
              e1000_rx_checksum(adapter, rx_desc, skb);
              /*获取skb的上层类型。这里指的是IP层的协议类型。*/
              skb->protocol = eth_type_trans(skb, netdev);
#ifdef CONFIG_E1000_NAPI     
/*调用函数直接将skb向上层协议处理函数递交,而不是插入什么队列等待继续处理,因此这里可能存在一个问题,如果数据包比较大,处理时间相对较长,则可能造成系统效率的下降。*/
                     netif_receive_skb(skb);
      
#else /* CONFIG_E1000_NAPI */
              /*如果采用中断模式,则调用netif_rx()将数据包插入队列中,在随后的软中断处理函数中调用netif_receive_skb(skb)向上层协议处理函数递交。这里就体现出了中断处理机制和轮询机制之间的差别。*/     
                     netif_rx(skb);
#endif /* CONFIG_E1000_NAPI */
              /*用全局时间变量修正当前设备的最后数据包接收时间。*/
              netdev->last_rx = jiffies;
 
              rx_desc->status = 0;
              buffer_info->skb = NULL;
              /*这里是处理环形缓冲区达到队列末尾的情况,因为是环形的,所以到达末尾的下一个就是队列头,这样整个环形队列就不断的循环处理。然后获取下一个描述符的状态,看看是不是处于删除状态。如果处于这种状态就会将新到达的数据覆盖旧的的缓冲区,如果不处于这种状态跳出循环。并且将当前缓冲区索引号置为下一次查询的目标。*/
              if(++i == rx_ring->count) i = 0;
 
              rx_desc = E1000_RX_DESC(*rx_ring, i);
       }
 
       rx_ring->next_to_clean = i;
       /*为下一次接收skb做好准备,分配sk_buff内存。出于效率的考虑,如果下一个要使用的缓冲区的sk_buff还没有分配,就分配,如果已经分配,则可以重用。*/
       e1000_alloc_rx_buffers(adapter);
 
       return cleaned;
}
下面分析的这个函数有助于我们了解环形接收缓冲区的结构和工作:
static void e1000_alloc_rx_buffers(struct e1000_adapter *adapter)
{
       struct e1000_desc_ring *rx_ring = &adapter->rx_ring;
       struct net_device *netdev = adapter->netdev;
       struct pci_dev *pdev = adapter->pdev;
       struct e1000_rx_desc *rx_desc;
       struct e1000_buffer *buffer_info;
       struct sk_buff *skb;
       int reserve_len = 2;
       unsigned int i;
       /*接收队列中下一个用到的缓冲区索引,初始化是0。并且获取该索引对应的缓冲区信息结构指针buffer_info。*/
       i = rx_ring->next_to_use;
       buffer_info = &rx_ring->buffer_info[i];
       /*如果该缓冲区还没有为sk_buff分配内存,则调用dev_alloc_skb函数分配内存,默认的e1000网卡的接收缓冲区长度是2048字节加上保留长度。
注意:在e1000_open()->e1000_up()中已经调用了这个函数为环形缓冲区队列中的每一个缓冲区分配了sk_buff内存,但是如果接收到数据以后,调用netif_receive_skb (skb)向上层提交数据以后,这段内存将始终被这个skb占用(直到上层处理完以后才会调用__kfree_skb释放,但已经跟这里没有关系了),换句话说,就是当前缓冲区必须重新申请分配sk_buff内存,为了下一个数据做准备。*/
       while(!buffer_info->skb) {
              rx_desc = E1000_RX_DESC(*rx_ring, i);
 
              skb = dev_alloc_skb(adapter->rx_buffer_len + reserve_len);
 
              if(!skb) {
                     /* Better luck next round */
                     break;
              }
              skb_reserve(skb, reserve_len);
 
              skb->dev = netdev;
              /*映射DMA缓冲区,DMA通道直接将收到的数据写到我们提供的这个缓冲区内,每次必须将缓冲区与DMA通道解除映射关系,才能读取缓冲区内容。*/
              buffer_info->skb = skb;
              buffer_info->length = adapter->rx_buffer_len;
              buffer_info->dma =
                     pci_map_single(pdev,
                                    skb->data,
                                    adapter->rx_buffer_len,
                                    PCI_DMA_FROMDEVICE);
 
              rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
 
              if(++i == rx_ring->count) i = 0;
              buffer_info = &rx_ring->buffer_info[i];
       }
       rx_ring->next_to_use = i;
}
阅读(977) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~