概述
用户层面的tc配置命令如下:
$ sudo tc qdisc add dev ens38 root fq
$
$ sudo tc -s qdisc show dev ens38
qdisc fq 8001: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 orphan_mask 1023 quantum 3028 initial_quantum 15140 low_rate_threshold 550Kbit refill_delay 40.0ms
TC工具处理
TC在解析tc配置命令时,由函数fq_parse_opt进行处理,需要注意的是buckets参数,TC首先将其转换为以2为底的对数形式;之后,在下发到内核中。
static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
{
...
if (buckets) {
unsigned int log = ilog2(buckets);
addattr_l(n, 1024, TCA_FQ_BUCKETS_LOG, &log, sizeof(log));
}
同样的,在显示fq的buckets参数时,将由内核取得的值左移位,还原为设置值。
static int fq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
{
...
if (tb[TCA_FQ_BUCKETS_LOG] &&
RTA_PAYLOAD(tb[TCA_FQ_BUCKETS_LOG]) >= sizeof(__u32)) {
buckets_log = rta_getattr_u32(tb[TCA_FQ_BUCKETS_LOG]);
print_uint(PRINT_ANY, "buckets", "buckets %u ",
1U << buckets_log);
}
内核配置接口
内核中函数fq_change处理fq队列配置。首先对于buckets参数,其合法的范围是:[1, 256],即buckets的数量应为:1到256K。
static int fq_change(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack)
{
struct fq_sched_data *q = qdisc_priv(sch);
struct nlattr *tb[TCA_FQ_MAX + 1];
...
err = nla_parse_nested(tb, TCA_FQ_MAX, opt, fq_policy, NULL);
if (err < 0)
return err;
sch_tree_lock(sch);
fq_log = q->fq_trees_log;
if (tb[TCA_FQ_BUCKETS_LOG]) {
u32 nval = nla_get_u32(tb[TCA_FQ_BUCKETS_LOG]);
if (nval >= 1 && nval <= ilog2(256*1024))
fq_log = nval;
else
err = -EINVAL;
}
另外,参数quantum的值不能小于零。参数defrate目前没有使用。
if (tb[TCA_FQ_PLIMIT])
sch->limit = nla_get_u32(tb[TCA_FQ_PLIMIT]);
if (tb[TCA_FQ_QUANTUM]) {
u32 quantum = nla_get_u32(tb[TCA_FQ_QUANTUM]);
if (quantum > 0)
q->quantum = quantum;
else
err = -EINVAL;
}
if (tb[TCA_FQ_INITIAL_QUANTUM])
q->initial_quantum = nla_get_u32(tb[TCA_FQ_INITIAL_QUANTUM]);
if (tb[TCA_FQ_FLOW_DEFAULT_RATE])
pr_warn_ratelimited("sch_fq: defrate %u ignored.n",
nla_get_u32(tb[TCA_FQ_FLOW_DEFAULT_RATE]));
内核变量rate_enable用来标识TC命令行的pacing参数。
if (tb[TCA_FQ_FLOW_MAX_RATE]) {
u32 rate = nla_get_u32(tb[TCA_FQ_FLOW_MAX_RATE]);
q->flow_max_rate = (rate == ~0U) ? ~0UL : rate;
}
if (tb[TCA_FQ_LOW_RATE_THRESHOLD])
q->low_rate_threshold =
nla_get_u32(tb[TCA_FQ_LOW_RATE_THRESHOLD]);
if (tb[TCA_FQ_RATE_ENABLE]) {
u32 enable = nla_get_u32(tb[TCA_FQ_RATE_ENABLE]);
if (enable <= 1)
q->rate_enable = enable;
else
err = -EINVAL;
}
参数refill_delay在内核中使用jiffies表示。参数ce_threshold以纳秒为单位。
if (tb[TCA_FQ_FLOW_REFILL_DELAY]) {
u32 usecs_delay = nla_get_u32(tb[TCA_FQ_FLOW_REFILL_DELAY]) ;
q->flow_refill_delay = usecs_to_jiffies(usecs_delay);
}
if (tb[TCA_FQ_ORPHAN_MASK])
q->orphan_mask = nla_get_u32(tb[TCA_FQ_ORPHAN_MASK]);
if (tb[TCA_FQ_CE_THRESHOLD])
q->ce_threshold = (u64)NSEC_PER_USEC * nla_get_u32(tb[TCA_FQ_CE_THRESHOLD]);
如果以上参数顺利处理完成,fq_resize根据buckets数量重新调整FQ队列。
if (!err) {
sch_tree_unlock(sch);
err = fq_resize(sch, fq_log);
sch_tree_lock(sch);
}
函数最后,如果tc命令更新了参数limit的值,以下检查当前队列的长度是否超过了设定值limit,将队列中的报文进行出队列操作,并释放,保证队列长度小于等于limit的值。
while (sch->q.qlen > sch->limit) {
struct sk_buff *skb = fq_dequeue(sch);
if (!skb)
break;
drop_len += qdisc_pkt_len(skb);
rtnl_kfree_skbs(skb, skb);
drop_count++;
}
qdisc_tree_reduce_backlog(sch, drop_count, drop_len);
sch_tree_unlock(sch);
调整FQ队列
这里主要是根据新的buckets参数的值进行调整,buckets参数在内核中使用以2为底的对数表示,即变量fq_trees_log,如果其没有改变,不进行调整。
static int fq_resize(struct Qdisc *sch, u32 log)
{
struct fq_sched_data *q = qdisc_priv(sch);
struct rb_root *array;
void *old_fq_root;
if (q->fq_root && log == q->fq_trees_log)
return 0;
根据buckets参数指定的数量分配一个rb_root结构的数组,即array数组。对于新添加的FQ队列,其fq_root为空,没有旧的buckets。直接将新分配的array赋值给fq_root,并且更新fq_tree_log的值。
array数组中的每个元素都初始化为一个红黑树的树根(RB_ROOT)。
/* If XPS was setup, we can allocate memory on right NUMA node */
array = kvmalloc_node(sizeof(struct rb_root) << log, GFP_KERNEL | __GFP_RETRY_MAYFAIL,
netdev_queue_numa_node_read(sch->dev_queue));
if (!array) return -ENOMEM;
for (idx = 0; idx < (1U << log); idx++)
array[idx] = RB_ROOT;
sch_tree_lock(sch);
old_fq_root = q->fq_root;
if (old_fq_root)
fq_rehash(q, old_fq_root, q->fq_trees_log, array, log);
q->fq_root = array;
q->fq_trees_log = log;
sch_tree_unlock(sch);
fq_free(old_fq_root);
对于FQ队列的修改,使用函数fq_rehash将原buckets中的表项重新hash到新的buckets中。以下for循环遍历旧的bucket数组,将每个bucket对于的红黑树中的元素添加到新的bucket的红黑树中。
static void fq_rehash(struct fq_sched_data *q,
struct rb_root *old_array, u32 old_log,
struct rb_root *new_array, u32 new_log)
{
struct rb_node *op, **np, *parent;
struct rb_root *oroot, *nroot;
struct fq_flow *of, *nf;
for (idx = 0; idx < (1U << old_log); idx++) {
oroot = &old_array[idx];
while ((op = rb_first(oroot)) != NULL) {
rb_erase(op, oroot);
of = rb_entry(op, struct fq_flow, fq_node);
if (fq_gc_candidate(of)) {
fcnt++;
kmem_cache_free(fq_flow_cachep, of);
continue;
}
以上对于满足回收要求的不活动流,进行释放,不再添加到新的bucket中。否则,根据流的哈希值找到对应的bucket,遍历红黑树,插入流。
nroot = &new_array[hash_ptr(of->sk, new_log)];
np = &nroot->rb_node;
parent = NULL;
while (*np) {
parent = *np;
nf = rb_entry(parent, struct fq_flow, fq_node);
BUG_ON(nf->sk == of->sk);
if (nf->sk > of->sk) np = &parent->rb_right;
else np = &parent->rb_left;
}
rb_link_node(&of->fq_node, parent, np);
rb_insert_color(&of->fq_node, nroot);
}
}
函数最后,更新FQ队列的流统计信息。
q->flows -= fcnt;
q->inactive_flows -= fcnt;
q->stat_gc_flows += fcnt;
内核版本 5.0
最后
以上就是舒心电灯胆为你收集整理的Linux公平队列FQ接口实现的全部内容,希望文章能够帮你解决Linux公平队列FQ接口实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复