# 2.7.4 负载追踪 - 更新负载

知道了负载及其计算原理之后，更新负载的逻辑就比较好理解了。系统通过函数 `update_load_avg` 来完成对调度实体与其cfsrq的负载更新，该函数的实现如下：

```
/* file: kernel/sched/fair.c */
static inline void update_load_avg(struct cfs_rq *cfs_rq,
                                   struct sched_entity *se, int flags) {
    /* 当前时间，精度为ns */
    u64 now = cfs_rq_clock_pelt(cfs_rq);
    int decayed;

    if (se->avg.last_update_time && !(flags & SKIP_AGE_LOAD))
        __update_load_avg_se(now, cfs_rq, se); /* 更新se的负载信息 */

    /* 其余代码删除 */
}
```

这里我们只关注更新 se 负载的逻辑，该部分逻辑通过 `__update_load_avg_se` 完成：

```
/* file: kernel/sched/pelt.c */
int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq,
                         struct sched_entity *se) {
    /* 先更新se 的负载总和 */
    if (___update_load_sum(now, &se->avg, !!se->on_rq, se_runnable(se),
                           cfs_rq->curr == se)) {
        /* 更新 se 的平均负载，平均负载的计算逻辑与负载总和与权重有关 */
        ___update_load_avg(&se->avg, se_weight(se));
        cfs_se_util_change(&se->avg);
        trace_pelt_se_tp(se);
        return 1;
    }

    return 0;
}
```

其中函数 `___update_load_sum` 用来更新负载的累计总和，而 `___update_load_avg` 会根据se的负载总和算出其平均负载，计算时会考虑se的负载总和与权重。前者的实现如下：

```
/* file: kernel/sched/pelt.c */
static __always_inline int ___update_load_sum(u64 now, struct sched_avg *sa,
                                              unsigned long load,
                                              unsigned long runnable,
                                              int running) {
    u64 delta;

    /* 距离上一次更新负载的时间差，单位是ns，该时间为墙上时间 */
    delta = now - sa->last_update_time;

    if ((s64)delta < 0) {
        sa->last_update_time = now;
        return 0;
    }

    /* delta的精度是ns, 所以右移10位变为us, 这里假设 1us = 1024ns 以简化计算 */
    delta >>= 10;
    if (!delta)
        return 0;

    /* 这里更新时间时，对delta的位移运算相当于抹掉了最近1024ns内的那部分时间，因为这部分时间就是记录在低十位数里面的。
     * 但末尾的ns尾数太小了可以忽略了，计算负载时精度考虑到 us 级别就行。
     * */
    sa->last_update_time += delta << 10;

    /* 调用函数accumulate_sum更新负载总和，该函数的实现上一节已经有详细分析 */
    if (!accumulate_sum(delta, sa, load, runnable, running))
        return 0;

    return 1;
}
```

该函数主要就是调用前面已经讨论过的 `accumulate_sum` 更新负载总和，并且记录一下更新的时间点。我们再看一下系统如何计算平均负载：

```
/* file: kernel/sched/pelt.c */
static __always_inline void ___update_load_avg(struct sched_avg *sa,
                                               unsigned long load) {
    u32 divider = get_pelt_divider(sa);

    /* 计算平均负载 */
    sa->load_avg = div_u64(load * sa->load_sum, divider);
    sa->runnable_avg = div_u64(sa->runnable_sum, divider);
    WRITE_ONCE(sa->util_avg, sa->util_sum / divider);
}

static inline u32 get_pelt_divider(struct sched_avg *avg) {
    /* 计算公式为：LOAD_AVG_MAX*y + sa->period_contrib,
     * LOAD_AVG_MAX就是1024(1 + y + y^2 + ... + y^n)当n趋近无穷时的值
     * 再将其乘以y就是再衰减一次，因为我们还需要加上sa->period_contrib
     * */
    return LOAD_AVG_MAX - 1024 + avg->period_contrib;
}
```

平均负载的计算公式为： `load_avg = weight * decay(runnable time) / decay(total time)`, 其中 `decay(runnable time)` 就是负载总和，而 `decay(total time)` 是按照计算负载总和的相同算法计算出来的所有时间的总和，其结果就是函数 `get_pelt_divider` 的返回值，上一节我们已经详细讨论过了如何求级数之和，这里不再详述。

通过该算法我们知道，平均负载是任务的权重与运行时间的综合考量，如果一个任务一直运行，那么其平均负载就会越来越趋近于它的权重。


---

# 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/load-trace/load-trace-update.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.
