日期:2014-05-16  浏览次数:20739 次

Linux发送函数dev_queue_xmit分析 --转

当上层准备好一个包之后,交给下面这个函数处理:

int dev_queue_xmit(struct sk_buff *skb)   
{   
    struct net_device *dev = skb->dev;   
    struct netdev_queue *txq;   
    struct Qdisc *q;   
    int rc = -ENOMEM;   
    /* GSO will handle the following emulations directly. */  
    if (netif_needs_gso(dev, skb))   
        goto gso;   
    //首先判断skb是否被分段,如果分了段并且网卡不支持分散读的话需要将所有段重新组合成一个段   
    //这里__skb_linearize其实就是__pskb_pull_tail(skb, skb->data_len),这个函数基本上等同于pskb_may_pull   
    //pskb_may_pull的作用就是检测skb对应的主buf中是否有足够的空间来pull出len长度,   
    //如果不够就重新分配skb并将frags中的数据拷贝入新分配的主buff中,而这里将参数len设置为skb->datalen,   
    //也就是会将所有的数据全部拷贝到主buff中,以这种方式完成skb的线性化   
    if (skb_shinfo(skb)->frag_list &&   
        !(dev->features & NETIF_F_FRAGLIST) &&   
        __skb_linearize(skb))   
        goto out_kfree_skb;   
    /* Fragmented skb is linearized if device does not support SG,  
     * or if at least one of fragments is in highmem and device  
     * does not support DMA from it.  
     */  
     //如果上面已经线性化了一次,这里的__skb_linearize就会直接返回   
     //注意区别frags和frag_list,   
     //前者是将多的数据放到单独分配的页面中,sk_buff只有一个。而后者则是连接多个sk_buff   
    if (skb_shinfo(skb)->nr_frags &&   
        (!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) &&   
        __skb_linearize(skb))   
        goto out_kfree_skb;   
    /* 如果此包的校验和还没有计算并且驱动不支持硬件校验和计算,那么需要在这里计算校验和*/  
    if (skb->ip_summed == CHECKSUM_PARTIAL) {   
        skb_set_transport_header(skb, skb->csum_start -   
                          skb_headroom(skb));   
        if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb))   
            goto out_kfree_skb;   
    }   
gso:   
    /* Disable soft irqs for various locks below. Also  
     * stops preemption for RCU.  
     */  
    rcu_read_lock_bh();   
    //选择一个发送队列,如果设备提供了select_queue回调函数就使用它,否则由内核选择一个队列   
    //大部分驱动都不会设置多个队列,而是在调用alloc_etherdev分配net_device时将队列个数设置为1   
    //也就是只有一个队列   
    txq = dev_pick_tx(dev, skb);   
    //从netdev_queue结构上取下设备的qdisc   
    q = rcu_dereference(txq->qdisc);   
#ifdef CONFIG_NET_CLS_ACT   
    skb->tc_verd = SET_TC_AT(skb->tc_verd,AT_EGRESS);   
#endif   
    //上面说大部分驱动只有一个队列,但是只有一个队列也不代表设备准备使用它   
    //这里检查这个队列中是否有enqueue函数,如果有则说明设备会使用这个队列,否则需另外处理   
    //关于enqueue函数的设置,我找到dev_open->dev_activate中调用了qdisc_create_dflt来设置,   
    //不知道一般驱动怎么设置这个queue   
    //需要注意的是,这里并不是将传进来的skb直接发送,而是先入队,然后调度队列,   
    //具体发送哪个包由enqueue和dequeue函数决定,这体现了设备的排队规则   
    if (q->enqueue) {   
        spinlock_t *root_lock = qdisc_lock(q);   
        spin_lock(root_lock);   
        if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {   
            kfree_skb(skb);   
            rc = NET_XMIT_DROP;   
        } else {   
            //将skb加入到设备发送队列中,然后调用qdisc_run来发送   
            rc = qdisc_enqueue_root(skb, q);   
            qdisc_run(q); //下面看   
        }   
        spin_unlock(root_lock);   
        goto out;   
    }   
    //下面是处理不使用发送队列的情况,注意看下面一段注释   
    /* The device has no queue. Common case for software devices:  
       loopback, all the sorts of tunnels...  
       Really, it is unlikely that netif_tx_lock protection is necessary  
       here.  (f.e. loopback and IP tunnels are clean ignoring statistics  
       counters.)  
       However, it is possible, that they rely on protection  
       made by us here.  
       Check this and shot the lock. It is not prone from deadlocks.  
       Either shot noqueue qdisc, it is even simpler 8)  
     */  
    //要确定设备是开启的,下面还要确定队列是运行的。启动和停止队列由驱动程序决定   
    //详见ULNI中文版P251   
    //如上面英文注释所说,设备没有输出队列典型情况是回环设备   
    //我们所要做的就是直接调用驱动的hard_start_xmit将它发送出去   
    //如果发送失败就直接丢弃,因为没有队列可以保存它   
    if (dev->flags & IFF_UP) {   
        int cpu = smp_processor_id(); /* ok because BHs are off */  
        if (txq->xmit_lock_owner != cpu) {   
            HARD_TX_LOCK(dev, txq, cpu);