網絡設備使用隊列來管理數據幀的輸出流量,每個隊列可以使用隊列策略算法來安排發送幀的優先級,使發送過程更高效。詳細的隊列策略處理屬於流量控制子系統的內容,本人還沒來的及研究,這裏先不涉及。本章討論沒配置隊列策略的情況下設備的發送隊列。
數據關聯:
dev->dev_queue->qdisc
網絡設備->發送隊列->隊列策略
每個網絡設備可以有自己的發送隊列,隊列數量可以自定義。
每個發送隊列可以有自己的隊列策略,隊列策略數量可以自定義。
網絡設備可以有自己的發送隊列,也可以沒有自己的發送隊列(這種情況下發送隊列長度爲零)。
1、一般虛擬網絡設備沒有自己的發送隊列。對於沒有發送隊列的網絡設備,其直接調用自己的發送函數進行發送,不需要由發包軟中斷來調度發送。如果發送失敗,就丟棄報文,不進行緩存。
2、有自己的發送隊列的網絡設備,如果發送失敗,會緩存到發送隊列,等待資源可用後再重新發送,這時報文緩存在隊列策略管理的優先級隊列中。其發送報文是由發包軟中斷進行調度來發送的。跟NAPI接收相似,NAPI接收時調度使用NAPI,發送時調度使用隊列策略qdisc。沒有配置隊列策略的發送隊列會使用默認的隊列策略pfifo_fast。
數據結構定義:
1、在struct net_device 結構體中定義如下幾個發送對列相關的字段:
struct net_device { /*指向設備的發送隊列列表*/ struct netdev_queue *_tx; /*發送隊列的個數*/ unsigned int num_tx_queues; /*現在使用的發送隊列個數*/ unsigned int real_num_tx_queues; /*設備使用的默認隊列策略*/ struct Qdisc *qdisc; /*每個發送隊列的最大長度*/ unsigned long tx_queue_len; /*發送隊列的全局鎖*/ spinlock_t tx_global_lock; }
2、發送隊列結構體
struct netdev_queue { /*隊列所屬網絡設備*/ struct net_device *dev; /*發送隊列對應的隊列策略結構體鏈表*/ struct Qdisc *qdisc; /*隊列的運行狀態*/ unsigned long state; struct Qdisc *qdisc_sleeping; spinlock_t _xmit_lock ____cacheline_aligned_in_smp; /*持有該隊列鎖的cpu id*/ int xmit_lock_owner; /*本次隊列最進一次開始發送的時間戳*/ unsigned long trans_start; /*隊列對發送報文的統計信息*/ unsigned long tx_bytes; unsigned long tx_packets; unsigned long tx_dropped; } ____cacheline_aligned_in_smp;
3、發送隊列策略結構體
struct Qdisc { /*報文入隊操作函數*/ int (*enqueue)(struct sk_buff *skb, struct Qdisc *dev); /*報文出隊操作*/ struct sk_buff * (*dequeue)(struct Qdisc *dev); unsigned flags; #define TCQ_F_BUILTIN 1 #define TCQ_F_THROTTLED 2 #define TCQ_F_INGRESS 4 #define TCQ_F_CAN_BYPASS 8 #define TCQ_F_MQROOT 16 #define TCQ_F_WARN_NONWC (1 << 16) /*申請隊列策略內存中爲字節對齊所預留的長度*/ int padded; /*隊列策略自己的一些操作函數, *申請隊列策略時根據這些函數來初始化隊列策略的一些字段 */ struct Qdisc_ops *ops; struct qdisc_size_table *stab; struct list_head list; u32 handle; u32 parent; /*引用計數*/ atomic_t refcnt; struct gnet_stats_rate_est rate_est; int (*reshape_fail)(struct sk_buff *skb, struct Qdisc *q); void *u32_node; /* This field is deprecated, but it is still used by CBQ * and it will live until better solution will be invented. */ struct Qdisc *__parent; /*所屬發送隊列*/ struct netdev_queue *dev_queue; /*有報文發送時把隊列策略掛到softnet_data->out_queue上*/ struct Qdisc *next_sched; /*指向最近一次發送失敗的報文,下次發送時優先發送該報文*/ struct sk_buff *gso_skb; /* * For performance sake on SMP, *we put highly modified fields at the end */ /*隊列策略的運行調度狀態*/ unsigned long state; /*隊列策略的默認緩存報文的隊列*/ struct sk_buff_head q; /*一些統計信息*/ struct gnet_stats_basic_packed bstats; struct gnet_stats_queue qstats; };
流量控制系統中每種隊列策略都提供了自己的函數,供數據鏈路層調用來完成隊列的操作。
enqueue:向隊列加入一個報文。
dequeue:從隊列中摘一個報文。
4、發送隊列的操作函數:
struct Qdisc_ops { struct Qdisc_ops *next; const struct Qdisc_class_ops *cl_ops; /*隊列策略的名字*/ char id[IFNAMSIZ]; /*隊列策略的私有數據結構體大小*/ int priv_size; /*入隊操作函數*/ int (*enqueue)(struct sk_buff *, struct Qdisc *); /*出隊操作函數*/ struct sk_buff * (*dequeue)(struct Qdisc *); /*初始化操作函數*/ int (*init)(struct Qdisc *, struct nlattr *arg); /*把隊列策略和發送隊列關聯的操作函數*/ void (*attach)(struct Qdisc *); struct module *owner; };
網絡設備發送隊列的創建
在申請設備時創建dev 的發送隊列,如果調用alloc_netdev()時,默認創建一個隊列。要創建多個發送隊列,調用alloc_netdev_mq().
#define alloc_netdev(sizeof_priv, name, setup) \ alloc_netdev_mq(sizeof_priv, name, setup, 1)
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name, void (*setup)(struct net_device *), unsigned int queue_count) { struct netdev_queue *tx; struct net_device *dev; ...... tx = kcalloc(queue_count, sizeof(struct netdev_queue), GFP_KERNEL); dev->_tx = tx; dev->num_tx_queues = queue_count; dev->real_num_tx_queues = queue_count; netdev_init_queues(dev); ...... return dev; }
發送隊列策略的創建
struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, struct Qdisc_ops *ops) { void *p; struct Qdisc *sch; unsigned int size; int err = -ENOBUFS; /* ensure that the Qdisc and *the private data are 32-byte aligned */ size = QDISC_ALIGN(sizeof(*sch)); /*申請隊列策略時,隊列結構體下緊接着 *放着隊列策略的私有結構體 */ size += ops->priv_size + (QDISC_ALIGNTO - 1); p = kzalloc(size, GFP_KERNEL); if (!p) { goto errout; } sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p); /*設置padded 字段*/ sch->padded = (char *) sch - (char *) p; INIT_LIST_HEAD(&sch->list); skb_queue_head_init(&sch->q); sch->ops = ops; /*根據ops 裏定義的函數來初始化隊列策略的一些函數指針*/ sch->enqueue = ops->enqueue; sch->dequeue = ops->dequeue; sch->dev_queue = dev_queue; dev_hold(qdisc_dev(sch)); atomic_set(&sch->refcnt, 1); return sch; errout: return ERR_PTR(err); }
根據給出的ops ,給發送隊列來創建一個默認的隊列策略
struct Qdisc * qdisc_create_dflt(struct net_device *dev, struct netdev_queue *dev_queue, struct Qdisc_ops *ops, unsigned int parentid) { struct Qdisc *sch; /*申請一個隊列策略*/ sch = qdisc_alloc(dev_queue, ops); if (IS_ERR(sch)) { goto errout; } sch->parent = parentid; /*調用ops->init來初始化隊列策略*/ if (!ops->init || ops->init(sch, NULL) == 0) { return sch; } qdisc_destroy(sch); errout: return NULL; }