1、本文档的 Copyleft 归 yfydz 所有,使用 GPL 发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。msn: yfydz_来源:http:/1. 前言linux 内核中提供了流量控制的相关处理功能,相关代码在 net/sched 目录下;而应用层上的控制是通过 iproute2 软件包中的 tc 来实现,tc 和 sched 的关系就好象 iptables 和netfilter 的关系一样,一个是用户层接口,一个是具体实现,关于 tc 的使用方法可详将Linux Advanced Routing HOWTO,本文主要分析内核中的具体实现。流控包括几个部分:
2、 流控算法 , 通常在 net/sched/sch_*.c 中实现, 缺省的是 FIFO, 是比较典型的黑盒模式, 对外只看到入队和出对两个操作; 流控结构的操作处理; 和用户空间的控制接口, 是通过rtnetlink 实现的。以下内核代码版本为 2.6.19.2。2. 控制入口2.1 控制入口linux 流控功能反映为网卡设备的属性,表明是网络最底层的处理部分, 已经和上层的网络协议栈无关了:/* include/linux/netdevice.h */struct net_device/* Cache line mostly used on queue transmit path (qdi
3、sc)*/* device queue lock */spinlock_t queue_lock _cacheline_aligned_in_smp;/ 这是发送数据时的队列处理struct Qdisc *qdisc;/ 网卡停止时保存网卡活动时的队列处理方法struct Qdisc *qdisc_sleeping;/ 网卡处理的数据队列链表struct list_head qdisc_list;/ 最大队列长度unsigned long tx_queue_len; /* Max frames per queue allowed */* Partially transmitted GSO pa
4、cket. */struct sk_buff *gso_skb;/* ingress path synchronizer */ 输入流控锁spinlock_t ingress_lock;/ 这是对于接收数据时的队列处理struct Qdisc *qdisc_ingress;2.1.2 输出流控数据发出流控处理时,上层的所有处理已经完成,数据包已经交到网卡设备进行发送,在数据发送时进行相关的流控处理网络数据的出口函数为 dev_queue_xmit(); 如果是接收流控, 数据只是刚从网卡设备中收到, 还未交到网络上层处理, 不过网卡的输入流控不是必须的, 缺省情况下并不进行流控,输入流控入口函
5、数为 ing_filter()函数,该函数被skb_receive_skb()调用:/* net/core/dev.c */int dev_queue_xmit(struct sk_buff *skb)struct net_device *dev = skb-dev;struct Qdisc *q;int rc = -ENOMEM;/* Updates of qdisc are serialized by queue_lock. * The struct Qdisc which is pointed to by qdisc is now a * rcu structure - it may b
6、e accessed without acquiring * a lock (but the structure may be stale.) The freeing of the* qdisc will be deferred until its known that there are no * more references to it.* * If the qdisc has an enqueue function, we still need to * hold the queue_lock before calling it, since queue_lock* also seri
7、alizes access to the device queue.*/ 获取网卡的 qdisc 指针, 此出不需要锁, 是各个 CPU 的私有数据q = rcu_dereference(dev-qdisc);#ifdef CONFIG_NET_CLS_ACTskb-tc_verd = SET_TC_AT(skb-tc_verd,AT_EGRESS);#endif/ 如果队列输入非空, 将数据包入队/ 对于物理网卡设备, 缺省使用的是 FIFO qdisc, 该成员函数非空, 只有逻辑网卡/ 才可能为空if (q-enqueue) /* Grab device queue */ 加锁spin_
8、lock(/ 可以直接访问 dev-qdisc 了q = dev-qdisc;if (q-enqueue) / 入队处理rc = q-enqueue(skb, q);/ 运行流控, 出队列操作qdisc_run(dev);spin_unlock(rc = rc = NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;goto out;spin_unlock(/ 出队操作static inline void qdisc_run(struct net_device *dev)if (!netif_queue_stopped(dev) /* net/sched/sch_g
9、eneric.c */void _qdisc_run(struct net_device *dev)/ 如果是 noop_qdisc 流控, 实际是丢包if (unlikely(dev-qdisc = while (qdisc_restart(dev) state);/* Kick device.Note, that this procedure can be called by a watchdog timer, so thatwe do not check dev-tbusy flag here.Returns: 0 - queue is empty.0 - queue is not em
10、pty, but throttled.tbusy != 0.NOTE: Called under dev-queue_lock with locally disabled BH.*/static inline int qdisc_restart(struct net_device *dev)struct Qdisc *q = dev-qdisc;struct sk_buff *skb;/* Dequeue packet */ 数据包出队if (skb = dev-gso_skb) | (skb = q-dequeue(q) unsigned nolock = (dev-features dev
11、-gso_skb = NULL;2.1.3 输入流控输入流控好象不是必须的,目前内核需要配置 CONFIG_NET_CLS_ACT 选项才起作用:/* net/core/dev.c */int netif_receive_skb(struct sk_buff *skb)#ifdef CONFIG_NET_CLS_ACTif (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev = NULL; /* noone else should process this after*/ else skb-tc_verd = SET_TC_O
12、K2MUNGE(skb-tc_verd);ret = ing_filter(skb);if (ret = TC_ACT_SHOT | (ret = TC_ACT_STOLEN) kfree_skb(skb);goto out;skb-tc_verd = 0;ncls:#endifstatic int ing_filter(struct sk_buff *skb) struct Qdisc *q;struct net_device *dev = skb-dev;int result = TC_ACT_OK;/ 如果网卡设备有输入流控处理if (dev-qdisc_ingress) _u32 tt
13、l = (_u32) G_TC_RTTL(skb-tc_verd);if (MAX_RED_LOOP %s)n“,skb-input_dev-name, skb-dev-name);return TC_ACT_SHOT;/ 设置数据包的 TC 参数skb-tc_verd = SET_TC_RTTL(skb-tc_verd,ttl);skb-tc_verd = SET_TC_AT(skb-tc_verd,AT_INGRESS);spin_lock(if (q = dev-qdisc_ingress) != NULL)/ 数据入队result = q-enqueue(skb, q);spin_un
14、lock(return result;2.2 初始化本节先跟踪一下网卡设备的 qdisc 指针是如何被赋值的, 缺省赋值为何值.在网卡设备的初始化函数 register_netdevice()函数中调用 dev_init_scheduler()函数对网卡设备的流控队列处理进行了初始化, 也就是说每个网络网卡设备的 qdisc 指针都不会是空的:/* net/sched/sch_gentric.c */void dev_init_scheduler(struct net_device *dev)qdisc_lock_tree(dev);/ 处理发出数据的 qdisc 是必须的, 而处理输入数据的
15、 qdisc_ingress 则不是必须的/ 缺省情况下的 qdiscdev-qdisc = dev-qdisc_sleeping = INIT_LIST_HEAD(qdisc_unlock_tree(dev);dev_watchdog_init(dev);当然 noop_qdisc 中的调度是不可用的, 只进行丢包处理;网卡在激活(dev_open) 时会调用dev_activate()函数重新对 qdisc 指针赋值,但未对 qdisc_ingress 赋值:/* net/sched/sch_generic.c */void dev_activate(struct net_device *
16、dev)/* No queueing discipline is attached to device;create default one i.e. pfifo_fast for devices,which need queueing and noqueue_qdisc forvirtual interfaces*/ 如果当前的 qdisc_sleeping 是 noop_qdisc,重新找一个流控操作指针if (dev-qdisc_sleeping = / 前提条件是发送队列长度非 0, 这正常情况肯定满足的if (dev-tx_queue_len) / 对 dev 设备建立 fifo 处
17、理 , 只是缺省的网卡发送策略: FIFO, 先入先出qdisc = qdisc_create_dflt(dev, if (qdisc = NULL) printk(KERN_INFO “%s: activation failedn“, dev-name);return;write_lock(list_add_tail(write_unlock( else qdisc = write_lock(/ 对 qdisc_sleeping 赋值dev-qdisc_sleeping = qdisc;write_unlock(/ 如果现在网线没插, 返回if (!netif_carrier_ok(dev)
18、/* Delay activation until next carrier-on event */return;spin_lock_bh(/ 将网卡当前的 qdisc 赋值为 qdisc_sleeping 所指的 qdiscrcu_assign_pointer(dev-qdisc, dev-qdisc_sleeping);if (dev-qdisc != dev_watchdog_up(dev);spin_unlock_bh(qdisc_sleeping 用于保存在网卡起效时使用的 qdisc, 因为在网卡 down 或网线拔除不可用时, 网卡设备的 qdisc 指针会指向 noqueue_
19、qdisc, 该 qdisc 操作就只是丢包, 这就是为什么在没插网线情况下也可以调用发包函数处理的原因, 结果就是直接丢包。/* net/sched/sch_generic.c */void dev_deactivate(struct net_device *dev)struct Qdisc *qdisc;spin_lock_bh(qdisc = dev-qdisc;/ 将网卡当前 qdisc 设置为 noop_qdiscdev-qdisc = / 释放原来的 qdiscqdisc_reset(qdisc);spin_unlock_bh(dev_watchdog_down(dev);/* W
20、ait for outstanding dev_queue_xmit calls. */synchronize_rcu();/* Wait for outstanding qdisc_run calls. */while (test_bit(_LINK_STATE_QDISC_RUNNING, if (dev-gso_skb) kfree_skb(dev-gso_skb);dev-gso_skb = NULL;3. 数据结构流控处理对外表现是一个黑盒,外部只能看到数据入队和出队,但内部队列是如何操作和管理外面是不知道的;另外处理队列处理外,流控还有一个调度器,该调度器将数据进行分类,然后对不同
21、类型的数据采取不同的流控处理,所分的类型可能是多级的,形成一个树型的分类树。流控的基本数据结构是 struct Qdisc(queueing discipline,直译是“排队纪律”,意译为“流控”),这是内核中为数不多的以大写字母开头结构名称之一:/* include/net/sch_generic.h */struct Qdisc/ 入队操作int (*enqueue)(struct sk_buff *skb, struct Qdisc *dev);/ 出队操作struct sk_buff * (*dequeue)(struct Qdisc *dev);/ 标志unsigned flags
22、;#define TCQ_F_BUILTIN 1#define TCQ_F_THROTTLED 2#define TCQ_F_INGRESS 4int padded;/ Qdisc 的基本操作结构struct Qdisc_ops *ops;/ 句柄u32 handle;u32 parent;atomic_t refcnt;/ 数据包链表头struct sk_buff_head q;/ 网卡设备struct net_device *dev;struct list_head list;/ 统计信息struct gnet_stats_basic bstats;struct gnet_stats_qu
23、eue qstats;/ 速率估计struct gnet_stats_rate_est rate_est;/ 流控锁spinlock_t *stats_lock;struct rcu_head q_rcu;int (*reshape_fail)(struct sk_buff *skb,struct Qdisc *q);/* This field is deprecated, but it is still used by CBQ* and it will live until better solution will be invented.*/ 父节点, 但基本已经被淘汰了struct Qd
24、isc *_parent;流控队列的基本操作结构:struct Qdisc_ops/ 链表中的下一个struct Qdisc_ops *next;/ 类别操作结构struct Qdisc_class_ops *cl_ops;/ Qdisc 的名称, 从数组大小看应该就是网卡名称char idIFNAMSIZ;/ 私有数据大小int priv_size;/ 入队int (*enqueue)(struct sk_buff *, struct Qdisc *);/ 出队struct sk_buff * (*dequeue)(struct Qdisc *);/ 将数据包重新排队int (*requeu
25、e)(struct sk_buff *, struct Qdisc *);/ 丢弃unsigned int (*drop)(struct Qdisc *);/ 初始化int (*init)(struct Qdisc *, struct rtattr *arg);/ 复位为初始状态,释放缓冲, 删除定时器,清空计数器void (*reset)(struct Qdisc *);/ 释放void (*destroy)(struct Qdisc *);/ 更改 Qdisc 参数int (*change)(struct Qdisc *, struct rtattr *arg);/ 输出int (*dum
26、p)(struct Qdisc *, struct sk_buff *);int (*dump_stats)(struct Qdisc *, struct gnet_dump *);struct module *owner;流控队列类别操作结构:struct Qdisc_class_ops/* Child qdisc manipulation */ 减子节点int (*graft)(struct Qdisc *, unsigned long cl,struct Qdisc *, struct Qdisc *);/ 增加子节点struct Qdisc * (*leaf)(struct Qdisc
27、 *, unsigned long cl);/* Class manipulation routines */ 获取, 增加使用计数unsigned long (*get)(struct Qdisc *, u32 classid);/ 释放, 减少使用计数void (*put)(struct Qdisc *, unsigned long);/ 改变int (*change)(struct Qdisc *, u32, u32,struct rtattr *, unsigned long *);/ 删除int (*delete)(struct Qdisc *, unsigned long);/ 遍
28、历void (*walk)(struct Qdisc *, struct qdisc_walker * arg);/* Filter manipulation */struct tcf_proto * (*tcf_chain)(struct Qdisc *, unsigned long);/ tc 捆绑unsigned long (*bind_tcf)(struct Qdisc *, unsigned long,u32 classid);/ tc 解除void (*unbind_tcf)(struct Qdisc *, unsigned long);/* rtnetlink specific
29、*/ 输出int (*dump)(struct Qdisc *, unsigned long,struct sk_buff *skb, struct tcmsg*);int (*dump_stats)(struct Qdisc *, unsigned long,struct gnet_dump *);/ 流控速率控制表结构struct qdisc_rate_tablestruct tc_ratespec rate;u32 data256;struct qdisc_rate_table *next;int refcnt;4. 基本操作各种流控算法是通过流控操作结构实现,然后这些操作结构登记到内核
30、的流控链表,在使用时可为网卡构造新的流控结构,将该结构和某种流控操作结构挂钩,这样就实现网卡采用某种策略发送数据进行流控,所有操作在用户空间都可通过 tc 程序设置。4.1 Qdisc 的一些基本操作4.1.1 分配新的流控结构/* net/sched/sch_generic.c */ 分配新的 Qdisc 结构, Qdisc 的操作结构由函数参数指定struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops)void *p;struct Qdisc *sch;unsigned int size;int err
31、 = -ENOBUFS;/* ensure that the Qdisc and the private data are 32-byte aligned */ Qdisc 空间按 32 字节对齐size = QDISC_ALIGN(sizeof(*sch);/ 增加私有数据空间size += ops-priv_size + (QDISC_ALIGNTO - 1);p = kzalloc(size, GFP_KERNEL);if (!p)goto errout;/ 确保从缓冲区中的 sch 到缓冲区结束点空间是 32 字节对齐的sch = (struct Qdisc *) QDISC_ALIG
32、N(unsigned long) p);/ 填充字节的数量sch-padded = (char *) sch - (char *) p;/ +-+/ |_|_|/ / | pad | |/ p sch / 初始化链表, 将用于挂接到 dev 的 Qdisc 链表INIT_LIST_HEAD(/ 初始化数据包链表skb_queue_head_init(/ Qdisc 结构参数sch-ops = ops;sch-enqueue = ops-enqueue;sch-dequeue = ops-dequeue;sch-dev = dev;/ 网卡使用计数增加dev_hold(dev);sch-stat
33、s_lock = atomic_set(return sch;errout:return ERR_PTR(-err);struct Qdisc * qdisc_create_dflt(struct net_device *dev, struct Qdisc_ops *ops)struct Qdisc *sch;/ 分配 Qdisc 结构 sch = qdisc_alloc(dev, ops);if (IS_ERR(sch)goto errout;/ 如果没有初始化函数或者初始化成功, 返回 Qdisc 结构if (!ops-init | ops-init(sch, NULL) = 0)retu
34、rn sch;/ 初始化失败, 释放 Qdiscqdisc_destroy(sch);errout:return NULL;/* Under dev-queue_lock and BH! */ 调用 Qdisc 操作函数中的 reset 函数void qdisc_reset(struct Qdisc *qdisc)struct Qdisc_ops *ops = qdisc-ops;if (ops-reset)ops-reset(qdisc);/* this is the rcu callback function to clean up a qdisc when there * are no
35、further references to it */ 真正释放 Qdisc 缓冲区static void _qdisc_destroy(struct rcu_head *head)struct Qdisc *qdisc = container_of(head, struct Qdisc, q_rcu);/ qdisc-padded 就是缓冲区头的位置kfree(char *) qdisc - qdisc-padded);/* Under dev-queue_lock and BH! */ 释放 Qdiscvoid qdisc_destroy(struct Qdisc *qdisc)struc
36、t Qdisc_ops *ops = qdisc-ops;/ 检查 Qdisc 的使用计数if (qdisc-flags / 将 Qdisc 从网卡设备的 Qdisc 链表中断开list_del(#ifdef CONFIG_NET_ESTIMATORgen_kill_estimator(#endif/ 复位操作if (ops-reset)ops-reset(qdisc);/ 内部释放操作if (ops-destroy)ops-destroy(qdisc);/ 减少操作结构的模块计数module_put(ops-owner);/ 减少网卡使用计数dev_put(qdisc-dev);/ 对每个
37、CPU 的数据进行具体空间释放call_rcu(/* include/net/sch_generic.h */ 将 skb 包添加到数据队列最后static inline int _qdisc_enqueue_tail(struct sk_buff *skb, struct Qdisc *sch,struct sk_buff_head *list)/ 将数据包连接到数据包链表尾_skb_queue_tail(list, skb);/ 更新统计信息/ 当前队列中数据包的数据长度增加sch-qstats.backlog += skb-len;/ Qdisc 处理的数据包数字节数增加sch-bsta
38、ts.bytes += skb-len;sch-bstats.packets+;return NET_XMIT_SUCCESS;static inline int qdisc_enqueue_tail(struct sk_buff *skb, struct Qdisc *sch)return _qdisc_enqueue_tail(skb, sch, / 将队列头的数据包出队列static inline struct sk_buff *_qdisc_dequeue_head(struct Qdisc *sch,struct sk_buff_head *list)/ 从 skb 链表中头 skb
39、 包出队列struct sk_buff *skb = _skb_dequeue(list);/ 减少当前数据队列数据长度计数if (likely(skb != NULL)sch-qstats.backlog -= skb-len;return skb;static inline struct sk_buff *qdisc_dequeue_head(struct Qdisc *sch)return _qdisc_dequeue_head(sch, / 将队列尾的数据包出队列static inline struct sk_buff *_qdisc_dequeue_tail(struct Qdisc
40、 *sch,struct sk_buff_head *list)/ 从链表为提出数据包struct sk_buff *skb = _skb_dequeue_tail(list);if (likely(skb != NULL)sch-qstats.backlog -= skb-len;return skb;static inline struct sk_buff *qdisc_dequeue_tail(struct Qdisc *sch)return _qdisc_dequeue_tail(sch, / 将数据包重新入队static inline int _qdisc_requeue(struc
41、t sk_buff *skb, struct Qdisc *sch,struct sk_buff_head *list)/ 添加到队列头_skb_queue_head(list, skb);/ 增加队列数据长度计数, 但不增加 Qdisc 处理的数据包数字节数sch-qstats.backlog += skb-len;sch-qstats.requeues+;return NET_XMIT_SUCCESS;static inline int qdisc_requeue(struct sk_buff *skb, struct Qdisc *sch)return _qdisc_requeue(sk
42、b, sch, / 复位 Qdisc 队列static inline void _qdisc_reset_queue(struct Qdisc *sch,struct sk_buff_head *list)/* We do not know the backlog in bytes of this list, it* is up to the caller to correct it*/ 释放 Qdisc 当前数据包队列中的所有数据包skb_queue_purge(list);static inline void qdisc_reset_queue(struct Qdisc *sch)_qdi
43、sc_reset_queue(sch, sch-qstats.backlog = 0;/ 丢弃 Qdisc 数据队列尾的数据包static inline unsigned int _qdisc_queue_drop(struct Qdisc *sch,struct sk_buff_head *list)/ 取队列尾数据包struct sk_buff *skb = _qdisc_dequeue_tail(sch, list);if (likely(skb != NULL) / 释放该数据包unsigned int len = skb-len;kfree_skb(skb);return len;r
44、eturn 0;static inline unsigned int qdisc_queue_drop(struct Qdisc *sch)return _qdisc_queue_drop(sch, / 丢弃数据包static inline int qdisc_drop(struct sk_buff *skb, struct Qdisc *sch)/ 释放数据包kfree_skb(skb);/ 丢包计数增加sch-qstats.drops+;return NET_XMIT_DROP;/ 整形失败丢包static inline int qdisc_reshape_fail(struct sk_b
45、uff *skb, struct Qdisc *sch)sch-qstats.drops+;#ifdef CONFIG_NET_CLS_POLICEif (sch-reshape_fail = NULL | sch-reshape_fail(skb, sch)goto drop;return NET_XMIT_SUCCESS;drop:#endifkfree_skb(skb);return NET_XMIT_DROP;/* net/sched/sch_api.c */* We know handle. Find qdisc among all qdiscs attached to device
46、(root qdisc, all its children, children of children etc.)*/ 根据句柄查找 Qdisc, 句柄是个 32 位整数用于标识 Qdisc 的struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)struct Qdisc *q;read_lock(/ 遍历 dev 设备 Qdisc 链表list_for_each_entry(q, return q;read_unlock(return NULL;/ 返回指定类别的 Qdisc 叶节点static struct Qdisc
47、*qdisc_leaf(struct Qdisc *p, u32 classid)unsigned long cl;struct Qdisc *leaf;/ Qdisc 类别操作struct Qdisc_class_ops *cops = p-ops-cl_ops;if (cops = NULL)return NULL;/ 获取指定 classid 类型的类别句柄cl = cops-get(p, classid);if (cl = 0)return NULL;/ 调用类别操作结构的 left 成员函数获取叶 Qdisc 节点leaf = cops-leaf(p, cl);cops-put(p,
48、 cl);return leaf;/* Graft qdisc “new“ to class “classid“ of qdisc “parent“ orto device “dev“.Old qdisc is not destroyed but returned in *old.*/ “嫁接 “Qdisc, 将新的 Qdisc 节点添加到父节点作为叶节点static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,u32 classid,struct Qdisc *new, struct Qdisc *old)int err = 0;struct Qdisc *q = *old;if (parent = NULL) / 父 qdisc 节点为空, 将新节点作为 dev 的基本 qdisc, 返回 dev 原来的老的 qdiscif (q el