# 2.6.4.3 带宽控制 - 挂起与解挂

当需要挂起一个`cfs_rq`时，系统调用函数 `throttle_cfs_rq` 来实现，挂起的意思就是不要让CFS再调度到该`cfs_rq`中的任何任务，也就是说，我们需要将整个`cfs_rq`从该CPU的rq中移除，实际上就是移除上层`cfs_rq`中指向当前`cfs_rq`的那个调度实体。整个流程我们可以参考下图：&#x20;

![](/files/Vdt3fBMzwdpDzXRbVUfd)

这里系统将要挂起CPU0队列里面的 `cfs_rq_2`, 此时我们需要将 `cfs_rq_1` 中指向它的调度实体 `SE_R1` 从 `cfs_rq_1` 中删除，而将 `SE_R1` 删除之后， `cfs_rq_1` 就是一个空队列了，这时又需要将指向它的调度实体 `SE_R0` 从 `cfs_rq_0` 中删除。该过程需要一直沿着cfsrq的parent属性往上走，直到遇上不需要删除的`cfs_rq`为止，在上图中，该`cfs_rq`就是 `cfs_rq_0`. 整个挂起操作完成后，CPU0的队列示意图为：&#x20;

![](/files/e9WucMbF5tUjvDU1LP8y)

当然挂起 `cfs_rq` 时，系统还需要更新所有受影响的 `cfs_rq` 中的对应字段。函数 `throttle_cfs_rq` 的实现如下：

```
/* file: kernel/sched/fair.c */
static bool throttle_cfs_rq(struct cfs_rq *cfs_rq) {
    struct rq *rq = rq_of(cfs_rq);
    struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg);
    struct sched_entity *se;
    long task_delta, idle_task_delta, dequeue = 1;

    raw_spin_lock(&cfs_b->lock);
    /* 再最后尝试一把，如果定时器在此刻之前已经更新了任务组的时间了的话，那我们就不用挂起了
     */
    if (__assign_cfs_rq_runtime(cfs_b, cfs_rq, 1)) {
        dequeue = 0;
    } else {
        /* 确实需要挂起，将cfs_rq加入到cfs_bandwidth的挂起列表中，以便后期定时器为其重置时间
         */
        list_add_tail_rcu(&cfs_rq->throttled_list, &cfs_b->throttled_cfs_rq);
    }
    raw_spin_unlock(&cfs_b->lock);

    if (!dequeue)
        return false; /* Throttle no longer required. */

    /* 通过task_group->se拿到对应CPU队列中指向cfs_rq的那个se */
    se = cfs_rq->tg->se[cpu_of(rq_of(cfs_rq))];

    /* freeze hierarchy runnable averages while throttled */
    rcu_read_lock();
    /* 更新以cfs_rq->tg为顶点的所有任务组中cfs_rq的throttle_count字段，将其加一 */
    walk_tg_tree_from(cfs_rq->tg, tg_throttle_down, tg_nop, (void *)rq);
    rcu_read_unlock();

    /* cfs_rq 及其所有子孙 cfs_rq 队列中的可运行任务的总数 */
    task_delta = cfs_rq->h_nr_running;
    idle_task_delta = cfs_rq->idle_h_nr_running;
    /* 删除上层队列中对应的 se, 如上图中所示 */
    for_each_sched_entity(se) {
        struct cfs_rq *qcfs_rq = cfs_rq_of(se);
        /* throttled entity or throttle-on-deactivate */
        if (!se->on_rq)
            goto done;

        /* 从上层 cfs_rq 中删除对应的se */
        dequeue_entity(qcfs_rq, se, DEQUEUE_SLEEP);

        /* 更新上层 cfs_rq 中的对应字段 */
        qcfs_rq->h_nr_running -= task_delta;
        qcfs_rq->idle_h_nr_running -= idle_task_delta;

        /* qcfs_rq->load.weight == 0 的话，说明 qcfs_rq 删除 se
         * 后就是空队列了，否则就可以退出循环了 */
        if (qcfs_rq->load.weight) {
            /* Avoid re-evaluating load for this entity: */
            se = parent_entity(se);
            break;
        }
    }

    /* 经过上面的循环之后，虽然此时se及其父节点都不需要dequeue了，但仍然需要更新对应的字段，因此这里再循环处理一次
     */
    for_each_sched_entity(se) {
        struct cfs_rq *qcfs_rq = cfs_rq_of(se);
        /* throttled entity or throttle-on-deactivate */
        if (!se->on_rq)
            goto done;

        update_load_avg(qcfs_rq, se, 0);
        se_update_runnable(se);

        qcfs_rq->h_nr_running -= task_delta;
        qcfs_rq->idle_h_nr_running -= idle_task_delta;
    }

    /* At this point se is NULL and we are at root level*/
    sub_nr_running(rq, task_delta);

done:
    /*
     * 设置 throttle 的标记位，并记录时间
     */
    cfs_rq->throttled = 1;
    cfs_rq->throttled_clock = rq_clock(rq);
    return true;
}
```

最后 `cfs_rq->throttled` 被设置为1后，整个`cfs_rq`就被挂起了，被挂起的`cfs_rq`已经不在CPU的运行队列中，因此其中的任务就不会被调度到了。

上面是挂起操作，解挂的逻辑由函数 `unthrottle_cfs_rq` 完成，解挂是挂起的逆操作，代码结构几乎都可以对应上，主要就是将`cfs_rq`入队、更新对应se节点的信息、最后有必要的话调用 `resched_curr` 触发调度，感兴趣的读者可以自行阅读源码，这里不再深究。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://s3.shizhz.me/linux-sched/cfs-sched/bandwidth-throttle.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
