在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; }