Linux核心概念详解
  • 0. Linux核心概念详解
  • 1. 调试环境
  • 2. Linux 调度器
    • 2.1 任务
    • 2.2 核心概念
      • 2.2.1 核心概念 - 调度实体
      • 2.2.2 核心概念 - 调度类
      • 2.2.3 核心概念 - 调度策略
      • 2.2.4 核心概念 - 运行队列
      • 2.2.5 核心概念 - 优先级
    • 2.3 演进历史
      • 2.3.1 O(n)调度器 - 调度逻辑
      • 2.3.2 O(n)调度器 - 时间分配
      • 2.3.3 O(n)调度器 - 调度时机
      • 2.3.4 O(1)调度器 - 简介
      • 2.3.5 O(1)调度器 - 调度逻辑
      • 2.3.6 O(1)调度器 - 时间分配
      • 2.3.7 RSDL
      • 2.3.8 CFS
    • 2.4 DL调度器
      • 2.4.1 DL调度器 - 调度算法
      • 2.4.2 DL调度器 -核心代码
    • 2.5 RT调度器
    • 2.6 CFS
      • 2.6.1 公平性
      • 2.6.2 调度逻辑
      • 2.6.2.1 调度逻辑 - 数据结构
      • 2.6.2.2 调度逻辑 - vruntime
      • 2.6.2.3 调度逻辑 - 调度周期
      • 2.6.2.4 调度逻辑 - 调度节拍
      • 2.6.2.5 调度逻辑 - 任务抢占
      • 2.6.2.6 调度逻辑 - 调度时机
      • 2.6.3 组调度
      • 2.6.3.1 组调度 - 数据结构
      • 2.6.3.2 组调度 - 调度逻辑
      • 2.6.3.3 组调度 - 时间分配
      • 2.6.3.4 组调度 - 任务组权重
      • 2.6.4 带宽控制
      • 2.6.4.1 带宽控制 - 数据结构
      • 2.6.4.2 带宽控制 - 带宽时间
      • 2.6.4.3 带宽控制 - 挂起与解挂
      • 2.6.4.3 带宽控制 - 定时器
    • 2.7 负载追踪
      • 2.7.1 负载追踪 - 简介
      • 2.7.2 负载追踪 - 数据结构
      • 2.7.3 负载追踪 - 计算负载
      • 2.7.4 负载追踪 - 更新负载
    • 2.8 负载均衡
      • 2.8.1 简介
      • 2.8.2 CPU的拓扑结构
      • 2.8.3 数据结构
      • 2.8.4 算法思路
      • 2.8.5 触发时机
      • 2.8.6 总结
  • 3. LINUX 内存管理
    • 3.1 寻址模式
      • 3.1.1 地址
      • 3.1.2 地址转换
      • 3.1.3 Linux的地址空间
    • 3.2 物理内存
      • 3.2.1 数据结构
      • 3.2.2 初始化
      • 3.2.3 物理内存模型
      • 3.2.4 Buddy System(伙伴系统)
      • 3.2.5 SLAB/SLUB/SLOB
Powered by GitBook
On this page

Was this helpful?

  1. 2. Linux 调度器
  2. 2.7 负载追踪

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 的返回值,上一节我们已经详细讨论过了如何求级数之和,这里不再详述。

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

Previous2.7.3 负载追踪 - 计算负载Next2.8 负载均衡

Last updated 3 years ago

Was this helpful?