發送報文過程的調度 (linux網絡子系統學習 第十二節 )

linux協議棧中,發送隊列管理隊列策略,而直接管理髮送報文的是隊列策略。所有發包軟中斷中調度的是隊列策略,而不是發送隊列。


一、softnet_date 結構體中爲報文的發送定義如下字段:

struct softnet_data
{
    //有報文需要發送的隊列策略鏈表。
    struct Qdisc    *output_queue;
    //調用 dev_kfree_skb_irq()函數延遲釋放的skb鏈表。
    struct sk_buff  *completion_queue;
};


二、隊列策略的調度:


1、隊列策略的運行狀態:

運行狀態是由Qdisc->state字段來標記的。該字段使用的是位圖,每一位定義如下

enum qdisc_state_t
{
    //隊列策略處於運行狀態
    __QDISC_STATE_RUNNING,
                                                                                                                                                                                                                                                                                                                                               
    //隊列策略已經被調度了,既已經添加到了軟中斷管理的鏈表中了。
    __QDISC_STATE_SCHED,
                                                                                                                                                                                                                                                                                                                                               
    //隊列策略被顯示的禁用了。
    __QDISC_STATE_DEACTIVATED,
};

2、發送隊列策略的調度:

void __netif_schedule(struct Qdisc *q)
{
    /*如果隊列策略沒被調度,設置隊列策略爲調度狀態*/
    if (!test_and_set_bit(__QDISC_STATE_SCHED, &q->state))
    {
        /*調度設備隊列策略*/
        __netif_reschedule(q);
    }
}
static inline void __netif_reschedule(struct Qdisc *q)
{
    struct softnet_data *sd;
    unsigned long flags;
    /*關閉硬中斷*/
    local_irq_save(flags);
    /*取得當前CPU上的softnet_data結構*/
    sd = &__get_cpu_var(softnet_data);
    /*把設備的qdisc隊列策略掛到softnet_data的 output_queue上*/
    q->next_sched = sd->output_queue;
    sd->output_queue = q;
    /*調度發包軟中斷*/
    raise_softirq_irqoff(NET_TX_SOFTIRQ);
    /*恢復硬中斷*/
    local_irq_restore(flags);
}


三、啓動/停止設備發送數據

網絡設備的發送的控制是由發送隊列來控制的。通過設置發送隊列的運行狀態來控制網絡設備的發送。


1、發送隊列的運行狀態:

隊列運行狀態dev_queue->state:該字段是用位圖來標記隊列的運行狀態。


enum netdev_queue_state_t
{
    /*如果設置了該位,表示設備發送隊列被關閉。
      清除該位,表示開啓發送隊列。*/
    __QUEUE_STATE_XOFF,
                                                                                                                                                                                                                                                                                   
    //如果設置了該位,表示設備發送隊列已經僵死。
    __QUEUE_STATE_FROZEN,
};

2、開啓設備的發送:

void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
    clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
void netif_start_queue(struct net_device *dev)
{
    netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}
/*開啓全部發送隊列:*/
void netif_tx_start_all_queues(struct net_device *dev)
{
    unsigned int i;
    for (i = 0; i < dev->num_tx_queues; i++)
    {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
        netif_tx_start_queue(txq);
    }
}

設備有一個隊列時調用netif_start_queue(),有多個發送隊列時用netif_tx_start_all_queues()。

開啓發送隊列一般由dev->netdev_ops-> ndo_open(dev)函數進行設置。


3、關閉設備的發送:

void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
    set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
void netif_stop_queue(struct net_device *dev)
{
    netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
}
/*關閉全部發送隊列:*/
void netif_tx_stop_all_queues(struct net_device *dev)
{
    unsigned int i;
    for (i = 0; i < dev->num_tx_queues; i++)
    {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
        netif_tx_stop_queue(txq);
    }
}

設備有一個隊列時調用netif_stop_queue(),有多個發送隊列時調用netif_tx_stop_all_queues()。

關閉發送隊列場景一般如下:

(1)、由dev->netdev_ops->ndo_stop(dev)函數進行設置。

(2)、當驅動程序意識到硬件沒有足夠的空間緩存新的發送報文時,驅動程序調用函數關閉發送隊列,這樣避免在將來的發送過程中浪費資源,因爲內核知道發送會失敗,就不會再發送新的報文給網絡設備。


4、喚醒網絡設備的發送隊列:

喚醒網絡設備發送隊列,重新啓動網絡設備的發送功能。

void netif_tx_wake_queue(struct netdev_queue *dev_queue)
{
    /*開啓發送隊列,重新調度發送隊列的隊列策略*/
    if (test_and_clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state))
        __netif_schedule(dev_queue->qdisc);
}
void netif_wake_queue(struct net_device *dev)
{
    netif_tx_wake_queue(netdev_get_tx_queue(dev, 0));
}
void netif_tx_wake_all_queues(struct net_device *dev)
{
    unsigned int i;
    for (i = 0; i < dev->num_tx_queues; i++) {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
        netif_tx_wake_queue(txq);
    }
}

當發送隊列已經被關閉,但網卡現在已經有資源可用了,就需要重新啓動設備的發送過程,調用以上函數來喚醒發送隊列。


四、發包軟中斷:

1、發包軟中斷的註冊:

static int __init net_dev_init(void)
{
    。。。。。。
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    。。。。。。
}


2、發包軟中斷處理函數:

該函數做兩件工作

(1)、回收調用dev_kfree_skb函數後延遲釋放的報文。

(2)、調用qdisc_restart()來處理等待發送的隊列策略上的報文。


static void net_tx_action(struct softirq_action *h)
{
    /*取得本地cpu的softnet_data*/
    struct softnet_data *sd = &__get_cpu_var(softnet_data);
                                                                                                                                                      
    /*如果completion_queue上有要釋放的報文,就把上面所有的報文釋放掉*/
    if (sd->completion_queue)
    {
        /*取得要釋放的報文鏈,把softnet_data的completion_queue置空*/
        struct sk_buff *clist;
        local_irq_disable();
        clist = sd->completion_queue;
        sd->completion_queue = NULL;
        local_irq_enable();
                                                                                                                                                          
                                                                                                                                                    
        /*循環釋放所有報文*/
        while (clist)
        {
             struct sk_buff *skb = clist;
             clist = clist->next;
             WARN_ON(atomic_read(&skb->users));
             __kfree_skb(skb);
        }
    }
                                                                                                                                                      
    /*如果softnet_data上有等待發送報文的隊列策略*/
    if (sd->output_queue)
    {
        /*取得等待發送報文的隊列策略*/
        struct Qdisc *head;
        local_irq_disable();
        head = sd->output_queue;
        sd->output_queue = NULL;
        local_irq_enable();
        /*循環處理每個等待發送報文的隊列策略*/
        while (head)
        {
            struct Qdisc *q = head;
            spinlock_t *root_lock;
            head = head->next_sched;
            /*發送隊列策略上的發送隊列由qdisc->q.lock來保護*/
            root_lock = qdisc_lock(q);
            /*如果獲得鎖成功,就處理隊列策略上的報文*/
            if (spin_trylock(root_lock))
            {
                smp_mb__before_clear_bit();
                /*清除隊列策略的被調度狀態*/
                clear_bit(__QDISC_STATE_SCHED,
                          &q->state);
                /*調用qdisc_run 來處理隊列策略上要發送的報文*/
                qdisc_run(q);
                spin_unlock(root_lock);
            }
            else
            {
                /*如果獲得鎖失敗,並且隊列策略沒有被禁止,
                 就重新調度隊列策略*/
                if (!test_bit(__QDISC_STATE_DEACTIVATED,
                              &q->state))
                {
                    __netif_reschedule(q);
                }
                /*如果獲得鎖失敗,並且隊列策略已經被禁止,
                 就清除隊列策略的調度狀態*/
                else
                {
                    smp_mb__before_clear_bit();
                    clear_bit(__QDISC_STATE_SCHED,
                                &q->state);
                }
            }
        }
    }
}

3處理隊列策略上報文的函數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))
    {
        /*如果需要重新調度或運行時間已經超過了一個jiffies了,
          就停止運行並重新調度該隊列策略*/
        if (need_resched() || jiffies != start_time)
        {
             __netif_schedule(q);
             break;
        }
    }
    /*處理完成,清除隊列策略的運行狀態*/
    clear_bit(__QDISC_STATE_RUNNING, &q->state);
}


處理隊列策略上的一個報文

static inline int qdisc_restart(struct Qdisc *q)
{
    struct netdev_queue *txq;
    struct net_device *dev;
    spinlock_t *root_lock;
    struct sk_buff *skb;
    /* 從隊列策略上取一個報文 */
    skb = dequeue_skb(q);
    if (unlikely(!skb))
        return 0;
    root_lock = qdisc_lock(q);
    dev = qdisc_dev(q);
    /*根據skb找到所屬設備的發送隊列*/
    txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
    /*直接發送出去,詳見下一節*/
    return sch_direct_xmit(skb, q, dev, txq, root_lock);
}


從隊列策略所管理的報文隊列上取的一個報文:

static inline struct sk_buff *dequeue_skb(struct Qdisc *q)
{
    struct sk_buff *skb = q->gso_skb;
   /*如果q->gso_skb不爲空,表示上次發送時有發送失敗的報文,
     這時要先發該報文,把該報文摘下來後返回*/
    if (unlikely(skb))
    {
        struct net_device *dev = qdisc_dev(q);
        struct netdev_queue *txq;
        /* check the reason of requeuing without tx lock first */
        txq = netdev_get_tx_queue(dev,
                       skb_get_queue_mapping(skb));
        if (!netif_tx_queue_stopped(txq) &&
            !netif_tx_queue_frozen(txq))
        {
            q->gso_skb = NULL;
            q->q.qlen--;
        }
        else
        {
            skb = NULL;
        }
    }
                                                                                                                            
    /*q->gso_skb上沒有報文,就調用隊列策略自己的出隊函數
     取一個skb並返回*/
    else
    {
        skb = q->dequeue(q);
    }
    return skb;
}


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