内核如何检测SOFT LOCKUP与HARD LOCKUP?

所谓lockup,是指某段内核代码占着CPU不放。Lockup严重的情况下会导致整个系统失去响应。Lockup有几个特点:

  • 首先只有内核代码才能引起lockup,因为用户代码是可以被抢占的,不可能形成lockup(只有一种情况例外,就是SCHED_FIFO优先级为99的实时进程即使在用户态也可能使[watchdog/x]内核线程抢不到CPU而形成soft lock
  • 其次内核代码必须处于禁止内核抢占的状态(preemption disabled),因为Linux是可抢占式的内核,只在某些特定的代码区才禁止抢占,在这些代码区才有可能形成lockup。

Lockup分为两种:soft lockup 和 hard lockup,它们的区别是 hard lockup 发生在CPU屏蔽中断的情况下。

  • Soft lockup是指CPU被内核代码占据,以至于无法执行其它进程。检测soft lockup的原理是给每个CPU分配一个定时执行的内核线程[watchdog/x],如果该线程在设定的期限内没有得到执行的话就意味着发生了soft lockup,[watchdog/x]是SCHED_FIFO实时进程,优先级为最高的99,拥有优先运行的特权。
  • Hard lockup比soft lockup更加严重,CPU不仅无法执行其它进程,而且不再响应中断。检测hard lockup的原理利用了PMU的NMI perf event,因为NMI中断是不可屏蔽的,在CPU不再响应中断的情况下仍然可以得到执行,它再去检查时钟中断的计数器hrtimer_interrupts是否在保持递增,如果停滞就意味着时钟中断未得到响应,也就是发生了hard lockup。

Linux kernel设计了一个检测lockup的机制,称为NMI Watchdog,是利用NMI中断实现的,用NMI是因为lockup有可能发生在中断被屏蔽的状态下,这时唯一能把CPU抢下来的方法就是通过NMI,因为NMI中断是不可屏蔽的。NMI Watchdog 中包含 soft lockup detector 和 hard lockup detector,2.6之后的内核的实现方法如下。

NMI Watchdog 的触发机制包括两部分:

  • 一个高精度计时器(hrtimer),对应的中断处理例程是kernel/watchdog.c: watchdog_timer_fn(),在该例程中:
    • 要递增计数器hrtimer_interrupts,这个计数器供hard lockup detector用于判断CPU是否响应中断;
    • 还要唤醒[watchdog/x]内核线程,该线程的任务是更新一个时间戳;
    • soft lock detector检查时间戳,如果超过soft lockup threshold一直未更新,说明[watchdog/x]未得到运行机会,意味着CPU被霸占,也就是发生了soft lockup。
  • 基于PMU的NMI perf event,当PMU的计数器溢出时会触发NMI中断,对应的中断处理例程是 kernel/watchdog.c: watchdog_overflow_callback(),hard lockup detector就在其中,它会检查上述hrtimer的中断次数(hrtimer_interrupts)是否在保持递增,如果停滞则表明hrtimer中断未得到响应,也就是发生了hard lockup。

hrtimer的周期是:softlockup_thresh/5。

注:

  • 在2.6内核中:
    softlockup_thresh的值等于内核参数kernel.watchdog_thresh,默认60秒;
  • 而到3.10内核中:
    内核参数kernel.watchdog_thresh名称未变,但含义变成了hard lockup threshold,默认10秒;
    soft lockup threshold则等于(2*kernel.watchdog_thresh),即默认20秒。

NMI perf event是基于PMU的,触发周期(hard lockup threshold)在2.6内核里是固定的60秒,不可手工调整;在3.10内核里可以手工调整,因为直接对应着内核参数kernel.watchdog_thresh,默认值10秒。

检测到 lockup 之后怎么办?可以自动panic,也可输出条信息就算完了,这是可以通过内核参数来定义的:

  • kernel.softlockup_panic: 决定了检测到soft lockup时是否自动panic,缺省值是0;
  • kernel.nmi_watchdog: 定义是否开启nmi watchdog、以及hard lockup是否导致panic,该内核参数的格式是”=[panic,][nopanic,][num]”.
    (注:最新的kernel引入了新的内核参数kernel.hardlockup_panic,可以通过检查是否存在 /proc/sys/kernel/hardlockup_panic来判断你的内核是否支持。)

REAL-TIME进程会导致系统LOCKUP吗?

Linux kernel支持两种实时(real-time)调度策略(scheduling policy):SCHED_FIFO和SCHED_RR,无论是哪一种,实时进程的优先级范围[0~99]都高于普通进程[100~139],始终优先于普通进程得到运行。如果实时进程是CPU消耗型的,会不会导致其它进程得不到运行机会,造成系统lockup呢?

这里指的调度是进程调度。
IO 调度器可以通过 cat /sys/block/vda/queue/scheduler 查看

这实际上是两个问题,不能混为一谈,第一个问题是会不会造成系统lockup,第二个问题是会不会导致其它进程得不到运行机会。我们一个一个分别来谈。

Linux 进程调度器

image.png

  • CFS完全公平调度: CFS的出发点基于一个简单的理念:即所有进程实际占用处理器CPU的时间应为一致,目的是确保每个进程公平的处理器使用比,即最大的利用了计算资源。
  • FIFO先入先出队列:不基于时间片调度,处于可运行状态的SCHED_FIFO级别的进程比SCHED_NORMAL有更高优先级得到调度,一旦SCHED_FIFO级别的进程处于可执行的状态,它就会一致运行,直到进程阻塞或者主动释放。
  • RR(Round-Robin):SCHED_RR级别的进程在耗尽事先分配的时间片之后就不会继续执行。即可以理解将RR调度理解为带有时间片的SCHED_FIFO。

FIFO和RR调度算法都为静态优先级。内核不为实时进程计算动态优先级,保证了优先级别高的实时进程总能抢找优先级比它低的进程。

image.png

Scheduling Class 的优先级顺序为 Stop_ask > Real_Time > Fair > Idle_Task,开发者可以根据己的设计需求,來把所属的Task配置到不同的Scheduling Class中。其中的 Real_time 和 Fair 是最最常用的

  • Fair 调度使用的是 CFS 的调度算法,即完全公平调度器
    • CFS直接分配的不是时间片,而是CPU使用比,这个比例会受到nice值得影响,然后你懂,nice值低比重就高,nice高比重就低
  • Real_Time 对于实时调度策略分为两种:SCHED_FIFO 和 SCHED_RR

Linux的进程调度并未使用直接均分时间片的方式,而是对优先级进行了改进,采用了两种不同的优先级范围

  • 一种是nice值,范围是-20到+19,越大的nice值意味着更低的优先级,低nice值的进程会获得更多的处理器时间(按比例获得)
  • 第二种范围是实时优先级,其值是可配置的,默认情况下它的变化范围是从0到99,与nice值意义相反,越高的实时优先级数值意味着进程优先级越高,任何实时进程的优先级都高于普通进程

设置 nice (对 CFS 有效)

我们可以通过NICE命令来对一个将要执行的bash命令进行NICE值设置,方法是:

nice -n 10 bash 

这样我就又打开了一个bash,并且其nice值设置为10,而默认情况下,进程的优先级应该是从父进程继承来的,这个值一般是0。

另外,使用renice命令可以对一个正在运行的进程进行nice值的调整

设置优先级 (静态优先级,高于 CFS 调度)

实时进程可以指定的优先级范围为1-99,将一个要执行的程序以实时方式执行的方法为:

# 设置
chrt 10 bash 
# 查看
chrt -p $$ 
pid 14840's current scheduling policy: SCHED_RR 
pid 14840's current scheduling priority: 10 

可以看到,新打开的bash已经是实时进程,默认调度策略为SCHED_RR,优先级为10。如果想修改调度策略,就加个参数:

chrt -f 10 bash 
chrt -p $$ 
pid 14843's current scheduling policy: SCHED_FIFO 
pid 14843's current scheduling priority: 10 

刚才说过,SCHED_RR和SCHED_FIFO都是实时调度策略,只能给实时进程设置。对于所有实时进程来说,优先级高的(就是priority数字小的)进程一定会保证先于优先级低的进程执行。

实时进程会不会造成系统lockup?

Lockup分为soft lockup和hard lockup,Hard lockup发生在CPU中断被屏蔽的情况下,因为实时进程本身并不会屏蔽CPU中断,hrtimer时钟中断是可以得到响应的,所以不会导致hard lockup。

Soft lockup发生在内核线程[watchdog/x]得不到运行的情况下,理论上如果实时进程占着CPU不放,确实有可能导致[watchdog/x]得不到运行而发生soft lockup,然而这个可能性并不大,因为[watchdog/x]本身也是实时进程,调度策略为SCHED_FIFO,优先级已经是最高的99:

$ ps -ef | grep watchdog
root         6     2  0 Feb24 ?        00:00:18 [watchdog/0]
root        10     2  0 Feb24 ?        00:00:16 [watchdog/1]
root        14     2  0 Feb24 ?        00:00:13 [watchdog/2]
root        18     2  0 Feb24 ?        00:00:12 [watchdog/3]
 
$ chrt -p 6
pid 6's current scheduling policy: SCHED_FIFO
pid 6's current scheduling priority: 99

如果占着CPU不放的实时进程也是SCHED_FIFO并且优先级为99,就有可能导致soft lockup。为什么呢?我们看一下实时进程的调度策略就明白了:

  • 在多个实时进程之间,优先级更高的会抢先运行
    (注:实时进程的优先级数字越大则优先级越高,99最高,0最低;而普通进程正好相反,优先级数字越大则优先级越低,139最低,100最高);
  • 优先级相同的实时进程之间,不会互相抢占,只能等对方主动释放CPU;
  • SCHED_FIFO调度策略的特点是,进程会一直保持运行直到发生以下情况之一:
    1. 进程主动调用sched_yield(2)放弃运行,自动排到运行队列的队尾,等到相同优先级的其它进程运行之后才有机会再运行;
    2. 进程进入睡眠状态(比如由于等待I/O的原因),唤醒后自动排到运行队列的队尾,等到相同优先级的其它进程运行之后才有机会再运行;
    3. 被优先级更高的实时进程抢占,这种情况下会自动排到运行队列的队首,下次运行的机会排在相同优先级的其它进程的前面。
  • SCHED_RR进程与SCHED_FIFO唯一不同的是,实时进程的运行时间是分为一段一段的,在相同优先级的进程之间轮流运行,每个进程运行完一个时间段之后,必须让给下一个进程(强调:仅对相同优先级而言,不同优先级的进程之间仍然会互相抢占)。

所以,如果占着CPU不放的实时进程的调度策略是SCHED_FIFO,并且优先级为与[watchdog/x]相同的99,SCHED_FIFO的调度策略决定了只要它不放手,[watchdog/x]就无法运行,结果是会导致soft lockup。

实时进程会不会导致其它进程得不到运行机会?

如果实时进程占着CPU不放,会不会导致其它进程得不到运行机会,包括管理员的shell也无法运行、连基本的管理任务也进行不了,最终造成整个系统失去控制?

通常不会。因为Linux kernel有一个RealTime Throttling机制,就是为了防止CPU消耗型的实时进程霸占所有的CPU资源而造成整个系统失去控制。它的原理很简单,就是保证无论如何普通进程都能得到一定比例(默认5%)的CPU时间,可以通过两个内核参数来控制:

  • /proc/sys/kernel/sched_rt_period_us:缺省值是1,000,000 μs (1秒),表示实时进程的运行粒度为1秒。(注:修改这个参数请谨慎,太大或太小都可能带来问题)。
  • /proc/sys/kernel/sched_rt_runtime_us:缺省值是 950,000 μs (0.95秒),表示在1秒的运行周期里所有的实时进程一起最多可以占用0.95秒的CPU时间。如果sched_rt_runtime_us=-1,表示取消限制,意味着实时进程可以占用100%的CPU时间(慎用,有可能使系统失去控制)。

所以,Linux kernel默认情况下保证了普通进程无论如何都可以得到5%的CPU时间,尽管系统可能会慢如蜗牛,但管理员仍然可以利用这5%的时间设法恢复系统,比如停掉失控的实时进程,或者给自己的shell进程赋予更高的实时优先级以便执行管理任务,等等。