简介

在多核 SMP、NUMA 架构的 Linux 服务器、嵌入式平台中,CFS 调度器的负载均衡是保障整机 CPU 利用率、降低业务时延的核心能力。Linux 针对 CPU 进入空闲状态的场景,专门设计了Newidle 空闲负载均衡:当单个 CPU 即将切换为空闲态时,主动扫描同调度域内其他繁忙 CPU,拉取过载任务实现负载分摊。

但负载均衡本身存在 CPU 开销:跨 CPU 遍历调度域、任务筛选、上下文迁移、缓存失效都会消耗时钟周期。如果无限制执行 Newidle 均衡,频繁的扫描与迁移反而会拖慢系统性能,尤其在高吞吐、低延迟的业务场景下,额外开销带来的负面影响会远大于负载均衡的收益。

为解决均衡开销失控问题,Linux 内核在调度域sched_domain中引入了max_newidle_lb_cost参数,它作为 Newidle 均衡的成本阈值控制器,统计并限制单次、周期内空闲均衡的最大耗时,当预估开销超过 CPU 空闲收益时,直接终止均衡流程,做到 “收益大于成本才执行均衡”。

该机制广泛应用于云计算服务器集群、边缘嵌入式设备、实时音视频服务、数据库集群、工业控制多核系统。对于内核开发、Linux 运维、性能调优、嵌入式工程师而言,吃透max_newidle_lb_cost的统计逻辑、衰减规则、判断条件与源码实现,是排查 CPU 抖动、负载不均、软中断耗时过高、调度开销异常的必备技能。同时该内容可作为内核研究报告、学术论文、性能优化方案的核心参考,本文结合源码、实操命令、调试案例完整拆解整套机制。

一、核心概念与术语解析

1.1 负载均衡分类与 Newidle 均衡定位

Linux CFS 调度器共有三类主流负载均衡,max_newidle_lb_cost仅作用于Newidle 均衡

  1. 周期性均衡(Tick Balance):由时钟中断定时触发,兜底修正长期负载不均,优先级最低;
  2. Nohz 空闲均衡:CPU 深度休眠(NOHZ)时触发,针对长时间空闲场景;
  3. Newidle 均衡(CPU_NEWLY_IDLE)CPU 刚要进入空闲状态的瞬间触发,是最频繁、最灵敏的均衡方式,也是本文研究对象。

Newidle 均衡触发时机:schedule()函数中,当前运行队列无就绪任务,CPU 即将切换至 idle 线程时,调用idle_balance()发起均衡。

1.2 核心结构体与关键字段

1.2.1 调度域 sched_domain

内核以sched_domain(调度域)划分 CPU 拓扑,同域内 CPU 互相执行负载均衡,多级域自底向上遍历。max_newidle_lb_costsched_domain的内置成员:

struct sched_domain {
    /* 省略拓扑、亲和性、均衡间隔等通用字段 */
    u64 max_newidle_lb_cost;      // Newidle均衡最大允许开销(单位:纳秒)
    unsigned long next_decay_max_lb_cost; // 下一次成本衰减的时间点(jiffies)
};
  • max_newidle_lb_cost:记录当前调度域内,历史 Newidle 均衡的最大耗时成本,作为后续均衡的判断阈值;
  • next_decay_max_lb_cost:标记下一次执行成本衰减的时刻,默认每秒触发一次衰减。
1.2.2 运行队列 rq

每个 CPU 独占一个struct rq运行队列,汇总调度域的均衡成本:

struct rq {
    /* 省略CFS队列、实时队列、任务统计等字段 */
    u64 max_idle_balance_cost;    // 汇总所有调度域的最大空闲均衡成本
    u64 avg_idle;                 // CPU平均空闲时长(纳秒)
};
  • avg_idle:衡量 CPU 空闲收益的核心指标,代表该 CPU 平均能空闲多久;
  • max_idle_balance_cost:聚合多级sched_domainmax_newidle_lb_cost,作为全局判断依据。

1.3 max_newidle_lb_cost 核心规则

  1. 成本统计:每次执行 Newidle 均衡,内核记录本次耗时,更新max_newidle_lb_cost为历史最大值;
  2. 周期性衰减:为避免历史高成本永久限制均衡,每 1 秒自动衰减约 1%,公式:new_cost = old_cost * 253 / 256
  3. 判断逻辑:仅当 CPU平均空闲时长(avg_idle) > 当前均衡预估成本 时,才允许执行 Newidle 均衡。直白解释:CPU 空闲时间足够长,能覆盖均衡带来的开销,均衡才有意义

1.4 配套内核参数

  • sysctl_sched_migration_cost_ns:单次任务迁移的基础耗时基准(纳秒),默认值 500000;
  • HZ:系统节拍,x86 平台默认 1000,代表 1 秒对应 1000 个 jiffies,用于定时衰减。

二、环境准备

2.1 软硬件环境清单

环境分类 版本 / 配置要求 用途说明
操作系统 Ubuntu 20.04/22.04 64 位、CentOS Stream 9 主流发行版,内核逻辑通用
内核版本 Linux 5.4、5.15、6.1 LTS(推荐) 长期支持版,Newidle 均衡逻辑无大幅变更
硬件架构 x86_64 多核 CPU(≥4 核) 必须多核才能观测负载均衡效果
编译工具 gcc 9+、make、binutils 内核编译、模块编译
调试工具 ftrace、perf、gdb、sysctl、procfs 跟踪函数、查看参数、性能采样
依赖库 libncurses-dev、bison、flex、libelf-dev 内核配置与编译依赖

2.2 环境搭建与内核配置

2.2.1 安装基础依赖

所有命令可直接复制执行,适配 Debian/Ubuntu 系列:

# 更新软件源并安装编译、调试全套依赖
sudo apt update && sudo apt install -y build-essential libncurses-dev \
bison flex libssl-dev libelf-dev trace-cmd gdb sysstat
2.2.2 内核源码获取与配置

max_newidle_lb_cost 全部逻辑集中在 kernel/sched/fair.c,需开启调度调试与跟踪功能:

# 下载Linux 6.1 LTS内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
tar -xf linux-6.1.tar.xz
cd linux-6.1

# 复用当前系统内核配置
cp /boot/config-$(uname -r) .config

# 图形化配置内核
make menuconfig

必须开启的配置项(开启后才能调试、跟踪负载均衡):

CONFIG_SCHED_FAIR=y              # 启用CFS调度器(默认开启)
CONFIG_SCHED_DEBUG=y             # 调度器调试开关
CONFIG_FTRACE=y                  # 函数跟踪,追踪均衡流程
CONFIG_SCHEDSTATS=y              # 调度统计,查看sched_domain信息
CONFIG_DEBUG_KERNEL=y            # 内核基础调试
2.2.3 编译并安装内核
# 多核编译,加速编译过程
make -j$(nproc)
# 安装内核模块
sudo make modules_install
# 安装内核镜像
sudo make install
# 更新系统引导项
sudo update-grub

执行完成后重启服务器,在 GRUB 菜单中选择新编译的内核进入。

2.2.4 验证环境

执行以下命令校验内核与工具是否就绪:

# 查看当前内核版本
uname -r
# 查看调度迁移基础成本
cat /proc/sys/kernel/sched_migration_cost_ns
# 查看CPU运行队列统计(需CONFIG_SCHEDSTATS)
cat /proc/schedstat

2.3 源码文件定位

核心代码路径(全文反复引用):

  1. 负载均衡、max_newidle_lb_cost 逻辑:kernel/sched/fair.c
  2. 调度域、运行队列结构体定义:kernel/sched/sched.h
  3. 调度拓扑解析:kernel/sched/topology.c

三、应用场景

max_newidle_lb_cost 作为开销控制器,在多核业务系统中起到 “节流” 作用。在云服务器场景中,单宿主机运行数十个容器,CPU 频繁在忙闲状态切换,Newidle 均衡持续触发,该参数可避免大量容器任务迁移导致的软中断飙升、CPU 使用率虚高。在嵌入式多核工业网关设备上,业务线程周期性启停,CPU 不断进入空闲态,严格限制均衡开销能保证控制指令低延迟响应。数据库服务器中,读写线程随机分布在不同核心,无节制的负载均衡会破坏 CPU 缓存命中率,max_newidle_lb_cost 过滤低收益均衡行为,兼顾负载均衡与缓存性能。此外,实时音视频转码集群、边缘计算节点、分布式存储服务均依赖该机制,在负载均衡、CPU 开销、缓存命中率三者之间取得平衡,防止调度自身成为性能瓶颈。

四、实际案例与源码深度剖析

本章节结合内核源码、Shell 实操命令、用户态测试程序,分步拆解max_newidle_lb_cost的更新、衰减、判断、执行全流程,所有代码均可直接编译、运行、复现。

4.1 整体调用链路梳理

Newidle 均衡完整调用链: schedule() → idle_balance() → load_balance() → rebalance_domains() → 成本判断 → 任务迁移 其中rebalance_domains函数是max_newidle_lb_cost的核心判断入口。

4.2 核心源码解析:成本判断逻辑

4.2.1 均衡执行前置判断(fair.c)

该代码段决定是否允许执行本次 Newidle 均衡,是max_newidle_lb_cost最核心的逻辑,附带逐行注释:

// kernel/sched/fair.c
static int rebalance_domains(struct rq *rq, enum cpu_idle_type idle)
{
    struct sched_domain *sd;
    int this_cpu = rq->cpu;
    u64 max_cost = 0;

    /* 仅针对 NEWLY_IDLE(即将空闲)场景生效 */
    if (idle != CPU_NEWLY_IDLE)
        goto skip_cost_check;

    /* 自底向上遍历所有层级调度域 */
    for_each_domain(this_cpu, sd) {
        /* 汇总所有调度域的最大均衡成本 */
        if (sd->max_newidle_lb_cost > max_cost)
            max_cost = sd->max_newidle_lb_cost;
    }

    /* 更新运行队列的全局最大空闲均衡成本 */
    rq->max_idle_balance_cost = max(sysctl_sched_migration_cost_ns, max_cost);

    /* 核心判断规则:CPU平均空闲时长 < 均衡总成本 → 直接放弃均衡 */
    if (rq->avg_idle < rq->max_idle_balance_cost) {
        return 0; // 开销大于收益,终止本次负载均衡
    }

skip_cost_check:
    /* 后续执行正常的调度域遍历、任务查找、迁移逻辑 */
    // ...省略非核心均衡代码
    return 1;
}

代码作用说明

  1. 仅对CPU_NEWLY_IDLE(Newidle)场景做成本校验,周期性均衡、Nohz 均衡不受该参数限制;
  2. 遍历当前 CPU 所有调度域,取出全局最大max_newidle_lb_cost
  3. 对比avg_idle(CPU 平均空闲时间)与均衡总成本,空闲时间不足以覆盖开销则直接退出。
4.2.2 max_newidle_lb_cost 周期性衰减逻辑

为了不让历史高开销永久禁用均衡,内核每秒执行一次成本衰减,源码如下:

// kernel/sched/fair.c
static void decay_max_newidle_lb_cost(struct sched_domain *sd)
{
    /* 判断是否到达衰减时间点(1秒执行一次) */
    if (time_after(jiffies, sd->next_decay_max_lb_cost)) {
        /* 衰减公式:乘以253,除以256,等效衰减约1.17% */
        sd->max_newidle_lb_cost = (sd->max_newidle_lb_cost * 253) / 256;
        /* 重置下一次衰减时间:当前jiffies + 1秒(HZ) */
        sd->next_decay_max_lb_cost = jiffies + HZ;
    }
}

代码解释

  • 253/256:二进制快速运算,避免浮点计算,每轮衰减约 1.17%;
  • HZ:系统节拍,x86 默认 1000,代表间隔 1 秒;
  • 衰减逻辑依附于调度域遍历流程,在每次均衡检查时触发判断。
4.2.3 均衡耗时统计与 max_newidle_lb_cost 更新

每次成功执行 Newidle 均衡后,内核统计耗时并更新最大值:

// kernel/sched/fair.c
static int load_balance(int this_cpu, struct rq *this_rq,
                        struct sched_domain *sd, enum cpu_idle_type idle,
                        int *continue_balancing)
{
    u64 start_time, cost;
    int ret = 0;

    /* 仅统计Newidle均衡的耗时 */
    if (idle == CPU_NEWLY_IDLE)
        start_time = sched_clock(); // 记录开始时间(高精度时钟)

    // 省略任务查找、迁移核心逻辑
    ret = do_task_move(this_cpu, this_rq, sd);

    if (idle == CPU_NEWLY_IDLE) {
        /* 计算本次均衡实际耗时 */
        cost = sched_clock() - start_time;
        /* 仅当本次耗时大于历史最大值时,才更新阈值 */
        if (cost > sd->max_newidle_lb_cost)
            sd->max_newidle_lb_cost = cost;
    }

    decay_max_newidle_lb_cost(sd); // 执行成本衰减
    return ret;
}

代码作用

  1. 使用内核高精度时钟sched_clock()统计均衡耗时;
  2. 只有本次均衡耗时突破历史最大值,才刷新max_newidle_lb_cost
  3. 均衡结束后立即调用衰减函数,完成一轮闭环。

4.3 实操案例 1:查看与修改内核参数

通过 proc 文件系统查看、临时调整均衡基础成本,所有命令可直接复制运行。

4.3.1 查看默认迁移成本
# 查看单次任务迁移基准耗时(纳秒)
cat /proc/sys/kernel/sched_migration_cost_ns

输出示例:500000,代表默认单次迁移基础开销为 500 微秒。

4.3.2 临时修改参数(重启失效)

调高迁移成本,模拟均衡开销变大,观察 Newidle 均衡被限制的现象:

# 修改为1000000纳秒(1毫秒)
sudo echo 1000000 > /proc/sys/kernel/sched_migration_cost_ns
4.3.3 永久修改参数(sysctl)
# 编辑sysctl配置文件
sudo vi /etc/sysctl.conf
# 添加如下内容
kernel.sched_migration_cost_ns = 500000

# 生效配置
sudo sysctl -p

4.4 实操案例 2:使用 ftrace 跟踪成本相关函数

利用 ftrace 跟踪decay_max_newidle_lb_costload_balance等核心函数,观测衰减与判断流程:

# 1. 挂载debugfs(内核调试文件系统)
sudo mount -t debugfs none /sys/kernel/debug

# 2. 清空历史跟踪日志
sudo echo > /sys/kernel/debug/tracing/trace

# 3. 设置需要跟踪的函数
sudo echo load_balance >> /sys/kernel/debug/tracing/set_ftrace_filter
sudo echo decay_max_newidle_lb_cost >> /sys/kernel/debug/tracing/set_ftrace_filter
sudo echo rebalance_domains >> /sys/kernel/debug/tracing/set_ftrace_filter

# 4. 开启函数跟踪
sudo echo function > /sys/kernel/debug/tracing/current_tracer
sudo echo 1 > /sys/kernel/debug/tracing/tracing_on

压测触发 Newidle 均衡:新开终端运行 CPU 压力测试,让 CPU 频繁忙闲切换:

# 安装压测工具
sudo apt install -y stress
# 启动4个CPU压力进程,运行30秒
stress -c 4 -t 30

停止跟踪并查看日志

# 关闭跟踪
sudo echo 0 > /sys/kernel/debug/tracing/tracing_on
# 查看完整调用日志
sudo cat /sys/kernel/debug/tracing/trace

日志解读:可以清晰看到rebalance_domains不断被调用,每秒触发一次decay_max_newidle_lb_cost衰减,完整复现源码逻辑。

4.5 实操案例 3:编写测试程序制造 CPU 忙闲切换

编写用户态程序,周期性休眠、占用 CPU,主动触发 Newidle 均衡,方便观测max_newidle_lb_cost变化:

/* newidle_test.c 测试程序:制造CPU忙闲交替 */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define LOOP_CNT 100000000

// 线程函数:占用CPU 1秒,休眠1秒,循环往复
void *cpu_load_thread(void *arg)
{
    while(1)
    {
        // 空循环占用CPU
        for(int i = 0; i < LOOP_CNT; i++);
        // 休眠,让CPU进入空闲态,触发Newidle均衡
        usleep(1000000);
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    // 创建子线程
    pthread_create(&tid, NULL, cpu_load_thread, NULL);
    // 主线程持续休眠
    while(1)
    {
        sleep(1);
    }
    return 0;
}

编译与运行命令

# 编译多线程程序
gcc newidle_test.c -o newidle_test -pthread
# 后台运行程序
./newidle_test &
# 查看CPU负载与线程状态
top -H

使用场景:该程序让 CPU 持续在 “繁忙 - 空闲” 之间切换,高频触发 Newidle 均衡,配合 ftrace 可精准观测max_newidle_lb_cost的更新与衰减。

4.6 实操案例 4:查看调度域统计信息

开启CONFIG_SCHEDSTATS后,通过/proc/schedstat查看调度域均衡统计:

# 查看调度域全局统计
cat /proc/schedstat | grep domain

结合压测前后的统计数据对比,可直观判断 Newidle 均衡是否被max_newidle_lb_cost限制。

五、常见问题与解答

Q1:max_newidle_lb_cost 为什么要做周期性衰减?直接清零不行吗?

解答:如果直接清零,历史突发的高耗时均衡会被彻底忽略,无法反映真实负载;采用缓慢衰减(每秒 1.17%)是折中方案:短期保留历史峰值、长期逐步弱化老旧数据,保证阈值贴合当前系统负载特征。

Q2:修改 sched_migration_cost_ns 会对 max_newidle_lb_cost 产生什么影响?

解答sched_migration_cost_ns是基础迁移开销,当max_newidle_lb_cost过小时,内核会取该参数作为兜底阈值。调大该值会抬高均衡总成本,更容易触发avg_idle < 总成本的判断,导致 Newidle 均衡被禁用。

Q3:ftrace 跟踪不到 decay_max_newidle_lb_cost 函数调用是什么原因?

解答:1. 内核未开启CONFIG_FTRACECONFIG_SCHED_DEBUG;2. CPU 长期处于繁忙状态,从未进入CPU_NEWLY_IDLE场景,均衡未触发;3. 函数名填写错误,核对fair.c内函数名称;4. debugfs 未正常挂载,重新执行mount -t debugfs none /sys/kernel/debug

Q4:CPU avg_idle 持续偏低,Newidle 均衡完全不执行,如何排查?

解答:1. 查看cat /proc/schedstat确认avg_idle数值;2. 检查max_newidle_lb_cost是否因历史高均衡耗时居高不下;3. 临时调低sched_migration_cost_ns测试是否恢复均衡;4. 排查业务线程是否密集,CPU 几乎无空闲时间。

Q5:多核 NUMA 架构下,不同调度域的 max_newidle_lb_cost 是独立的吗?

解答:完全独立。每个sched_domain维护自身的max_newidle_lb_cost与衰减时间,遍历调度域时会取所有域的最大值作为全局阈值,NUMA 跨节点调度域的开销阈值互不干扰。

六、实践建议与最佳实践

6.1 性能调优最佳实践

  1. 高负载业务(数据库、中间件) 这类业务 CPU 缓存命中率优先级高于负载均衡,建议适度调高sched_migration_cost_ns,让max_newidle_lb_cost更容易限制 Newidle 均衡,减少任务迁移,保护 CPU 缓存。

  2. 低负载、多容器场景 服务器 CPU 普遍空闲,可调低迁移成本,放宽均衡限制,充分利用多核算力,避免部分 CPU 空载、部分 CPU 过载。

  3. 嵌入式实时设备 实时系统追求调度确定性,建议固定max_newidle_lb_cost上限,或关闭 Newidle 均衡,防止均衡开销抢占实时任务 CPU 时间。

6.2 调试与排障技巧

  1. 区分均衡类型 负载不均时,先判断是周期性均衡还是 Newidle 均衡问题:使用 ftrace 跟踪CPU_NEWLY_IDLE相关调用,定位问题范围。

  2. 阈值观测技巧 长期观测系统时,结合sar -u查看 CPU 忙闲状态,配合/proc/schedstat观察avg_idle与均衡次数,判断成本阈值是否合理。

  3. 临时关闭 Newidle 均衡 排查问题时,可临时拉高sched_migration_cost_ns,让均衡开销大于 CPU 空闲时间,强制禁用 Newidle 均衡,对比业务性能变化。

6.3 内核定制开发建议

  1. 二次开发调度策略时,不要删除max_newidle_lb_cost成本控制逻辑,负载均衡天生存在开销,无限制均衡一定会引发性能退化。
  2. 若需要自定义均衡开销算法,可基于原有衰减逻辑改造,保留 “空闲收益> 均衡成本” 的核心判断原则。
  3. 针对异构多核平台,可给不同调度域配置差异化的衰减系数,适配不同核心的算力特征。

6.4 线上运维规范

  1. 线上环境禁止频繁动态修改sched_migration_cost_ns,如需调整,先在测试环境压测验证;
  2. 服务器压测、版本上线前,提前观测max_newidle_lb_cost基线值,作为故障排查参考;
  3. 集群内所有主机保持调度参数一致,避免单机负载均衡策略不同引发集群性能差异。

七、总结与应用延伸

本文从背景概念、环境搭建、内核结构体、核心源码、实操命令、测试案例、问题排查、调优规范全链路,完整解析了max_newidle_lb_cost对 Linux Newidle 负载均衡的成本控制机制。

核心要点回顾:

  1. max_newidle_lb_cost 挂载在sched_domain调度域中,用于统计 Newidle 均衡的历史最大耗时;
  2. 内核通过 ** 每秒衰减 1.17%** 的规则动态更新阈值,兼顾历史数据与当前负载;
  3. 核心判断逻辑:仅当 CPU 平均空闲时长大于均衡总成本时,才允许执行负载均衡,做到 “算收益再执行”;
  4. 整套机制的目标:限制负载均衡自身的 CPU 开销,防止调度逻辑成为系统性能瓶颈。

从工程落地角度,该机制是 Linux 多核系统 “负载均衡” 与 “性能开销” 之间的核心平衡点,广泛应用于云计算、嵌入式、数据库、工业控制、音视频服务等所有基于 Linux 的多核业务。对于性能调优工程师,掌握该参数可解决 CPU 负载不均、软中断过高、缓存命中率下降等疑难问题;对于内核研究者,该源码体现了 Linux “开销可控” 的设计思想,可作为调度子系统论文、技术报告的核心素材。

建议读者基于本文提供的源码、测试程序、ftrace 命令,在测试机上反复复现实验,修改衰减系数、调整迁移成本,观测系统行为变化。将这套成本控制思想运用到实际项目的性能优化、内核裁剪、调度策略定制中,真正做到理论结合实战。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐