找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1615

积分

1

好友

227

主题
发表于 3 天前 | 查看: 8| 回复: 0

想象一家名为“Linux餐厅”的自助餐厅,它有独特的运营规则:

传统餐厅(时间片轮转调度)

  • 每人固定用餐时间10分钟
  • 时间到必须离开,无论吃饱与否
  • 老板和实习生待遇一样

Linux餐厅(CFS调度)

  • 每人有个“饥饿度”评分
  • 饥饿度 = 实际用餐时间 × 标准权重 / 个人权重
  • 厨师总是优先服务饥饿度最低的顾客
  • 老板权重高,吃10分钟可能只算5分钟饥饿度
  • 实习生权重低,吃10分钟可能算20分钟饥饿度

CFS(Completely Fair Scheduler)作为Linux内核的默认进程调度器,通过精妙的算法,致力于在单处理器上为所有进程模拟出“完全公平”的运行体验。理解其工作原理是深入Linux系统调度的核心。

公平性的数学定义:什么才算“完全公平”?

CFS要解决的终极问题是:如何在单CPU上模拟出所有进程同时运行的公平感?

// 理想中的完全公平:假设有n个进程,在T时间内,每个进程应该获得T/n的CPU时间
// 但在现实中,CPU一次只能运行一个进程
// CFS的解决方案:引入虚拟时间(vruntime)
vruntime_i = (实际运行时间_i × NICE_0_LOAD) / weight_i
其中:
- NICE_0_LOAD = 1024(基准权重)
- weight_i 是进程i的权重,由nice值决定

权重函数:nice值的指数魔法

nice值(-20到19)到权重的映射不是线性的,而是近似指数关系:

// 内核中的实际转换表(kernel/sched/core.c)
static const int prio_to_weight[40] = {
    /* -20 */ 88761, 71755, 56483, 46273, 36291,  // 高优先级
    /* -15 */ 29154, 23254, 18705, 14949, 11916,
    /* -10 */  9548,  7620,  6100,  4904,  3906,
    /*  -5 */  3121,  2501,  1991,  1586,  1277,
    /*   0 */  1024,   820,   655,   526,   423,  // nice=0的基准权重
    /*   5 */   335,   272,   215,   172,   137,
    /*  10 */   110,    87,    70,    56,    45,  // 低优先级
    /*  15 */    36,    29,    23,    18,    15,
};
// 数学规律:每增加一个nice值,权重减少约10%
// 这意味着nice=0的进程比nice=10的进程多获得约10倍CPU时间

运维意义:调整进程的nice值,实际上是在调整其权重的指数级别renice -n 5不是让进程慢一点,而是让它慢很多。

虚拟时间的物理意义

vruntime不是真实时间,而是经过优先级加权后的“公平时间”:

// 假设两个进程A和B
进程A: nice=-10, weight=88761
进程B: nice=10, weight=110

// 都实际运行1毫秒后
vruntime_A = 1ms × 1024 / 88761 ≈ 0.0115ms
vruntime_B = 1ms × 1024 / 110 ≈ 9.309ms

// 9.309 / 0.0115 ≈ 809倍差异!
// 这意味着进程B的饥饿度增长比进程A快809倍

关键洞察:CFS通过vruntime将所有进程映射到同一个“公平维度”,在这个维度上比较谁“更饥饿”。

从简单链表到红黑树的演进

早期调度器使用简单的运行队列链表,查找下一个进程需要O(n)时间。CFS需要频繁找到vruntime最小的进程,这引出了红黑树的选择。

// CFS运行队列的核心数据结构
struct cfs_rq {
    struct load_weight load;           // 总负载权重
    unsigned int nr_running;           // 可运行进程数
    u64 min_vruntime;                  // 最小vruntime(基准)

    // 红黑树根节点,按vruntime排序
    struct rb_root_cached tasks_timeline;

    // 缓存:下一次调度时最可能运行的进程
    struct sched_entity *next;
    struct sched_entity *last;
    struct sched_entity *skip;
};

// 红黑树节点的嵌入
struct sched_entity {
    struct load_weight load;          // 进程权重
    struct rb_node run_node;          // 红黑树节点
    u64 vruntime;                     // 虚拟运行时间
    u64 exec_start;                   // 本次调度开始时间
    u64 sum_exec_runtime;            // 总运行时间
    // ... 其他字段
};

红黑树的性能优势

为什么选择红黑树而不是其他数据结构?

# 性能比较(n=可运行进程数):
链表:查找O(n),插入O(1),删除O(n)
二叉堆:查找O(1),插入O(log n),删除O(log n)
红黑树:查找O(log n),插入O(log n),删除O(log n)

# 对于调度器,需要频繁:
1. 查找vruntime最小的进程(最左节点)-> 红黑树O(log n)
2. 插入刚被抢占的进程 -> 红黑树O(log n)
3. 删除结束的进程 -> 红黑树O(log n)

# 红黑树是平衡的,最坏情况仍为O(log n)
# 而普通二叉搜索树可能退化为链表O(n)

调度周期的动态计算

CFS不是固定时间片,而是根据进程数动态调整:

// 计算调度周期(kernel/sched/fair.c)
static u64 __sched_period(unsigned long nr_running)
{
    // 默认调度周期:6毫秒
    u64 period = sysctl_sched_latency;

    // 如果进程太多,按最小粒度计算
    // 保证每个进程至少获得最小粒度的时间
    if (unlikely(nr_running > sched_nr_latency)) {
        period = (u64)nr_running * sysctl_sched_min_granularity;
    }

    return period;
}

// 计算每个进程的理想时间片
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    u64 slice = __sched_period(cfs_rq->nr_running);

    // 按权重比例分配
    slice *= se->load.weight;
    slice = div64_ul(slice, cfs_rq->load.weight);

    return slice;
}

实际规则

  • 进程数 ≤ 8:调度周期=6ms,每个进程至少 6ms/n
  • 进程数 > 8:调度周期=进程数×0.75ms,每个进程至少0.75ms

睡眠补偿:交互式进程的优待

CFS对睡眠进程给予vruntime补偿,提高交互式体验:

static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
    u64 vruntime = cfs_rq->min_vruntime;

    if (initial) {
        // 新进程:给予半个调度周期的优惠
        vruntime += sched_vslice(cfs_rq, se) / 2;
    } else {
        // 唤醒的进程:根据睡眠时间给予补偿
        u64 sleep_time = rq_clock_task(rq_of(cfs_rq)) - se->exec_start;

        if (sleep_time) {
            // 将睡眠时间转换为vruntime的减少
            vruntime -= calc_delta_fair(sleep_time, se);

            // 限制最大补偿,防止过度抢占
            vruntime = max_vruntime(vruntime, cfs_rq->min_vruntime);
        }
    }

    se->vruntime = max_vruntime(se->vruntime, vruntime);
}

数学原理

  • 交互式进程频繁睡眠(等待I/O)
  • 睡眠期间vruntime不增加
  • 醒来时vruntime相对较小,更容易被调度
  • 这解释了为什么交互式应用(编辑器、浏览器)响应迅速

监控调度性能

#!/bin/bash
# 监控CFS调度性能的实用命令
echo "=== CFS调度性能监控 ==="

# 1. 查看运行队列统计
echo "1. 运行队列统计:"
echo -n "  可运行进程数: "
cat /proc/sched_debug | grep "\.nr_running" | head -1 | awk '{print $3}'
echo -n "  调度器调用次数: "
cat /proc/sched_debug | grep "\.sched_count" | head -1 | awk '{print $3}'

# 2. 查看负载均衡统计
echo "2. 负载均衡统计:"
for cpu in $(seq 0 $(($(nproc)-1))); do
    echo -n "  CPU$cpu 迁移次数: "
    cat /proc/schedstat | grep "cpu$cpu" | awk '{print $8}'
done

# 4. 查看上下文切换率
echo "4. 上下文切换率:"
vmstat 1 2 | tail -1 | awk '{printf "  %d次/秒\n", $12}'

调整CFS参数优化性能

根据不同负载类型调整CFS参数,这是运维/DevOps实践中常见的性能调优手段:

# I/O密集型应用(如Nginx、数据库)
# 目标:低延迟,频繁调度
echo "优化I/O密集型负载:"
echo 3000000 > /proc/sys/kernel/sched_latency_ns      # 调度周期3ms
echo 500000 > /proc/sys/kernel/sched_min_granularity_ns   # 最小粒度0.5ms
echo 500000 > /proc/sys/kernel/sched_wakeup_granularity_ns # 唤醒抢占粒度0.5ms

# CPU密集型应用(如编译、科学计算)
# 目标:高吞吐,减少上下文切换
echo "优化CPU密集型负载:"
echo 20000000 > /proc/sys/kernel/sched_latency_ns     # 调度周期20ms
echo 4000000 > /proc/sys/kernel/sched_min_granularity_ns  # 最小粒度4ms

使用cgroups进行精细控制

# 创建cgroup限制CPU使用
mkdir -p /sys/fs/cgroup/cpu/myservice

# 限制为单核的30%(每100ms周期内可使用30ms)
echo 30000 > /sys/fs/cgroup/cpu/myservice/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/myservice/cpu.cfs_period_us

# 设置权重(类似nice值,但范围1-10000)
echo 500 > /sys/fs/cgroup/cpu/myservice/cpu.shares  # 默认1024

# 将进程加入cgroup
echo $(pidof myservice) > /sys/fs/cgroup/cpu/myservice/cgroup.procs

cgroups提供了比传统nice值更强大的资源隔离与控制能力,是现代云原生基础设施中容器资源管理的基石。

CFS的设计哲学总结

  1. 比例公平而非绝对平等:CFS不追求每个进程获得相同时间,而是按权重比例分配。高权重进程获得更多时间,但vruntime增长慢。
  2. 虚拟时间作为统一维度:通过vruntime将所有进程映射到同一维度比较,简化了调度决策。
  3. 红黑树实现高效调度:O(log n)复杂度确保即使数千进程也能快速调度。
  4. 动态参数适应不同负载:调度周期、最小粒度等参数根据进程数动态调整。
  5. 补偿机制优化交互体验:睡眠补偿使交互式进程获得更好响应性。

运维启示:理解CFS原理有助于诊断调度问题。当应用响应慢时,不要只看CPU使用率,还要关注运行队列长度、上下文切换率和调度延迟。




上一篇:架构师绩效被打C:技术团队管理中日报制度的反思与实战策略
下一篇:API权限漏洞实战:缺失IDOR检查导致用户数据泄露风险
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 19:15 , Processed in 0.277501 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表