`
shaojiashuai123456
  • 浏览: 262795 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
社区版块
存档分类
最新评论

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);   
            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_gso(dev, skb)) {   
            if (unlikely(dev_gso_segment(skb)))   
                goto out_kfree_skb;   
            if (skb->next)   
                goto gso;   
        }   
        //这个就是驱动提供的发送回调函数了   
        return dev->hard_start_xmit(skb, dev);   
    }   
gso:   
    do {   
        struct sk_buff *nskb = skb->next;   
        int rc;   
        skb->next = nskb->next;   
        nskb->next = NULL;   
        rc = dev->hard_start_xmit(nskb, dev);   
        if (unlikely(rc)) {   
            nskb->next = skb->next;   
            skb->next = nskb;   
            return rc;   
        }   
        if (unlikely(netif_tx_queue_stopped(txq) && skb->next))   
            return NETDEV_TX_BUSY;   
    } while (skb->next);   
    skb->destructor = DEV_GSO_CB(skb)->destructor;   
out_kfree_skb:   
    kfree_skb(skb);   
    return 0;   
}  

  

   qdisc_run和__qdisc_run的功能很简单,就是检查队列是否处于运行状态

  

static inline void qdisc_run(struct Qdisc *q)   
{   
    if (!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))   
        __qdisc_run(q);   
}   
void __qdisc_run(struct Qdisc *q)   
{   
    unsigned long start_time = jiffies;   
    //真正的操作在这个函数里面   
    while (qdisc_restart(q)) {   
        /*  
         * Postpone processing if  
         * 1. another process needs the CPU;  
         * 2. we've been doing it for too long.  
         */  
        if (need_resched() || jiffies != start_time) {   
            //当需要进行调度或者时间超过了1个时间片的时候就退出循环,退出之前发出软中断请求   
            __netif_schedule(q);   
            break;   
        }   
    }   
    clear_bit(__QDISC_STATE_RUNNING, &q->state);   
}  

 

     然后循环调用qdisc_restart发送数据
     下面这个函数qdisc_restart是真正发送数据包的函数
     它从队列上取下一个帧,然后尝试将它发送出去
     若发送失败则一般是重新入队。
     此函数返回值为:发送成功时返回剩余队列长度
     发送失败时返回0(若发送成功且剩余队列长度为0也返回0)

    

static inline int qdisc_restart(struct Qdisc *q)   
{   
    struct netdev_queue *txq;   
    int ret = NETDEV_TX_BUSY;   
    struct net_device *dev;   
    spinlock_t *root_lock;   
    struct sk_buff *skb;   
    /* Dequeue packet */  
    if (unlikely((skb = dequeue_skb(q)) == NULL))   
        return 0;   
    root_lock = qdisc_lock(q);   
    /* And release qdisc */  
    spin_unlock(root_lock);   
    dev = qdisc_dev(q);   
    txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));   
    HARD_TX_LOCK(dev, txq, smp_processor_id());   
    if (!netif_tx_queue_stopped(txq) &&   
        !netif_tx_queue_frozen(txq))   
        ret = dev_hard_start_xmit(skb, dev, txq);   
    HARD_TX_UNLOCK(dev, txq);   
    spin_lock(root_lock);   
    switch (ret) {   
    case NETDEV_TX_OK:   
        /* Driver sent out skb successfully */  
        ret = qdisc_qlen(q);   
        break;   
    case NETDEV_TX_LOCKED:   
        /* Driver try lock failed */  
        //有可能其他CPU正占有这个锁,这里处理冲突   
        //函数就不看了,里面的处理流程简单说下   
        //分两种情况,如果占有锁的cpu就是当前cpu,那么释放这个包同时打印一条警告   
        //否则将包重新入队   
        ret = handle_dev_cpu_collision(skb, txq, q);   
        break;   
    default:   
        /* Driver returned NETDEV_TX_BUSY - requeue skb */  
        //当发送队列处于停止状态并且队列中有数据待发送时会返回NETDEV_TX_BUSY   
        //代表发送忙,这种情况下就将包重新入队   
        if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))   
            printk(KERN_WARNING "BUG %s code %d qlen %d\n",   
                   dev->name, ret, q->q.qlen);   
        ret = dev_requeue_skb(skb, q);   
        break;   
    }   
    if (ret && (netif_tx_queue_stopped(txq) ||   
            netif_tx_queue_frozen(txq)))   
        ret = 0;   
    return ret;   
}  

 

  至此,dev_queue_xmit到驱动层的发送流程就分析完了。

 已经有了dev_queue_xmit函数,为什么还需要软中断来发送呢?
我们可以看到在dev_queue_xmit中将skb进行了一些处理(比如合并成一个包,计算校验和等)
处理完的skb是可以直接发送的了,这时dev_queue_xmit也会先将skb入队(skb一般都是在这个函数中入队的)
并且调用qdisc_run尝试发送,但是有可能发送失败,这时就将skb重新入队,调度软中断,并且自己直接返回。
软中断只是发送队列中的skb以及释放已经发送的skb,它无需再对skb进行线性化或者校验和处理
另外在队列被停止的情况下,dev_queue_xmit仍然可以把包加入队列,但是不能发送
这样在队列被唤醒的时候就需要通过软中断来发送停止期间积压的包
简而言之,dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去。
(其实发送软中断还有一个作用,就是释放已经发送的包,因为某些情况下发送是在硬件中断中完成的,
为了提高硬件中断处理效率,内核提供一种方式将释放skb放到软中断中进行,
这时只要调用dev_kfree_skb_irq,它将skb加入softnet_data的completion_queue中,然后开启发送软中断,
net_tx_action会在软中断中将completion_queue中的skb全部释放掉)

 

 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/peimichael/archive/2009/10/19/4699609.aspx#

分享到:
评论

相关推荐

    使用dev_queue_xmit、dev_hard_start_xmit的方法实现数据包的发送.docx )

    `dev_queue_xmit`和`dev_hard_start_xmit`是Linux内核网络栈中非常重要的两个函数,它们共同作用于数据包的发送过程。理解这两个函数的工作原理对于进行内核级网络编程至关重要。通过对`dev_queue_xmit`内部逻辑的...

    linux内核-网络数据包传送通道解析

    驱动层通过device结构的hard_start_xmit函数指针向链路层提供发送函数,链路层提供dev_queue_xmit发送函数供网络层调用,网络层则提供ip_queue_xmit函数供传输层调用。以TCP协议为例,数据包从应用层的write函数开始...

    linux_net.pdf

    linux tcp/ip分析 两台主机建立udp通信所走过的函数列表: | sys_read fs/read_write.c | sock_read net/socket.c | sock_recvmsg net/socket.c | inet_recvmsg net/ipv4/af_inet.c | udp_recvmsg net/ipv4/udp.c | ...

    Linux协议栈的实现

    `dev_queue_xmit()`用于将数据包加入网络接口的发送队列。 ###### 5.3.2 DEVICE->hard_start_xmit()-devicedependent, drivers/net/DEVICE.c `hard_start_xmit()`是由具体的网络设备驱动提供的,用于真正发送数据包...

    linux下vlan功能实现.docx

    - **IP协议处理**:IP协议的发送和接收流程在`ip_input.c`和`ip_output.c`中,最后都会调用`dev_queue_xmit()`发送数据包。设备接收数据包后,会通过协议类型调用相应的处理函数,如`ip_rcv()`,并可能涉及VLAN处理...

    linux 网络数据流程

    7. 链路层由dev_queue_xmit函数处理,它将数据包传给网卡驱动。 8. 网卡驱动的hard_start_xmit函数最终将数据包通过DMA传输到网卡,并由网卡发送到物理媒介。 在整个流程中,数据包可能会经过分片、重组、拷贝等...

    基于Linux下IEC61850的研究.pdf

    要从协议层向设备发送数据,需要使用dev_queue_xmit函数。这个函数对数据进行排队,并交由底层设备驱动程序发送数据包。报文的接收通常使用netif_rx执行。当底层设备驱动程序收到一个报文(包含在所分配的sk_buff中...

    Linux网络驱动程序分析.pdf

    在数据传输阶段,网络设备驱动程序会将数据包发送到网络设备中,例如通过调用协议接口代码中的 deb_queue_xmit 函数来发送数据包。 2.3 数据接收 在数据接收阶段,网络设备驱动程序会从网络设备中接收数据包,例如...

    linux qos的学习

    网络数据的出口函数为 dev_queue_xmit()。 Linux QoS 的输入流控 在数据接收流控时,数据只是刚从网卡设备中收到,还未交到网络上层处理。网卡的输入流控不是必须的,缺省情况下并不进行流控。输入流控入口函数为...

    Linux网络体系架构和网卡驱动设计.pdf

    此外,Linux内核还提供了内存管理功能,如dev_alloc_skb用于分配sk_buff,而函数dev_kfree_skb和kfree_skb用于释放sk_buff。这些操作对于驱动程序来说非常关键,因为它们直接关系到网络数据传输的效率和稳定性。 ...

    Linux内核QoS实现机制.doc

    - 出口队列调度的入口在`dev_queue_xmit`函数,通过`rcu_dereference(dev->qdisc)`获取设备的root qdisc,处理出站流量。 Linux内核与用户空间的交互主要通过Netlink协议,允许用户通过`tc`命令配置QoS的队列结构...

    Linux内核 tcp ip协议栈源码分析

    中的`netif_rx()`和`dev_queue_xmit()`函数进行。这两个函数分别处理从硬件接收到的数据包和向硬件发送的数据包。 6. **协议栈优化**: Linux内核的TCP/IP协议栈还包括了多种优化措施,如快速重传、快速恢复、延迟...

    linux网络协议栈(UDP收发)

    最后,`dev_queue_xmit`函数将数据包放入网卡的发送队列,等待硬件进行实际的物理层传输。 在整个过程中,`skb`起到了承上启下的关键作用,它不仅保存了网络数据,还包含了一系列处理信息,如协议类型、数据长度、...

    Linux系统中网络设备驱动的研究.pdf

    Linux内核提供了丰富的系统函数来支持网络设备驱动的开发,如net_device结构体定义了设备的相关属性和操作,如dev_queue_xmit用于发送数据包,netif_rx接收并处理数据帧,hard_start_xmit用于实际的数据发送等。...

    Linux网络设备驱动程序培训教材.pptx

    例如,当需要发送数据包时,上层协议会调用`dev_queue_xmit()`函数;接收数据包时,则通过`netif_rx()`函数。`sk_buff`(套接字缓冲区)是网络子系统的核心数据结构,包含了关于数据包的所有关键信息,如设备指针、...

    linux驱动开发 linux

    `dev_queue_xmit()`用于交付数据包,而`netif_rx()`用于接收数据包。驱动程序还包括中断处理程序、配置、资源管理和状态报告等方法。 在Linux驱动开发中,从2.0.xx版本到2.2.xx版本的驱动程序移植通常较为简单。...

    Linux下网卡驱动程序源码分析.rar

    数据包的发送通常通过`dev_queue_xmit()`函数,而接收则涉及中断处理程序和软中断。`netif_rx()`函数用于将接收到的数据包放入接收队列。 5. **中断处理**: Linux使用中断处理程序来响应硬件事件,如数据包接收...

    linux 网络驱动开发

    - **发送数据**: 通过`dev_queue_xmit()`将数据包提交给网络设备。 #### 五、中断处理 - **中断上下文**: 在中断上下文中,不能睡眠或阻塞操作,因此需要避免执行耗时的操作。 - **软中断**: 使用`schedule_work()...

Global site tag (gtag.js) - Google Analytics