文章目录
TBF(Token Bucket Filter)令牌桶过滤器,可以用来做流量整形(就是限制速率),它可以将速率限制为整体不超过某个值,但是允许一定的突发数据量传输(可能会超过限制的速率)。
算法思想
限制速率直接的思路是在数据传输过程中,同时测量当前数据传输速率,当速率超过指定值时,限制速率传输。这种思路很直接,但是软件实现上较为困难,所以换一种思路。
以一定的速率生成令牌放入到一个令牌桶中,当发生数据传输时,先查看是否有令牌,如果有则传输数据并消耗一定的令牌;如果没有或者令牌不足,那么则将数据入队列,等待令牌足够后再继续发送数据。
上述过程会导致有三种情况:
- 要传输的数据速率 < 令牌生成速率,此时数据可以无延时的传输,并且会导致令牌积累,当令牌集满令牌桶后,桶中可用令牌数不再继续增加;
- 要传输的数据速率 = 令牌生成速率,此时数据仍可以无延时的传输,但是令牌桶中不会有令牌积累;
- 要传输的数据速率 > 令牌生成速率,此时数据开始可以无延时的传输,但是当桶中令牌消耗完毕后,会导致数据积压到队列中,当积压一直持续发生时,积压数据会超过上限,进而发生丢包,这种情况也称作“越限”。
tc参数
更详细的参考见tc-tbf(8)。
-
limit/latency
指定了最多可以有多少字节数的数据包可以在队列中等待令牌可用。latency指定了一个数据包可以在队列中等待令牌可用的最长时间,该参数会影响令牌桶大小、以及令牌生成速率的计算。这两个参数是互斥的,配置时同时指定其中一个。
-
burst
令牌桶的大小(以字节为单位),也就是一次可以突发传输的数据量,越大的速率约需要越大的令牌桶,如果大速率配置一个小桶,会导致每次软中断调度只能发送较小的数据量,最终的结果就是速率难以达到实际配置的值。
内核在实现时是基于HZ生成令牌,所以每秒最少可以生成HZ个令牌(每个tick生成1个令牌),因此如果想达到速率配置的值,用rate/HZ可以得出最小的burst配置大小。
brust也可以携带一个分辨率参数(如6000/16),其中16就是分辨率,该值必须是2的整数幂,影响下面的cell_log值,不过通常该值无需指定。特别注意如果指定的过小会导致队列无法处理更大的数据包而丢弃,进而引发更加严重的问题,见内核max_size的取值。
-
mpu
零字节的数据包实际上也会消耗链路资源,比如以太网实际发送的最小报文为64字节,所以可以通过该字段指定最小包大小为多少字节,使得字节数小于该值的数据包也会最起码消耗mpu字节的令牌。
-
rate
即令牌生成的速率,也就是要限制的数据传输速率。
-
peakrate
指定了peakrate也必须指定mtu。burst如果较大,那么由于令牌可以积累,那么突发的数据传输可能会超过rate参数指定的速率,如果不希望如此或者想要限制突发的数据速率,那么可以指定本参数,本参数指定了算法的出队列速率。指定该参数后,相当于出队列时会先将数据包从大队列移动到该小队列,在小队列再次进行限速。
-
mtu/minburst
指定了小队列的长度,要想实现精准的控制,该值应该是网络设备的MTU。
配置示例
tc qdisc add dev eth0 root tbf rate 220kbit latency 50ms burst 1540
用户态实现
用户态配置可以用成熟的tc(8)命令,也可以基于Netlink套接字自行编程,下面的代码来自tc(8)源码的q_tbf.c。
数据结构
TBF参数: tc_tbf_qopt
该结构封装了TBF算法的绝大多数配置参数,最终会以属性TCA_TBF_PARMS封装到Netlink消息中。
// 流量控制通用的速率规格定义
struct tc_ratespec
{
unsigned char cell_log; // 默认为8
unsigned char __reserved;
unsigned short overhead;
short cell_align; // 基本是-1
unsigned short mpu;
__u32 rate; // 32位的速率
};
struct tc_tbf_qopt
{
// 根据tc(8)的rate参数构造
struct tc_ratespec rate;
// 根据tc(8)的peakrate参数构造
struct tc_ratespec peakrate;
// 和tc(8)的limit参数对应,如果用的latency,则由latency计算而来
__u32 limit;
// 和tc(8)的burst参数对应
__u32 buffer;
// 和tc(8)的mtu参数对应
__u32 mtu;
};
配置参数解析: tbf_parse_opt()
static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
{
int ok = 0;
// tbf核心配置参数
struct tc_tbf_qopt opt = {};
__u32 rtab[256];
__u32 ptab[256];
unsigned buffer = 0, mtu = 0, mpu = 0, latency = 0;
int Rcell_log = -1, Pcell_log = -1;
unsigned short overhead = 0;
unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
struct rtattr *tail;
__u64 rate64 = 0, prate64 = 0;
// 参数解析
while (argc > 0) {
if (matches(*argv, "limit") == 0) { // 处理limit参数
NEXT_ARG();
if (opt.limit) {
fprintf(stderr, "tbf: duplicate \"limit\" specification\n");
return -1;
}
if (latency) { // limit参数和latecny参数不能同时指定
fprintf(stderr, "tbf: specifying both \"latency\" and \"limit\" is not allowed\n");
return -1;
}
// 将limit参数指定的数值转换为字节单位保存
if (get_size(&opt.limit, *argv)) {
explain1("limit", *argv);
return -1;
}
ok++;
} else if (matches(*argv, "latency") == 0) { // 处理latency参数
NEXT_ARG();
if (latency) {
fprintf(stderr, "tbf: duplicate \"latency\" specification\n");
return -1;
}
if (opt.limit) { // limit参数和latecny参数不能同时指定
fprintf(stderr, "tbf: specifying both \"limit\" and \"/latency\" is not allowed\n");
return -1;
}
// 将指定的时间以微妙为单位保存
if (get_time(&latency, *argv)) {
explain1("latency", *argv);
return -1;
}
ok++;
} else if (matches(*argv, "burst") == 0 ||
strcmp(*argv, "buffer") == 0 ||
strcmp(*argv, "maxburst") == 0) { // burst/buffer/maxburst是一个参数
const char *parm_name = *argv;
NEXT_ARG();
if (buffer) {
fprintf(stderr, "tbf: duplicate \"buffer/burst/maxburst\" specification\n");
return -1;
}
// buffer参数以字节为单位保存buffer变量中,如果有指定分辨率,
// 则将分辨率的幂次方保存到Rcell_log中,后面根据rate计算rate table需要
if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) {
explain1(parm_name, *argv);
return -1;
}
ok++;
} else if (strcmp(*argv, "mtu") == 0 ||
strcmp(*argv, "minburst") == 0) { // 解析mtu参数
const char *parm_name = *argv;
NEXT_ARG();
if (mtu) {
fprintf(stderr, "tbf: duplicate \"mtu/minburst\" specification\n");
return -1;
}
// 和buffer参数类似
if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) {
explain1(parm_name, *argv);
return -1;
}
ok++;
} else if (strcmp(*argv, "mpu") == 0) { // 解析mpu参数
NEXT_ARG();
if (mpu) {
fprintf(stderr, "tbf: duplicate \"mpu\" specification\n");
return -1;
}
if (get_size(&mpu, *argv)) {
explain1("mpu", *argv);
return -1;
}
ok++;
} else if (strcmp(*argv, "rate") == 0) { // 解析rate参数
NEXT_ARG();
if (rate64) {
fprintf(stderr, "tbf: duplicate \"rate\" specification\n");
return -1;
}
// 速率转换成byte per second的单位保存
if (get_rate64(&rate64, *argv)) {
explain1("rate", *argv);
return -1;
}
ok++;
} else if (matches(*argv, "peakrate") == 0) { // 解析peakrate参数
NEXT_ARG();
if (prate64) {
fprintf(stderr, "tbf: duplicate \"peakrate\" specification\n");
return -1;
}
// 类似rate参数
if (get_rate64(&prate64, *argv)) {
explain1("peakrate", *argv);
return -1;
}
ok++;
} else if (matches(*argv, "overhead") == 0) { // 解析overhead参数
NEXT_ARG();
if (overhead) {
fprintf(stderr, "tbf: duplicate \"overhead\" specification\n");
return -1;
}
if (get_u16(&overhead, *argv, 10)) {
explain1("overhead", *argv); return -1;
}
} else if (matches(*argv, "linklayer") == 0) {
// 指定链路类型,后面在计算参数时针对特定的链路类型会有特殊处理,我们忽略
NEXT_ARG();
if (get_linklayer(&linklayer, *argv)) {
explain1("linklayer", *argv); return -1;
}
} else if (strcmp(*argv, "help") == 0) {
explain();
return -1;
} else {
fprintf(stderr, "tbf: unknown parameter \"%s\"\n", *argv);
explain();
return -1;
}
argc--; argv++;
}
int verdict = 0;
/* Be nice to the user: try to emit all error messages in
* one go rather than reveal one more problem when a
* previous one has been fixed.
*/
// 校验参数,一次性将所有的错误暴露给使用者
if (rate64 == 0) {
fprintf(stderr, "tbf: the \"rate\" parameter is mandatory.\n");
verdict = -1;
}
if (!buffer) {
fprintf(stderr, "tbf: the \"burst\" parameter is mandatory.\n");
verdict = -1;
}
if (prate64) {
// 指定了peakrate,那么也必须指定mtu
if (!mtu) {
fprintf(stderr, "tbf: when \"peakrate\" is specified, \"mtu\" must also be specified.\n");
verdict = -1;
}
}
// limit和latency必须二选一指定
if (opt.limit == 0 && latency == 0) {
fprintf(stderr, "tbf: either \"limit\" or \"latency\" is required.\n");
verdict = -1;
}
// 参数错误,结束处理
if (verdict != 0) {
explain();
return verdict;
}
// opt中可保存的最大速率是32位无符号整数的最大值4,294,967,296bps,如果要指定的速率超过了该最大值,
// 则opt中保存全1,完整的速率参数用属性TCA_TBF_RATE64配置到内核(最初的内核版本并不支持该属性)
opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
// 类似的,使用TCA_TBF_PRATE64属性配置64位的peak rate
opt.peakrate.rate = (prate64 >= (1ULL << 32)) ? ~0U : prate64;
// 根据latency参数计算limit参数。TIME_UNITS_PER_SEC表示1微妙
if (opt.limit == 0) {
// 最大时延*速率就是队列中需要缓存的最大数据量,为什么和令牌桶的buffer大小有关?
double lim = rate64*(double)latency/TIME_UNITS_PER_SEC + buffer;
if (prate64) {
double lim2 = prate64*(double)latency/TIME_UNITS_PER_SEC + mtu;
if (lim2 < lim)
lim = lim2;
}
opt.limit = lim;
}
opt.rate.mpu = mpu;
opt.rate.overhead = overhead;
// 计算rate table
if (tc_calc_rtable(&opt.rate, rtab, Rcell_log, mtu, linklayer) < 0) {
fprintf(stderr, "tbf: failed to calculate rate table.\n");
return -1;
}
// buffer的含义由可保存多少令牌(表示字节数)转换为令牌桶装满后可以供以配置的最大速率传输多长时间数据(表示时间)
opt.buffer = tc_calc_xmittime(opt.rate.rate, buffer);
// 计算peak rate table
if (opt.peakrate.rate) {
opt.peakrate.mpu = mpu;
opt.peakrate.overhead = overhead;
if (tc_calc_rtable(&opt.peakrate, ptab, Pcell_log, mtu, linklayer) < 0) {
fprintf(stderr, "tbf: failed to calculate peak rate table.\n");
return -1;
}
opt.mtu = tc_calc_xmittime(opt.peakrate.rate, mtu);
}
// 封装Netlink消息
tail = NLMSG_TAIL(n);
addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
addattr_l(n, 2024, TCA_TBF_PARMS, &opt, sizeof(opt));
addattr_l(n, 2124, TCA_TBF_BURST, &buffer, sizeof(buffer));
if (rate64 >= (1ULL << 32))
addattr_l(n, 2124, TCA_TBF_RATE64, &rate64, sizeof(rate64));
addattr_l(n, 3024, TCA_TBF_RTAB, rtab, 1024);
if (opt.peakrate.rate) {
if (prate64 >= (1ULL << 32))
addattr_l(n, 3124, TCA_TBF_PRATE64, &prate64, sizeof(prate64));
addattr_l(n, 3224, TCA_TBF_PBURST, &mtu, sizeof(mtu));
addattr_l(n, 4096, TCA_TBF_PTAB, ptab, 1024);
}
tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
return 0;
}
计算速率表: tc_calc_rtable()
/* rtab[pkt_len>>cell_log] = pkt_xmit_time 表示以配置的速率发送pkg_len字节的数据需要多长时间 */
int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab, int cell_log, unsigned int mtu,
enum link_layer linklayer)
{
int i;
unsigned int sz;
unsigned int bps = r->rate;
unsigned int mpu = r->mpu;
if (mtu == 0)
mtu = 2047;
// 默认的cell_log为8
if (cell_log < 0) {
cell_log = 0;
while ((mtu >> cell_log) > 255)
cell_log++;
}
// 依次表示以速率bps传输1 << 8 = 256, 2 << 8 = 512, 3 << 8 = 768..., 255 << 8 = 25680字节的数据量需要多长时间
for (i = 0; i < 256; i++) {
// sz: 1 << 8 = 256, 2 << 8 = 512, 3 << 8 = 768..., 255 << 8 = 25680
sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);
rtab[i] = tc_calc_xmittime(bps, sz);
}
r->cell_align = -1;
r->cell_log = cell_log;
r->linklayer = (linklayer & TC_LINKLAYER_MASK);
return cell_log;
}
// 计算以速率rate发送size字节的数据需要多长时间
unsigned int tc_calc_xmittime(__u64 rate, unsigned int size)
{
return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/(double)rate));
}
// tick_in_usec来自于内核节点/proc/net/psched,表示1微妙有多少个tick,实际配置基本就是1
static double tick_in_usec = 1;
unsigned int tc_core_time2tick(unsigned int time)
{
return time * tick_in_usec;
}
内核实现
数据结构
qdisc私有数据: tbf_sched_data
struct tbf_sched_data
{
/* Parameters */
u32 limit; /* Maximal length of backlog: bytes */
u32 buffer; /* Token bucket depth/rate: MUST BE >= MTU/B */
u32 mtu;
// 算法允许入队列的数据包的最大长度,必须要大于skb->len,否则会导致丢包,通常该值很大,并不会导致问题
u32 max_size;
struct qdisc_rate_table *R_tab; // rate table
struct qdisc_rate_table *P_tab; // peak rate table
/* Variables */
long tokens; /* Current number of B tokens */
long ptokens; /* Current number of P tokens */
psched_time_t t_c; /* Time check-point */
struct Qdisc *qdisc; /* Inner qdisc, default - bfifo queue */
struct qdisc_watchdog watchdog; /* Watchdog timer */
};
qdisc操作集: tbf_qdisc_ops
static struct Qdisc_ops tbf_qdisc_ops __read_mostly = {
.next = NULL,
.cl_ops = &tbf_class_ops,
.id = "tbf",
.priv_size = sizeof(struct tbf_sched_data),
.enqueue = tbf_enqueue,
.dequeue = tbf_dequeue,
.peek = qdisc_peek_dequeued,
.drop = tbf_drop,
.init = tbf_init,
.reset = tbf_reset,
.destroy = tbf_destroy,
.change = tbf_change,
.dump = tbf_dump,
.owner = THIS_MODULE,
};
初始化: tbf_init()
static int tbf_init(struct Qdisc* sch, struct nlattr *opt)
{
struct tbf_sched_data *q = qdisc_priv(sch);
if (opt == NULL)
return -EINVAL;
// 初始化时间戳
q->t_c = psched_get_time();
qdisc_watchdog_init(&q->watchdog, sch);
q->qdisc = &noop_qdisc;
// 解析TBF配置参数
return tbf_change(sch, opt);
}
参数修改: tbf_change()
static int tbf_change(struct Qdisc* sch, struct nlattr *opt)
{
int err;
struct tbf_sched_data *q = qdisc_priv(sch);
struct nlattr *tb[TCA_TBF_PTAB + 1];
struct tc_tbf_qopt *qopt;
struct qdisc_rate_table *rtab = NULL;
struct qdisc_rate_table *ptab = NULL;
struct qdisc_rate_table *tmp;
struct Qdisc *child = NULL;
int max_size,n;
// 解析配置参数
err = nla_parse_nested(tb, TCA_TBF_PTAB, opt, tbf_policy);
if (err < 0)
return err;
// 必须指定TCA_TBF_PARMS属性
err = -EINVAL;
if (tb[TCA_TBF_PARMS] == NULL)
goto done;
qopt = nla_data(tb[TCA_TBF_PARMS]);
// 根据指定的rate参数,找到对应的rate table,该rate tabl是用户态算好传下来的,
// 由于速率表由很多算法都会用到,所以这里由qdisc公共模块统一维护,让所有算法共享
rtab = qdisc_get_rtab(&qopt->rate, tb[TCA_TBF_RTAB]);
if (rtab == NULL)
goto done;
// 类似的,如果有指定peakrate,找到对应的peak rate table
if (qopt->peakrate.rate) {
if (qopt->peakrate.rate > qopt->rate.rate)
ptab = qdisc_get_rtab(&qopt->peakrate, tb[TCA_TBF_PTAB]);
if (ptab == NULL)
goto done;
}
// 一个数据包最大可以为令牌桶大小,因为最多令牌可以积累到令牌桶大小,如果最大都不够
// 单个数据包消耗,那么算法是无法处理的
for (n = 0; n < 256; n++)
if (rtab->data[n] > qopt->buffer) break;
max_size = (n << qopt->rate.cell_log)-1;
if (ptab) {
int size;
for (n = 0; n < 256; n++)
if (ptab->data[n] > qopt->mtu) break;
size = (n << qopt->peakrate.cell_log)-1;
if (size < max_size) max_size = size;
}
if (max_size < 0)
goto done;
// 该值一定是大于0的,因为limit或latency参数必须指定一个。创建一个孩子qdisc,默认类型为bfifo
if (qopt->limit > 0) {
child = fifo_create_dflt(sch, &bfifo_qdisc_ops, qopt->limit);
if (IS_ERR(child)) {
err = PTR_ERR(child);
goto done;
}
}
sch_tree_lock(sch);
if (child) {
// 将tbf_qdisc原本默认的孩子销毁,并且将新建的bfifo_qdisc与其关联
qdisc_tree_decrease_qlen(q->qdisc, q->qdisc->q.qlen);
qdisc_destroy(q->qdisc);
q->qdisc = child;
}
// 将配置参数保存到tbf的qdisc中
q->limit = qopt->limit;
q->mtu = qopt->mtu;
q->max_size = max_size;
q->buffer = qopt->buffer;
q->tokens = q->buffer;
q->ptokens = q->mtu;
// 将新的rate table关联到qdisc,rtab和ptab指向旧的,对应真正的参数修改场景,这样可以将旧的释放
tmp = q->R_tab;
q->R_tab = rtab;
rtab = tmp;
tmp = q->P_tab;
q->P_tab = ptab;
ptab = tmp;
sch_tree_unlock(sch);
err = 0;
done:
if (rtab)
qdisc_put_rtab(rtab);
if (ptab)
qdisc_put_rtab(ptab);
return err;
}
数据包入队: tbf_enqueue()
static int tbf_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
struct tbf_sched_data *q = qdisc_priv(sch);
int ret;
// 长度超过max_size大小的数据包会被丢弃
if (qdisc_pkt_len(skb) > q->max_size)
return qdisc_reshape_fail(skb, sch);
// 将数据包放入孩子qdisc(默认为bfifo)的队列中
ret = qdisc_enqueue(skb, q->qdisc);
if (ret != 0) {
// 入队列失败,更新统计信息,丢弃数据包
if (net_xmit_drop_count(ret))
sch->qstats.drops++;
return ret;
}
// 入队列成功,更新统计信息,注意统计信息是在tbf_qdisc中
sch->q.qlen++;
sch->bstats.bytes += qdisc_pkt_len(skb);
sch->bstats.packets++;
return 0;
}
数据包出队列: tbf_dequeue()
static struct sk_buff *tbf_dequeue(struct Qdisc* sch)
{
struct tbf_sched_data *q = qdisc_priv(sch);
struct sk_buff *skb;
// 先peek一个skb,peek操作只是获得下一个要出队的数据包,并不会真的将数据包从队列中移除
skb = q->qdisc->ops->peek(q->qdisc);
if (skb) {
// 队列中有数据包,下面判断是否满足出队列的速率要求
psched_time_t now;
long toks;
long ptoks = 0;
unsigned int len = qdisc_pkt_len(skb); // 下一个要发送的数据包长度(字节)
// 计算目前有多少令牌可用,最大为q->buffer即令牌桶大小(代码逻辑实际上是“可用发送时间配额”)
now = psched_get_time();
// (now - q->t_c)为从上一次发送到现在的时间(单位为微妙),这个数目和令牌桶大小取较小值.
// 算法逻辑上表示这段时间新生成了令牌
toks = psched_tdiff_bounded(now, q->t_c, q->buffer);
if (q->P_tab) {
ptoks = toks + q->ptokens;
if (ptoks > (long)q->mtu)
ptoks = q->mtu;
ptoks -= L2T_P(q, len);
}
// 和之前未使用的令牌(q->tokens)累加,与令牌桶大小取较小值,得到的toks就是当前发送可用的令牌数
toks += q->tokens;
if (toks > (long)q->buffer)
toks = q->buffer;
// L2T()通过查询rate table获得发送本数据包需要消耗的时间,从toks减去消耗的时间,为剩余可用时间,
// 如果时间不足,那么toks的结果为复数,下方的toks|ptoks就一定会小于0,进而表示令牌不足
toks -= L2T(q, len);
if ((toks|ptoks) >= 0) {
// 可以出队列,返回出队列的数据包
skb = qdisc_dequeue_peeked(q->qdisc);
if (unlikely(!skb))
return NULL;
q->t_c = now;
q->tokens = toks;
q->ptokens = ptoks;
sch->q.qlen--;
sch->flags &= ~TCQ_F_THROTTLED;
return skb;
}
// 出队列失败,启动watchdog定时器,当令牌生成时可以立刻触发数据包发送
qdisc_watchdog_schedule(&q->watchdog, now + max_t(long, -toks, -ptoks));
/* Maybe we have a shorter packet in the queue,
which can be sent now. It sounds cool,
but, however, this is wrong in principle.
We MUST NOT reorder packets under these circumstances.
Really, if we split the flow into independent
subflows, it would be a very good solution.
This is the main idea of all FQ algorithms
(cf. CSZ, HPFQ, HFSC)
*/
// 速率受限次数统计
sch->qstats.overlimits++;
}
return NULL;
}
速率表查询: L2T()
查询的结果是:以配置的速率传输指定字节的数据,需要消耗多少时间。
#define L2T(q,L) qdisc_l2t((q)->R_tab,L)
/* Length to Time (L2T) lookup in a qdisc_rate_table, to determine how
long it will take to send a packet given its size.
*/
static inline u32 qdisc_l2t(struct qdisc_rate_table* rtab, unsigned int pktlen)
{
// 要发送的数据包长度
int slot = pktlen + rtab->rate.cell_align + rtab->rate.overhead;
if (slot < 0)
slot = 0;
// 根据cell_log计算速率表索引,然后查表
slot >>= rtab->rate.cell_log;
if (slot > 255)
return (rtab->data[255]*(slot >> 8) + rtab->data[slot & 0xFF]);
return rtab->data[slot];
}
watchdog定时器: qdisc_watchdog()
static enum hrtimer_restart qdisc_watchdog(struct hrtimer *timer)
{
struct qdisc_watchdog *wd = container_of(timer, struct qdisc_watchdog,
timer);
wd->qdisc->flags &= ~TCQ_F_THROTTLED;
// 让软中断重新调度该qdisc
__netif_schedule(qdisc_root(wd->qdisc));
return HRTIMER_NORESTART;
}
小结
实现上有下面几个要点需要特别说明一下。
关于算法分类
从实现上看,TBF实际上是一个有类qdisc,它内部有默认的孩子qdisc,并且该孩子qdisc也是可以通过tc(8)修改为其它无类qdisc的。TBF引入孩子qdisc完全是为了实现限制最大出队速率,即上面的peak rate。
关于速率表
可以看到,对于令牌桶的实现,并非直接配置的是字节,而是将以指定速率rate传输数据时,数据长度作为自变量,传输时间作为因变量,在自变量的有限取值(256个取值)下将因变量的结果保存为一张速率表(256个值),这些值表示以rate传输对应字节数据需要消耗的时长。
为什么要这么实现呢?个人理解可能是为了避免在内核做乘法,进而不会影响数据包的传输效率:
如果不做转换,那么当发送数据包时,内核需要用rate乘以时间先得到这段时间生成的令牌,然后用令牌数和要传输的数据量做比较,进而决定是否可以传输。
如果做转换,那么当发送数据包时,内核只需要查表获取要消耗的时间,然后比较可用时间配额和消耗的时间大小即可决定是否可以传输,过程简化了不少。