Linux内核QoS实现机制.docx
《Linux内核QoS实现机制.docx》由会员分享,可在线阅读,更多相关《Linux内核QoS实现机制.docx(24页珍藏版)》请在冰豆网上搜索。
Linux内核QoS实现机制
Linux内核QoS实现机制
1.QoS介绍
QoS(QualityofService)即服务质量。
对于网络业务,服务质量包括传输的带宽、传送的时延、数据的丢包率等。
在网络中可以通过保证传输的带宽、降低传送的时延、降低数据的丢包率以及时延抖动等措施来提高服务质量。
网络资源总是有限的,只要存在抢夺网络资源的情况,就会出现服务质量的要求。
服务质量是相对网络业务而言的,在保证某类业务的服务质量的同时,可能就是在损害其它业务的服务质量。
例如,在网络总带宽固定的情况下,如果某类业务占用的带宽越多,那么其他业务能使用的带宽就越少,可能会影响其他业务的使用。
因此,网络管理者需要根据各种业务的特点来对网络资源进行合理的规划和分配,从而使网络资源得到高效利用。
流量控制包括以下几种方式:
⏹SHAPING(限制)
当流量被限制,它的传输速率就被控制在某个值以下。
限制值可以大大小于有效带宽,这样可以平滑突发数据流量,使网络更为稳定。
shaping(限制)只适用于向外的流量。
⏹SCHEDULING(调度)
通过调度数据包的传输,可以在带宽范围内,按照优先级分配带宽。
SCHEDULING(调度)也只适于向外的流量。
⏹POLICING(策略)
SHAPING用于处理向外的流量,而POLICIING(策略)用于处理接收到的数据。
⏹DROPPING(丢弃)
如果流量超过某个设定的带宽,就丢弃数据包,不管是向内还是向外。
2.内核实现过程
图表1流量控制过程
绿色部分就是Linux内核实现的QoS模块,其中ingresspolicing是处理输入数据包的,而outputqueueing则是处理输出数据包的。
2.1.Ingress实现机制
IngressQOS在内核的入口点有两个,但是不能同时启用,这取决于内核编译选项。
当打开了CONFIG_NET_CLS_ACT(from2.6.8releasestillavailableon2.6.39release)时,入口点在src/net/core/dev.c的netif_receive_skb函数中;当没有打开CONFIG_NET_CLS_ACT,而是打开了CONFIG_NET_CLS_POLICE(from2.6.9releaseto2.6.24,thusthisisanobsoleteconfiguration)和CONFIG_NETFILTER时,就会在netfilter的PREROUTING钩子点处调用ing_hook函数。
图表2ingress策略实现
当filter中有规则时,遍历规则表,寻找与skb->mark(由ebtables或iptables来配置)相匹配的表项,如果找到了则会进一步调用tcf_exts_exec函数对扩展的action进行处理。
图表3FW分类器实现过程
2.2.Egress实现机制
系统在注册网络设备时会在register_netdevice函数中调用dev_init_scheduler函数注册一个qdisc的接口,它是一个特殊的qdisc,不做任何处理。
当创建好设备,用ifconfigup命令把设备拉起后,会调用到内核的src/net/core/dev.c中的dev_open函数,在dev_open函数中又会调用到src/net/sched/sch_generic.c中的dev_activate函数,给设备配置默认的rootqdisc处理机制:
pfifo_fast。
图表4以prioqdisc为例的egress初始化过程
出口队列调度的入口点在src/net/core/dev.c的dev_queue_xmit函数中,通过q=rcu_dereference(dev->qdisc)可以获取到设备上rootqdisc的指针q(structQdisc*)。
在下面的处理过程中并没有判断q是否为NULL,这就说明设备上一定会存在egressqdisc,这一点和ingress是不同的,一个设备上可以没有ingressqdisc,即dev->qdisc_ingress指针一般是NULL,除非通过tcqdisc命令配置了ingressqdisc。
图表5以htb+pfifoqdisc为例的egress处理流程
3.内核与TC的交互
3.1.交互方式
Netlink
3.2.内核处理接口
⏹Qdisc和class交互接口
tcqdisc和tcclsss配置命令对应的配置函数在src/net/sched/sch_api.c的pktsched_init函数中进行了初始化注册,由subsys_initcall函数对系统进行申明,该函数在linux系统初始化的时候会被调用到。
代码片断如下:
if(link_p){
link_p[RTM_NEWQDISC-RTM_BASE].doit=tc_modify_qdisc;
link_p[RTM_DELQDISC-RTM_BASE].doit=tc_get_qdisc;
link_p[RTM_GETQDISC-RTM_BASE].doit=tc_get_qdisc;
link_p[RTM_GETQDISC-RTM_BASE].dumpit=tc_dump_qdisc;
link_p[RTM_NEWTCLASS-RTM_BASE].doit=tc_ctl_tclass;
link_p[RTM_DELTCLASS-RTM_BASE].doit=tc_ctl_tclass;
link_p[RTM_GETTCLASS-RTM_BASE].doit=tc_ctl_tclass;
link_p[RTM_GETTCLASS-RTM_BASE].dumpit=tc_dump_tclass;
}
⏹filter交互接口
tcfilter配置命令对应的配置函数在src/net/sched/cls_api.c的tc_filter_init函数中进行了初始化注册,该函数也会在系统初始化的时候被调用到。
代码片段如下:
if(link_p){
link_p[RTM_NEWTFILTER-RTM_BASE].doit=tc_ctl_tfilter;
link_p[RTM_DELTFILTER-RTM_BASE].doit=tc_ctl_tfilter;
link_p[RTM_GETTFILTER-RTM_BASE].doit=tc_ctl_tfilter;
link_p[RTM_GETTFILTER-RTM_BASE].dumpit=tc_dump_tfilter;
}
3.3.TC与netlink交互的数据格式
Netlink数据头部+tc消息头部
⏹Netlink头部
structmsg_to_kernel/*自定义消息首部,它仅包含了netlink的消息首部*/
{
structnlmsghdrhdr;
};
structnlmsghdr
{
__u32nlmsg_len;/*Lengthofmessage*/
__u16nlmsg_type;/*Messagetype*/
__u16nlmsg_flags;/*Additionalflags*/
__u32nlmsg_seq;/*Sequencenumber*/
__u32nlmsg_pid;/*SendingprocessPID*/
};
⏹TC数据头部:
structtcmsg{
unsignedchartcm_family;
unsignedchartcm__pad1;
unsignedshorttcm__pad2;
inttcm_ifindex;
__u32tcm_handle;
__u32tcm_parent;
__u32tcm_info;
};
4.TC规则
4.1.流量控制模型
图表7流量控制实现模型
4.2.流量控制处理对象
流量的处理由三种对象控制,它们是:
qdisc(排队规则)、class(类别)和filter(过滤器)。
4.2.1.Qdisc
QDisc(排队规则)是queueingdiscipline的简写,它是理解流量控制(trafficcontrol)的基础。
无论何时,内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的qdisc(排队规则)把数据包加入队列。
然后,内核会尽可能多地从qdisc里面取出数据包,把它们交给网络适配器驱动模块。
最简单的QDisc是pfifo它不对进入的数据包做任何的处理,数据包采用先入先出的方式通过队列。
不过,它会保存网络接口一时无法处理的数据包。
QDISC分为无类队列(CLASSLESSQDisc)和分类队列(CLASSFULQDISC)类别。
使用中,无类队列只能作为叶子节点出现,而分类队列作为双亲节点出现,不能作为叶子节点出现。
4.2.1.1.无类队列
a)Pfifo_fast
默认参数,先进先出的队列,内核参照数据包的TOS标记,将数据包分为3个频道。
如果在0频道有数据包等待发送,1频道的包就不会被处理,1频道和2频道之间的关系也是如此。
⏹TOS字段说明:
TOS字段的4个bit定义:
二进制
十进制
意义
1000
8
最小延迟(md)
0100
4
最大throughput(mt)
0010
2
最大可靠性(mr)
0001
1
最小成本(mmc)
0000
0
正常服务
图表6TOS值与频道的对应
b)TBF
只允许以不超过事先设定的速率到来的数据包通过,但可能允许短暂突发流量超过设定值。
3种情景:
1)数据流以等于令牌流的速率到达TBF。
这种情况下,每个到来的数据包都能对应一个令牌,然后无延迟地通过队列。
2)数据流以小于令牌流的速度到达TBF。
通过队列的数据包只消耗了一部分令牌,剩下的令牌会在桶里积累下来,直到桶被装满。
剩下的令牌可以在需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发传输。
3)数据流以大于令牌流的速率到达TBF。
这意味着桶里的令牌很快就会被耗尽。
导致TBF中断一段时间,称为“越限”。
如果数据包持续到来,将发生丢包。
⏹参数选项:
Ølimit/latency:
limit确定最多有多少数据(字节数)在队列中等待可用令牌。
你也可以通过设置latency参数来指定这个参数,latency参数确定了一个包在TBF中等待传输的最长等待时间。
后者计算决定桶的大小、速率和峰值速率。
Øburst/buffer/maxburst:
桶的大小,以字节计。
这个参数指定了最多可以有多少个令牌能够即刻被使用。
通常,管理的带宽越大,需要的缓冲器就越大。
在Intel体系上,10兆bit/s的速率需要至少10k字节的缓冲区才能达到期望的速率。
如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会导致潜在的丢包。
ØMpu:
一个零长度的包并不是不耗费带宽。
比如以太网,数据帧不会小于64字节。
Mpu(MinimumPacketUnit,最小分组单位)决定了令牌的最低消耗。
ØRate:
速度操纵杆,参见上面的limits。
ØPeakrate:
如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样。
那可能并不是你希望的,特别是你有一个比较大的桶的时候。
峰值速率可以用来指定令牌以多块的速度被删除。
用书面语言来说,就是:
释放一个数据包,但后等待足够的时间后再释放下一个。
我们通过计算等待时间来控制峰值速率。
然而,由于UNIX定时器的分辨率是10毫秒,如果平均包长10kbit,我们的峰值速率被限制在了1Mbps。
Ømtu/minburst:
但是如果你的常规速率比较高,1Mbps的峰值速率对我们就没有什么价值。
要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包。
最有效的办法就是:
再创建一个令牌桶!
⏹配置范例:
#tcqdiscadddevppp0roottbfrate220kbitlatency50msburst1540
c)SFQ
SFQ(StochasticFairnessQueueing,随机公平队列)是公平队列算法家族中的一个简单实现。
它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的计算量却很少。
SFQ的关键词是“会话”(或称作“流”),主要针对一个TCP会话或者UDP流。
流量被分成相当多数量的FIFO队列中,每个队列对应一个会话。
数据按照简单轮转的方式发送,每个会话都按顺序得到发送机会。
⏹参数选项:
ØPerturb:
多少秒后重新配置一次散列算法。
如果取消设置,散列算法将永远不会重新配置(不建议这样做)。
10秒应该是一个合适的值。
ØQuantum:
一个流至少要传输多少字节后才切换到下一个队列。
却省设置为一个最大包的长度(MTU的大小)。
不要设置这个数值低于MTU!
⏹配置范例:
#tcqdiscadddevppp0rootsfqperturb10
d)[p|b]fifo
使用最简单的qdisc,纯粹的先进先出。
只有一个参数:
limit,用来设置队列的长度,pfifo是以数据包的个数为单位;bfifo是以字节数为单位。
⏹参数选项
ØLimit:
规定了队列的长度。
对于bfifo用字节计,对于pfifo用包的个数计。
缺省值就是网卡的txqueuelen个包那么长(参见pfifo_fast那一章),对于bfifo就是txqueuelen*mtu个字节。
e)red
red是RandomEarlyDetection(随机早期探测)的简写。
如果使用这种QDISC,当带宽的占用接近于规定的带宽时,系统会随机地丢弃一些数据包。
它非常适合高带宽应用。
为了使用RED,你必须确定三个参数:
min、max和burst。
Min设置了队列达到多少字节时开始进行丢包,Max是一个软上限,让算法尽量不要超过,burst设置了最多有多少个数据包能够突发通过。
4.2.1.2.分类队列
a)HTB
HTB是HierarchyTokenBucket的缩写。
通过在实践基础上的改进,它实现了一个丰富的连接共享类别体系。
使用HTB可以很容易地保证每个类别的带宽,虽然它也允许特定的类可以突破带宽上限,占用别的类的带宽。
HTB可以通过TBF(TokenBucketFilter)实现带宽限制,也能够划分类别的优先级。
⏹参数选项
ØRate:
限速;
ØBurst:
令牌桶的大小;
ØCeil:
上行限速,用于满带宽时对总带宽的占有比;
ØPrio:
配置优先级;
b)CBQ
CBQ是ClassBasedQueueing(基于类别排队)的缩写。
它实现了一个丰富的连接共享类别结构,既有限制(shaping)带宽的能力,也具有带宽优先级管理的能力。
带宽限制是通过计算连接的空闲时间完成的。
空闲时间的计算标准是数据包离队事件的频率和下层连接(数据链路层)的带宽。
⏹CBQ常规参数:
ØAvpkt:
平均包大小,以字节计。
计算maxidle时需要,maxidle从maxburst得出。
ØBandwidth:
网卡的物理带宽,用来计算闲置时间。
ØCell:
一个数据包被发送出去的时间可以是基于包长度而阶梯增长的。
一个800字节的包和一个806字节的包可以认为耗费相同的时间。
也就是说它设置时间粒度。
通常设置为8,必须是2的整数次幂。
ØMaxburst:
这个参数的值决定了计算maxidle所使用的数据包的个数。
在avgidle跌落到0之前,这么多的数据包可以突发传输出去。
这个值越高,越能够容纳突发传输。
你无法直接设置maxidle的值,必须通过这个参数来控制。
ØMinburst:
如前所述,发生越限时CBQ会禁止发包。
实现这个的理想方案是根据事先计算出的闲置时间进行延迟之后,发一个数据包。
然而,UNIX的内核一般来说都有一个固定的调度周期(一般不大于10ms),所以最好是这样:
禁止发包的时间稍长一些,然后突发性地传输minburst个数据包,而不是一个一个地传输。
等待的时间叫做offtime。
从大的时间尺度上说,minburst值越大,整形越精确。
但是,从毫秒级的时间尺度上说,就会有越多的突发传输。
ØMinidle:
如果avgidle值降到0,也就是发生了越限,就需要等待,直到avgidle的值足够大才发送数据包。
为避免因关闭链路太久而引起的以外突发传输,在avgidle的值太低的时候会被强制设置为minidle的值。
参数minidle的值是以负微秒记的。
所以10代表avgidle被限制在-10us上。
ØMpu:
最小包尺寸,因为即使是0长度的数据包,在以太网上也要生成封装成64字节的帧,而需要一定时间去传输。
为了精确计算闲置时间,CBQ需要知道这个值。
ØRate:
期望中的传输速率
⏹WRR(weightedroundrobin,加权轮转)参数:
ØAllot:
当从外部请求一个CBQ发包的时候,它就会按照“priority”参数指定的顺序轮流尝试其内部的每一个类的队列规定。
当轮到一个类发数据时,它只能发送一定量的数据。
“allot”参数就是这个量的基值。
更多细节请参照“weight”参数。
ØPrio:
CBQ可以象PRIO设备那样工作。
其中“prio”值较低的类只要有数据就必须先服务,其他类要延后处理。
ØWeight:
“weight”参数控制WRR过程。
每个类都轮流取得发包的机会。
如果其中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发送更多的数据。
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以。
人们常把“速率/10”作为参数的值来使用,实际工作得很好。
归一化值后的值乘以“allot”参数后,决定了每次传输多少数据。
⏹决定链路的共享和借用的CBQ参数:
ØIsolated/sharing:
凡是使用“isolated”选项配置的类,就不会向其兄弟类出借带宽。
如果你的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选项。
选项“sharing”是“isolated”的反义选项。
Øbounded/borrow:
一个类也可以用“bounded”选项配置,意味着它不会向其兄弟类借用带宽。
选项“borrow”是“bounded”的反义选项。
c)PRIO
PRIOQDisc不能限制带宽,因为属于不同类别的数据包是顺序离队的。
使用PRIOQDisc可以很容易对流量进行优先级管理,只有属于高优先级类别的数据包全部发送完毕,才会发送属于低优先级类别的数据包。
为了方便管理,需要使用iptables或者ipchains处理数据包的服务类型(TypeOfService,ToS)。
⏹参数选项
ØBands:
创建频道的数目,每个频道实际上就是一个类。
ØPriomap:
如果你不给tc提供任何过滤器,PRIO队列规定将参考TC_PRIO的优先级来决定如何给数据包入队。
4.2.2.Class
某些QDisc(排队规则)可以包含一些类别,不同的类别中可以包含更深入的QDisc(排队规则),通过这些细分的QDisc还可以为进入的队列的数据包排队。
通过设置各种类别数据包的离队次序,QDisc可以为设置网络数据流量的优先级。
4.2.3.Filter
Filter(过滤器)用于为数据包分类,决定它们按照何种QDisc进入队列。
无论何时数据包进入一个划分子类的类别中,都需要进行分类。
分类的方法可以有多种,使用fileter(过滤器)就是其中之一。
使用filter(过滤器)分类时,内核会调用附属于这个类(class)的所有过滤器,直到返回一个判决。
如果没有判决返回,就作进一步的处理,而处理方式和QDISC有关。
需要注意的是,filter(过滤器)是在QDisc内部,它们不能作为主体。
过滤器在使用时结合具体的分类器使用:
a)fw
根据防火墙如何对这个数据包做标记进行判断,结合iptable工具进行使用。
b)u32
根据数据包中的各个字段进行判断,如源IP地址等等。
c)route
根据数据包将被哪条路由进行路由来判断。
d)rsvp,rsvp6
根据数据包的RSVP情况进行判断。
只能用于你自己的网络,互联网并不遵守RSVP。
e)tcindex
用于DSMARK队列规定,参见相关章节。
4.3.TC操作命令
Øtcqdisc[add|change|replace|link]devDEV[parentqdisc-id|root][handleqdisc-id]qdisc[qdiscspecificparameters]
Øtcclass[add|change|replace]devDEVparentqdisc-id[classidclass-id]qdisc[qdiscspecificparameters]
Øtcfilter[add|change|replace]devDEV[parentqdisc-id|root]protocolprotocolpriopriorityfiltertype[filtertypespecificparameters]flowidflow-id
Øtc[-s|-d]qdiscshow[devDEV]
Øtc[-s|-d]classshowdevDEV
ØtcfiltershowdevDEV
tc可以使用以下命令对QDisc、类和过滤器进行操作:
Øadd:
在一个节点里加入一个QDisc、类或者过滤器。
添加时,需要传递一个祖先作为参数,传递参数时既可以使用ID也可以直接传递设备的根。
如果要建立一个QDisc或者过滤器,可以使用句柄(handle)来命名;如果要建立一个类,可以使用类识别符(classid)来命名。
ØRemove:
删除有某个句柄(handle)指定的QDisc,根QDisc(root)也可以删除。
被删除QDisc上的所有子类以及附属于各个类的过滤器都会被自动删除。
ØChange:
以替代的方式修改某些条目。
除了句柄(handle)和祖先不能修改以外,change命令的语法和add命令相同。
换句话说,change命令不能一定节点的位置。
ØReplace:
对一个现有节点进行近于原子操作的删除/添加。
如果节点不存在,这个命令就会建立节点。
ØLink:
只适用于DQisc,替代一个现有的节点。
4.4.流量控制建立步骤
1)针对网络物理设备(如以太网卡eth0)绑定一个队列QDisc;
2)在该队列上建立分类class;
3)为每一分类建立一个基于路由的过滤器filter;
4)最后与过滤器等相配合,建立特定分类规则,如路由表(可选)。
4.5.TC规则如何选择
下列提示可以帮你决定使用哪一种队列:
Ø如果想单纯地降低出口速率,使用令牌桶过滤器。
调整桶的配置后可用于控制很高的带宽。
Ø如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,使用随机公平队列。
Ø如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随机丢包(参见“高级”那一章