Linux 网络协议栈开发代码分析篇之数据收发(二) —— dev_queue_xmit()函数

     当所有的信息都准备好了之后,例如,出口设备,下一跳的地址,以及链路层地址。就会调用dev.c文件中的dev_queue_xmin函数,该函数是设备驱动程序执行传输的接口。也就是所有的数据包在填充完成后,最终发送数据时,都会调用该函数。

     Dev_queue_xmit函数只接收一个skb_buff结构作为输入的值。此数据结构包含了此函数所需要的一切信息。Skb->dev是出口设备,skb->data为有效的载荷的开头,其长度为skb->len

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);     
            if (!netif_tx_queue_stopped(txq)) {     
                rc = 0;     
                //对于loopback设备,它的hard_start_xmit函数是loopback_xmit     
                //我们可以看到,在loopback_xmit末尾直接调用了netif_rx函数     
                //将带发送的包直接接收了回来     
                //这个函数下面具体分析,返回0表示成功,skb已被free     
                if (!dev_hard_start_xmit(skb, dev, txq)) {      
                    HARD_TX_UNLOCK(dev, txq);     
                    goto out;     
                }     
            }     
            HARD_TX_UNLOCK(dev, txq);     
            if (net_ratelimit())     
                printk(KERN_CRIT "Virtual device %s asks to "    
                       "queue packet!\n", dev->name);     
        } else {     
            /* Recursion is detected! It is possible,   
             * unfortunately */    
            if (net_ratelimit())     
                printk(KERN_CRIT "Dead loop on virtual device "    
                       "%s, fix it urgently!\n", dev->name);     
        }     
    }     
    rc = -ENETDOWN;     
    rcu_read_unlock_bh();     
out_kfree_skb:     
    kfree_skb(skb);     
    return rc;     
out:     
    rcu_read_unlock_bh();     
    return rc;     
}     

    从此函数可以看出,当驱动使用发送队列的时候会循环从队列中取出包发, 而不使用队列的时候只发送一次,如果没发送成功就直接丢弃

    下面看下第二个函数:

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,     
            struct netdev_queue *txq)     
{     
    if (likely(!skb->next)) {     
        //从这里可以看出,对于每一个发送的包也会给ptype_all一份,     
        //而packet套接字创建时对于proto为ETH_P_ALL的会在ptype_all中注册一个成员     
        //因此对于协议号为ETH_P_ALL的packet套接字来说,发送和接受的数据都能收到     
        //而其他成员似乎不行,这个要回去试试     
        if (!list_empty(&ptype_all))       
            dev_queue_xmit_nit(skb, dev);     
        if (netif_needs_gs
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值