報文的發送函數 (linux網絡子系統學習 第十三節)

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);









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章