FQ队列流结构回收

在FQ队列中,如果流结构队列为空,并且超过3秒(FQ_GC_AGE)没有接收到相应的报文,系统将其进行回收,如下判断函数fq_gc_candidate。但是在每次fq_gc回收函数执行时,最大释放8个(FQ_GC_MAX)流结构。

/* limit number of collected flows per round */
#define FQ_GC_MAX 8
#define FQ_GC_AGE (3*HZ)

static bool fq_gc_candidate(const struct fq_flow *f)
{
    return fq_flow_is_detached(f) &&
           time_after(jiffies, f->age + FQ_GC_AGE);
}

以下看一下流结构进入detached状态的条件,在fq_dequeue函数中,如果old_flows链表中的流结构队列为空,设置其为detach状态。但是对于new_flows链表上的流结构,如果其队列为空,仅当old_flows链表同时为空时,将其设置为detached状态,否则,作为过渡,先将new_flows中的空队列移动到old_flows链表尾部。

static struct sk_buff *fq_dequeue(struct Qdisc *sch)
{
    ...
    skb = fq_dequeue_head(sch, f);
    if (!skb) {
        head->first = f->next;
        /* force a pass through old_flows to prevent starvation */
        if ((head == &q->new_flows) && q->old_flows.first) {
            fq_flow_add_tail(&q->old_flows, f);
        } else {
            fq_flow_set_detached(f);
            q->inactive_flows++;
        }
        goto begin;
    }

如下设置detached状态的函数,在设置detached状态的同时,记录下此刻的时间戳。

static void fq_flow_set_detached(struct fq_flow *f)
{
    f->next = &detached;
    f->age = jiffies;
}

static bool fq_flow_is_detached(const struct fq_flow *f)
{
    return f->next == &detached;
}

1 流回收处理

函数fq_gc负责回收操作,其遍历红黑树列表,找到合适的可回收流结构,由函数fq_gc_candidate进行判断,将待释放的流放入tofree数组中,此数组长度为8(FQ_GC_MAX),即一次释放不超过8个流结构。

注意,如果流结构的键值sk与fq_gc的参数sk相等,不能释放此流接口。参数sk表示的为刚刚接收到的数据包的键值,马上就要使用相应的流结构。在下节函数fq_classify中将会看到调用。

static void fq_gc(struct fq_sched_data *q, struct rb_root *root, struct sock *sk)
{
    struct fq_flow *f, *tofree[FQ_GC_MAX];
    struct rb_node **p, *parent;
    int fcnt = 0;

    p = &root->rb_node;
    parent = NULL;
    while (*p) {
        parent = *p;

        f = rb_entry(parent, struct fq_flow, fq_node);
        if (f->sk == sk)
            break;

        if (fq_gc_candidate(f)) {
            tofree[fcnt++] = f;
            if (fcnt == FQ_GC_MAX)
                break;
        }
        if (f->sk > sk)
            p = &parent->rb_right;
        else
            p = &parent->rb_left;
    }

更新流相关统计计数,由红黑树中删除要释放的流结构节点,最后释放掉其占用空间。

    q->flows -= fcnt;
    q->inactive_flows -= fcnt;
    q->stat_gc_flows += fcnt;
    while (fcnt) {
        struct fq_flow *f = tofree[--fcnt];

        rb_erase(&f->fq_node, root);
        kmem_cache_free(fq_flow_cachep, f);
    }

2 回收时机

在报文的入队列函数fq_enqueue中,首先需要查找是否存在对应此报文的流结构,不存在的话,需要进行创建,这些都由函数fq_classify实现。在fq_classify函数中,如果流结构的数量超过或等于红黑树数量的2倍,并且非活动流结构在整体流结构中占比超过一半时,尝试在此报文的键值(套接口sk)对应的红黑树上执行回收操作(fq_gc)。

默认情况下,红黑树的数量(buckets)为1024,变量fq_trees_log值为对数形式的值:10。即当流结构数量大于等于2048时,才有可能进行回收。

static struct fq_flow *fq_classify(struct sk_buff *skb, struct fq_sched_data *q)
{
    struct sock *sk = skb->sk;
    struct rb_root *root;

    ...
    root = &q->fq_root[hash_ptr(sk, q->fq_trees_log)];

    if (q->flows >= (2U << q->fq_trees_log) &&
        q->inactive_flows > q->flows/2)
        fq_gc(q, root, sk);

3 FQ重配置

在重新设置FQ队列时,如果修改了bucket的数量,即红黑树的数量。需要重新分配buckets数组,并且将旧的红黑树上的流结构重新哈希到新的红黑树上,但是对于满足回收条件的流,直接进行释放,不在移动到新的红黑树上。

与fq_gc回收不同,这里没有回收数量的限制。

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;
            }
            nroot = &new_array[hash_ptr(of->sk, new_log)]; 

内核版本 5.0

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