Linux公平队列FQ接口实现

用户层面的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

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页