温暖的电波  

一 问题背景

    在CPU数量众多、多层级且cgroup数量也众多的环境中,偶发CFS带宽时钟中断处理过程中出现hardlockup。

[exception RIP: tg_unthrottle_up+25]
RIP: ffffffff810c9658 RSP: ffff882f7fc83dc8 RFLAGS: 00000046
RAX: ffff885d4767d800 RBX: ffff885f7e4d6c40 RCX: ffff8830767f2930
RDX: 000000000000005b RSI: ffff885f7e4d6c40 RDI: ffff8830767f2800
RBP: ffff882f7fc83dc8 R8: ffff885f697c7900 R9: 0000000000000001
R10: 0000000000000000 R11: 0000000000000000 R12: ffff8830764e5400
R13: ffffffff810c9640 R14: 0000000000000000 R15: ffff8830767f2800
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
--- <NMI exception stack> ---
#12 [ffff882f7fc83dc8] tg_unthrottle_up at ffffffff810c9658
#13 [ffff882f7fc83dd0] walk_tg_tree_from at ffffffff810c17db
#14 [ffff882f7fc83e20] unthrottle_cfs_rq at ffffffff810d1675
#15 [ffff882f7fc83e58] distribute_cfs_runtime at ffffffff810d18e2
#16 [ffff882f7fc83ea0] sched_cfs_period_timer at ffffffff810d1a7f
#17 [ffff882f7fc83ed8] __hrtimer_run_queues at ffffffff810b4d72
#18 [ffff882f7fc83f30] hrtimer_interrupt at ffffffff810b5310
#19 [ffff882f7fc83f80] local_apic_timer_interrupt at ffffffff81050fd7
#20 [ffff882f7fc83f98] smp_apic_timer_interrupt at ffffffff8169978f

2 原因分析

2.1 CFS带宽的原理

    CFS带宽功能提供了cgroup级别的CPU资源限制功能;这个功能的核心原理就是为一个CPU cgroup提供两个要素:period和quota,即在period周期内这个CPU cgroup能够使用的CPU资源。为此内核为各个CPU cgroup安装了一个高精度定时器,其周期为period;当这个定时器period到期后,就会去检查这个CPU cgroup的消耗的时间。

    由于一个CPU cgroup内有多个cfs_rq队列,CPU cgroup的带宽quota也由这些cfs_rq所共享;同时这些cfs_rq就绪队列上的任务对CPU时间的需求量不一致,为了保证各个cfs_rq上的CPU时间分配的相对合理(即不浪费,又不太频繁的申请),各个CPU上的cfs_rq每次只从带宽的quota中申请“一小片”时间片。这个“时间片”的大小可以通过/proc/sys/kernel/sched_cfs_bandwidth_slice_us(默认值为5ms)接口来调整。

    当CFS带宽的周期时钟到期后,在时钟中断里面会检查该CPU cgroup中的所有时间片用完的cfs_rq,(这些cfs_rq的时间片用完与否的检查是在其他点做的。比如调度实体入队时enqueue_entity(),如果超过了时间片就会通过cfs_rq->throttled_list把cfs_rq挂到cfs_b->throttled_cfs_rq链表)。如果这个周期内CPU cgroup还有quota剩余,则从剩余的quota中为各个cfs_rq补充时间片。补充完时间片后还会遍历cfs_rq及其所有的parent将它们从throttled状态改变为unthrottled,这个过程就叫做unthrottle_cfs_rq(cfs_rq)

2.2 hardlockup原因分析

    考虑一种情况在一个CPU核数很多的机器上创建有多层级、大量的cgroup;如果在某个周期这些cgroup中各个CPU上cfs_rq队列的时间片都用完被挂到了cfs_b->throttled_cfs_rq链表,这时候就需要去遍历、并去unthrottle_cfs_rq(cfs_rq),这个时间维度就是nr_cpus*nr_cgroups级别;假设nr_cpus==256,nr_cgroups=1000,这里将会有25W次的轮巡,同时这个轮巡过程是在时钟中断上下文,处于关中断状态,一次轮巡平均10us就会导致高达10秒的延迟,进而触发hardlockup。 

3 修复说明

    这个问题在Linux社区也有提出,社区对该问题也进行了修复,将原来一个CPU上的unthrottle_cfs_rq()改为异步方式来实现。

    其思路就是将原来一个CPU上执行的nr_cpus*nr_cgroups次的unthrottle_cfs_rq()改造为只对this_cpu的cfs_rq进行直接unthrottle_cfs_rq();而对于其他CPU的cfs_rq,将他们挂到一个对应CPU的rq->cfsb_csd_list链表,然后通过smp_call_function_single_async()的IPI方式来通知到其他CPU去做unthrottle_cfs_rq();这样原本一个CPU上nr_cpus*nr_cgroups次的unthrottle_cfs_rq()就变成了1*nr_cgroups,减少了CPU数量太多的影响,时间复杂度变得可控。

    具体修复方案:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v6.7-rc5&id=8ad075c2eb1f6b4b33436144ea1ef2619f3b6398

posted on 2024-05-08 07:43  温暖的电波  阅读(122)  评论(0编辑  收藏  举报