LINUX TC中文版

9.1. 解释队列和队列规定
利用队列,我们决定了数据被发送的方式.必须认识到,我们只能对发送数据进
行整形.
根据Internet的工作方式,我们无法直接控制别人向我们发送什么数据.有点象
我们家里的信报箱,你不可能控制全世界,联系每一个人,修改别人对你发送邮
件的数量.
然而,Internet主要依靠TCP/IP,它的一些特性很有用.因为TCP/IP没办法知道
两个主机之间的网络容量,所以它会试图越来越快地发送数据(所谓的"慢起技
术") ,当因为网络容量不够而开始丢失数据时,再放慢速度.实际情况要比这
种方法更聪明,我们以后再讨论.
这就象当你尚未读完一半邮件时,希望别人停止给你寄信.与现实世界不同,在
Internet上可以做到这一点.(译注:这个例子并不恰当,TCP/IP的这种机制并不
是在网络层实现的,而是要靠传输层的TCP协议)
如果你有一个路由器,并且希望能够防止某些主机下载速度太快,你需要在你路
由器的内网卡——也就是向你的网内主机发送数据包的网卡——上进行流量整
形.
你还要保证你正在控制的是瓶颈环节.如果你有一个100M以太网卡,而你的路
由器的链路速度是256k,你必须保证你发送的数据量没有超过路由器的处理能
力.否则,就是路由器在控制链路和对带宽进行整形,而不是你.可以说,我们
需要拥有的队列必须是一系列链路中最慢的环节.幸运的是这很容易.
36
9.2. 简单的无类队列规定
如前所述,利用队列,我们决定了数据被发送的方式.无类队列规定就是那样,
能够接受数据和重新编排,延迟或丢弃数据包.
这可以用作对于整个网卡的流量进行整形,而不细分各种情况.在我们进一步学
习分类的队列规定之前,理解这部分是必不可少的!
最广泛应用的规定是pfifo_fast队列规定,因为它是缺省配置.这也解释了为什
么其它那些复杂的功能为何如此健壮,因为那些都与缺省配置相似,只不过是其
他类型的队列而已.
每种队列都有它们各自的优势和弱点.
9.2.1. pfifo_fast
这个队列的特点就象它的名字——先进先出(FIFO),也就是说没有任何数据包
被特殊对待.至少不是非常特殊.这个队列有3个所谓的"频道".FIFO规则应
用于每一个频道.并且:如果在0频道有数据包等待发送,1频道的包就不会被
处理,1频道和2频道之间的关系也是如此.
内核遵照数据包的TOS标记,把带有"最小延迟"标记的包放进0频道.
不要把这个无类的简单队列规定与分类的PRIO相混淆!虽然它们的行为有些类
似,但对于无类的pfifo_fast而言,你不能使用tc命令向其中添加其它的队列规
定.
9.2.1.1. 参数与使用
pfifo_fast队列规定作为硬性的缺省设置,你不能对它进行配置.它缺省是这样
配置的:
priomap:
内核规定,根据数据包的优先权情况,对应相应的频道.这个对应是根据
数据包的TOS字节进行的.TOS看上去是这样的:
0 1 2 3 4 5 6 7
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | |
| 优先权 | TOS | MBZ |
| | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
TOS字段的4个bit是如下定义的:
二进制 十进制 意义
-----------------------------------------
1000 8 最小延迟 (md)
0100 4 最大throughput (mt)
0010 2 最大可靠性 (mr)
37
7),它们不对应TOS映射,但是有其它的意图.
下表来自RFC 1349,告诉你应用程序可能如何设置它们的TOS:
TELNET 1000 (minimize delay)
控制 1000 (minimize delay) FTP
数据 0100 (maximize throughput)
TFTP 1000 (minimize delay)
命令阶段 1000 (minimize delay) SMTP
数据阶段 0100 (maximize throughput)
UDP 查询 1000 (minimize delay)
TCP 查询 0000
Domain Name Service
区域传输 0100 (maximize throughput)
NNTP 0001 (minimize monetary cost)
报错 0000
请求 0000 (mostly)
ICMP
响应 (mostly)
txqueuelen
38
队列的长度来自网卡的配置,你可以用ifconfig和ip命令修改.如设置队
列长度为10,执行:ifconfig eth0 txqueuelen 10
你不能用tc命令设置这个!
9.2.2. 令牌桶过滤器(TBF)
令牌桶过滤器(TBF)是一个简单的队列规定:只允许以不超过事先设定的速率到
来的数据包通过,但可能允许短暂突发流量朝过设定值.
TBF很精确,对于网络和处理器的影响都很小.所以如果您想对一个网卡限速,
它应该成为您的第一选择!
TBF的实现在于一个缓冲器(桶),不断地被一些叫做"令牌"的虚拟数据以特定
速率填充着. (token rate).桶最重要的参数就是它的大小,也就是它能够存储
令牌的数量.
每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除.这个算法关
联到两个流上——令牌流和数据流,于是我们得到3种情景:
数据流以等于令牌流的速率到达TBF.这种情况下,每个到来的数据包都
能对应一个令牌,然后无延迟地通过队列.
数据流以小于令牌流的速度到达TBF.通过队列的数据包只消耗了一部分
令牌,剩下的令牌会在桶里积累下来,直到桶被装满.剩下的令牌可以在
需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发
传输.
数据流以大于令牌流的速率到达TBF.这意味着桶里的令牌很快就会被耗
尽.导致TBF中断一段时间,称为"越限".如果数据包持续到来,将发
生丢包.
最后一种情景非常重要,因为它可以用来对数据通过过滤器的速率进行整形.
令牌的积累可以导致越限的数据进行短时间的突发传输而不必丢包,但是持续越
限的话会导致传输延迟直至丢包.
请注意,实际的实现是针对数据的字节数进行的,而不是针对数据包进行的.
9.2.2.1. 参数与使用
即使如此,你还是可能需要进行修改,TBF提供了一些可调控的参数.第一个参
数永远可用:
limit/latency
limit确定最多有多少数据(字节数)在队列中等待可用令牌.你也可以
通过设置latency参数来指定这个参数,latency参数确定了一个包在TBF
中等待传输的最长等待时间.后者计算决定桶的大小,速率和峰值速率.
39
burst/buffer/maxburst
桶的大小,以字节计.这个参数指定了最多可以有多少个令牌能够即刻被
使用.通常,管理的带宽越大,需要的缓冲器就越大.在Intel体系上,
10兆bit/s的速率需要至少10k字节的缓冲区才能达到期望的速率.
如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会
导致潜在的丢包.
mpu
一个零长度的包并不是不耗费带宽.比如以太网,数据帧不会小于64字
节.Mpu(Minimum Packet Unit,最小分组单位)决定了令牌的最低消耗.
rate
速度操纵杆.参见上面的limits!
如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况).If the
bucket contains tokens and is allowed to empty, by default it does so at infinite speed.
如果不希望这样,可以调整入下参数:
peakrate
如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样.
那可能并不是你希望的,特别是你有一个比较大的桶的时候.
峰值速率可以用来指定令牌以多块的速度被删除.用书面语言来说,就是:
释放一个数据包,但后等待足够的时间后再释放下一个.我们通过计算等
待时间来控制峰值速率
然而,由于UNIX定时器的分辨率是10毫秒,如果平均包长10k bit,我
们的峰值速率被限制在了1Mbps.
mtu/minburst
但是如果你的常规速率比较高,1Mbps的峰值速率对我们就没有什么价
值.要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包.最
有效的办法就是:再创建一个令牌桶!
这第二个令牌桶缺省情况下为一个单个的数据包,并非一个真正的桶.
要计算峰值速率,用mtu乘以100就行了. (应该说是乘以HZ数,Intel
体系上是100,Alpha体系上是1024)
9.2.2.2. 配置范例
这是一个非常简单而实用的例子:
# tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
为什么它很实用呢?如果你有一个队列较长的网络设备,比如DSL modem或者
cable modem什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上
40
载数据绝对会破坏交互性.
这是因为上载数据会充满modem的队列,而这个队列为了改善上载数据的吞吐
量而设置的特别大.但这并不是你需要的,你可能为了提高交互性而需要一个不
太大的队列.也就是说你希望在发送数据的时候干点别的事情.
上面的一行命令并非直接影响了modem中的队列,而是通过控制Linux中的队
列而放慢了发送数据的速度.
把220kbit修改为你实际的上载速度再减去几个百分点.如果你的modem确实很
快,就把"burst"值提高一点.
9.2.3. 随机公平队列(SFQ)
SFQ(Stochastic Fairness Queueing,随机公平队列)是公平队列算法家族中的一个
简单实现.它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的
计算量却很少.
SFQ的关键词是"会话"(或称作"流") ,主要针对一个TCP会话或者UDP
流.流量被分成相当多数量的FIFO队列中,每个队列对应一个会话.数据按照
简单轮转的方式发送, 每个会话都按顺序得到发送机会.
这种方式非常公平,保证了每一个会话都不会没其它会话所淹没.SFQ之所以被
称为"随机",是因为它并不是真的为每一个会话创建一个队列,而是使用一个
散列算法,把所有的会话映射到有限的几个队列中去.
因为使用了散列,所以可能多个会话分配在同一个队列里,从而需要共享发包的
机会,也就是共享带宽.为了不让这种效应太明显,SFQ会频繁地改变散列算法,
以便把这种效应控制在几秒钟之内.
有很重要的一点需要声明:只有当你的出口网卡确实已经挤满了的时候,SFQ才
会起作用!否则在你的Linux机器中根本就不会有队列,SFQ也就不会起作用.
稍后我们会描述如何把SFQ与其它的队列规定结合在一起,以保证两种情况下
都比较好的结果.
特别地,在你使用DSL modem或者cable modem的以太网卡上设置SFQ而不进
行任何进一步地流量整形是无谋的!
9.2.3.1. 参数与使用
SFQ基本上不需要手工调整:
perturb
多少秒后重新配置一次散列算法.如果取消设置,散列算法将永远不会重
新配置(不建议这样做).10秒应该是一个合适的值.
quantum
41
一个流至少要传输多少字节后才切换到下一个队列.却省设置为一个最大
包的长度(MTU的大小).不要设置这个数值低于MTU!
9.2.3.2. 配置范例
如果你有一个网卡,它的链路速度与实际可用速率一致——比如一个电话
MODEM——如下配置可以提高公平性:
# tc qdisc add dev ppp0 root sfq perturb 10
# tc -s -d qdisc ls
qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb 10sec
Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)
"800c:"这个号码是系统自动分配的一个句柄号,"limit"意思是这个队列中可
以有128个数据包排队等待.一共可以有1024个散列目标可以用于速率审计,
而其中128个可以同时激活.(no more packets fit in the queue!)每隔10秒种散列
算法更换一次.
9.3. 关于什么时候用哪种队列的建议
总之,我们有几种简单的队列,分别使用排序,限速和丢包等手段来进行流量整
形.
下列提示可以帮你决定使用哪一种队列.涉及到了第14章 所描述的的一些队列
规定:
如果想单纯地降低出口速率,使用令牌桶过滤器.调整桶的配置后可用于
控制很高的带宽.
如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,
使用随机公平队列.
如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随
机丢包(参见"高级"那一章).
如果希望对入口流量进行"整形"(不是转发流量),可使用入口流量策略,
注意,这不是真正的"整形".
如果你正在转发数据包,在数据流出的网卡上应用TBF.除非你希望让数
据包从多个网卡流出,也就是说入口网卡起决定性作用的时候,还是使用
入口策略.
如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载
而需要使用队列,使用pfifo队列(不是pfifo_fast).它缺乏内部频道但是
可以统计backlog.
最后,你可以进行所谓的"社交整形".你不能通过技术手段解决一切问
题.用户的经验技巧永远是不友善的.正确而友好的措辞可能帮助你的正
确地分配带宽!
42
9.4. 术语
为了正确地理解更多的复杂配置,有必要先解释一些概念.由于这个主题的历史
不长和其本身的复杂性,人们经常在说同一件事的时候使用各种词汇.
以下来自draft-ietf-diffserv-model-06.txt,Diffserv路由器的建议管理模型. 可以
在以下地址找到:
http://www.ietf.org/internet-drafts/draft-ietf-diffserv-model-06.txt.
关于这些词语的严格定义请参考这个文档.
队列规定
管理设备输入(ingress)或输出(egress)的一个算法.
无类的队列规定
一个内部不包含可配置子类的队列规定.
分类的队列规定
一个分类的队列规定内可一包含更多的类.其中每个类又进一步地包含一
个队列规定,这个队列规定可以是分类的,也可以是无类的.根据这个定
义,严格地说pfifo_fast算是分类的,因为它实际上包含3个频道(实际上
可以认为是子类).然而从用户的角度来看它是无类的,因为其内部的子
类无法用tc工具进行配置.

一个分类的队列规定可以拥有很多类,类内包含队列规定.
分类器
每个分类的队列规定都需要决定什么样的包使用什么类进行发送.分类器
就是做这个用的.
过滤器
分类是通过过滤器完成的.一个过滤器包含若干的匹配条件,如果符合匹
配条件,就按此过滤器分类.
调度
在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据
包之前发送.这种处理叫做"调度",比如此前提到的pfifo_fast就是这样
的.调度也可以叫做"重排序",但这样容易混乱.
整形
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速
率,这种处理叫做"整形".整形在egress处进行.习惯上,通过丢包来
降速也经常被称为整形.
43

| 队列规定 /__队列规定4__/ |
| /-队列规定N_/ |
| |
+------------------------------------------------------------+
感谢Jamal Hadi Salim制作的ASCII字符图像.
整个大方框表示内核.最左面的箭头表示从网络上进入机器的数据包.它们进入
Ingress队列规定,并有可能被某些过滤器丢弃.即所谓策略.
这些是很早就发生的(在进入内核更深的部分之前).这样早地丢弃数据有利于
节省CPU时间.
数据包顺利通过的话,如果它是发往本地进程的,就会进入IP协议栈处理并提
交给该进程.如果它需要转发而不是进入本地进程,就会发往egress.本地进程
也可以发送数据,交给Egress分类器.
然后经过审查,并放入若干队列规定中的一个进行排队.这个过程叫做"入队".
在不进行任何配置的情况下,只有一个egress队列规定——pfifo_fast——总是接
收数据包.
数据包进入队列后,就等待内核处理并通过某网卡发送.这个过程叫做"出队".
44
这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况.
每块网卡都有它自己的ingress和egress.
9.5. 分类的队列规定
如果你有多种数据流需要进行区别对待,分类的队列规定就非常有用了.众多分
类的队列规定中的一种——CBQ(Class Based Queueing,基于类的队列)——经常
被提起,以至于造成大家认为CBQ就是鉴别队列是否分类的标准,这是不对的.
CBQ不过是家族中最大的孩子而已,同时也是最复杂的.它并不能为你做所有
的事情.对于某些人而言这有些不可思议,因为他们受"sendmail效应"影响较
深,总是认为只要是复杂的并且没有文档的技术肯定是最好的.
9.5.1. 分类的队列规定及其类中的数据流向
一旦数据包进入一个分类的队列规定,它就得被送到某一个类中——也就是需要
分类.对数据包进行分类的工具是过滤器.一定要记住:"分类器"是从队列规
定内部调用的,而不是从别处.
过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排
队.每个子类都可以再次使用它们的过滤器进行进一步的分类.直到不需要进一
步分类时,数据包才进入该类包含的队列规定排队.
除了能够包含其它队列规定之外,绝大多数分类的队列规定黑能够流量整形.这
对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用.如果你用一个
高速网卡(比如以太网卡)连接一个低速设备(比如cable modem或者ADSL modem)
时,也可以应用.
如果你仅仅使用SFQ,那什么用也没有.因为数据包进,出路由器时没有任何延
迟.虽然你的输出网卡远远快于实际连接速率,但路由器中却没有队列可以调度.
9.5.2. 队列规定家族:根,句柄,兄弟和父辈
每块网卡都有一个出口"根队列规定",缺省情况下是前面提到的pfifo_fast队列
规定.每个队列规定都指定一个句柄,以便以后的配置语句能够引用这个队列规
定.除了出口队列规定之外,每块网卡还有一个入口,以便policies进入的数据流.
队列规定的句柄有两个部分:一个主号码和一个次号码.习惯上把根队列规定称
为"1:",等价于"1:0".队列规定的次号码永远是0.
类的主号码必须与它们父辈的主号码一致.
9.5.2.1. 如何用过滤器进行分类
下图给出一个典型的分层关系:
45
12:2
也就是说,根所附带的一个过滤器要求把数据包直接交给12:2.
9.5.2.2. 数据包如何出队并交给硬件
当内核决定把一个数据包发给网卡的时候,根队列规定1:会得到一个出队请求,
然后把它传给1:1,然后依次传给10:,11:和12:,which each query their siblings,
然后试图从它们中进行dequeue()操作.也就是说,内和需要遍历整颗树,因为
只有12:2中才有这个数据包.
换句话说,类及其兄弟仅仅与其"父队列规定"进行交谈,而不会与网卡进行交
谈.只有根队列规定才能由内核进行出队操作!
更进一步,任何类的出队操作都不会比它们的父类更快.这恰恰是你所需要的:
我们可以把SFQ作为一个子类,放到一个可以进行流量整形的父类中,从而能
够同时得到SFQ的调度功能和其父类的流量整形功能.
9.5.3. PRIO队列规定
PRIO队列规定并不进行整形,它仅仅根据你配置的过滤器把流量进一步细分.
你可以认为PRIO队列规定是pfifo_fast的一种衍生物,区别在每个频道都是一
个单独的类,而非简单的FIFO.
当数据包进入PRIO队列规定后,将根据你给定的过滤器设置选择一个类.缺省
情况下有三个类,这些类仅包含纯FIFO队列规定而没有更多的内部结构.你可
以把它们替换成你需要的任何队列规定.
每当有一个数据包需要出队时,首先处理:1类.只有当标号更小的类中没有需要
处理的包时,才会标号大的类.
46
当你希望不仅仅依靠包的TOS,而是想使用tc所提供的更强大的功能来进行数
据包的优先权划分时,可以使用这个队列规定.它也可以包含更多的队列规定,
而pfifo_fast却只能包含简单的fifo队列规定.
因为它不进行整形,所以使用时与SFQ有相同的考虑:要么确保这个网卡的带
宽确实已经占满,要么把它包含在一个能够整形的分类的队列规定的内部.后者
几乎涵盖了所有cable modems和DSL设备.
严格地说,PRIO队列规定是一种Work-Conserving调度.
9.5.3.1. PRIO的参数与使用
tc识别下列参数:
bands
创建频道的数目.每个频道实际上就是一个类.如果你修改了这个数值,
你必须同时修改:
priomap
如果你不给tc提供任何过滤器,PRIO队列规定将参考TC_PRIO的优先
级来决定如何给数据包入队.
它的行为就像前面提到过的pfifo_fast队列规定,关于细节参考前面章节.
频道是类,缺省情况下命名为主标号:1到主标号:3.如果你的PRIO队列规定是
12:,把数据包过滤到12:1将得到最高优先级.
注意:0频道的次标号是1!1频道的次标号是2,以此类推.
9.5.3.2. 配置范例
我们想创建这个树:
root 1: prio
/ | /
1:1 1:2 1:3
| | |
10: 20: 30:
sfq tbf sfq
band 0 1 2
大批量数据使用30:,交互数据使用20:或10:.
命令如下:
# tc qdisc add dev eth0 root handle 1: prio
## 这个命令立即创建了类: 1:1, 1:2, 1:3
# tc qdisc add dev eth0 parent 1:1 handle 10: sfq
# tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
现在,我们看看结果如何:
47
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 132 bytes 2 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 174 bytes 3 pkts (dropped 0, overlimits 0)
如你所见,0频道已经有了一些流量,运行这个命令之后发送了一个包!
现在我们来点大批量数据传输(使用能够正确设置TOS标记的工具):
# scp tc ahu@10.0.0.11:./
ahu@10.0.0.11's password:
tc 100% |*****************************| 353 KB 00:00
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 2230 bytes 31 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 389140 bytes 326 pkts (dropped 0, overlimits 0)
如你所见,所有的流量都是经过30:处理的,优先权最低.现在我们验证一下交
互数据传输经过更高优先级的频道,我们生成一些交互数据传输:
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 14926 bytes 193 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 401836 bytes 488 pkts (dropped 0, overlimits 0)
正常——所有额外的流量都是经10:这个更高优先级的队列规定处理的.与先前
的整个scp不同,没有数据经过最低优先级的队列规定.
9.5.4. 著名的CBQ队列规定
如前所述,CBQ是最复杂,最琐碎,最难以理解,最刁钻的队列规定.这并不
是因为其作者的恶毒或者不称职,而是因为CBQ算法本身的不精确,而且与
Linux的内在机制不协调造成的.
48
除了可以分类之外,CBQ也是一个整形器,但是从表面上看来工作得并不好.
它应该是这样的:如果你试图把一个10Mbps的连接整形成1Mbps的速率,就应
该让链路90%的时间处于闲置状态,必要的话我们就强制,以保证90%的闲置
时间.
但闲置时间的测量非常困难,所以CBQ就采用了它一个近似值——来自硬件层
的两个传输请求之间的毫秒数——来代替它.这个参数可以近似地表征这个链路
的繁忙程度.
这样做相当慎重,而且不一定能够得到正确的结论.比如,由于驱动程序方面或
者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?
由于总线设计的原因,PCMCIA网卡永远也不会达到100Mbps.那么我们该怎么
计算闲置时间呢?
如果我们引入非物理网卡——像PPPoE,PPTP——情况会变得更糟糕.因为相
当一部分有效带宽耗费在了链路维护上.
那些实现了测量的人们都发现CBQ总不是非常精确甚至完全失去了其本来意
义.
但是,在很多场合下它还是能够很好地工作.根据下面的文档,你应该能够较好
地配置CBQ来解决答多数问题.
9.5.4.1. CBQ整形的细节
如前所述,CBQ的工作机制是确认链路的闲置时间足够长,以达到降低链路实
际带宽的目的.为此,它要计算两个数据包的平均发送间隔.
操作期间,有效闲置时间的测量使用EWMA(exponential weighted moving average,
指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按
指数增加.UNIX的平均负载也是这样算出来的.
计算出来的平均时间值减去EWMA测量值,得出的结果叫做"avgidle".最佳的
链路负载情况下,这个值应当是0:数据包严格按照计算出来的时间间隔到来.
在一个过载的链路上,avgidle值应当是负的.如果这个负值太严重,CBQ就会
暂时禁止发包,称为"overlimit"(越限).
相反地,一个闲置的链路应该有很大的avgidle值,这样闲置几个小时后,会造成
链路允许非常大的带宽通过.为了避免这种局面,我们用maxidle来限制avgidle
的值不能太大.
理论上讲,如果发生越限,CBQ就会禁止发包一段时间(长度就是事先计算出来
的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包.但是最
好参照一下下面的minburst参数.
下面是配置整形时需要指定的一些参数:
avpkt
平均包大小,以字节计.计算maxidle时需要,maxidle从maxburst得出.
49
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
期望中的传输速率.也就是"油门"!
在CBQ的内部由很多的微调参数.比如,那些已知队列中没有数据的类就不参
加计算,越限的类将被惩罚性地降低优先级等等.都非常巧妙和复杂.
9.5.4.2. CBQ在分类方面的行为
除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把
50
各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理.
每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted round
robin,加权轮转)过程,从优先权值小的类开始.
那些队列中有数据的类就会被分组并被请求出队.在一个类收到允许若干字节数
据出队的请求之后,再尝试下一个相同优先权值的类.
下面是控制WRR过程的一些参数:
allot
当从外部请求一个CBQ发包的时候,它就会按照"priority"参数指定的
顺序轮流尝试其内部的每一个类的队列规定.当轮到一个类发数据时,它
只能发送一定量的数据."allot"参数就是这个量的基值.更多细节请参
照"weight"参数.
prio
CBQ可以象PRIO设备那样工作.其中"prio"值较低的类只要有数据就
必须先服务,其他类要延后处理.
weight
"weight"参数控制WRR过程.每个类都轮流取得发包的机会.如果其
中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发
送更多的数据.
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任
意定,只要保持比例合适就可以.人们常把"速率/10"作为参数的值来
使用,实际工作得很好.归一化值后的值乘以"allot"参数后,决定了每
次传输多少数据.
请注意,在一个CBQ内部所有的类都必须使用一致的主号码!
9.5.4.3. 决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪
些类借用或者出借一部分带宽.
Isolated/sharing
凡是使用"isolated"选项配置的类,就不会向其兄弟类出借带宽.如果你
的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选
项.
选项"sharing"是"isolated"的反义选项.
bounded/borrow
一个类也可以用"bounded"选项配置,意味着它不会向其兄弟类借用带
宽.选项"borrow"是"bounded"的反义选项.
51
一个典型的情况就是你的一个链路上有多个客户都设置成了"isolated"和
"bounded",那就是说他们都被限制在其要求的速率之下,且互相之间不会借用
带宽.
在这样的一个类的内部的子类之间是可以互相借用带宽的.
9.5.4.4. 配置范例
这个配置把WEB服务器的流量控制为5Mbps,SMTP流量控制在3Mbps上.而
且二者一共不得超过6Mbps,互相之间允许借用带宽.我们的网卡是100Mbps
的.
# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit /
avpkt 1000 cell 8
# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit /
rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 /
avpkt 1000 bounded
这部分按惯例设置了根为1:0,并且绑定了类1:1.也就是说整个带宽不能超过
6Mbps.
如前所述,CBQ需要调整很多的参数.其实所有的参数上面都解释过了.相应
的HTB配置则要简明得多.
# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit /
rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 /
avpkt 1000
# tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit /
rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 /
avpkt 1000
我们建立了2个类.注意我们如何根据带宽来调整weight参数的.两个类都没
有配置成"bounded",但它们都连接到了类1:1上,而1:1设置了"bounded".
所以两个类的总带宽不会超过6Mbps.别忘了,同一个CBQ下面的子类的主号
码都必须与CBQ自己的号码相一致!
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
# tc qdisc add dev eth0 parent 1:4 handle 40: sfq
缺省情况下,两个类都有一个FIFO队列规定.但是我们把它换成SFQ队列,以
保证每个数据流都公平对待.
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip /
sport 80 0xffff flowid 1:3
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip /
sport 25 0xffff flowid 1:4
这些命令规定了根上的过滤器,保证数据流被送到正确的队列规定中去.
注意:我们先使用了"tc class add" 在一个队列规定中创建了类,然后使用"tc
qdisc add"在类中创建队列规定.
你可能想知道,那些没有被那两条规则分类的数据流怎样处理了呢?从这个例子
来说,它们被1:0直接处理,没有限制.
如果SMTP+web的总带宽需求大于6Mbps,那么这6M带宽将按照两个类的
52
weight参数的比例情况进行分割:WEB服务器得到5/8的带宽,SMTP得到3/8
的带宽.
从这个例子来说,你也可以这么认为:WEB数据流总是会得到
5/8*6Mbps=3.75Mbps的带宽.
9.5.4.5. 其它CBQ参数:split和defmap
如前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个
类去排队.
除了调用过滤器,CBQ还提供了其他方式,defmap和split.很难掌握,但好在
无关大局.但是现在是解释defmap和split的最佳时机,我会尽力解释.
因为你经常是仅仅需要根据TOS来进行分类,所以提供了一种特殊的语法.当
CBQ需要决定了数据包要在哪里入队时,要检查这个节点是否为"split节点".
如果是,子队列规定中的一个应该指出它接收所有带有某种优先权值的数据包,
权值可以来自TOS字段或者应用程序设置的套接字选项.
数据包的优先权位与defmap字段的值进行"或"运算来决定是否存在这样的匹
配.换句话说,这是一个可以快捷创建仅仅匹配某种优先权值数据包的过滤器的
方法.如果defmap等于0xff,就会匹配所有包,0则是不匹配.这个简单的配
置可以帮助理解:
# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 /
cell 8 avpkt 1000 mpu 64
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit /
rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 /
avpkt 1000
一个标准的CBQ前导.
Defmap参照TC_PRIO位(我从来不直接使用数字!):
TC_PRIO.. Num 对应 TOS
-------------------------------------------------
BESTEFFORT 0 最高可靠性
FILLER 1 最低成本
BULK 2 最大吞吐量(0x8)
INTERACTIVE_BULK 4
INTERACTIVE 6 最小延迟(0x10)
CONTROL 7
TC_PRIO..的数值对应它右面的bit.关于TOS位如何换算成优先权值的细节可
以参照pfifo_fast有关章节.
然后是交互和大吞吐量的类:
# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit /
rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 /
avpkt 1000 split 1:0 defmap c0
# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit /
rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 /
avpkt 1000 split 1:0 defmap 3f
53
"split队列规定"是1:0,也就是做出选择的地方.c0是二进制的11000000,3F
是00111111,所以它们共同匹配所有的数据包.第一个类匹配第7和第6位,也
就是负责"交互"和"控制"的数据包.第二个类匹配其余的数据包.
节点1:0现在应该有了这样一个表格:
priority send to
0 1:3
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
为了更有趣,你还可以传递一个"change掩码",确切地指出你想改变哪个优先
权值.你只有在使用了"tc class change"的时候才需要.比如,往1:2中添加best
effort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01
现在,1:0上的优先权分布应该是:
priority send to
0 1:2
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
求助: 尚未测试过"tc class change",资料上这么写的.
9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶)
Martin Devera ()正确地意识到CBQ太复杂,而且并没有按照多数常见情
况进行优化.他的Hierarchical能够很好地满足这样一种情况:你有一个固定速
率的链路,希望分割给多种不同的用途使用.为每种用途做出带宽承诺并实现定
量的带宽借用.
HTB就象CBQ一样工作,但是并不靠计算闲置时间来整形.它是一个分类的令
牌桶过滤器.它只有很少的参数,并且在它的网站能够找到很好的文档.
随着你的HTB配置越来越复杂,你的配置工作也会变得复杂.但是使用CBQ的
话,即使在很简单的情况下配置也会非常复杂!HTB3 (关于它的版本情况,请参
阅它的网站)已经成了官方内核的一部分(2.4.20-pre1,2.5.31及其后).然而,你
可能仍然要为你的tc命令打上HTB3支持补丁,否则你的tc命令不理解HTB3.
如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用HTB.
54
9.5.5.1. 配置范例
环境与要求与上述CBQ的例子一样.
# tc qdisc add dev eth0 root handle 1: htb default 30
# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k
作者建议2在那些类的下方放置SFQ:
# tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
# tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
# tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
添加过滤器,直接把流量导向相应的类:
# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20
这就完了——没有没见过的或者没解释过的数字,没有不明意义的参数.
HTB完成得相当不错——如果10:和20:都得到了保证的速率,剩下的就是分割
了,它们借用的比率是5:3,正如你其网的那样.
未被分类的流量被送到了30:,仅有一点点带宽,但是却可以任意借用剩下的带
宽.因为我们内部使用了SFQ,而可以公平发包.
9.6. 使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的"分类器链" 进行选择.这个
链中包含了这个分类队列规定所需的所有过滤器.
重复前面那棵树:
根1:
|
_1:1_
/ | /
/ | /
/ | /
10: 11: 12:
/ / / /
10:1 10:2 12:1 12:2
当一个数据包入队的时候,每一个分支处都会咨询过滤器链如何进行下一步.典
型的配置是在1:1处有一个过滤器把数据包交给12:,然后12:处的过滤器在把包
交给12:2.
你可以把后一个过滤器同时放在1:1处,可因为…having more specific tests lower
in the chain.…而得到效率的提高.
55
另外,你不能用过滤器把数据包向"上"送.而且,使用HTB的时候应该把所
有的规则放到根上!
再次强调:数据包只能向"下"进行入队操作!只有处队的时候才会上到网卡所
在的位置来.他们不会落到树的最底层后送到网卡!
9.6.1. 过滤器的一些简单范例
就象在"分类器"那章所解释的,借助一些复杂的语法你可以详细地匹配任何事
情.下面我们就开始,从简单地匹配一些比较明显的特征开始.
比方说,我们有一个PRIO队列规定,叫做"10:",包含3个类,我们希望把去
往22口的数据流发送到最优先的频道中去.应该这样设置过滤器:
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
match ip dport 22 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
match ip sport 80 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2
什么意思呢?是说:
向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是去往22口(精
确匹配)的IP数据包,发送到频道10:1.
向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是来自80口(精
确匹配)的IP数据包,发送到频道10:1.
向eth0上的10:节点添加一个过滤规则,它的优先权是2:凡是上面未匹配的IP
数据包,发送到频道10:2.
别忘了添加"dev eth0"(你的网卡或许叫别的名字),因为每个网卡的句柄都有
完全相同的命名空间.
想通过IP地址进行筛选的话,这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
match ip dst 4.3.2.1/32 flowid 10:1
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
match ip src 1.2.3.4/32 flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 /
flowid 10:2
这个例子把去往4.3.2.1和来自1.2.3.4的数据包送到了最高优先的队列,其它的
则送到次高权限的队列.
你可以连续使用match,想匹配来自1.2.3.4的80口的数据包的话,就这么敲:
56
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 match ip
sport 80 0xffff flowid 10:1
9.6.2. 常用到的过滤命令一览
这里列出的绝大多数命令都根据这个命令改编而来:
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 ……
这些是所谓的"u32"匹配,可以匹配数据包的任意部分.
根据源/目的地址
源地址段 'match ip src 1.2.3.0/24'
目的地址段 'match ip dst 4.3.2.0/24'
单个IP地址使用"/32"作为掩码即可.
根据源/目的端口,所有IP协议
源 'match ip sport 80 0xffff'
目的 'match ip dport 80 0xffff'
根据IP协议 (tcp, udp, icmp, gre, ipsec)
使用/etc/protocols所指定的数字.
比如: icmp是1:'match ip protocol 1 0xff'.
根据fwmark
你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过
网卡的路由过程中保留下来.如果你希望对来自eth0并从eth1发出的数
据包做整形,这就很有用了.语法是这样的:
t
c filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1
注意,这不是一个u32匹配!
你可以象这样给数据包打标记:
#
iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
数字6是可以任意指定的.
如果你不想去学习所有的tc语法,就可以与iptables结合,仅仅学习按
fwmark匹配就行了.
按TOS字段
选择交互和最小延迟的数据流:
#
tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 /
57
m
atch ip tos 0x10 0xff flowid 1:4
想匹配大量传输的话,使用"0x08 0xff".
关于更多的过滤命令,请参照"高级过滤"那一章.
9.7. IMQ(Intermediate queueing device,中介队列
设备)
中介队列设备不是一个队列规定,但它的使用与队列规定是紧密相连的.就Linux
而言,队列规定是附带在网卡上的,所有在这个网卡上排队的数据都排进这个队
列规定.根据这个概念,出现了两个局限:
1. 只能进行出口整形(虽然也存在入口队列规定,但在上面实现分类的队列规定
的可能性非常小).
2. 一个队列规定只能处理一块网卡的流量,无法设置全局的限速.
IMQ就是用来解决上述两个局限的.简单地说,你可以往一个队列规定中放任
何东西.被打了特定标记的数据包在netfilter的NF_IP_PRE_ROUTING 和
NF_IP_POST_ROUTING两个钩子函数处被拦截,并被送到一个队列规定中,该
队列规定附加到一个IMQ设备上.对数据包打标记要用到iptables的一种处理方
法.
这样你就可以对刚刚进入网卡的数据包打上标记进行入口整形,或者把网卡们当
成一个个的类来看待而进行全局整形设置.你还可以做很多事情,比如:把http
流量放到一个队列规定中去,把新的连接请求放到一个队列规定中去,……
9.7.1. 配置范例
我们首先想到的是进行入口整形,以便让你自己得到高保证的带宽 .就象配置
其它网卡一样:
tc qdisc add dev imq0 root handle 1: htb default 20
tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit
tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
tc qdisc add dev imq0 parent 1:20 handle 20: sfq
tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match /
ip dst 10.0.0.230/32 flowid 1:10
在这个例子中,使用了u32进行分类.其它的分类器应该也能实现.然后,被打
上标记的包被送到imq0排队.
iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0
58
ip link set imq0 up
iptables的IMQ处理方法只能用在PREROUTING和POSTROUTING链的mangle
表中.语法是:
IMQ [ --todev n ]
n: imq设备的编号
注:ip6tables也提供了这种处理方法.
请注意,如果数据流是事后才匹配到IMQ处理方法上的,数据就不会入队.数
据流进入imq的确切位置取决于这个数据流究竟是流进的还是流出的.下面是
netfilter(也就是iptables)在内核中预先定义优先级:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
};
对于流入的包,imq把自己注册为优先权等于NF_IP_PRI_MANGLE+1,也就是
说数据包在经过了PREROUTING链的mangle表之后才进入imq设备.
对于流出的包,imq使用优先权等于NF_IP_PRI_LAST,也就是说不会白白处理
本应该被filter表丢弃的数据包.
关于补丁和更多的文档请参阅imq网站.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值