# 2.6.4.2 带宽控制 - 带宽时间

前文提到实际的带宽控制是在任务组下属的`cfs_rq`队列中进行的，而`cfs_rq` 对带宽时间的操作归总起来就两点：更新与申请。

cfsrq申请到的时间保存在字段 `runtime_remaining` 中，每当更新任务的时间时，系统也会更新该字段。前面讨论vruntime时我们提到系统通过函数 `update_curr` 来更新与任务相关的时间信息，实际上该函数也会更新与带宽控制相关的时间：

```
/* file: kernel/sched/fair.c */
static void update_curr(struct cfs_rq *cfs_rq) {
    struct sched_entity *curr = cfs_rq->curr;
    u64 now = rq_clock_task(rq_of(cfs_rq));
    u64 delta_exec;

    /* 本次更新 vruntime 与上次更新 vruntime 之间的差值 */
    delta_exec = now - curr->exec_start;

    /* 省略其他更新时间的代码，这部分在讨论vruntime时已经讨论过 */

    /* 更新与带宽控制相关的字段 */
    account_cfs_rq_runtime(cfs_rq, delta_exec);
}

/* 判断是否启用了带宽控制，如果是的话调用函数__account_cfs_rq_runtime更新对应字段
 */
static __always_inline void account_cfs_rq_runtime(struct cfs_rq *cfs_rq,
                                                   u64 delta_exec) {
    if (!cfs_bandwidth_used() || !cfs_rq->runtime_enabled)
        return;

    __account_cfs_rq_runtime(cfs_rq, delta_exec);
}

static void __account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) {
    /* 从剩余时间中减去这次消耗掉的部分，注意这里可能导致cfs_rq->runtime_remaining为负数
     */
    cfs_rq->runtime_remaining -= delta_exec;

    /* 如果还有剩余时间，则函数返回 */
    if (likely(cfs_rq->runtime_remaining > 0))
        return;

    /* 接下来要向所在的任务组申请时间，但如果当前cfs_rq已经被挂起了的话，就不用麻烦了
     */
    if (cfs_rq->throttled)
        return;
    /*
     * 此时我们调用函数assign_cfs_rq_runtime向任务组申请时间。如果申请时间失败，则cfs_rq应该被挂起。
     * 这里调用resched_curr标记cfs_rq->curr的TIF_NEED_RESCHED位，以便随后将其调度出去
     */
    if (!assign_cfs_rq_runtime(cfs_rq) && likely(cfs_rq->curr))
        resched_curr(rq_of(cfs_rq));
}
```

更新带宽时间的逻辑其实很简单，就是从 `cfs->runtime_remaining` 减去本次执行的物理时间。如果此时调度器发现时间余额已经耗尽，则会立即尝试从任务组中申请，如果申请失败，说明在本周期内整个任务组的时间都已经耗尽了，而如果当前正在执行的任务在本cfsrq中的话，则需要将其调度出去。从任务组中申请时间的函数是 `assign_cfs_rq_runtime`:

```
/* file: kernel/sched/fair.c */
static int assign_cfs_rq_runtime(struct cfs_rq *cfs_rq) {
    /* 返回task_group的cfs_bandwidth字段，该字段封装着整个任务组的带宽数据 */
    struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg);
    int ret;

    raw_spin_lock(&cfs_b->lock);
    /* 实际的申请逻辑，第三个参数是期望申请的时间额度，默认是5ms */
    ret = __assign_cfs_rq_runtime(cfs_b, cfs_rq, sched_cfs_bandwidth_slice());
    raw_spin_unlock(&cfs_b->lock);

    return ret;
}

static int __assign_cfs_rq_runtime(struct cfs_bandwidth *cfs_b,
                                   struct cfs_rq *cfs_rq, u64 target_runtime) {
    u64 min_amount, amount = 0;

    lockdep_assert_held(&cfs_b->lock);

    /* 前文聊到更新带宽时间时，提到 cfs_rq->runtime_remaining
     * 可能为负，这说明上次运行时已经透支了部分时间，这里补回来。
     */
    min_amount = target_runtime - cfs_rq->runtime_remaining;

    /* 没有限制，则要多少分配多少 */
    if (cfs_b->quota == RUNTIME_INF)
        amount = min_amount;
    else {
        /* 保证定时器是打开的，保证周期性地为任务组重置带宽时间 */
        start_cfs_bandwidth(cfs_b);

        /* 如果本周期内还有时间，则可以分配 */
        if (cfs_b->runtime > 0) {
            /* 确保不要透支 */
            amount = min(cfs_b->runtime, min_amount);
            cfs_b->runtime -= amount;
            cfs_b->idle = 0;
        }
    }

    /* 将新申请到的时间补充给cfs_rq */
    cfs_rq->runtime_remaining += amount;

    /* 是否成功地从task_group中申请到了时间 */
    return cfs_rq->runtime_remaining > 0;
}
```


---

# 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-time.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.
