1. 简介
    Linux内核网络协议栈从2.2.x开始,就实现了对服务质量的支持模块。具体的代码位于net/sched/目录。在Linux里面,对这个功能模块的称呼是Traffic Control ,简称TC。TC是一个在上层协议处添加Qos功能的工具,原理上看,它实质是专门供用户利用内核Qos调度模块去定制Qos的中间件。

Tc用于Linux内核的流量控制,流量控制包括以下几种方式:
 SHAPING(限制)
当流量被限制,它的传输速率就被控制在某个值以下。限制值可以大大小于有效带宽,这样可以平滑突发数据流量,使网络更为稳定。shaping(限制)只适用于向外的流量。
 SCHEDULING(调度)
通过调度数据包的传输,可以在带宽范围内,按照优先级分配带宽。SCHEDULING(调度)也只适于向外的流量。
 POLICING(策略)
SHAPING用于处理向外的流量,而POLICIING(策略)用于处理接收到的数据。
 DROPPING(丢弃)
如果流量超过某个设定的带宽,就丢弃数据包,不管是向内还是向外。

所谓的递归控制就是分层次地控制,而对于每个层次,控制方式都是一致的。流量的处理由三种对象控制,它们是:qdisc(排队规则)、class(类别)和filter(过滤器),按照Qdisc –class –filter 的树型组织模式.
 qdisc 队列规则(queueing discipline):
用来实现控制网络的收发速度.通过队列,linux可以将网络数据包缓存起来,然后根据用户的设置,在尽量不中断连接(如 tcp)的前提下来平滑网络流量.需要注意的是,linux 对接收队列的控制不够好,所以我们一般只用发送队列,即"控发不控收".它封装了其他两个主要 tc 组件(类和分类器).内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的 qdisc 队列规则把数据包加入队列.然后,内核会尽可能多地从 qdisc里面取出数据包,把它们交给网络适配器驱动模块。
最简单的 QDisc 是 pfifo 它不对进入的数据包做任何的处理,数据包采用先入先出的方式通过队列.不过,它会保存网络接口一时无法处理的数据包.常有的队列规则包括 FIFO 先进先出,RED 随机早期探测,SFQ 随机公平队列和令牌桶 Token Bucket,类基队列 CBQ,CBQ 是一种超级队列,即它能够包含其它队列,甚至其它CBQ。
 Class 类
class 用来表示控制策略.很显然,很多时候,我们很可能要对不同的IP实行不同的流量控制策略,这时候我们就得用不同的class来表示不同的控制策略了。
 Filter 规则
filter 用来将用户划入到具体的控制策略中
目前,tc可以使用的过滤器有:fwmark分类器,u32 分类器,基于路由的分类器和 RSVP 分类器(分别用于IPV6、IPV4)等;其中,fwmark 分类器允许我们使用 Linux netfilter 代码选择流量,而 u32 分类器允许我们选择基于 ANY 头的流量 .需要注意的是,filter (过滤器)是在QDisc 内部,它们不能作为主体。

操作原理:
类(Class)组成一个树,每个类都只有一个父类,而一个类可以有多个子类。某些QDisc(例如:CBQ和HTB)允许在运行时动态添加类,而其它的QDisc(例如:PRIO)不允许动态建立类。
允许动态添加类的QDisc可以有零个或者多个子类,由它们为数据包排队。
此外,每个类都有一个叶子QDisc,默认情况下,这个叶子QDisc使用pfifo的方式排队,我们也可以使用其它类型的QDisc代替这个默认的QDisc。而且,这个叶子叶子QDisc有可以分类,不过每个子类只能有一个叶子QDisc。
当一个数据包进入一个分类QDisc,它会被归入某个子类。我们可以使用以下三种方式为数据包归类,不过不是所有的QDisc都能够使用这三种方式。

 tc过滤器(tc filter)
如果过滤器附属于一个类,相关的指令就会对它们进行查询。过滤器能够匹配数据包头所有的域,也可以匹配由ipchains或者iptables做的标记。
 服务类型(Type of Service)
某些QDisc有基于服务类型(Type of Service,ToS)的内置的规则为数据包分类。
skb->priority
用户空间的应用程序可以使用SO_PRIORITY选项在skb->priority域设置一个类的ID。
树的每个节点都可以有自己的过滤器,但是高层的过滤器也可以直接用于其子类。
如果数据包没有被成功归类,就会被排到这个类的叶子QDisc的队中。相关细节在各个QDisc的手册页中。
 命名规则
所有的QDisc、类和过滤器都有ID。ID可以手工设置,也可以有内核自动分配。
ID由一个主序列号和一个从序列号组成,两个数字用一个冒号分开。
 QDISC
一个QDisc会被分配一个主序列号,叫做句柄(handle),然后把从序列号作为类的命名空间。句柄采用象10:一样的表达方式。习惯上,需要为有子类的QDisc显式地分配一个句柄。
 类(CLASS)
在同一个QDisc里面的类分享这个QDisc的主序列号,但是每个类都有自己的从序列号,叫做类识别符(classid)。类识别符只与父QDisc有关,和父类无关。类的命名习惯和QDisc的相同。
 过滤器(FILTER)
过滤器的ID有三部分,只有在对过滤器进行散列组织才会用到。

  1. 转发流程
    首先我们了解一下Linux网络协议栈在没有TC模块时发送数据包的大致流程。如图1。

为了支持QoS,Linux的设计者在发送数据包的代码中加入了TC模块。从而可以对数据包进行分类,管理,检测拥塞和处理拥塞。为了避免和以前的代码冲突,并且让用户可以选择是否使用TC。内核开发者在上图中的两个红色圆圈之间添加了TC模块。(实际上在TC模块中,发送数据包也实现对AF_PACKET协议的支持,本文为了描述方便,把两个地方的AF_PACKET协议处理分开来了)。
下面从具体的代码中分析一下对TC模块的支持。
net/core/dev.c: dev_queue_xmit函数中略了部分代码:
int dev_queue_xmit(struct sk_buff *skb)
{
……………….

q = dev->qdisc;
if (q->enqueue) {/*如果这个设备启动了TC,那么把数据包压入队列*/int ret = q->enqueue(skb, q);/*启动这个设备发送*/qdisc_run(dev);return;

}

发送数据包的流程应该是这样的:
(1) 上层协议开始发送数据包
(2) 获得当前设备所采用的策略对象
(3) 调用此对象的enqueue方法把数据包压入队列
(4) 调用此对象的dequeue方法从队列中取出数据包
(5) 调用网卡驱动的发送函数发送

  1. 初始化处理流程
    在网卡注册的时候,都会调用register_netdevice,给设备安装一个Qdisc和Qdisc_ops。

int register_netdevice(struct net_device *dev)
{
………………….
dev_init_scheduler(dev);
………………….
}
void dev_init_scheduler(struct net_device *dev)
{
………….

/*安装设备的qdisc为noop_qdisc*/
dev->qdisc = &noop_qdisc;

………….

dev->qdisc_sleeping = &noop_qdisc;
dev_watchdog_init(dev);

}
/ 此时,网卡设备刚注册,还没有UP,采用的是noop_qdisc /
struct Qdisc noop_qdisc =
{

noop_enqueue,
noop_dequeue,
TCQ_F_BUILTIN,
&noop_qdisc_ops,    

};
noop_qdisc采用的数据包处理方法是noop_qdisc_ops,
struct Qdisc_ops noop_qdisc_ops =
{

NULL,
NULL,
"noop",
0,
noop_enqueue,
noop_dequeue,
noop_requeue,

};
从noop_enqueue,noop_dequeue,noop_requeue函数的定义可以看出,他们并没有对数据包进行任何的分类或者排队,而是直接释放掉skb。所以此时网卡设备还不能发送任何数据包。必须ifconfig up起来之后才能发送数据包。
调用ifconfig up来启动网卡设备会走到dev_open函数。
int dev_open(struct net_device *dev)
{
…………….
dev_activate(dev);
……………..
}
void dev_activate(struct net_device *dev)
{
…………. if (dev->qdisc_sleeping == &noop_qdisc) {

        qdisc = qdisc_create_dflt(dev, &pfifo_fast_ops);/*安装缺省的qdisc*/

}
……………
if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc) {
……………./.安装特定的qdisc/

}

……………..
}
设备启动之后,此时当前设备缺省的Qdisc->ops是pfifo_fast_ops。如果需要采用不同的ops,那么就需要为设备安装其他的Qdisc。本质上是替换掉dev->Qdisc指针。见sched/sch_api.c 的dev_graft_qdisc函数。
static struct Qdisc *
dev_graft_qdisc(struct net_device dev, struct Qdisc qdisc)
{
……………
oqdisc = dev->qdisc_sleeping;
/ 首先删除掉旧的qdisc /
if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)

qdisc_reset(oqdisc);
/*安装新的qdisc */
if (qdisc == NULL)qdisc = &noop_qdisc;dev->qdisc_sleeping = qdisc;dev->qdisc = &noop_qdisc;
/*启动新安装的qdisc*/
if (dev->flags & IFF_UP)dev_activate(dev);

…………………
}
从dev_graft_qdisc可以看出,如果需要使用新的Qdisc,那么首先需要删除旧的,然后安装新的,使dev->qdisc_sleeping 为新的qdisc,然后调用dev_activate函数来启动新的qdisc。结合dev_activate函数中的语句:
if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc)
可以看出,此时的dev->qdisc所指的就是新的qdisc。(注意,上面语句中左边是一个赋值语句。)
在网卡down掉的时候,通过调用dev_close -> dev_deactivate重新使设备的qdisc为noop_qdisc,停止发送数据包。
Linux中的所有的QoS策略最终都是通过上面这个方法来安装的。在sch_api.c中,对dev_graft_qdisc函数又封装了一层函数(register_qdisc),供模块来安装新的Qdisc。如RED(早期随即检测队列)模块,就调用register_qdisc来安装RED对象(net/sched/sch_red.c->init_module())。

  1. 管理QOS队列
    TC是一个在上层协议处添加Qos功能的工具,原理上看,它实质是专门供用户利用内核Qos调度模块去定制Qos的中间件,本节主要是阐述TC工具是如何去队列规则的,以及内部是如何实现的。

首先需要了解的是,TC作为一个应用工具,它又是如何与内核去实现通讯的?很简单,消息机制,所借助的工具则是Netlink,而所使用的协议正是NETLINK_ROUTE。
在此可以说明下TC源代码中是如何初始化rtnetlink(可以理解为专门为路由设计的netlink)socket的。
struct rtnl_handle
{

int         fd;
struct sockaddr_nl  local;
struct sockaddr_nl  peer;
__u32           seq;
__u32           dump;    

};
struct rtnl_handle *rth
rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
...
rth->local.nl_family = AF_NETLINK;
rth->local.nl_groups = 0;
bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local);
下面主要以TC工具对qdisc操作(包括增加,修改,取代等等)的实现。对qdisc规则解析代码是在tc_qdisc_modify函数中完成的,然后通过消息机制交给内核相关模块去处理。下面是其中一段消息初始化代码片段:

struct {struct nlmsghdr     n;struct tcmsg        t;char            buf[TCA_BUF_MAX];

} req;

struct tcmsg
{unsigned char   tcm_family;unsigned char   tcm__pad1;unsigned short  tcm__pad2;int     tcm_ifindex;__u32       tcm_handle;__u32       tcm_parent;__u32       tcm_info;
};
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
req.n.nlmsg_flags = NLM_F_REQUEST|flags;
req.n.nlmsg_type = RTM_NEWQDISC;
req.t.tcm_family = AF_UNSPEC;

有一点值得注意的是,因为针对各种不同的调度机制,有着不一样的参数选项,如sfq所对应的参数就有quantum, perturb, limit等,而htb则有r2q, default,在TC工具中针对这些不同的调度机制,定义了不一样的解析函数。如sfq和htb中的定义如下:

struct qdisc_util htb_qdisc_util = {.id         = "htb",.parse_qopt = htb_parse_opt,.print_qopt = htb_print_opt,.print_xstats   = htb_print_xstats,.parse_copt = htb_parse_class_opt,.print_copt = htb_print_opt,
};
struct qdisc_util sfq_qdisc_util = {.id     = "sfq",.parse_qopt = sfq_parse_opt,.print_qopt = sfq_print_opt,
};

而在tc_qdisc_modify函数中则是首先get_qdisc_kind去获取对应的调度机制名,然后调用跟此种调度机制对应的解析参数函数去执行,对应代码片段如下:

q = get_qdisc_kind(k);
...
if (q->parse_qopt(q, argc, argv, &req.n))return 1;

所有的参数均解析完成之后,接下来就是将消息发给内核(接着内核将会处理所收到的消息请求),并及时接受内核的回复消息。
当内核接收到请求消息后,按照消息的什么内容去完成消息的处理呢?消息的类型!前面总结了tc工具在不同的规则下有着对应的消息类型,例如,add, change, replace等操作所对应的消息类型则是RTM_NEWQDISC,因此,内核在收到此种消息类型之后会调用相应的模块去进行处理。这些消息处理模块全部放在了sch_api.c文件中,相关代码如下:
static int __init pktsched_init(void)
{

  register_qdisc(&pfifo_qdisc_ops);register_qdisc(&bfifo_qdisc_ops);proc_net_fops_create(&init_net, "psched", 0, &psched_fops);rtnl_register(PF_UNSPEC, RTM_NEWQDISC, tc_modify_qdisc, NULL);rtnl_register(PF_UNSPEC, RTM_DELQDISC, tc_get_qdisc, NULL);rtnl_register(PF_UNSPEC, RTM_GETQDISC, tc_get_qdisc, tc_dump_qdisc);rtnl_register(PF_UNSPEC, RTM_NEWTCLASS, tc_ctl_tclass, NULL);rtnl_register(PF_UNSPEC, RTM_DELTCLASS, tc_ctl_tclass, NULL);rtnl_register(PF_UNSPEC, RTM_GETTCLASS, tc_ctl_tclass, tc_dump_tclass);return 0;

}
从上面这段代码可以看出,模块中注册了消息类型以及与处理函数的对应关系。此处以RTM_NEWQDISC消息类型为例,此时需要调用tc_modify_qdisc函数去处理。处理的基本思想是这样的:因为不同的规则可能对应着相同的消息类型(如RTM_NEWQDISC),此时就需要再通过消息的标志量做进一步的操作,最后通过调用内核中有关qdisc的API函数去完成。
从上面的片段中可以看出,根据不同的标志量,调用不同的API函数去完成最后的功能,如qdisc_change用于去修改原qdisc规则,修改完成之后然后调用qdisc_notify去回复响应TC,qdisc_create用于去重新创建一个新的qdisc队列规则,qdisc_graft函数用于去将qdisc移植到某个对象上去。
以上以TC工具对Qdisc操作为例简单地阐述了TC工具是如何与内核进行交互的,以及内核又是如何响应请求并作出处理的,下节将探讨在ATM设备上如何设置Qos。

  1. 处理流程举例
    Linux缺省策略对象pfifo_fast_ops分析

在Linux中,如果设备启动之后,没有配置特定的QoS策略,内核对每个设备采用缺省的策略,pfifo_fast_ops。下面的pfifo_fast_ops进行详细的分析。
上图中的信息可以对应于pfifo_fast_ops结构体的每个部分:
static struct Qdisc_ops pfifo_fast_ops =
{

NULL,
NULL,
"pfifo_fast",                            /*ops名称*/
3 * sizeof(struct sk_buff_head),            /*数据包skb队列*/
pfifo_fast_enqueue,            /*入队列函数*/
pfifo_fast_dequeue,            /*出队列函数*/
pfifo_fast_requeue,            /*重新压入队列函数*/
NULL,
pfifo_fast_init,                /*队列管理初始化函数*/
pfifo_fast_reset,                /*队列管理重置函数*/

};
在注册pfifo_fast_ops的时候首先会调用pfifo_fast_init来初始化队列管理,见qdisc_create_dflt函数。
static int pfifo_fast_init(struct Qdisc qdisc, struct rtattr opt),init函数的作用就是初始化3个队列。

{
………

for (i=0; i<3; i++)skb_queue_head_init(list+i);        /*初始化3个优先级队列*/

……….
}

在注销一个Qdisc的时候都会调用Qdisc的ops的reset函数。见dev_graft_qdisc函数。
static void
pfifo_fast_reset(struct Qdisc* qdisc)
{
…………..

for (prio=0; prio < 3; prio++)skb_queue_purge(list+prio);        /*释放3个优先级队列中的所有数据包*/

…………..
}
在数据包发送的时候会调用Qdisc->enqueue函数(在qdisc_create_dflt函数中已经将Qdisc_ops的enqueue,dequeue,requeue函数分别赋值于Qdisc分别对应的函数指针)。
int dev_queue_xmit(struct sk_buff *skb)
{
……………….

q = dev->qdisc;
if (q->enqueue) {/* 对应于pfifo_fast_enqueue 函数*/int ret = q->enqueue(skb, q);/*启动这个设备的发送,这里涉及到两个函数pfifo_fast_dequeue ,pfifo_fast_requeue 稍后介绍*/qdisc_run(dev);return;
}

……………
}
入队列函数pfifo_fast_enqueue:
static int
pfifo_fast_enqueue(struct sk_buff skb, struct Qdisc qdisc)
{
…………..

list = ((struct sk_buff_head*)qdisc->data) +prio2band[skb->priority&TC_PRIO_MAX];/*首先确定这个数据包的优先级,决定放入的队列*/
if (list->qlen <= skb->dev->tx_queue_len) {__skb_queue_tail(list, skb);    /*将数据包放入队列的尾部*/qdisc->q.qlen++;return 0;
}

……………..
}
在数据包放入队列之后,调用qdisc_run来发送数据包。
static inline void qdisc_run(struct net_device *dev)
{

while (!netif_queue_stopped(dev) &&qdisc_restart(dev)<0)/* NOTHING */;

}
在qdisc_restart函数中,首先从队列中取出一个数据包(调用函数pfifo_fast_dequeue)。然后调用网卡驱动的发送函数(dev->hard_start_xmit)发送数据包,如果发送失败,则需要将这个数据包重新压入队列(pfifo_fast_requeue),然后启动协议栈的发送软中断进行再次的发送。
static struct sk_buff *
pfifo_fast_dequeue(struct Qdisc* qdisc)
{
…………..

for (prio = 0; prio < 3; prio++, list++) {skb = __skb_dequeue(list);if (skb) {qdisc->q.qlen--;return skb;}
}

……………….
}
从dequeue函数中可以看出,pfifo的策略是:从高优先级队列中取出数据包,只有高优先级的队列为空,才会对下一优先级的队列进行处理。
requeue函数重新将数据包压入相应优先级队列的头部。
static int
pfifo_fast_requeue(struct sk_buff skb, struct Qdisc qdisc)
{

struct sk_buff_head *list;
list = ((struct sk_buff_head*)qdisc->data) +prio2band[skb->priority&TC_PRIO_MAX];/*确定相应优先级的队列*/
__skb_queue_head(list, skb);/*将数据包压入队列的头部*/
qdisc->q.qlen++;
return 0;

}

  1. 命令格式
  2. qdisc [ add | change | replace | link ] dev DEV [ parent qdisc-id | root ] [ handle qdisc-id ] qdisc [ qdisc specific parameters ]

tc class [ add | change | replace ] dev DEV parent qdisc-id [ classid class-id ] qdisc [ qdisc specific parameters ]
tc filter [ add | change | replace ] dev DEV [ parent qdisc-id | root ] protocol protocol prio priority filtertype [ filtertype specific parameters ] flowid flow-id
tc [-s | -d ] qdisc show [ dev DEV ]
tc [-s | -d ] class show dev DEV tc filter show dev DEV
tc qdisc del dev eth0 root 2>/dev/null 清除 eth0 所有队列规则

tc可以使用以下命令对QDisc、类和过滤器进行操作:
 add
在一个节点里加入一个QDisc、类或者过滤器。添加时,需要传递一个祖先作为参数,传递参数时既可以使用ID也可以直接传递设备的根。如果要建立一个
QDisc或者过滤器,可以使用句柄(handle)来命名;如果要建立一个类,可以使用类识别符(classid)来命名。
 remove
删除有某个句柄(handle)指定的QDisc,根QDisc(root)也可以删除。被删除QDisc上的所有子类以及附属于各个类的过滤器都会被自动删除。
 change
以替代的方式修改某些条目。除了句柄(handle)和祖先不能修改以外,change命令的语法和add命令相同。换句话说,change命令不能一定节点的位置。
 replace
对一个现有节点进行近于原子操作的删除/添加。如果节点不存在,这个命令就会建立节点。
 link
只适用于DQisc,替代一个现有的节点。

CLASSLESS QDisc(不可分类QDisc)
无类别QDISC包括:
 [p|b]fifo
使用最简单的qdisc,纯粹的先进先出。只有一个参数:limit,用来设置队列的长度,pfifo是以数据包的个数为单位;bfifo是以字节数为单位。
 pfifo_fast
在编译内核时,如果打开了高级路由器(Advanced outer)编译选项,pfifo_fast就是系统的标准QDISC。它的队列包括三个波段(band)。在每个波段里面,使用先进先出规则。而三个波段(band)的优先级也不相同,band 0的优先级最高,band 2的最低。如果band里面有数据包,系统就不会处理band1里面的数据包,band 1和band 2之间也是一样。数据包是按照服务类型(Type ofService,TOS)被分配多三个波段(band)里面的。
 red
red是Random Early Detection(随机早期探测)的简写。如果使用这种QDISC,当带宽的占用接近于规定的带宽时,系统会随机地丢弃一些数据包。它非常适合高带宽应用。
 sfq
sfq是Stochastic Fairness Queueing的简写。它按照会话(session--对应于每个TCP连接或者UDP流)为流量进行排序,然后循环发送每个会话的数据包。
 tbf
tbf是Token Bucket Filter的简写,适合于把流速降低到某个值。
如果没有可分类QDisc,不可分类QDisc只能附属于设备的根。它们的用法如下:
tc qdisc add dev DEV root QDISC QDISC-PARAMETERS
要删除一个不可分类QDisc,需要使用如下命令:
tc qdisc del dev DEV root

一个网络接口上如果没有设置QDisc,pfifo_fast就作为缺省的QDisc。

CLASSFUL QDISC(分类QDisc)
可分类的QDisc包括:
 CBQ
CBQ 是Class Based
Queueing(基于类别排队)的缩写。它实现了一个丰富的连接共享类别结构,既有限制(shaping)带宽的能力,也具有带宽优先级管理的能力。带
宽限制是通过计算连接的空闲时间完成的。空闲时间的计算标准是数据包离队事件的频率和下层连接(数据链路层)的带宽。
 HTB
HTB是 Hierarchy Token
Bucket的缩写。通过在实践基础上的改进,它实现了一个丰富的连接共享类别体系。使用HTB可以很容易地保证每个类别的带宽,虽然它也允许特定的类可
以突破带宽上限,占用别的类的带宽。HTB可以通过TBF(Token Bucket Filter)实现带宽限制,也能够划分类别的优先级。
 PRIO
PRIO QDisc不能限制带宽,因为属于不同类别的数据包是顺序离队的。使用PRIO
QDisc可以很容易对流量进行优先级管理,只有属于高优先级类别的数据包全部发送完毕,才会发送属于低优先级类别的数据包。为了方便管理,需要使用
iptables或者ipchains处理数据包的服务类型(Type Of Service,ToS)。
6 应用举例
在Linux中,流量控制都是通过TC这个工具来完成的。通常,要对网卡进行流量控制的配置,需要进行如下的步骤:
   ◆ 为网卡配置一个队列;
   ◆ 在该队列上建立分类;
   ◆ 根据需要建立子队列和子分类;
◆ 为每个分类建立过滤器。

在Linux中,可以配置很多类型的队列,比如CBQ、HTB等,其中CBQ 比较复杂,不容易理解。HTB(Hierarchical Token Bucket)是一个可分类的队列, 与其他复杂的队列类型相比,HTB具有功能强大、配置简单及容易上手等优点。在TC中,使用"major:minor"这样的句柄来标识队列和类别,其中major和minor都是数字。
对于队列来说,minor总是为0,即"major:0"这样的形式,也可以简写为"major: "比如,队列1:0可以简写为1:。需要注意的是,major在一个网卡的所有队列中必须是惟一的。对于类别来说,其major必须和它的父类别或父队列的major相同,而minor在一个队列内部则必须是惟一的(因为类别肯定是包含在某个队列中的)。举个例子,如果队列2:包含两个类别,则这两个类别的句柄必须是2:x这样的形式,并且它们的x不能相同,比如2:1和2:2。

 简单环境举例:
以HTB队列为主,结合需求来讲述TC的使用。假设eth0出口有100mbit/s的带宽,分配给WWW、E-mail和Telnet三种数据流量,其中分配给WWW的带宽为40Mbit/s,分配给Email的带宽为40Mbit/s,分配给Telnet的带宽为20Mbit/S。

  需要注意的是,在TC 中使用下列的缩写表示相应的带宽:◆ Kbps : kilobytes per second,千字节每秒 ;◆ Mbps : megabytes per second,兆字节每秒 ,◆ Kbit : kilobits per second,千比特每秒 ;◆ Mbit : megabits per second, 兆比特每秒 。

 创建HTB队列
有关队列的TC命令的一般形式为:

tc qdisc [add | change | replace | link] dev DEV [parent qdisk-id |root] [handle qdisc-id] qdisc [qdisc specific parameters]

首先,需要为网卡eth0配置一个HTB队列,使用下列命令:
#tc qdisc add dev eth0 root handle 1:htb default 11
这里,命令中的”add”表示要添加,”dev eth0”表示要操作的网卡为eth0。”root”表示为网卡eth0添加的是一个根队列。”handle 1:”表示队列的句柄为1: 。”htb”表示要添加的队列为HTB队列。命令最后的”default 11”是htb特有的队列参数,意思是所有未分类的流量都将分配给类别1:11。
 为根队列创建相应的类别
有关类别的TC 命令的一般形式为:

tc class [add | change | replace] dev DEV parent qdisc-id [classid class-id] qdisc [qdisc specific parameters]

可以利用下面这三个命令为根队列1创建三个类别,分别是1:1 1、1:12和1:13,它们分别占用40、40和20mb[t的带宽。
#tc class add dev eth0 parent 1: classid 1:1 htb rate 40mbit ceil 40mbit
#tc class add dev eth0 parent 1: classid 1:12 htb rate 40mbit ceil 40mbit
#tc class add dev eth0 parent 1: cllassid 1:13 htb rate 20mbit ceil 20mbit
命令中,”parent 1:”表示类别的父亲为根队列1: 。”classid1:11”表示创建一个标识为1:11的类别,”rate 40mbit”表示系统将为该类别确保带宽40mbit,”ceil 40mbit”,表示该类别的最高可占用带宽为40mbit。

 为各个类别设置过滤器
有关过滤器的TC 命令的一般形式为:

tc filter [add | change | replace] dev DEV [parent qdisc-id | root] protocol protocol prio priority filtertype [filtertype specific parameters] flowid flow-id

由于需要将WWW、E-mail、Telnet三种流量分配到三个类别,即上述1:11、1:12和1:13,因此,需要创建三个过滤器,如下面的三个命令:
#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:11
#tc filter add dev eth0 prtocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:12
#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 23 oxffff flowid 1:13
这里,”protocol ip”表示该过滤器应该检查报文分组的协议字段。”prio 1” 表示它们对报文处理的优先级是相同的,对于不同优先级的过滤器,系统将按照从小到大的优先级顺序来执行过滤器,对于相同的优先级,系统将按照命令的先后顺序执行。这几个过滤器还用到了u32选择器(命令中u32后面的部分)来匹配不同的数据流。以第一个命令为例,判断的是dport字段,如果该字段与Oxffff进行与操作的结果是8O,则”flowid 1:11”表示将把该数据流分配给类别1:1 1。更加详细的有关TC的用法可以参考TC的手册页。

 复杂环境举例
在上面的例子中, 三种数据流(www、Email、Telnet)之间是互相排斥的。当某个数据流的流量没有达到配额时,其剩余的带宽并不能被其他两个数据流所借用。在这里将涉及如何使不同的数据流可以共享一定的带宽。
首先需要用到HTB的一个特性, 即对于一个类别中的所有子类别,它们将共享该父类别所拥有的带宽,同时,又可以使得各个子类别申请的各自带宽得到保证。这也就是说,当某个数据流的实际使用带宽没有达到其配额时,其剩余的带宽可以借给其他的数据流。而在借出的过程中,如果本数据流的数据量增大,则借出的带宽部分将收回,以保证本数据流的带宽配额。
下面考虑这样的需求,同样是三个数据流WWW、E-mail和Telnet, 其中的Telnet独立分配20Mbit/s的带宽。另一方面,WWW 和SMTP各自分配40Mbit/s的带宽。同时,它们又是共享的关系,即它们可以互相借用带宽。如图3所示。

需要的TC命令如下:
#tc qdisc add dev eth0 root handle 1: htb default 21
#tc class add dev eth0 partent 1: classid 1:1 htb rate 20mbit ceil 20mbit
#tc class add dev eth0 parent 1: classid 1:2 htb rate 80mbit ceil 80mbit
#tc class add dev eth0 parent 1: classid 1:21 htb rate 40mbit ceil 20mbit
#tc class add dev eth0 parent 1:2 classid 1:22 htb rate 40mbit ceil 80mbit
#tc filter add dev eth0 protocol parent 10 prio 1 u32 match ip dport 80 0xffff flowid 1:21
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:22
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 23 0xffff flowid 1:1
这里为根队列1创建两个根类别,即1:1和1:2,其中1:1对应Telnet数据流,1:2对应80Mbit的数据流。然后,在1:2中,创建两个子类别1:21和1:22,分别对应WWW和E-mail数据流。由于类别1:21和1:22是类别1:2的子类别,因此他们可以共享分配的80Mbit带宽。同时,又确保当需要时,自己的带宽至少有40Mbit。

LINUX TC介绍相关推荐

  1. 弱网环境搭建之 Linux tc iptables 详解

    弱网环境搭建之 Linux tc 详解 0. 背景 1. 工具选择 2. 搭建流程 2.1 Linux tc 简介 2.2 弱网搭建思路 2.3 完整代码展示 0. 背景 笔者有一个需要搭建弱网环境来 ...

  2. linux系统服务介绍

    linux系统服务介绍 在windows系统中,我们可以打开任务管理器来打开或者关闭某些服务.在Linux系统下也同样有这样的需求,那么linux下怎么打开类似于windows下的"任务管理 ...

  3. Linux虚拟化介绍

    一.Linux虚拟化介绍 1.虚拟化模型如下,通过虚拟化工具把cpu.内存.硬盘等真实硬件资源模拟成更少的虚拟硬件资源 2.为什么使用虚拟化? 硬件资源使用率最大化,独立出多台机器出来,把空闲资源利用 ...

  4. linux tf命令,Linux系统命令介绍之vmstat命令详解

    今天小编要跟大家介绍的vmstat命令详解.熟悉Linux系统和使用Linux系统工作的小伙伴都知道Linux的命令有很多,而真正在工作中用到的命令应该不超过几十个,为了让大家更好的掌握这些命令,小编 ...

  5. linux htb 源代码,LINUX TC:HTB相关源码

    LINUX TC:HTB相关源码 收藏 HTB(hierarchy token buffer)是linux tc(traffic control)模块中的排队队列的一种.它的配置比CBQ要简单.同时实 ...

  6. linux内核模块是什么,Linux内核模块介绍,使用Linux模块的优点

    描述 1.1 Linux内核模块介绍1.1.1 Linux内核模块概述 嵌入式设备驱动开发中将驱动程序以模块的形式发布,更是极大地提高了设备使用的灵活性--用户只需要拿到相关驱动模块,再插入到用户的内 ...

  7. 【安全牛学习笔记】Kali Linux***测试介绍

    Kali Linux***测试介绍  安全问题的根源 优点:分工明确,工作效率高. 缺点:从业人员对系统没有整体的认识,对安全认识较为片面. 最大威胁是人,人都会犯错,安全问题不能100%绝对根除. ...

  8. arch linux安装命令,arch linux:安装Arch Linux方法介绍

    今天来聊聊一篇关于arch linux:安装Arch Linux方法介绍的文章,现在就为大家来简单介绍下arch linux:安装Arch Linux方法介绍,希望对各位小伙伴们有所帮助. Arch ...

  9. 11款国内外多厂家linux面板介绍(含视频对比)

    在linux服务器运维管理方面linux面板可是举足轻重的地位,因为它具有方便管理服务器的优势,尤其是界面化的操作,适合大部分高中低的用户体验. 这里要介绍的就是现在我用过的国内外的linux面板,介 ...

最新文章

  1. Cocos2d-x 3.0 rc0中加入附加项目,解决无法打开包括文件:“extensions/ExtensionMacros.h”...
  2. 学习AOP 之前必须明白的几个概念
  3. 领域应用 | 机器知道哪吒是部电影吗?解读阿里巴巴概念图谱AliCG
  4. 【tensorflow】常量张量的初始化
  5. php查询框,html查找框功能
  6. boost基础——variant的原理及基本用法
  7. @OneToOne or @ManyToOne on references an unknown entity:
  8. python数据分析与挖掘pdf_python数据分析与挖掘实战
  9. 移动端项目实现手机定位
  10. tomcat对session钝化活化以及idea上的差异【记录】
  11. Dell R640服务器centos系统增加万兆网卡设置
  12. 46、建筑防烟排烟系统的维护保养要求
  13. VC++ Hook截取鼠标点击窗口消息的问题!全局钩子
  14. 榆熙教育有限公司:拼多多商家应该了解的常用推广方式
  15. 微信android 7.0版本下载地址,微信7.0官方版本下载,微信7.0官方版本下载 v7.0.15-安卓乐园安卓软件网...
  16. I.MX6ULL ARM Linux学习笔记
  17. 7.8 Git 工具 - 高级合并
  18. 简易电子章制作小模块(VBA)
  19. MyBatis入门学习(二)
  20. 【转】区块链:DeFi 的理论与实践

热门文章

  1. 【Cheatsheet】Java的常用代码(以及eclipse技巧)
  2. 用51单片机(STC89C52RC、STC12C5A60S2、STC15W104)驱动MzLH03-12864液晶显示模块
  3. iphone照片恢复至android,将照片从Android传输到iPhone的8种方法很容易
  4. POE方案之SI3402-B
  5. (附源码)Springboot网上购物系统 毕业设计 311236
  6. php判断区间数字,如何快速判断数字在那个区间?
  7. html 与 css 画哆啦A梦
  8. Photoshop标尺单位介绍以及录制动作标尺单位选择的重要性
  9. [SHOI2008] 小约翰的游戏
  10. Sign in with Apple(苹果授权登陆) java jwt方式验证