网络设备使用队列来管理数据帧的输出流量,每个队列可以使用队列策略算法来安排发送帧的优先级,使发送过程更高效。详细的队列策略处理属于流量控制子系统的内容,本人还没来的及研究,这里先不涉及。本章讨论没配置队列策略的情况下设备的发送队列。
数据关联:
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; }