简介

在多核 Linux 服务器、嵌入式多核工控平台、边缘实时计算集群中,系统普遍存在CPU 算力冷热不均问题:部分核心长期满载运行业务进程,其余 CPU 核心处于深度空闲状态,硬件资源利用率严重偏低,同时空闲 CPU 无法及时承接负载,出现整体系统吞吐上不去、单核心压力过大、调度延迟抖动异常等线上通病。

Linux 内核为解决多核调度负载倾斜问题,设计了多种负载均衡机制,其中Newidle 空闲负载均衡是最轻量化、触发时机最特殊的一类均衡策略。区别于周期性主动均衡、唤醒均衡、迁移均衡等常规均衡逻辑,Newidle 均衡专属触发场景为当前 CPU 即将进入 idle 空闲状态,内核利用 CPU 空闲前的短暂窗口,主动从集群内其他繁忙 CPU 调度队列中拉取可迁移就绪任务,填补空闲算力,从根源避免多核资源闲置浪费。

内核为严控均衡带来的 CPU 扫描开销、进程迁移开销,引入curr_cost开销阈值机制,严格限制单次空闲均衡的遍历范围与任务筛选数量,在提升 CPU 利用率控制均衡性能损耗之间做到精准权衡。

对于后端服务器研发、嵌入式 Linux 驱动工程师、内核调优工程师、实时系统架构师而言,吃透 Newidle 负载均衡的触发条件、执行流程、开销管控规则、源码执行逻辑,是完成多核调度性能调优、解决业务线程扎堆绑定核心、优化整机算力利用率、排查空闲 CPU 无法纳管负载等线上疑难问题的核心必备知识。本文以一线内核调试工程师实战视角,从基础概念、环境搭建、源码拆解、实操调试、问题排障到工程最佳实践完整讲解,内容可直接用于技术报告撰写、内核论文调研、线上生产环境调度优化落地。

一、核心概念与调度基础术语

1.1 CPU Idle 空闲状态分级

Linux 多核架构下 CPU 运行分为两大核心状态:

  1. 运行态:CPU 执行用户进程、内核线程、中断软中断,运行调度队列内就绪任务;
  2. Idle 空闲态:调度队列无就绪可执行任务,CPU 进入低功耗休眠 C-State,停止业务调度。

Newidle 均衡仅在CPU 即将切换至 Idle 空闲的临界时机触发,CPU 正常繁忙运行时不会执行该均衡逻辑,最大程度不侵占业务运行算力。

1.2 常规负载均衡与 Newidle 均衡核心差异

均衡类型 触发时机 执行优先级 性能开销 适用场景
周期性均衡 固定时钟节拍触发 中等 全局批量均衡
唤醒均衡 进程被唤醒就绪时触发 较低 新任务快速找空闲核心
Newidle 空闲均衡 CPU 即将空闲瞬间触发 最高 极低 闲置 CPU 主动揽收负载

1.3 Newidle 核心核心机制定义

  1. 空闲触发机制:当前 CPU 调度器判定本核心无本地就绪任务,准备进入 idle 循环前,优先执行 newidle 均衡流程;
  2. 主动拉取逻辑:以自身空闲 CPU 为主体,主动遍历调度域内其他繁忙 CPU 运行队列,筛选符合迁移条件的普通就绪任务;
  3. curr_cost 开销限制:内核定义均衡扫描开销计数器curr_cost,单次空闲均衡过程中,一旦累计开销超出预设阈值,立刻终止遍历,避免无限制扫描多核队列造成耗时卡顿;
  4. 调度域划分:均衡仅在同级调度域内执行,不会跨 NUMA 节点无差别拉取任务,规避跨 NUMA 内存访问延迟带来的性能倒退。

1.4 关键结构体与全局控制参数

调度域核心结构
struct sched_domain {
    /* 调度域层级、CPU掩码、亲和性配置 */
    struct sched_domain *parent;
    struct cpumask span;
    /* newidle均衡专属开销阈值 */
    unsigned int newidle_cost;
    /* 单次均衡最大允许开销 */
    unsigned int max_newidle_lb_cost;
    /* 均衡延迟、重试间隔 */
    int lb_idle_cost;
};

字段释义

  • newidle_cost:单次扫描一个远端 CPU 队列产生的基础开销值;
  • max_newidle_lb_cost:单次 newidle 均衡允许累计最大开销,即curr_cost上限;
  • curr_cost:动态累计开销变量,遍历远端 CPU 时持续累加,超上限直接退出均衡流程。
内核全局调控参数
/* 控制是否开启CPU空闲newidle负载均衡 */
sysctl_sched_newidle_balance = 1;
/* 限制空闲均衡扫描CPU数量上限 */
sysctl_sched_nr_migrate_idle = 8;

1.5 可迁移任务基础条件

能被 Newidle 均衡拉取的任务必须满足全部条件:

  1. 属于普通 CFS 调度任务,不支持实时 FIFO、RR、Deadline 高优先级实时任务迁移;
  2. 无强 CPU 亲和性绑定,未被sched_setaffinity固定指定运行核心;
  3. 任务当前处于就绪运行态,非阻塞、非休眠、非停止状态;
  4. 目标空闲 CPU 负载低于远端繁忙 CPU,具备负载迁移收益。

二、环境准备

2.1 软硬件实验环境标准配置

环境类别 详细版本与配置要求
操作系统 Ubuntu 20.04 LTS / Ubuntu 22.04 LTS 64 位服务版
内核版本 Linux 5.4、5.10、5.15、6.1 主流 LTS 稳定内核(逻辑完全通用)
硬件平台 x86_64 多核 CPU(最低 4 核,推荐 8 核及以上),16G 内存
编译依赖 gcc 9.0+、make、libncurses-dev、flex、bison、libelf-dev
调试工具 perf、ftrace、trace-cmd、gdb、sysctl、taskset、pidstat
辅助工具 htop、mpstat、stress-ng(压力测试造负载)

2.2 开发编译环境一键部署命令

# 更新软件源并安装全套编译调试依赖
sudo apt update && sudo apt install -y build-essential libncurses-dev \
bison flex libssl-dev libelf-dev perf trace-cmd htop stress-ng

2.3 内核源码获取与调试配置

# 下载Linux5.15长期支持内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.tar.xz
tar -xf linux-5.15.tar.xz
cd linux-5.15
# 复用当前系统内核配置
cp /boot/config-$(uname -r) .config
# 打开内核图形化配置界面
make menuconfig

必须开启核心配置项

CONFIG_SCHED_DEBUG=y          # 调度器调试开关
CONFIG_FTRACE=y               # 函数跟踪,追踪newidle执行流程
CONFIG_SMP=y                  # 开启多核调度支持
CONFIG_NUMA_BALANCING=y       # 支持NUMA架构负载均衡
CONFIG_DEBUG_SCHED=y          # 调度详细日志输出

执行编译安装内核:

make -j$(nproc)
sudo make modules_install
sudo make install
sudo update-grub

重启设备选择新内核进入实验环境。

2.4 核心源码路径定位

Newidle 负载均衡全部逻辑集中在以下内核文件,论文调研、源码研读直接定位:

kernel/sched/fair.c      # CFS调度newidle均衡主体逻辑
kernel/sched/sched.h     # 调度域结构体、开销阈值定义
kernel/sched/balance.c   # 通用负载均衡迁移工具函数

三、实际应用场景

Newidle 空闲负载均衡在工业生产、服务器运维、嵌入式设备领域落地场景十分广泛。在中小型 Web 集群服务器中,夜间业务流量锐减,大量业务进程休眠,多数 CPU 核心进入空闲状态,Newidle 均衡可主动整合零散后台日志清理、数据备份、定时统计类轻量任务,集中填充空闲算力,避免硬件资源空转浪费。

在嵌入式多核工控设备中,主控 CPU 高频执行实时控制逻辑,副 CPU 长期闲置,依靠 Newidle 空闲拉取策略,将设备日志采集、外设状态巡检、离线数据缓存等非实时业务自动迁移至空闲核心,分离轻重业务负载,保障核心控制任务低延迟运行。

在大数据离线计算节点场景下,批量计算任务时常出现进程扎堆占用少数 CPU 核心,其余核心空载的情况,CPU 空闲触发的 newidle 均衡在不影响前台在线业务前提下,静默完成离线计算任务负载分流,在零侵入业务运行的前提下提升整机算力利用率,整体优化整机资源调度效率。

四、实战案例与内核源码深度解析

4.1 Newidle 均衡整体执行流程

完整执行链路: CPU 本地调度队列无就绪任务 → 调用idle_balance()入口函数 → 判定开启 newidle 均衡 → 初始化curr_cost=0开销计数器 → 遍历同级调度域远端 CPU → 累加扫描开销 → 筛选可迁移 CFS 任务 → 执行进程迁移 → 开销超限终止遍历 → CPU 正式进入 idle 空闲循环。

4.2 核心入口函数 idle_balance 源码实战

该函数是 CPU 空闲均衡唯一入口,所有 newidle 逻辑均由此发起:

// kernel/sched/fair.c
static int idle_balance(struct rq *this_rq, struct task_struct *next)
{
    struct sched_domain *sd;
    int pulled_task = 0;
    // 初始化当前累计扫描开销
    unsigned int curr_cost = 0;

    // 1. 本地运行队列无任务,才执行空闲均衡
    if (this_rq->nr_running > 0)
        return 0;
    
    // 全局开关关闭,直接退出
    if (!sysctl_sched_newidle_balance)
        return 0;

    // 遍历当前CPU所属所有层级调度域
    for (sd = this_rq->sd; sd; sd = sd->parent)
    {
        // 判定当前调度域支持newidle空闲均衡
        if (!(sd->flags & SD_BALANCE_NEWIDLE))
            continue;
        
        // 调用核心拉取函数,传入开销指针
        pulled_task += newidle_balance(this_rq, sd, &curr_cost);

        // 核心限制:累计开销超过阈值,立刻终止所有均衡流程
        if (curr_cost >= sd->max_newidle_lb_cost)
            break;
    }

    return pulled_task;
}

代码详细注释说明

  1. 优先判断本地运行队列任务数,有任务直接放弃空闲均衡,不做无效扫描;
  2. 读取系统全局调度开关,可通过 sysctl 动态关闭 / 开启;
  3. 逐层遍历调度域,由近及远扫描空闲可拉取负载;
  4. curr_cost作为全局开销计数器,全程累加扫描消耗,严格控制均衡耗时。

4.3 核心拉取函数 newidle_balance 实现逻辑

static int newidle_balance(struct rq *this_rq, struct sched_domain *sd, unsigned int *curr_cost)
{
    struct cpumask *cpus = sched_domain_span(sd);
    int cpu, nr_pulled = 0;
    struct rq *remote_rq;

    // 遍历调度域内所有CPU核心
    for_each_cpu(cpu, cpus)
    {
        // 跳过自身当前空闲CPU,无需扫描本地队列
        if (cpu == this_rq->cpu)
            continue;
        
        // 单次扫描远端CPU累加基础开销
        *curr_cost += sd->newidle_cost;
        // 提前判断开销超限,直接跳出循环
        if (*curr_cost >= sd->max_newidle_lb_cost)
            break;

        // 获取远端繁忙CPU运行队列
        remote_rq = cpu_rq(cpu);
        // 远端CPU本身空闲,无任务可拉取,直接跳过
        if (!remote_rq->nr_running)
            continue;
        
        // 筛选符合条件的可迁移就绪任务
        nr_pulled += pull_task(remote_rq, this_rq);
    }
    return nr_pulled;
}

核心逻辑拆解

  1. 遍历调度域内所有 CPU,跳过自身空闲核心,只扫描远端负载核心;
  2. 每扫描一个远端 CPU,立刻累加预设基础开销,实现低成本限流;
  3. 远端 CPU 无就绪任务直接跳过,减少无效队列遍历;
  4. 调用pull_task完成任务筛选与跨 CPU 迁移动作。

4.4 任务迁移筛选 pull_task 精简源码

static int pull_task(struct rq *src_rq, struct rq *dst_rq)
{
    struct task_struct *p;
    // 从源CPU队列取出优先级合适、无亲和绑定的就绪任务
    p = pick_eevdf_task(src_rq);

    // 过滤实时任务、绑定CPU任务、高优先级不可迁移任务
    if (!p || p->sched_class != &fair_sched_class || task_has_cpu_affinity(p))
        return 0;
    
    // 执行跨CPU任务迁移
    detach_task(p, src_rq);
    attach_task(p, dst_rq);
    return 1;
}

4.5 用户态实操:压测复现 Newidle 负载均衡现象

1. 关闭 / 开启 Newidle 均衡系统命令
# 临时关闭CPU空闲负载均衡
sudo sysctl kernel.sched_newidle_balance=0
# 临时开启CPU空闲负载均衡
sudo sysctl kernel.sched_newidle_balance=1
# 永久写入配置文件
echo "kernel.sched_newidle_balance=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
2. 制造单核满载压力测试
# 仅让0号CPU跑满CPU密集型任务
taskset -c 0 stress-ng --cpu 4 --timeout 120

执行后使用htop观察:CPU0 满载,其余 CPU 处于空闲状态。

3. 观察 Newidle 自动拉取负载效果
# 实时查看各CPU使用率
mpstat -P ALL 1
# 查看进程CPU运行绑定情况
pidstat -t 1

开启sysctl kernel.sched_newidle_balance=1后,CPU0 空闲瞬间,内核自动将 stress 进程拆分迁移至空闲 CPU,整机算力利用率显著提升。

4.6 Ftrace 跟踪 Newidle 内核执行流程

可直接复制使用,精准捕获空闲均衡调用链路:

# 挂载调试文件系统
sudo mount -t debugfs none /sys/kernel/debug
# 清空历史跟踪日志
sudo echo > /sys/kernel/debug/tracing/trace
# 指定跟踪核心函数
sudo echo idle_balance >> /sys/kernel/debug/tracing/set_ftrace_filter
sudo echo newidle_balance >> /sys/kernel/debug/tracing/set_ftrace_filter
# 开启函数跟踪模式
sudo echo function > /sys/kernel/debug/tracing/current_tracer
sudo echo 1 > /sys/kernel/debug/tracing/tracing_on

新开终端执行压测命令,结束后关闭跟踪查看日志:

sudo echo 0 > /sys/kernel/debug/tracing/tracing_on
sudo cat /sys/kernel/debug/tracing/trace

通过日志可清晰看到 CPU 进入空闲状态时,idle_balancenewidle_balance的调用时机、遍历 CPU 数量、开销累加过程。

五、常见问题与实战答疑

Q1:开启 Newidle 均衡后,空闲 CPU 依旧无法拉取任何任务?

解答:第一检查任务是否被taskset固定绑定 CPU 亲和性,绑定后的进程无法被空闲均衡拉取;第二确认任务为 SCHED_FIFO/SCHED_DEADLINE 实时任务,newidle 仅支持普通 CFS 任务;第三查看调度域层级限制,跨 NUMA 节点调度域未开启空闲均衡标识,无法跨节点拉取负载。

Q2:curr_cost 开销阈值设置过大,导致系统卡顿延迟升高?

解答:max_newidle_lb_cost 数值越大,单次空闲均衡扫描 CPU 数量越多、遍历队列耗时越长,CPU 空闲前均衡流程占用过多时间,拉长进入低功耗空闲状态的耗时。生产环境建议保持内核默认阈值,不要盲目调大开销上限。

Q3:高并发业务场景下,是否建议关闭 Newidle 空闲均衡?

解答:在线高并发流量业务、低延迟交易业务建议关闭,CPU 频繁空闲拉取任务会引发频繁进程迁移,造成 CPU 缓存失效、上下文切换抖动;离线计算、日志处理、后台服务场景建议开启,最大化利用闲置算力。

Q4:Newidle 均衡和 NUMA 内存均衡优先级谁更高?

解答:CPU 空闲触发的 Newidle 均衡优先级高于常规 NUMA 均衡,但 Newidle 不会跨 NUMA 强行迁移任务,优先同 NUMA 节点内完成负载分流,避免跨节点内存访问延迟拖慢业务性能。

Q5:如何统计系统内 Newidle 均衡成功迁移任务数量?

解答:通过内核调度统计文件查看:

cat /proc/schedstat

筛选空闲均衡迁移计数字段,统计单位时间内拉取成功的进程数量,判断均衡生效效率。

六、实践建议与线上最佳实践

6.1 业务场景开关选型最佳策略

  1. 低延迟在线业务:关闭sched_newidle_balance,杜绝空闲任务迁移带来的缓存抖动与调度延迟;
  2. 离线批量计算业务:全局开启空闲均衡,依靠 CPU 空闲窗口自动分流负载,提升集群整体算力利用率;
  3. 嵌入式工控实时设备:仅开启副 CPU newidle 均衡,主控实时 CPU 关闭均衡,保障核心控制任务绝对调度优先。

6.2 开销阈值调优技巧

线上环境禁止随意修改max_newidle_lb_cost内核默认值,若小核嵌入式设备均衡耗时过长,可适当降低阈值,减少单次扫描 CPU 数量;大尺寸多核服务器可小幅提升阈值,提升空闲负载收拢效率。

6.3 进程部署规避均衡冲突技巧

  1. 核心高优先级业务进程手动绑定固定 CPU 核心,避开空闲均衡自动迁移;
  2. 批量轻量后台进程不设置 CPU 亲和性,交由 Newidle 空闲均衡自动调度分配;
  3. 大批量 CPU 密集型任务采用进程组统一调度,减少碎片化任务频繁迁移。

6.4 线上问题排查固定流程

  1. 先通过mpstat确认 CPU 冷热负载分布,确认存在明显算力倾斜;
  2. 查看 sysctl 调度开关,确认 newidle 均衡处于正常开启状态;
  3. 使用 ftrace 追踪idle_balance调用频率,判断是否正常触发空闲均衡;
  4. 排查任务调度类别与 CPU 亲和性,确认任务满足迁移基础条件;
  5. 核对调度域 SD_BALANCE_NEWIDLE 标识,确认调度域允许空闲负载均衡。

6.5 内核定制优化方向

二次定制内核调度策略时,可在 newidle 均衡逻辑中增加业务类型过滤规则,仅允许日志、备份、统计类低优先级任务空闲迁移,直接过滤 IO 密集型、内存密集型任务,在资源利用率与业务稳定性之间做到极致平衡。

七、全文总结与技术延伸应用

本文从底层理论定义、实验环境搭建、内核结构体解析、核心执行源码逐行剖析、用户态实操复现、调试跟踪手段、线上问题排障到工程落地规范,完整梳理了 Linux 内核Newidle 空闲负载均衡整套技术体系。

Newidle 负载均衡作为多核调度体系中轻量化、低侵入式的负载分流方案,核心设计精髓在于借势空闲窗口期做负载调度,不侵占业务运行算力,同时依靠curr_cost动态开销计数器严格约束均衡扫描成本,完美解决传统周期性均衡抢占业务资源、空闲 CPU 资源长期闲置两大行业痛点。

从技术研究层面,掌握该机制能够完整打通 Linux 多核 CFS 调度全链路知识体系,填补空闲态调度逻辑知识盲区,可直接用于内核调度相关学术论文撰写、调度性能测试报告编写;从工程落地层面,熟练运用 Newidle 均衡开关控制、开销调优、任务亲和性搭配策略,能够快速解决生产服务器算力浪费、嵌入式设备负载分配不合理等实际线上问题。

后续开发者可基于本文源码逻辑,自行修改内核 newidle 均衡触发条件、开销计算规则、任务筛选策略,实现自研定制化空闲负载调度算法,深度适配自动驾驶、工业实时控制、云服务器资源超分等高端业务场景,真正做到吃透内核调度底层,掌控系统整机调度性能。

Logo

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

更多推荐