Chinaunix首页 | 论坛 | 博客
  • 博客访问: 505461
  • 博文数量: 157
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1608
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 09:30
文章存档

2010年(155)

2008年(2)

我的朋友

分类: LINUX

2010-03-17 13:48:08

数据包的发送是接收的反过程,所以有很多类似之处,不同的是发送过程没有分什么napi/no-napi方式,而增加了一个流量控制函数用于实现l2层的qos
 
首先从设备驱动disable  enable设备的发送功能说起
why,disable发送?
在旧版本的内核中当探测到网卡的内存用完时(每发送一个数据包都会临时用点内存),发送函数会先从队列中取出要发送的数据包然后再把该数据包放回到队列中(很明显之个过程是没必要的),因此在新的内核版本中 增加了新的机制已避免这种不必要的操作--当设备驱动检测到剩余的内存空间小于一个mtu大小时便调用netif_stop_queue()暂停设备的发送功能.
以3c59x为例
vortex_start_xmit(...){ //(hard_start_xmit    drivers/net/3c59x.c)
      ...
      outsl(ioaddr+TX_FIFO,skb->data,(skb->len+3)>>2);
      dev_kfree_skb(skb);
      if(inw(ioaddr_TxFree)>1536){
            netif_start_queue(dev);   //当内存足够时就enable发送
      }else{
             netif_stop_queue(dev);   //不满足时就暂停发送功能
             outw(SetTxThreashold+(1536>>2),ioaddr+EL3_CMD);     //这里会给设备发送一个setthreashold指令,指示该设备当检测到内存满足时产生一个中断,从而再reable发送
       ....
      }
}
我们再来看看中断处理函数
static void vortex_interrput(int irq,void *dev_id,struct pt_regs *regs){
       ....
       if (status & TxAvailable){
               ...
               outw(AckIntr|TxAvailable,ioaddr+EL3_CMD);
               netif_wake_queue(dev);     //TxAvailable标记表示内存空间足够, 而status则是此次中断产生的类型,这里的中断处理函数只写出了内存满足时产生的中断,其他类型的中断处理略
                                                  内存满足时调用netif_wake_queue 去唤醒设备的发送  这里使用netif_wake_queue而不是简单的调用netif_start_queue来enable发送是有原因的 接着往下看
       .....
       }
}
netif_wake_queue不但enable发送 还调用__netif_schedule()让内核检测此时是否有发送任务在输出队列中,其原因是在disable期间,任何尝试发送的数据包都将失败并放回输出队列,因此内
存满足时很可能有数据包正等待发送,所以调用__netif_schedule()是合适的
static inline void netif_wake_queue(struct net_device *dev){
        ...
        if (test_and_clear_bit(__LINK_STATE_XOFF,&dev->state))  //清除__LINK_STATE_XOFF标记  从而enable输出队列
             __netif_schedule(dev);      
}
netif_wake_queue()不仅会在内存满足条件时调用,还有一些地方也会调用,这里顺便说下, 内核为了防止长时间disable,引入了watchdog timer机制,通过运行一个定时器,并定期执行
netif_wake_queue()来reset nic
再看看__netif_schedule()
static inline void __netif_schedule(struct net_device *dev){          
        if(!test_and_set_bit(__LINK_STATE_SCHED,&dev->state)){   //如果一个设备状态为__LINK_STATE_SCHED,则表示该设备已经被schedule,此时就没有必要再次执行这个函数
                unsigned long flags;
                struct softnet_data *sd;
                local_irq_save(flags);
                    sd=&__get_cpu_var(softnet_data);
                dev->next_sched = sd->output_queue; //output_queue类似poll_list  将有数据要发送的设备加到output_queue队列头
               sd->output_queue = dev;
               raise_softirq_irqoff(cpu,NET_TX_SOFTIRQ);    //触发一个net_tx_softirq软中断 其他操作由net_tx_action()完成
               local_irq_restore(flags);
        }
}
到此为止 我们讲完了 内存不够disable发送 到 之后内存满足产生中断 enable设备的发送,并调用__netif_schedule() 将该设备加到output_queue中 再触发一个软中断 这一些列仅仅是发送数据包整个流程的一个分支
 
接下来我们从头来看看发送数据包的主分支
首先发送数据包第一个要调用的函数一定是dev_queue_xmit()   那么是谁去调用dev_queue_xmit这个我们先忽略
int dev_queue_xmit(struct sk_buff *skb){
         ...
        if(skb_shinfo(skb)->frag_list && !(dev->features&NETIF_F_FRAGLIST) &&__skb_linearize(skb,GFP_ATOMIC) )
               goto out_kfree_skb;
       if(skb_shinfo(skb)->nr_frags &&) !(dev->features&NETIF_F_SG|| illegal_highdma(dma,skb))&&__skb_linearize(skb,GFP_ATOMIC)
               goto out_kfree_skb;
                   //检测数据包是否是分片 设备是否支持scatter/gether dma特性
       if(skb->ip_summed ==CHECKSUM_HW && (! (dev->features &(NETIF_F_HW_CSUM|NETIF_F_NO_CSUM)) && (!(dev->features & NETIF_F_IP_CSUM) || skb->protocol !=htons
(ETH_P_IP) )))
           if (skb_checksum_help(skb,0))
                goto out_kfree_skb;
                    //做l4的checksum
      ....
      接下来的代码会对设备有无队列进行区分  我们先说设备无队列的情况
      有些设备 像回环设备就没有队列,因此也就不需要qos流程, 没有requeue过程,有问题的数据包直接丢掉,  在无队列下 会直接调用hard_start_xmit(loopback_xmit) 做一些回环数据包的发送
处理,最后直接传给该设备的接收函数netif_rx.
      ....
      呐 对于有队列设备 之后的过程就复杂了 要进行一个流量控制
      ....
      local_bh_disable();    //disable软中断
      q=rcu_dereference(dev->qdisc);    //获得设备用于流量控制的链表qdisc
      ..
      if(q->enqueue){      
              spin_lock(&dev->queue_lock);        //获得可以修改队列的锁
              rc=q->enqueue(skb,q);       //插入新的数据包skb 到队列中(这里说的队列是每个设备都有一个用于输出调度的队列不是output_queue)
              qdisc_run(dev);           //试图发送数据包,包括流量控制代码
              spin_unlock_bh(&dev->queue_lock);    //释放锁
              rc=rc==NET_XMIT_BYPASS?NET_XMIT_SUCCESS:
rc;
              goto out;
      }
     ....
}
static inline void qdisc_run(struct net_device *dev){
        while(!netif_queue_stopped(dev)&&qdisc_restart(dev)<0)
}
int qdisc_restart(struct net_device *dev){
        struct qdisc *q = dev->qdisc;
        struct sk_buff *skb;
        if((skb =q->dequeue(q)) !=NULL){      //从队列中取出一个数据包
               ...
               if(!spin_trylock(&dev->xmit_lock)){      //获得执行hard_start_xmit的锁    如果不能获得则说明有其他cpu正在执行hard_start_xmit()进行发送数据包,因此当前只能requeue了
                      collision:
                          goto requeue;
               }
              ....
       requeue:
              q->ops->requeue(skb,q);        //requeue  将刚刚取出的数据包重新放回队列中
               netif_schedule(dev);               //触发一个软中断 交由net_tx_action处理 不用担心 在net_tx_action还会再次调用qdisc_run的
             ...
             
              if (!netif_queue_stopped(dev)){        //第2次判断设备发送功能是否停用,出于如下考虑: 获得..xmit锁以后,其他cpu可能发送了数据而耗尽了设备的内存,致使发送暂停,
                      int ret;
                      if (netdev_nit)       //如果注册了protocol sniffer ,则调用dev_queue_xmit_nit将数据包分别复制给注册的sniffer
                            dev_queue_xmit_nit(skb,dev);
                      ret = dev->hard_start_xmit(skb,dev);     //设备驱动的虚拟函数 用于设备发送数据 
                      if (ret == NETDEV_TX_OK){       //发送处理成功  注意此时即便成功 也不会立刻释放buffer 而是等到下一次执行net_tx_action时根据completion_queue释放buffer
                             if (!nolock){
                                  dev->xmit_lock_owner = -1;
                                  spin_unlock(&dev->xmit_lock);
                             }
                             spin_lock(&dev->queue_lock);
                             return -1;
                      }
                      if (ret == NETDEV_TX_LOCKED &&nolock){    //支持NETIF_F_LLTX的驱动自己实现了xmit_lock锁的功能  才有可能返回NETDEV_TX_LOCKED表示该驱动被其他cpu占用,功能
于上面的..xmit锁相同,支持NETIF_F_LLTX的驱动就不使用xmit_lock锁了
                             spin_lock(&dev->queue_lock);
                             goto collision;
                      }
              }
        ...
...
hard_start_xmit() 设备驱动的虚拟函数  以e1000为例   设备驱动过程比较复杂 这里只简单说明一下流程
static int e1000_xmit_frame(struct sk_buff *skb,struct net_device *netdev){
        ....
        tx_ring = adapter->tx_ring;
        if(unlikely(skb->len<=0)){     //数据包长度小于等于0   当处理出现任何错误时调用dev_kfree_skb_any()直接释放该skb  类似的错误处理还有很多 这里就写出一个..
                dev_kfree_skb_any(skb);  
                     return NETDEV_TX_OK;
         }
        ...
       ... e1000_tx_map(adapter,skb,first,max+per_txd,nr_frags,mss);    //网卡dma操作  将数据传给网卡
        ...
        e1000_tx_queue(adapter,tx_flags,count);
        ...
        return NETDEV_TX_OK;  // 返回发送成功 内核发送数据包到网卡过程到此结束,当网卡把数据包完全发送出去以后 会产生一个中断 通知内核释放skb
}
为了了解"当网卡把数据包完全发送出去以后 会产生一个中断 通知内核释放skb" 这里过程 我们再来看看中断处理函数vortex_interrupt 这个函数上面提到过
static void vortex_interrput(int irq,void *dev_id,struct pt_regs *regs){
       ....
       if(status&DMADone){
              if(inw(ioaddr+wn7_MasterStatus)&0x1000){
                      outw(0x1000,ioaddr+Wn7_MasterStatus);
                      pci_unmap_single(VORTEX_PCI(vp),vp->tx_skb_dma,(vp->tx_skb_len+3)&~3,PCI_DMA_TODEVICE);
        &nb
sp;             dev_kfree_skb_irq(vp->tx_skb);      //已经发送完了 所以调用dev_kfree_skb_irq()释放skb
              ...
              }
       }
}
static inline void dev_kfree_skb_any (struct sk_buff *skb){
        if (in_irq()||irqs_disabled())     //判断是否运行在中断上下文中,  我们知道 当系统运行在中断上下文,它应该执行的时间越短越好,但如果我们需要在中断上下文中释放skb,这就需要比较长
的时间了,所以在这个时间段里处理skb的释放并不是一个好的选择,因此网络子系统在softirq_action结构中设置了一个完成队列completion_queue,当内核要在中断上下文释放skb时,则调用
dev_kfree_skb_irq()
               dev_kfree_skb_irq(skb);
        else
                dev_kfree_skb(skb);
}
static inline void dev_kfree_skb_irq(struct sk_buff *skb){
        if (atomic_dev_and_test(&skb->users)){    //当没人引用该skb时 才将该skb加入到completion_queue中,并触发一个net_tx_softirq软中断  将释放过程放到下半部中去执行, 在
net_tx_action中释放该skb
                struct softnet_data *sd;
                unsigned long flags;
                local_irq_save(flags);
                sd = &__get_cpu_var(softnet_data);
                skb->next = sd->completion_queue;
                sd->completion_queue = skb;
                raise_softirq_irqoff(NET_TX_SOFTIRQ);
                local_irq_restore(flags);
        }
}
最后我们来说net_tx_action
...net_tx_action(..){    //net_tx_action主要完成两项工作  首先根据completion_queue链表 释放skb空间   然后执行数据包的发送
        ..
        if(sd->completion_queue){
               struct sk_buff *clist;
               local_irq_disable();
               clist = sd->completion_queue;
               sd->completion_queue = NULL;
               local_irq_enable();
 
               while(clist !=NULL){
                       struct sk_buff *skb = clist;
                       clist = clist->next;
                       BUG_TRAP(!atomic_read(&skb->users));
                       __kfree_skb(skb);    //释放skb
               }
        }
        ..
        if(sd->output_queue){                  //遍历output_queue中所有有待发送任务的设备, 并对各个设备调用qdisc_run()
                struct net_device *head;
                local_irq_disable();
                head = sd->output_queue;
                sd->output_queue = NULL;
                local_irq_enable();
                while(head){
                        struct net_device *dev= head;
                        head = head->next_sched;
                        smp_mb__before_clear_bit();
                        clear_bit(__LINK_STATE_SCHED,&dev->state);
                  
                         if(spin_trylock(&dev->queue_lock)){
                                qdisc_run(dev);
                                spin_unlock(&dev->queue_lock);
                         }else
                                netif_schedule(dev);
                }                
        }
}
发送过程较接收过程简单一些 主分支 各函数之间关系见下图:
文章出处:DIY部落()
阅读(1737) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~