Linux QOS实现框架分析
作者:徐晓东(joyxxd@163.com)
Linux中的QOS分为入口(Ingress)部分和出口(Egress)部分,入口部分主要用于进行入口流量限速(policing),出口部分的QOS用于队列调度(queuing scheduling)。
以下分析所参考的linux内核版本为2.6.21。 1. Ingress QOS
Ingress QOS在内核的入口点有两个,但是不能同时启用,这取决于内核编译选项。当打开了
CONFIG_NET_CLS_ACT时,入口点在src/net/core/dev.c的netif_receive_skb函数中,代码片段如下: #ifdef CONFIG_NET_CLS_ACT if (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_OK2MUNGE(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: #endif
进入ing_filter后,代码片段如下: if ((q = dev->qdisc_ingress) != NULL) result = q->enqueue(skb, q);
我们可以看到,在这里判断了是否在设备上配置了ingress调度规则,如果配置了则调用其enqueue函数进行处理。在这里实际上调用的是src/net/core/sched/sch_ingress.c中的ingress_enqueue函数。
当没有打开CONFIG_NET_CLS_ACT,而是打开了CONFIG_NET_CLS_POLICE和CONFIG_NETFILTER时,就会在netfilter的PREROUTING钩子点处调用ing_hook函数,该函数的代码片段如下: if (dev->qdisc_ingress) {
spin_lock(&dev->queue_lock);
if ((q = dev->qdisc_ingress) != NULL) fwres = q->enqueue(skb, q); spin_unlock(&dev->queue_lock); }
可以看出与ing_filter的处理部分类似,最终都是调用了ingress qdisc的enqueue函数(即ingress_enqueue函数)。 ing_hook函数是在src/net/sched/sch_ingress.c文件中进行注册的,在sch_ingress文件中定义了一个结构实例ingress_qdisc_ops,如下所示: static struct Qdisc_ops ingress_qdisc_ops = { .next = NULL,
.cl_ops = &ingress_class_ops, .id = \
.priv_size = sizeof(struct ingress_qdisc_data), .enqueue = ingress_enqueue, .dequeue = ingress_dequeue, .requeue = ingress_requeue, .drop = ingress_drop, .init = ingress_init, .reset = ingress_reset, .destroy = ingress_destroy, .change = NULL,
.dump = ingress_dump, .owner = THIS_MODULE, };
所有的qdisc都会有这样的一个对象实例,在模块初始化时会调用register_qdisc函数将自己的struct Qdisc_ops结构实例注册到链表中,该链表头是qdisc_base,定义在sch_api.c文件中,static struct Qdisc_ops *qdisc_base。 当通过tc qdisc命令配置了ingress qdisc规则时,会调用到ingress_init函数,进入ingress_init函数代码片段如下:
#ifndef CONFIG_NET_CLS_ACT #ifdef CONFIG_NETFILTER if (!nf_registered) {
if (nf_register_hook(&ing_ops) < 0) {
printk(\ return -EINVAL; }
nf_registered++;
if (nf_register_hook(&ing6_ops) < 0) {
printk(\ \ } else
nf_registered++; }
#endif #endif
我们看到当没有定义CONFIG_NET_CLS_ACT,但是定义了CONFIG_NETFILTER时,会调用
nf_register_hook函数注册ing_ops和ing6_ops结构实例,ing_ops和ing6_ops的定义如下: static struct nf_hook_ops ing_ops = { .hook = ing_hook, .owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_FILTER + 1, };
static struct nf_hook_ops ing6_ops = { .hook = ing_hook, .owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_PRE_ROUTING, .priority = NF_IP6_PRI_FILTER + 1, };
针对IPV4和IPV6协议,注册的hook函数都是ing_hook,hook点是在PREROUTING,优先级低于NF_IP_PRI_FILTER。
当前比较推荐第一种使用方法,即打开CONFIG_NET_CLS_ACT选项,在netif_receive_skb函数中进入ingress的处理流程。
下面我们再跟踪一下ingress的enqueue处理流程,进入ingress_enqueue函数,代码片段如下: result = tc_classify(skb, p->filter_list, &res); ……
在ingress_enqueue函数中最主要的就是调用tc_classify函数进行分类操作,进入tc_classify函数,代码片段如下:
for ( ; tp; tp = tp->next) {
if ((tp->protocol == protocol ||
tp->protocol == __constant_htons(ETH_P_ALL)) && (err = tp->classify(skb, tp, res)) >= 0) { ……
其中tp是一个指向struct tcf_proto类型的指针,struct tcf_proto中包含了与filter相关的参数和函数指针。tp->classify调用了与该filter规则相相关联的classify函数,如果我们使用的是FW类型的filter,那么对应的classify函数就是fw_classify,该函数定义在src/net/sched/cls_fw.c文
件中。以FW分类器为例,我们再进入fw_classify函数,代码片段如下: struct fw_head *head = (struct fw_head*)tp->root; struct fw_filter *f; int r;
u32 id = skb->mark;
if (head != NULL) { id &= head->mask;
for (f=head->ht[fw_hash(id)]; f; f=f->next) { if (f->id == id) { *res = f->res;
#ifdef CONFIG_NET_CLS_IND
if (!tcf_match_indev(skb, f->indev)) continue;
#endif /* CONFIG_NET_CLS_IND */
r = tcf_exts_exec(skb, &f->exts, res); if (r < 0) continue;
return r; } }
} else {
/* old method */
if (id && (TC_H_MAJ(id) == 0 || !(TC_H_MAJ(id^tp->q->handle)))) { res->classid = id; res->class = 0; return 0; } }
return -1;
如上面代码所示,当filter中有规则时,遍历规则表,寻找与skb->mark(由ebtables或ip(6)tables来配置)相匹配的表项,如果找到了则会进一步调用tcf_exts_exec函数对扩展的action进行处理。再进入tcf_exts_exec函数,代码片段如下: #ifdef CONFIG_NET_CLS_ACT if (exts->action)
return tcf_action_exec(skb, exts->action, res); #elif defined CONFIG_NET_CLS_POLICE if (exts->police)
return tcf_police(skb, exts->police); #endif
如上面代码所示,当定义了CONFIG_NET_CLS_ACT选项,并且存在扩展action时调用tcf_action_exec函数进行处理,当定义了CONFIG_NET_CLS_POLICE选项,并且存在police扩展时会调用tcf_police函数。实际上CONFIG_NET_CLS_POLICE是一套老的police机制,它假定了在ingress处理
相关推荐: