tc参数
# 命令行语法
tc qdisc ... dev dev ( parent classid | root) [ handle major: ] htb [ default minor-id ]
tc class ... dev dev parent major:[minor] [ classid major:minor ] htb rate rate [ ceil rate ] burst bytes [ cburst bytes ] [ prio priority ]
-
“parent classid | root”: 如果htb作为队列的根qdisc,那么应该选择root。否则应该指明其属于哪个类的孩子;
-
“handle major:”: 指定该qdisc的句柄,该major无需和parent classid的主号码一致;
-
“default minor-id”: 表示如果数据包没有被分到某个class,那么应该将其分到哪个class,即默认class的此号码(主号码是明确的,就是该qdisc的主号码);
-
parent major:[minor]: 指定class的父亲,如果class的父亲就是qdisc,那么该class也称为根class,其父亲的minor可以不指定;其它class则必须指定其父亲class的句柄;
-
classid major:minor: 指定class自己的句柄;
-
rate rate: 指定该class要配置的速率;
-
ceil rate: htb在条件允许的情况下可以让class的速率超过rate参数,但是最大不能超过本参数。一个class其超过rate的速率其实是和父亲借来的(不用还),特别注意根class是不能向qdisc借用速率的;
-
prio priority: 指定class的优先级,值越小,优先级越高。当同一层的class都可以发送时,htb会对这些class进行遍历,每个class传输其quantum个数据包后轮询下一个;
配置示例
# htb为eth0的根qdisc,其句柄为1:,默认的数据包分类为1:12
tc qdisc add dev eth0 root handle 1: htb default 12
# 建立一个如右图所示的class树,rate分别代表其速率,ceil分别代表其最大速率
tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps 1:1
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps / | \
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps 1:10 1:11 1:12
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps
算法思想
htb将class组织成一颗树,它将待传输的数据包缓存在叶子class的队列中,其它class是不可以缓存流量的,但是孩子clas可以共享parent calss的流量配额。
htb中的每个class虽然都配置了rate,但是并不是说clas只能以指定的rate传输数据包,当网卡比较空闲时,是可以以高于rate的速率传输的,但不会高于ceil。简单来说,思想就是空闲时共享,繁忙时按照比例分配带宽。
每个class在某个时刻可以处于三种状态中的一种:
- CAN_SEND(令牌充足,此时发送速率小于rate);
- MAY_BORROW(没有令牌,但可借用,此时发送速率大于rate但小于ceil);
- CANT_SEND(没有令牌不可借用,此时发送速率大于ceil);
htb在收到出队请求时,按照下述原则出包:
-
htb算法从class树的底部开始往上找处于CAN_SEND状态的class,如果找到某一层有CAN_SEND状态的类则停止;
-
如果该层中有多个class处于CAN_SEND状态则选取优先级最高(priority最小)的class,如果最高优先级还是有多个class,那就在这些类中轮询处理,每个类每发送自己的quantum个字节后,轮到下一个类发送;
-
只有叶子class才可以缓存数据包,如果步骤1,2最终选到了中间类,那么会顺着树往下找,找到一个孩子叶子class,并且该叶子类处于MAY_BORROW状态,将自己富余的令牌借给该叶子class让其传输。同样的道理,可能会有多个孩子叶子class处于MAY_BORROW状态,这里的处理跟步骤2是一样的,也是按照轮询处理;
用户态实现
数据结构
htb全局配置参数: tc_htb_glob
struct tc_htb_glob {
__u32 version; /* to match HTB/TC */
__u32 rate2quantum; /* bps->quantum divisor */
__u32 defcls; /* default class number */
__u32 debug; /* debug flags */
/* stats */
__u32 direct_pkts; /* count of non shaped packets */
};
htb类配置参数: tc_htb_opt
struct tc_htb_opt {
struct tc_ratespec rate;
struct tc_ratespec ceil;
__u32 buffer;
__u32 cbuffer;
__u32 quantum;
__u32 level; /* out only */
__u32 prio; // 类的优先级
};
qdisc配置参数解析: htb_parse_opt()
static int htb_parse_opt(struct qdisc_util *qu, int argc, char **argv,
struct nlmsghdr *n)
{
unsigned int direct_qlen = ~0U;
struct tc_htb_glob opt = {
.rate2quantum = 10,
.version = 3,
};
struct rtattr *tail;
unsigned int i; char *p;
while (argc > 0) {
if (matches(*argv, "r2q") == 0) {
NEXT_ARG();
if (get_u32(&opt.rate2quantum, *argv, 10)) {
explain1("r2q"); return -1;
}
} else if (matches(*argv, "default") == 0) {
NEXT_ARG();
if (get_u32(&opt.defcls, *argv, 16)) {
explain1("default"); return -1;
}
} else if (matches(*argv, "debug") == 0) {
NEXT_ARG(); p = *argv;
for (i = 0; i < 16; i++, p++) {
if (*p < '0' || *p > '3') break;
opt.debug |= (*p-'0')<<(2*i);
}
} else if (matches(*argv, "direct_qlen") == 0) {
NEXT_ARG();
if (get_u32(&direct_qlen, *argv, 10)) {
explain1("direct_qlen"); return -1;
}
} else {
fprintf(stderr, "What is \"%s\"?\n", *argv);
explain();
return -1;
}
argc--; argv++;
}
tail = NLMSG_TAIL(n);
addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
addattr_l(n, 2024, TCA_HTB_INIT, &opt, NLMSG_ALIGN(sizeof(opt)));
if (direct_qlen != ~0U)
addattr_l(n, 2024, TCA_HTB_DIRECT_QLEN, &direct_qlen, sizeof(direct_qlen));
tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
return 0;
}
class配置参数解析: htb_parse_copt()
绝大多数参数和tbf的参数相同。
static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
{
int ok = 0;
struct tc_htb_opt opt = {
};
__u32 rtab[256], ctab[256];
unsigned buffer = 0, cbuffer = 0;
int cell_log = -1, ccell_log = -1;
unsigned int mtu = 1600; /* eth packet len */
unsigned short mpu = 0;
unsigned short overhead = 0;
unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
struct rtattr *tail;
__u64 ceil64 = 0, rate64 = 0;
while (argc > 0) {
if (matches(*argv, "prio") == 0) {
// 解析优先级参数
NEXT_ARG();
if (get_u32(&opt.prio, *argv, 10)) {
explain1("prio"); return -1;
}
ok++;
} else if (matches(*argv, "mtu") == 0) {
NEXT_ARG();
if (get_u32(&mtu, *argv, 10)) {
explain1("mtu"); return -1;
}
} else if (matches(*argv, "mpu") == 0) {
NEXT_ARG();
if (get_u16(&mpu,