一、进程调度器概述

Linux内核的进程调度器(Scheduler)是操作系统的核心组件之一,负责决定哪个进程获得CPU时间片、何时获得、获得多久。自Linux 2.6.23版本起,CFS(Completely Fair Scheduler,完全公平调度器)取代了之前的O(1)调度器,成为默认的普通进程调度算法。

调度器的核心挑战在于:如何在众多竞争CPU的进程中实现公平性,同时保证低延迟和高吞吐。CFS的创新之处在于它不使用传统的时间片概念,而是通过虚拟运行时间(vruntime)来追踪每个进程的CPU使用量,并总是选择vruntime最小的进程调度。

二、CFS完全公平调度算法原理

2.1 核心思想

CFS的核心目标是让所有可运行进程的vruntime尽可能相等。为了实现这一点,CFS为每个CPU维护一棵红黑树(rbtree),以vruntime为键值排序。调度时总是选择树最左侧(vruntime最小)的进程执行。

// 内核 CFS 核心结构 (简化)
struct cfs_rq {
    struct rb_root_cached tasks_timeline;  // 红黑树根
    struct rb_node *rb_leftmost;           // 缓存最左侧节点
    u64 min_vruntime;                      // 最小 vruntime
    unsigned long weight;                  // 队列总权重
};

struct sched_entity {
    u64              vruntime;    // 虚拟运行时间
    u64              exec_start;  // 开始执行时间
    u64              sum_exec_runtime; // 总实际运行时间
    int              weight;      // 调度权重(与nice值对应)
};

2.2 虚拟运行时间计算

vruntime的计算公式为:

vruntime += delta_exec * (NICE_0_LOAD / weight)

其中delta_exec是实际运行时间,weight由进程的nice值决定。优先级越高(nice值越小)的进程,vruntime增长越慢,因此获得更多的实际CPU时间。

nice值到权重的对应关系(部分):

Nice值权重CPU占比
-2088761~115%
-1011058~102%
01024基准100%
10110~15%
1915~1%

2.3 调度延迟与最小粒度

通过/proc/sys/kernel/sched_latency_ns和sched_min_granularity_ns两个参数控制。CFS保证每个可运行进程至少获得一次调度机会的时间不超过目标延迟,同时单次执行不少于最小粒度以避免过频繁的上下文切换。

# 默认值查看
sysctl kernel.sched_latency_ns             # 24,000,000 (24ms)
sysctl kernel.sched_min_granularity_ns     # 3,000,000 (3ms)
sysctl kernel.sched_wakeup_granularity_ns  # 4,000,000 (4ms)

三、Linux调度策略详解

3.1 实时调度策略

Linux 定义了三种实时调度策略:

  • SCHED_FIFO (1):先进先出实时调度。进入运行态后一直运行直到阻塞、退出或被更高优先级抢占
  • SCHED_RR (2):轮转实时调度。与SCHED_FIFO类似,但同优先级进程按时间片轮转
  • SCHED_DEADLINE (6):最后期限调度(Linux 3.14+)。基于EDF(最早截止时间优先),适用于周期性实时任务

3.2 普通调度策略

  • SCHED_OTHER/SCHED_NORMAL (0):默认策略,使用CFS
  • SCHED_BATCH (3):批处理策略,减少唤醒抢占,适合CPU密集型批处理任务
  • SCHED_IDLE (5):极低优先级,仅当系统空闲时才运行

四、核心调度API实战

4.1 设置调度策略和优先级

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>

int main() {
    struct sched_param param;
    int policy;
    
    // 获取当前进程的调度策略和优先级
    policy = sched_getscheduler(0);
    printf("Current policy: %d\n", policy);
    
    // 设置实时FIFO策略,优先级50
    param.sched_priority = 50;
    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler");
        return 1;
    }
    printf("Set to SCHED_FIFO with priority %d\n", param.sched_priority);
    return 0;
}

编译运行需要root权限:

gcc -o set_rt sched_rt.c
sudo ./set_rt

4.2 设置CPU亲和性

#include <sched.h>

void pin_to_cpu(int cpu_id) {
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(cpu_id, &cpuset);
    
    if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) == -1) {
        perror("sched_setaffinity");
    }
    printf("Pinned to CPU %d\n", cpu_id);
}

// 使用工具命令行
taskset -c 0,2 ./my_program     // 绑定到CPU 0和2
taskset -p -c 1,3 <pid>         // 修改运行中进程的亲和性

4.3 修改nice值和设置SCHED_DEADLINE

// 修改nice值(需要特权)
setpriority(PRIO_PROCESS, 0, -10);

// SCHED_DEADLINE 设置 (Linux 3.14+)
struct sched_attr attr = {
    .size = sizeof(attr),
    .sched_policy = SCHED_DEADLINE,
    .sched_runtime  = 10 * 1000 * 1000,  // 10ms
    .sched_deadline = 30 * 1000 * 1000,  // 30ms
    .sched_period   = 30 * 1000 * 1000   // 30ms
};
sched_setpid(0, &attr, 0);

五、进程优先级体系

Linux进程优先级从高到低排列:

  1. 实时优先级 (0-99):SCHED_FIFO/SCHED_RR/SCHED_DEADLINE使用,数值越大优先级越高
  2. 普通优先级 (100-139):对应nice值-20~19,SCHED_OTHER使用
  3. SCHED_IDLE (140):最低优先级
// 查看进程调度信息
chrt -p <pid>          // 查看实时优先级和策略
ps -eo pid,class,rtprio,pri,ni,comm | head

六、CFS组调度与带宽控制

6.1 CFS组调度 (Group Scheduling)

CFS支持将进程分组,在组间分配CPU资源。配合cgroups v1的cpu子系统使用:

# 创建cgroup
mkdir /sys/fs/cgroup/cpu/group1
echo 512 > /sys/fs/cgroup/cpu/group1/cpu.shares     // 相对份额(默认1024)

// 添加进程到组
echo <pid> > /sys/fs/cgroup/cpu/group1/cgroup.procs

6.2 CFS带宽控制 (CPU bandwidth)

通过cfs_quota_us和cfs_period_us限制组内进程的CPU使用上限:

# 限制组内进程每100ms最多使用50ms CPU时间(即最多50% CPU)
echo 100000 > /sys/fs/cgroup/cpu/group1/cpu.cfs_period_us
echo 50000  > /sys/fs/cgroup/cpu/group1/cpu.cfs_quota_us

6.3 cgroups v2 CPU控制

# cgroups v2
echo "50000 100000" > /sys/fs/cgroup/foo/cpu.max    // 50% CPU上限
echo "max 100000"    > /sys/fs/cgroup/foo/cpu.max    // 无限制

七、NUMA感知调度

在NUMA(非统一内存访问)系统中,进程访问本地节点的内存延迟远低于远程节点。Linux调度器提供以下NUMA优化:

  • 自动NUMA平衡:内核自动将进程迁移到靠近其内存的CPU节点
  • numactl工具:手动控制NUMA策略
  • sched_setaffinity:手动绑定CPU
# 查看NUMA拓扑
numactl --hardware

# 在节点0上运行程序,使用节点0内存
numactl --cpunodebind=0 --membind=0 ./program

# 禁止自动NUMA balancing
echo 0 > /proc/sys/kernel/numa_balancing

八、调度器性能调优实践

8.1 低延迟场景优化

# 减少调度延迟(适用于低延迟场景)
sysctl -w kernel.sched_latency_ns=6000000
sysctl -w kernel.sched_min_granularity_ns=750000
sysctl -w kernel.sched_wakeup_granularity_ns=1000000

# 关闭NUMA balancing(固定部署场景)
sysctl -w kernel.numa_balancing=0

8.2 CPU隔离与实时场景

# 内核启动参数隔离CPU(将CPU 2-7隔离)
isolcpus=2,3,4,5,6,7 nohz_full=2,3,4,5,6,7 rcu_nocbs=2,3,4,5,6,7

# 将实时任务绑定到隔离CPU
taskset -c 4 chrt -f 99 ./realtime_task

8.3 容器场景调度优化

# Docker CPU限制(对应cfs quota)
docker run --cpus=2.0 --cpu-shares=512 --cpuset-cpus=0-3 app

# Kubernetes resources 配置
resources:
  requests:
    cpu: "500m"        // 0.5 CPU guarantee
  limits:
    cpu: "2000m"       // 2 CPU quota limit

九、调度器性能分析工具

工具用途
perf sched调度事件追踪与分析
trace-cmdfunction tracer追踪调度函数
runqlatBPF工具,显示调度队列延迟
runqlenBPF工具,显示运行队列长度
top/htop实时查看进程CPU使用
chrt查看/设置实时调度策略和优先级
taskset设置CPU亲和性
tuned系统级性能调优配置文件
# perf sched 记录并分析调度延迟
perf sched record -- sleep 10
perf sched latency    // 显示每个进程的调度延迟分布
perf sched map        // 显示CPU上的调度事件图

# BPF/BCC runqlat
runqlat 1 10     // 每秒采样,共10秒

十、总结

Linux内核的进程调度器经过二十多年的演进,已经从简单的轮转调度发展到今天高度优化的CFS加多策略并行体系。深入理解调度器的工作原理,对于系统调优、性能瓶颈分析、实时系统设计都至关重要。

关键要点回顾:

  • CFS通过vruntime和红黑树实现O(log N)复杂度的公平调度
  • 实时策略(SCHED_FIFO/RR/DEADLINE)优先级始终高于普通策略
  • CPU亲和性加NUMA拓扑感知是高性能计算的关键优化手段
  • CFS带宽控制(cgroups)是容器隔离CPU资源的核心机制
  • 实时场景需要内核参数隔离配合启动参数配置
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部