linux协议栈中提供的报文发送函数有两个,一个是链路层提供给网络层的发包函数dev_queue_xmit()。另一个就是软中断发包函数直接调用的函数sch_direct_xmit()

这两个函数最终都会调用dev_hard_start_xmit()来发送报文。


一、发送函数的调用关系:

wKioL1L_JmyC7KXwAABRpw3Rwls656.jpg



二、发送过程中锁的使用:


1、队列策略缓存报文的队列使用 qdisc->q.lock来保护。使用函数qdisc_lock来返回锁。

static inline spinlock_t *qdisc_lock(struct Qdisc *qdisc)
{
    return &qdisc->q.lock;
}


2、调用网卡驱动发送报文时,使用锁dev->dev_queue->_xmit_lock来保护,防止多个cpu核同时操作网络设备。使用宏HARD_TX_LOCK 来上锁。


dev->dev_queue->xmt_lock_owner 记录持有该锁的cpu id.


static inline void __netif_tx_lock(struct netdev_queue *txq,
                                   int cpu)
{
    spin_lock(&txq->_xmit_lock);
    txq->xmit_lock_owner = cpu;
}
#define HARD_TX_LOCK(dev, txq, cpu) {           \
    if ((dev->features & NETIF_F_LLTX) == 0) {   \
        __netif_tx_lock(txq, cpu);      \
    }                       \
}
static inline void __netif_tx_lock(struct netdev_queue *txq, int cpu)
{
    spin_lock(&txq->_xmit_lock);
    //记录持有队列锁的cpu id。
    txq->xmit_lock_owner = cpu;
}

三、发送函数详解


1dev_hard_start_xmit()


调用该函数时必须持有dev->dev_queue->_xmit_lock锁。


int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
            struct netdev_queue *txq)
{
    /*获得发送设备的操作函数*/
    const struct net_device_ops *ops = dev->netdev_ops;
    int rc;
    /*当GSO发送大数据时,如果设备硬件不支持GSO功能,
    就需要使用软件来对大报文进行切割,
    这时切割后的所有小报文就挂在skb->next后面。
    进入该函数时,大报文一般还没经过GSO的切割,
        skb->next = =NULL*/
    if (likely(!skb->next))
    {
        /*如果注册有嗅探器来关注发送报文,
                  就拷贝一份报文给嗅探器*/
        if (!list_empty(&ptype_all))
        {
            dev_queue_xmit_nit(skb, dev);
        }
        /*如果报文需要进行GSO软件分割*/
        if (netif_needs_gso(dev, skb))
        {
            /*如果调用GSO分割大包失败,就丢弃报文*/
            if (unlikely(dev_gso_segment(skb)))
            {
                goto out_kfree_skb;
            }
            /*经过GSO分割后的报文不止一个,
                          跳到处理GSO处进行处理*/
            if (skb->next)
            {
                goto gso;
            }
        }
        /*如果报文不需要GSO软件分割,直接发送*/
        /*
         * If device doesnt need skb->dst,
                 * release it right now while
         * its hot in this cpu cache
         */
        if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
        {
            skb_dst_drop(skb);
        }
        /*调用网络驱动提供的发送函数把报文发送出去*/
        rc = ops->ndo_start_xmit(skb, dev);
        if (rc == NETDEV_TX_OK)
        {
            txq_trans_update(txq);
        }
                                                                    
        return rc;
    }
/*如果报文经过软件分割后,skb->next后挂着一串分割出的小包,
  循环把挂着的小包发送出去*/
gso:
    do
    {
        struct sk_buff *nskb = skb->next;
        skb->next = nskb->next;
        nskb->next = NULL;
        /*
         * If device doesnt need nskb->dst,
                 * release it right now while
         * its hot in this cpu cache
         */
        if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
        {
            skb_dst_drop(nskb);
        }
        rc = ops->ndo_start_xmit(nskb, dev);
        if (unlikely(rc != NETDEV_TX_OK))
        {
            nskb->next = skb->next;
            skb->next = nskb;
            return rc;
        }
                                                                    
        txq_trans_update(txq);
        if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
        {
            return NETDEV_TX_BUSY;
        }
    } while (skb->next);
    /*在GSO软件分割报文时,原始skb的destructor 函数被替换成
          释放一串报文的释放函数,一旦发送失败后
          保证分割后的一串skb 都能释放掉。
     现在一串skb 已经都发送成功了,
          还原原始skb  的destructor函数,释放原始skb时使用
        */
    skb->destructor = DEV_GSO_CB(skb)->destructor;
out_kfree_skb:
    kfree_skb(skb);
    return NETDEV_TX_OK;
}

2sch_direct_xmit


int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
            struct net_device *dev, struct netdev_queue *txq,
            spinlock_t *root_lock)
{
    int ret = NETDEV_TX_BUSY;
    /*调用该函数时队列策略的队列锁已经被锁了,现在解锁*/
    /* And release qdisc */
    spin_unlock(root_lock);
    /*取得发送队列的锁*/
    HARD_TX_LOCK(dev, txq, smp_processor_id());
    /*如果发送队列已经开启并且发送队列没有僵死,
         直接调用 dev_hard_start_xmit发送报文*/
    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 */
        ret = handle_dev_cpu_collision(skb, txq, q);
        break;
    /*否则,表明发送报文失败,这时把报文存入队列策略的发送队列中*/
    default:
        /* Driver returned NETDEV_TX_BUSY - requeue skb */
        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;
}

处理获取发送锁失败的函数:


static inline int handle_dev_cpu_collision(struct sk_buff *skb,
                       struct netdev_queue *dev_queue,
                       struct Qdisc *q)
{
    int ret;
    /*如果cpu 获取锁失败,并且该锁还被本cpu 占用,
         表示发送队列已经死锁,丢弃报文并打印警告*/
    if (unlikely(dev_queue->xmit_lock_owner ==
              smp_processor_id()))
        {
        /*
         * Same CPU holding the lock. It may be a transient
         * configuration error, when hard_start_xmit() recurses. We
         * detect it by checking xmit owner and drop the packet when
         * deadloop is detected. Return OK to try the next skb.
         */
        kfree_skb(skb);
        if (net_ratelimit())
            printk(KERN_WARNING "Dead loop on netdevice %s, "
                   "fix it urgently!\n", dev_queue->dev->name);
        ret = qdisc_qlen(q);
    }
    /*锁被其他cpu锁占用,这时我们把报文
          重新存入队列策略的发送队列,延迟发送*/
    else {
        /*
         * Another cpu is holding lock, requeue & delay xmits for
         * some time.
         */
        __get_cpu_var(netdev_rx_stat).cpu_collision++;
        ret = dev_requeue_skb(skb, q);
    }
    return ret;
}

发送失败后重新入队函数:


static inline int dev_requeue_skb(struct sk_buff *skb,
                                  struct Qdisc *q)
{
    //把skb 挂到 q->gs_skb上,下次优先发送
    q->gso_skb = skb;
    q->qstats.requeues++;
    /* it's still part of the queue */
    q->q.qlen++;
    //重新调度队列策略
    __netif_schedule(q);
    return 0;
}

3__dev_xmit_skb


static inline int __dev_xmit_skb(struct sk_buff *skb,
                                 struct Qdisc *q,
                 struct net_device *dev,
                 struct netdev_queue *txq)
{
    spinlock_t *root_lock = qdisc_lock(q);
    int rc;
    /*取得队列策略的锁*/
    spin_lock(root_lock);
    /*如果队列策略被显示的禁止,就丢弃报文*/
    if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
    {
        kfree_skb(skb);
        rc = NET_XMIT_DROP;
    }
    /*如果队列策略中没有等待发送的报文,发送该报文*/
    else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
           !test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))
    {
         __qdisc_update_bstats(q, skb->len);
        if (sch_direct_xmit(skb, q, dev, txq, root_lock))
        {
            /*如果发送报文返回不为0,
             表示队列策略队列中还存在等待发送的报文,
             这时重新调度队列策略*/
            __qdisc_run(q);
        }
        else
        {
            /*如果发送报文返回为0,
            表示发送成功后队列策略中没有等待发送的报文,
            这时清除队列策略的running 状态*/
            clear_bit(__QDISC_STATE_RUNNING, &q->state);
        }
        rc = NET_XMIT_SUCCESS;
    }
    /*如果队列策略中有等待发送的报文,
     就把该报文放入队列中,
     调用qdisc_run来进行调度发送*/
    else
    {
        rc = qdisc_enqueue_root(skb, q);
        qdisc_run(q);
    }
    spin_unlock(root_lock);
    return rc;
}

4dev_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,进行GSO分割处理*/
    if (netif_needs_gso(dev, skb))
    {
        goto gso;
    }
    /*如果skb 是一串ip 分片报文,
    并且网络硬件不支持自动重组操作,
    就进行分片重组成一个报文
    */
    /*如果失败,就丢弃报文*/
    if (skb_has_frags(skb) &&
      !(dev->features & NETIF_F_FRAGLIST) &&
       __skb_linearize(skb))
    {
        goto out_kfree_skb;
    }
    /*如果报文分别放在多个页中,
    并且网络硬件不支持 G/S功能,就进行重组*/
    /*如果重组失败,就丢弃报文*/
    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:
    rcu_read_lock_bh();
    /*根据报文来取得发送队列*/
    txq = dev_pick_tx(dev, skb);
    /*取得发送队列的队列策略*/
    q = rcu_dereference(txq->qdisc);
#ifdef CONFIG_NET_CLS_ACT
    skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
    /*如果队列策略实现了入队操作,
    就调用__dev_xmit_skb进行发送*/
    if (q->enqueue)
    {
        rc = __dev_xmit_skb(skb, q, dev, txq);
        goto out;
    }
    /*如果队列策略没有实现入队操作,表明发送队列长度为0,
     *一般发送队列长度为0 的设备是网络虚拟设备,发送失败
     *后不需要缓存*/
    /*如果设备是up 状态,进行报文的发送*/
    if (dev->flags & IFF_UP)
    {
        int cpu = smp_processor_id();
        /*如果设备的发送队列锁的持有cpu 不是本cpu时*/
        if (txq->xmit_lock_owner != cpu)
        {
            /*获取发送队列的锁*/
            HARD_TX_LOCK(dev, txq, cpu);
            /*如果发送队列时开启的*/
            if (!netif_tx_queue_stopped(txq))
            {
                rc = NET_XMIT_SUCCESS;
                /*调用 dev_hard_start_xmit发送报文*/
                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
        {
            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;
}
EXPORT_SYMBOL(dev_queue_xmit);