其实 Linux 核心已经提供我们许多 queueing disciplines 了。到目前为止﹐最为广泛应用的是 pfifo_fast 伫列演算法﹐也是预设使用的。这也解释了为何这些进阶功能如此强悍。他们除了只是 '另外的伫列' 以外﹐别无它物。
每一种伫列演算法都各有千秋。不过并非全部都经严密测试过的就是了。
望文生意﹐此伫列演算法就是先入先出(First In First Out)﹐接收到的封包﹐均一视同仁。这种伫列演算法有 3 个所谓的 'bands'。在每一个 band 中﹐均奉行 FIFO 的规则。然而﹐z只有在 band0 中的封包处理完之后﹐才轮到 band1 里面的。在 band1 和 band2 之间﹐也是同样的情形。
一如前述﹐SFQ 也不是放之四海皆准的﹐但(平均来说)还是可用的就是了。它主要的好处是它所占用的 CPU 和内存都很少﹐而?真正'的平等(fair) 伫列法需要核心追踪所有运作中的连线。
在平等伫列演算法家族中﹐Stochastic Fairness Queueing (SFQ) 算是较为简明的实作。虽然它不如其它演算法那么精确﹐不过在保持相当平等的前提下﹐它所需要的运算也较少。
在 SFQ 中的关键词是会谈(conversation)(或曰流程(flow))﹐在一连串的数据封包中找到足够的公约数﹐以区分出不同的 conversation。IP 封包里面都带有来源和目的位址﹐以及协定号码﹐此情形下﹐这些参数就派得上用场了。
SFQ 由动态分配的 FIFO queues 组成﹐一个 queue 负责一个 conversation。而 discipline 则以 round-robin 的形式执行﹐每次从一个 FIFO 中送出一个封包﹐这就是为何称之为平等(fair)的原因了。SFQ 的主要好处在于它让不同的程序平等的分享连线﹐而避免频宽被单一的客户程序所占据。然而﹐SFQ 却不能从 bulk flows 中判定出互动(interactive)的部份 --- 这需要先于前面的 CBQ 进行筛选﹐然后才将 bulk 流量导入 SFQ 中。
Token Bucket Filter(TBF)是一种简单的网路伫列演算法。当网路封包经过这个伫列时﹐就会受到一个预先设定的速率限制(封包数/时间)﹐利用这个﹐可以来减缓网路瞬增流量(buffer short bursts)所造成的网路效率降低。
至于 TBF 的实作﹐则由一个缓冲区(bucket) 构成﹐不断的被一些虚拟信息﹐称为 tokens ﹐按特定的速率(token rate) 填充。而缓冲区的重要参数是其体积﹐也就是它所能存储的 token 数目。
每一个抵达的 token 抵消一个离开伫列的传入数据封包﹐然后从缓冲区中清掉。另外﹐有两个流程(flow) 与此演算法息息相关的﹕ token 和 data﹐这一共会带出三种状况﹕
最后一种情形千万不能掉以轻心﹐因为这会强行将频宽拨给数据﹐而通过过滤器。至于 token 的积聚﹐则允许超出限制之瞬增流量仍可不被遗失地通过﹐但其后的持续过载﹐均会导致封包被持续的丢弃。
(译者注﹕这个 TBF 其实不难理解﹐就是 no token no data 原则。只有当 data 获得相应的 token 才能通过﹐而 token 的载入速率则是固定的。)
Linux 核心看起来似乎超过此一规格﹐而且还允许我们限制瞬增流量的速度。然而﹐Alexey 警告我们说﹕
注意﹕TBF 的最高峰值(peak rate) 相当高﹕当 MTU 为 1500 的时候﹐P_crit = 150Kbytes/sec。
所以﹐如果您需要更大的峰值﹐使用 HZ=1000 的 alpha 机器啰 :-)
FIXME﹕不清楚是否仍有 TSC (pentium+) ﹖嗯﹐看来有那么点儿
FIXME﹕若不然﹐要为提高的 HZ 另辟章节
RED 可算是身怀绝技。当一个 TCP/IP 连线建立起来的时候﹐连线两端都不清楚到底频宽会有多大。所以 TCP/IP 会先由低速开始然后逐渐加快脚步﹐最后受制于 ACKs 回应的延迟。
当一条线路满载的时候﹐RED 就会开始丢弃封包﹐告知 TCP/IP 这条线路已达拥塞状态﹐需要减低速度了。聪明之处在于 RED 会模拟真正的拥塞﹐同时在线路完全满载之前开始丢弃封包。一旦线路完全饱和﹐它就担当起交通警察的角色。
如需更详细的资料﹐请参考 Backbone 那章。
如果您不想借助于 router 或其它 Linux 机器﹐而想要限制特定的主机﹐那么 Ingress qdisc 会是您的随身暗器。当频宽超过您所设定的比率的时候﹐您可以管制入向频宽及丢弃封包。比方说﹐可以保护您的主机抵御 SYN flood 的攻击﹐而且也可以用来降低 TCP/IP 速度﹐也就是以丢弃封包的方法来减速。
FIXME﹕除了丢弃之外﹐我们是否可以将之分配给一个真实的 queue 呢﹖
FIXME﹕以丢弃封包来进行管制似乎并非上上之举﹐倒不如用 token 缓冲区过滤器。不敢莽断啦﹐Cisco CAR 也都用这个﹐而且人们似乎也受之若然。
请参考本文最后面的 IOS Committed Access Rate 。
简而言之﹕您可以用之来限制您电脑下载文档有多快﹐而腾出更多频宽给其它用途。
请参考 帮贵主机抵御 SYN floods 那章﹐那里有一个例子告诉您它是如何做到的。
本章由 Esteve Camps <esteve@hades.udg.es> 撰写。
首先﹐再首先﹐您最好先到 IETF DiffServ working Group web site 和 Werner Almesberger web site(在 Linux 支持 Differentiated Services 的程序正是由他写的) ﹐读一读 RFC 文件(RFC2474﹑RFC2475﹑RFC2597﹑以及 RFC2598)。
Dsmark 是一种伫列演算戒律(discipline)﹐主要用于 Differentiated Services (也称为 DiffServ 或简称 DS)。DiffServ 是两种 actual QoS 架构之一 (另外一个叫做 Intergrated Services)﹐主要依靠 IP 封包标头中的 DS 栏位所携带的数值进行判断。
最早在 IP 设计上所提供的 QoS 层级解决方案﹐其中之一就是 IP 标头中的 Type of Service 栏位(TOS byte)。改变这些数值﹐我们可以选择一个 高/低 等级的吞吐量﹑延迟﹑或是可靠度。但是这并不能提供足够的灵活性﹐以满足较新服务(如 real-time 应用程序﹑互动程序﹑和其它)的需求。有鉴于此﹐新的架构出现了。其一就是 DiffSserv﹐它会保留 TOS bits ﹐同时重新命名 DS 栏位。
DiffServ 是以群组为导向的(group-oriented)。我是说﹐我们无须知道流向(flows)是如何运作的(这是 Intergrated Services 的事情)﹔我们只知道流向聚集(flow aggregations)﹐以及根据封包所属的聚集如何应用不同的行为特性。
当封包抵达一个边缘节点(即 DiffServ domain 的入口节点)﹐并进入 DiffSer Domain 的时候﹐我们就需要建立一些原则(policy)﹐引导 和/或 标识这些封包(所谓标识﹐就是设定 DS 栏位的数值)。然后 DiffSer Domain 的内部/核心节点就检查这些标识/数值 ﹐以判定应用什么样的行为特性或 QoS 等级。
正如您所推断的﹐DiffServ 意味著一个应用到所有 DS 规则的 domain。事实上﹐您可以这样想像"我们会对所有进入 domain 的封包进行分类。一旦它们进入 domain﹐它们就逮属于分类所指定的规则﹐并且每一个穿越节点都会应用这个 QoS 等级"。
实际而言﹐您可以在本地 domains 里面应用您自定的原则﹐但是﹐当您连接到其它 DS domains 的时候﹐就需要顾及到某些 服务等级协议(Service Level Agreements) 。
至此﹐您或许满腹疑团吧。DiffServ 远比我所解释的要复杂得多。事实上﹐您不难想像﹐我可没那能耐将 3 个以上的 RFC 压缩在短短 50 行里面哦 :-)
根据 DiffServ 学科所指定﹐我们要区别出边界(boundary)节点和内部(interior)节点。在流量路径上有两个关键点﹐两者在封包到达的时候均会进行分类。在封包真正送出网路之前﹐其结果会在 DS 处理过程中的不同地方使用到。这是因为 DiffServ 程序提供了一个称为 sk_buff 的机制﹐包括一个新的栏位﹐称为 skb->tc_index﹐用来储存初始分类的结果﹐用于 DS 处理中的不同目的。
该 skb->tc_index 数值会被 DSMARK qdisc 用来做初始设定﹐从每一个接收封包的 IP 标头之 DS 栏位就可以获得。另外﹐cls_tcindex 分类器会读取全部或部份的 skb->tcindex 数值﹐用来选择等级(classes)。
然而﹐首先﹐请参阅一下 DSMARK qdisc 命令以及它的参数﹕
... dsmark indices INDICES [ default_index DEFAULT_INDEX ] [ set_tc_index ]
这些参数究竟代表什么呢﹖
然后让我们看看 DSMARK 的运作。
此 qdisc 的步骤如下﹕﹕
New_Ds_field = ( Old_DS_field & mask ) | value
skb->ihp->tos
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >
| | ^
| -- If you declare set_tc_index, we set DS | | <-----May change
| value into skb->tc_index variable | |O DS field
| A| |R
+-|-+ +------+ +---+-+ Internal +-+ +---N|-----|----+
| | | | tc |--->| | |--> . . . -->| | | D| | |
| | |----->|index |--->| | | Qdisc | |---->| v | |
| | | |filter|--->| | | +---------------+ | ---->(mask,value) |
-->| O | +------+ +-|-+--------------^----+ / | (. , .) |
| | | ^ | | | | (. , .) |
| | +----------|---------|----------------|-------|--+ (. , .) |
| | sch_dsmark | | | | |
+-|------------|---------|----------------|-------|------------------+
| | | <- tc_index -> | |
| |(read) | may change | | <--------------Index to the
| | | | | (mask,value)
v | v v | pairs table
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->
skb->tc_index
那如何做标识呢﹖只需修改您想重新标识的分类(class)之 mask 和 value 就可以了。请参考下一行程序﹕
tc class change dev eth0 classid 1:1 dsmark mask 0x3 value 0xb8
这会改变那对存于杂凑表格内的 mask 和 value ﹐并重新将封包标识为属于 class 1:1 。您必须 "修改" 这个数值﹐因为预设的数值 (mask, value) 会获得初始值(看后面的表格)。
现在﹐我们将解释一下 TC_INDEX 过滤器是如何工作的﹐以及如何满足它的需求。另外﹐TCINDEX 过滤器还可以用在其它设定上﹐并不只限于 DS 服务之中。
宣告一个 TC_INDEX 过滤器的基本命令如下﹕
... tcindex [ hash SIZE ] [ mask MASK ] [ shift SHIFT ]
[ pass_on | fall_through ]
[ classid CLASSID ] [ police POLICE_SPEC ]
这里﹐我们沿用过往例子解释 TC_INDEX 的运算模式。请特别留意标为粗体的文字﹕首先﹐假设我们收到一个标识为 EF 的封包。如果您读过 RFC2598﹐那您应该看到 EF 流量的 DSCP 建议值为 101110。这表示 DS 栏位将会是 10111000 (要知道 TOS byte 中的次重要位元并不会用于 DS 中)﹐或是以十六进位表示为 0xb8 。
TC INDEX
FILTER
+---+ +-------+ +---+-+ +------+ +-+ +-------+
| | | | | | | |FILTER| +-+ +-+ | | | |
| |----->| MASK | -> | | | -> |HANDLE|->| | | | -> | | -> | |
| | . | =0xfc | | | | |0x2E | | +----+ | | | | |
| | . | | | | | +------+ +--------+ | | | |
| | . | | | | | | | | |
-->| | . | SHIFT | | | | | | | |-->
| | . | =2 | | | +----------------------------+ | | |
| | | | | | CBQ 2:0 | | |
| | +-------+ +---+--------------------------------+ | |
| | | |
| +-------------------------------------------------------------+ |
| DSMARK 1:0 |
+-------------------------------------------------------------------------+
封包抵达之后﹐将 DS 栏位设定为 0xb8。正如我们前面所解释的﹐例中的 dsmark qdisc 被鉴定为 id 1:0 ﹐读取 DS 栏位﹐并存放于 skb->tc_index 变数之内。例中的下一步﹐相当于该 qdisc 关联的过滤器(例中第 2 行)。这将进行下一个运算﹕
Value1 = skb->tc_index & MASK
Key = Value1 >> SHIFT
例中﹐MASK=0xFC i SHIFT=2。
Value1 = 10111000 & 11111100 = 10111000
Key = 10111000 >> 2 = 00101110 -> 0x2E in hexadecimal
所返回的数值就是 qdisc 内部过滤器之 handle(本例中为 identifier 2:0)。假如找到这个 ID 的过滤器﹐原则和测量条件就获得确认 (本例中的过滤器就包括这个)﹐同时会返回 classid(本例中为﹕classid 2:1)﹐并且存放于 skb->tc_index 变数里面。
不过﹐假如找到任何带此 ID 的过滤器﹐其结果将取决于 fall_through 旗标的宣告。然则﹐返回的 classid 则以数值键值(value key)为准。否则﹐将返回一个错误﹐同时处理程序将继续剩余的过滤器。小心哦﹐如果您使用 fall_through 旗标的话﹐假如 skb->tc_index 变数之数值和 class id 之间存在一个简单的关联﹐即可完成。
最后要注解的参数是 hash 和 pass_on。前者关乎杂凑表格的体积﹔而 pass_on 则指示﹕如果没有发现与该过滤器结果相等的 classid﹐则尝试下一个过滤器。预设动作为 fall_through (看下一个表格)
最后﹐让我们看看有哪些可能的数值﹐是可以用来设定全部 TCINDEX 数值的﹕
TC Name Value Default
-----------------------------------------------------------------
Hash 1...0x10000 Implementation dependent
Mask 0...0xffff 0xffff
Shift 0...15 0
Fall through / Pass_on Flag Fall_through
Classid Major:minor None
Police ..... None
此类过滤器实在非常厉害。它必须探勘所有的可能性。另外﹐这个过滤器不仅可以用在 DiffServ 设定中﹐还可以应用在其它种类的过滤器上面。
我建议您抽空看看 iproute2 套件中的所有 DiffServ 范例。我向各位保证﹐我将会竭尽全力尽快完成文字的部份。另外﹐我所作的解释全都是许许多多测试的结果。