hard lockup与soft locup的区别
hard lockup与soft locup的区别
- [linux][异常检测] hung task, soft lockup, hard lockup, workqueue stall
- Softlockup与hardlockup检测机制(又名:nmi_watchdog)
hard lockup与soft lockup的区别
在计算机系统中,尤其是Linux内核中,"hard lockup"和"soft lockup"是指两种不同的系统异常状态,通常与CPU的执行状态和系统响应性有关。以下是它们的区别:
1. 定义
- Soft Lockup(软锁死):指的是某个CPU核心长时间被某个任务占用(例如运行一个死循环或高优先级任务),导致内核无法调度其他任务或响应中断,但该CPU仍然能够执行代码。这种情况通常是由于内核中的代码逻辑问题或调度问题导致。
- Hard Lockup(硬锁死):指的是某个CPU核心完全停止响应,甚至无法执行任何代码或响应硬件中断。这种情况通常是由硬件问题(如CPU故障)、严重的内核错误(如死锁或无限循环且禁用了中断)或外部干扰引起的。
2. 检测机制
- Soft Lockup:Linux内核通过"watchdog"机制检测。当一个CPU核心在一定时间(如默认20秒)内未执行调度(即未切换任务)时,内核会触发soft lockup警告。这种情况依赖软件层面的检测。
- Hard Lockup:Hard lockup通常由硬件支持的NMI(非屏蔽中断,Non-Maskable Interrupt)检测。如果CPU没有响应NMI定期发送的"心跳"信号,系统会认为发生了hard lockup。需要注意的是,hard lockup检测依赖硬件支持(如APIC或特定的watchdog硬件),如果没有启用相关功能,可能无法检测到。
3. 根本原因
- Soft Lockup:常见于软件问题,比如内核中的无限循环、过长的临界区(critical section)未释放、或者用户态程序通过高优先级抢占CPU。
- Hard Lockup:更多与硬件相关,比如CPU过热、内存错误、驱动程序导致的中断风暴,或者内核bug导致的中断被意外禁用。
4. 严重程度
- Soft Lockup:相对较轻,系统可能仍然部分可用,其他CPU核心可能还能正常工作。可以通过重启相关进程或调整代码解决。
- Hard Lockup:更严重,通常意味着系统某个核心完全不可用,可能导致整个系统崩溃(panic),需要重启系统甚至检查硬件。
5. 日志示例
- Soft Lockup日志:
1
watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [process_name:1234]
- Hard Lockup日志:
1
NMI watchdog: Watchdog detected hard LOCKUP on cpu 0
6. 解决方法
- Soft
Lockup:检查内核日志(dmesg),定位问题代码或进程,优化调度策略,或者调整watchdog阈值(
kernel.watchdog_thresh
)。 - Hard
Lockup:检查硬件状态(如温度、电源)、更新内核或驱动程序,确保NMI
watchdog功能正常开启(通过内核参数
nmi_watchdog=1
)。
总结
- Soft Lockup:软件层面,CPU被占用但仍在运行,系统可能还能恢复。
- Hard Lockup:硬件或严重内核问题,CPU完全无响应,通常需要更深入的排查。
触发 soft lockup 和 hard lockup 的示例代码
以下是分别可能触发 soft lockup 和 hard lockup 的示例代码。这些代码仅用于演示和学习目的,请勿在生产环境中运行,因为它们可能导致系统不稳定或崩溃。运行前请确保在测试环境(如虚拟机)中操作,并有权限访问内核相关功能。
1. Soft Lockup 示例代码
Soft lockup 通常由内核模块中的长时间循环或未释放CPU导致。以下是一个简单的Linux内核模块示例:
1 |
|
编译和运行
- 保存为
soft_lockup.c
。 - 创建一个
Makefile
:1
2
3
4
5obj-m += soft_lockup.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean - 编译:
make
- 加载模块:
sudo insmod soft_lockup.ko
- 检查日志:
dmesg | tail -n 20
预期结果
大约20秒后(取决于 kernel.watchdog_thresh
设置,默认10-20秒),内核会检测到soft lockup,并输出类似以下信息:
1
watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [insmod:1234]
清理
卸载模块:sudo rmmod soft_lockup
2. Hard Lockup 示例代码
Hard lockup 需要禁用中断并让CPU完全停止响应。以下是一个内核模块示例,禁用本地中断并进入死循环:
1 |
|
编译和运行
与soft lockup示例相同: 1. 保存为 hard_lockup.c
。 2.
使用相同的 Makefile
。 3. 编译:make
4.
加载模块:sudo insmod hard_lockup.ko
5.
检查日志:dmesg
预期结果
如果系统启用了NMI watchdog(通过内核参数 nmi_watchdog=1
或硬件支持),加载模块后,CPU会停止响应,最终触发hard lockup警告,例如:
1
NMI watchdog: Watchdog detected hard LOCKUP on cpu 0
注意事项
- 运行此代码可能导致系统崩溃,尤其是在单核系统中。
- 确保硬件和内核支持NMI(如通过
cat /proc/interrupts
检查NMI计数)。 - 清理方法:如果系统未崩溃,可尝试
sudo rmmod hard_lockup
;否则需要重启。
关键差异
- Soft Lockup 示例:没有禁用中断,CPU仍在执行代码,只是未调度其他任务。
- Hard Lockup 示例:禁用了中断,CPU完全停止响应,连内核都无法干预。
测试建议
- 在虚拟机(如QEMU或VirtualBox)中运行,设置单核CPU以更容易触发。
- 调整内核参数:
echo 10 > /proc/sys/kernel/watchdog_thresh
(缩短检测时间)。 - 检查NMI支持:
cat /proc/sys/kernel/nmi_watchdog
(1表示启用)。
Soft Lockup 示例代码中又没关闭cpu抢占,也没关中断,为啥会触发soft lockup?
你提出了一个非常好的问题!关于为什么我给出的 soft lockup 示例代码 既没有关闭CPU抢占(preemption),也没有禁用中断(interrupts),却仍然能触发soft lockup,让我详细解释一下。
示例代码回顾
1 |
|
Soft Lockup 的触发条件
在Linux内核中,soft lockup 并不是因为CPU完全无法响应(那是hard
lockup),而是由于某个CPU核心在一段时间内(默认由
kernel.watchdog_thresh
定义,通常是10-20秒)未能执行任务调度(schedule)。具体来说,soft lockup
的检测依赖于内核的 watchdog
机制,它通过一个高优先级的内核线程(watchdog/N)周期性地检查每个CPU是否“活跃”。活跃的标志是CPU是否在规定时间内调用了调度器(scheduler)。
触发soft lockup的关键条件是: 1. CPU长时间未让出控制权:某个任务(内核态或用户态)持续占用CPU,导致调度器无法运行。 2. 未禁用中断:soft lockup不要求中断被禁用,因为它关注的是调度,而不是硬件响应。
为什么这个代码会触发soft lockup?
尽管代码没有显式关闭抢占(preemption)或中断,以下是触发的原因:
1. 内核态执行的无限循环
- 这个代码运行在内核模块的
init
函数中,也就是内核态(kernel space)。在内核态运行的代码默认是不可抢占的(除非显式启用了内核抢占并在适当位置调用了preempt_enable()
或schedule()
)。 - 在默认的Linux内核配置中(非完全抢占式内核,
CONFIG_PREEMPT=n
或CONFIG_PREEMPT_VOLUNTARY=y
),内核态代码运行时不会被其他任务抢占,除非它主动调用schedule()
或其他让出CPU的函数。 - 示例中的
while (1)
循环没有调用任何调度相关的函数(如schedule()
或cond_resched()
),因此它会一直占用CPU。
2. mdelay(1000)
的行为
mdelay(1000)
是一个忙等待(busy-waiting)函数,它通过循环等待指定的毫秒数(这里是1秒)来实现延迟。它本质上是一个死循环,依赖硬件时钟计数,但不会主动让出CPU。- 在这个循环中,
mdelay()
不会触发任务切换或调用调度器,因此CPU仍然被当前任务(模块初始化代码)完全占用。 - 虽然
mdelay()
本身不禁用中断,但它不会让内核有机会运行调度器,因此watchdog线程无法被调度到该CPU上。
3. Watchdog检测机制
- 每个CPU上运行的watchdog线程会定期检查该CPU是否在
kernel.watchdog_thresh
时间内更新了“心跳”(通常通过touch_softlockup_watchdog()
或调度器调用实现)。 - 在这个代码中,CPU被无限循环和
mdelay()
占用,没有机会更新心跳。超过阈值(默认20秒)后,watchdog检测到该CPU“卡住”,触发soft lockup警告。
4. 未关闭中断的影响
- 中断没有被禁用(
local_irq_disable()
),所以硬件中断仍然可以发生。然而,soft lockup的检测不依赖中断是否开启,而是关注调度器是否运行。即使中断发生,它们不会自动触发任务切换(除非中断处理程序显式调用调度器,但这不是默认行为)。 - 因此,未关闭中断并不妨碍soft lockup的触发。
为什么不需要显式关闭抢占?
- 如果内核启用了完全抢占(
CONFIG_PREEMPT=y
),内核态代码可能被更高优先级的任务抢占,这种情况下这个代码可能不会触发soft lockup(因为调度器有机会运行)。 - 但在默认或部分抢占配置下(
CONFIG_PREEMPT=n
或CONFIG_PREEMPT_VOLUNTARY=y
),内核态代码需要主动让出CPU(例如通过cond_resched()
)。我的示例假设的是这种常见配置,因此不需要显式关闭抢占。
修改验证
如果想验证抢占和中断的影响,可以试试以下改动: 1.
添加主动调度: 1
2
3
4
5while (1) {
printk(KERN_INFO "Running in loop...\n");
mdelay(1000);
cond_resched(); /* 主动让出CPU */
}
- 用户态对比:
在用户态写一个类似的死循环(如下),通常不会触发soft
lockup,因为用户态进程会被调度器抢占:
1
2
3
4
5
6
7
8#include <stdio.h>
int main() {
while (1) {
printf("Running...\n");
sleep(1); /* 或usleep(1000000) */
}
return 0;
}
总结
这个代码触发soft lockup的原因是: - 它在内核态运行,默认不可抢占。 -
无限循环和 mdelay()
导致CPU长时间未调用调度器。 -
不需要关闭中断或抢占,因为soft lockup关注的是调度,而不是硬件响应。
为啥用户态进程会被调度器抢占?
用户态进程会被调度器抢占的原因,根源于操作系统设计中对任务管理的核心机制——多任务调度(multitasking)和抢占式调度(preemptive scheduling)。在Linux这样的现代操作系统中,用户态进程的抢占是由内核通过调度器实现的。以下是详细的解释:
1. 用户态与内核态的本质区别
- 用户态(User Space):运行用户程序(如应用程序)的环境,受限于较低的特权级(例如x86架构中的Ring 3)。用户态代码不能直接控制硬件或调度,必须依赖内核提供的系统调用。
- 内核态(Kernel Space):操作系统内核运行的环境,具有最高特权级(Ring 0),可以直接操作硬件、管理调度和中断。
用户态进程不像内核态代码那样可以“自我控制”执行流程,它们的运行完全受内核调度器的支配。
2. 抢占式调度的基本原理
Linux内核采用的是抢占式调度,这意味着调度器可以在特定时机(如时间片用尽或更高优先级任务出现)强制中断当前运行的进程,将CPU控制权交给另一个进程。用户态进程的抢占依赖以下机制:
a. 时间片(Time Slice)
- 每个用户态进程在运行时会被分配一个时间片(由调度器根据优先级和策略决定,通常是毫秒级,例如10ms)。
- 时间片的长度由内核的调度算法(如CFS,Completely Fair Scheduler)动态调整。
- 时间片用尽后,硬件时钟(timer)会触发一个中断,内核接管控制权,调用调度器(
schedule()
)选择下一个任务。
b. 时钟中断(Timer Interrupt)
- 内核通过硬件定时器(如PIT、HPET或TSC)定期产生时钟中断(频率由
HZ
参数定义,例如100Hz或1000Hz)。 - 每次时钟中断发生时,内核会检查当前进程的时间片是否用尽。如果用尽,调度器会切换到其他就绪进程。
- 即使进程在忙等待(如死循环),时钟中断仍然会发生,强制抢占。
c. 优先级和就绪队列
- 调度器维护一个就绪队列(run queue),跟踪所有可运行的任务。
- 如果一个更高优先级的任务进入就绪状态(例如I/O完成或新进程创建),调度器会在下一次调度点抢占当前进程。
3. 用户态进程无法阻止抢占
用户态进程无法阻止调度器抢占的原因如下:
a. 无权控制中断
- 用户态代码无法禁用硬件中断(
cli
或local_irq_disable()
是内核态指令,用户态无权限执行)。 - 即使进程在死循环中,定时器中断依然会触发,内核会接管CPU。
b. 无权控制调度器
- 用户态进程不能直接调用
schedule()
或修改调度策略,只能通过系统调用(如nice()
、sched_setscheduler()
)间接影响优先级,但最终控制权仍在内核。 - 内核在中断处理或系统调用返回时会检查是否需要调度(
need_resched
标志),用户态无法阻止这一检查。
c. 系统调用和异常
- 当用户态进程执行系统调用(如
read()
、write()
)或触发异常(如页面错误),控制权会转移到内核。此时,内核可能决定调度其他进程。
4. 用户态死循环的例子
以下是一个用户态死循环: 1
2
3
4
5
6
7#include <stdio.h>
int main() {
while (1) {
printf("Running...\n");
}
return 0;
}HZ
和时间片),时钟中断会触发,内核检查时间片并调用调度器。
- 调度器发现其他进程(如bash、系统服务)需要运行,就会抢占当前进程。 -
你可以通过 top
或 ps
看到这个进程占用CPU很高(接近100%),但系统仍然响应,因为其他进程也能获得CPU时间。
- 对比内核态:
- 如果在内核态运行类似的死循环(如同之前的soft
lockup示例),除非主动让出(如
cond_resched()
),调度器无法介入,因为内核态默认不可抢占(视CONFIG_PREEMPT
配置而定)。
- 如果在内核态运行类似的死循环(如同之前的soft
lockup示例),除非主动让出(如
5. 抢占的实现细节
在Linux中,用户态抢占的实现依赖以下关键点: -
中断驱动:时钟中断是抢占的“触发器”,由硬件保证,无法被用户态绕过。
- CONFIG_PREEMPT
配置: -
CONFIG_PREEMPT=n
:仅在用户态强制抢占,内核态依赖主动让出。
-
CONFIG_PREEMPT_VOLUNTARY=y
:增加了一些抢占点,但用户态依然强制抢占。
-
CONFIG_PREEMPT=y
:完全抢占,连内核态也能被抢占(但用户态抢占始终存在)。
- TIF_NEED_RESCHED
标志:内核在中断或系统调用时设置此标志,通知调度器在返回用户态前切换任务。
6. 验证实验
你可以试试以下方法验证用户态抢占: 1. 运行上述死循环程序。 2.
打开另一个终端,运行 top
,观察CPU使用率和系统响应性。 3. 用
sched_setscheduler()
设置高优先级(需要root权限):
1
2
3
4
5
6
7
8
9
10#include <sched.h>
#include <stdio.h>
int main() {
struct sched_param param = { .sched_priority = 99 };
sched_setscheduler(0, SCHED_FIFO, ¶m); // 实时调度
while (1) {
printf("Running...\n");
}
return 0;
}SCHED_FIFO
),系统仍能响应,因为内核保留了对系统任务(如watchdog、kworker)的调度能力。
总结
用户态进程会被调度器抢占,因为: 1. 内核掌控全局:调度器运行在内核态,通过时钟中断强制介入。 2. 用户态无特权:无法禁用中断或阻止调度。 3. 时间片机制:确保CPU公平分配给所有任务。
相比之下,内核态代码(如soft lockup示例)在默认配置下不可抢占,除非主动让出CPU。这就是用户态和内核态在调度行为上的根本差异。
在默认的Linux内核配置中(非完全抢占式内核,CONFIG_PREEMPT=n 或 CONFIG_PREEMPT_VOLUNTARY=y),内核态代码运行时不会被其他任务抢占,除非它主动调用 schedule() 或其他让出CPU的函数。为啥?
在默认的Linux内核配置中(CONFIG_PREEMPT=n
或
CONFIG_PREEMPT_VOLUNTARY=y
),内核态代码运行时不会被其他任务抢占,除非主动调用
schedule()
或其他让出CPU的函数,这是由Linux内核的抢占模型设计和实现决定的。让我详细解释原因:
1. Linux内核的抢占模型
Linux内核支持三种主要的抢占配置,它们决定了内核态代码是否以及何时可以被抢占:
-
CONFIG_PREEMPT=n
(无抢占):默认情况下,内核态代码完全不可抢占。
-
CONFIG_PREEMPT_VOLUNTARY=y
(自愿抢占):内核在特定点(如
cond_resched()
)提供自愿让出CPU的机会,但不强制抢占。 -
CONFIG_PREEMPT=y
(完全抢占):内核态代码可以在任意安全点(preemptible
point)被抢占。
在默认配置(特别是嵌入式系统或服务器发行版中常见的
CONFIG_PREEMPT=n
或
CONFIG_PREEMPT_VOLUNTARY=y
),内核倾向于非抢占式设计,这与性能、复杂性和历史演进有关。
2. 为什么内核态默认不可抢占?
在 CONFIG_PREEMPT=n
或
CONFIG_PREEMPT_VOLUNTARY=y
下,内核态代码不可被抢占的原因如下:
a. 简化内核设计
- 数据一致性:内核态代码经常操作共享资源(如内存管理器、文件系统、设备驱动)。如果允许抢占,另一个任务可能在关键代码执行到一半时介入,导致数据结构损坏或竞争条件(race condition)。
- 锁的复杂性:非抢占式内核可以用简单的自旋锁(spinlock)保护临界区,而抢占式内核需要更复杂的机制(如互斥锁或禁用抢占计数器),增加了实现难度和开销。
- 例如,一个非抢占式内核中,持有锁的代码只需确保不被中断打断(禁用中断即可),而抢占式内核还需要额外防止任务切换。
b. 性能优化
- 上下文切换开销:抢占意味着频繁的上下文切换(保存和恢复寄存器、切换栈等),这在高负载系统(如服务器)中会降低吞吐量。
- 延迟可预测性:非抢占式内核让开发者更容易预测代码执行时间,特别是在实时性要求不高的场景中。
- 在默认配置下,内核优先选择吞吐量而非低延迟,因此避免了不必要的抢占。
c. 历史原因
- Linux最初是为单任务系统设计的(早期的0.x版本),后来演变为多任务系统,但内核抢占是逐步引入的(2.6版本开始支持
CONFIG_PREEMPT
)。 - 默认配置保留了非抢占式模型,以兼容旧代码和减少对现有系统的破坏。
3. 非抢占式内核的工作方式
在 CONFIG_PREEMPT=n
下: -
内核态不可抢占:一旦CPU进入内核态(通过系统调用、中断或异常),当前任务会一直运行,直到:
1. 完成内核态工作并返回用户态。 2. 主动调用
schedule()
(例如在阻塞操作如 wait_event()
中)。 - 调度时机:调度器只在以下情况被调用: -
返回用户态时(检查 TIF_NEED_RESCHED
标志)。 - 显式调用
schedule()
。 -
中断不触发抢占:即使时钟中断发生,内核只会标记
need_resched
,但不会立即切换任务,必须等到内核态代码执行完毕。
在 CONFIG_PREEMPT_VOLUNTARY=y
下: -
自愿抢占点:内核在一些常用路径中插入了
cond_resched()
,允许任务在这些点让出CPU,但这仍然是主动行为,不是强制抢占。
- 如果代码没有调用这些函数(如同soft
lockup示例中的死循环),仍然不会被抢占。
4. 用户态与内核态的对比
- 用户态:总是抢占式的,因为内核通过时钟中断和时间片机制强制切换任务,用户态代码无权干预。
- 内核态(默认配置):不可抢占,因为内核需要控制执行流程,避免在关键操作(如分配内存、处理中断)中被打断。
例如: - 用户态死循环:时钟中断触发后,调度器接管,切换任务。 -
内核态死循环(如soft
lockup示例):没有主动让出,调度器无法介入,除非代码调用
schedule()
或返回用户态。
5. 为什么需要主动让出CPU?
在非抢占式内核中,调度器不会自动抢占内核态代码,必须依赖代码本身提供合作:
-
schedule()
:直接调用调度器,切换到就绪队列中的下一个任务。
- cond_resched()
:检查是否需要调度,如果
need_resched
标志置位,则调用 schedule()
。 -
阻塞操作:如 wait_event()
或
sleep()
,会隐式调用 schedule()
。
如果内核态代码陷入无限循环(如
while(1)
),且没有这些让出点,调度器无法运行,watchdog检测到CPU“卡住”,触发soft
lockup。
6. 抢占式内核(CONFIG_PREEMPT=y)的不同
在完全抢占式内核中: -
内核态代码可以在“安全点”(没有持有锁或禁用抢占计数器时)被抢占。 -
时钟中断或其他事件会直接触发
preempt_schedule()
,无需等待主动调用。 -
但这需要额外的同步机制(如 preempt_disable()
和
preempt_enable()
),增加了复杂性和开销,因此不是默认配置。
7. 验证与示例
以下代码在 CONFIG_PREEMPT=n
下会触发soft lockup:
1
2
3
4
5
6static int __init demo_init(void) {
while (1) {
/* 无让出点 */
}
return 0;
}schedule()
或 cond_resched()
,调度器无法介入。 - 添加
cond_resched()
后: 1
2
3while (1) {
cond_resched(); /* 让出CPU */
}
总结
在默认配置(CONFIG_PREEMPT=n
或
CONFIG_PREEMPT_VOLUNTARY=y
)下,内核态代码不可抢占,因为:
1.
设计选择:优先数据一致性和性能,避免抢占带来的复杂性。
2.
调度依赖合作:调度器只在显式让出点或返回用户态时运行。
3.
中断不直接抢占:时钟中断仅标记需求,实际切换需等待代码配合。
这与用户态的强制抢占形成鲜明对比,是Linux内核平衡安全性和效率的结果。
为啥死锁会导致soft locup?
你提到“死锁会导致soft lockup”,但严格来说,死锁(deadlock)本身并不直接导致soft lockup,而是可能在特定条件下引发类似soft lockup的行为。让我详细分析一下死锁与soft lockup的关系,并解释为什么会出现这种混淆,以及在什么情况下死锁可能间接导致soft lockup。
1. 死锁与Soft Lockup的定义
- 死锁(Deadlock):
- 死锁是指多个任务(进程或线程)互相等待对方持有的资源,导致所有任务都无法继续执行的一种状态。
- 在内核中,死锁通常发生在任务等待锁(如spinlock、mutex)或信号量时。
- 例如:任务A持有锁L1并等待锁L2,任务B持有锁L2并等待锁L1,双方陷入僵局。
- Soft Lockup:
- Soft
lockup是Linux内核中的一种检测机制,当某个CPU核心在一段时间内(默认
kernel.watchdog_thresh
,如20秒)未执行任务调度(即未调用调度器),watchdog会触发警告。 - 它表示CPU被某个任务“霸占”,但仍然在执行代码(未完全失控)。
- Soft
lockup是Linux内核中的一种检测机制,当某个CPU核心在一段时间内(默认
2. 死锁为什么通常不会直接导致Soft Lockup?
死锁本身并不必然触发soft lockup,因为死锁的任务通常是阻塞状态,而不是持续占用CPU。以下是原因:
- 阻塞状态不占用CPU:
- 在死锁中,任务通常在等待资源(如调用
wait_event()
、mutex_lock()
),这些操作会使任务进入睡眠状态(TASK_UNINTERRUPTIBLE
或TASK_INTERRUPTIBLE
)。 - 睡眠状态会调用
schedule()
,让出CPU给其他任务,调度器仍然可以运行,因此不会触发soft lockup。
- 在死锁中,任务通常在等待资源(如调用
- Soft Lockup需要忙等待:
- Soft lockup的典型触发条件是CPU被一个忙等待(busy-waiting)的任务占用,例如无限循环或长时间不释放CPU的操作。
- 例如:
while(1) {}
或mdelay(1000)
不让出CPU。
- 死锁的典型场景:
- 如果两个任务互相等待锁,它们会被挂起(pending),调度器会切换到其他就绪任务,CPU不会被“锁死”。
3. 死锁间接导致Soft Lockup的情况
尽管死锁本身不直接触发soft lockup,但在某些特定条件下,死锁可能间接导致类似soft lockup的现象。以下是可能的情况:
a. Spinlock死锁与忙等待
- 场景:
- 在内核中使用
spinlock
(自旋锁)时,如果发生死锁,任务不会睡眠,而是进入忙等待状态。 - 自旋锁是一种轻量级锁,调用
spin_lock()
时,如果锁不可用,任务会循环等待(spin),持续占用CPU。 - 示例:
1
2
3
4
5
6
7
8
9spinlock_t lock1, lock2;
void thread1(void) {
spin_lock(&lock1);
spin_lock(&lock2); // 等待lock2
}
void thread2(void) {
spin_lock(&lock2);
spin_lock(&lock1); // 等待lock1
} - 如果
thread1
和thread2
在不同CPU上运行,互相等待对方的锁,每个CPU都会陷入spin_lock()
的忙等待循环。
- 在内核中使用
- 结果:
- 每个CPU都在执行死循环(等待锁),没有调用
schedule()
,超过watchdog_thresh
时间后,触发soft lockup。 - 日志可能显示:
1
watchdog: BUG: soft lockup - CPU#0 stuck for 22s!
- 每个CPU都在执行死循环(等待锁),没有调用
b. 中断上下文中的死锁
- 场景:
- 如果死锁发生在中断处理程序中(例如持有spinlock时被中断嵌套),中断上下文无法让出CPU。
- 示例:
1
2
3
4
5
6spinlock_t lock;
void interrupt_handler(void) {
spin_lock(&lock);
// 嵌套中断或错误逻辑导致等待
spin_lock(&lock); // 自锁死
} - 中断上下文默认不可抢占(即使
CONFIG_PREEMPT=y
),且不会调用调度器。
- 结果:
- CPU被困在中断处理程序的死循环中,触发soft lockup。
c. 死锁导致调度器无法运行
- 场景:
- 如果死锁涉及调度器本身的关键资源(例如
rq->lock
,运行队列锁),可能导致调度器无法正常工作。 - 这种情况极少见,通常是内核bug。
- 如果死锁涉及调度器本身的关键资源(例如
- 结果:
- CPU无法切换任务,watchdog检测到长时间未调度,触发soft lockup。
4. 死锁与Soft Lockup的根本区别
- 死锁:
- 关注资源争用,任务可能阻塞或忙等待。
- 不一定影响CPU调度,可能只影响部分任务。
- Soft Lockup:
- 关注CPU调度,检测的是CPU是否被“霸占”而无法运行调度器。
- 通常由忙等待或不让出CPU的代码引起。
死锁只有在导致CPU持续忙等待、不让出控制权时,才可能触发soft lockup。
5. 示例代码:死锁触发Soft Lockup
以下是一个可能触发soft lockup的死锁示例(基于spinlock):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
MODULE_LICENSE("GPL");
static spinlock_t lock1, lock2;
static int __init deadlock_init(void)
{
unsigned long flags;
spin_lock_init(&lock1);
spin_lock_init(&lock2);
printk(KERN_INFO "Starting deadlock demo...\n");
/* 模拟CPU0上的任务 */
spin_lock_irqsave(&lock1, flags); // 获取lock1
printk(KERN_INFO "CPU0: Acquired lock1, waiting for lock2...\n");
spin_lock(&lock2); // 等待lock2,但永远不可用
spin_unlock(&lock2);
spin_unlock_irqrestore(&lock1, flags);
return 0;
}
static void __exit deadlock_exit(void)
{
printk(KERN_INFO "Exiting deadlock demo...\n");
}
module_init(deadlock_init);
module_exit(deadlock_exit);lock2
并等待lock1
,形成死锁。
- 这里简化为单线程,但实际需要多核或中断触发。 -
运行结果: -
当前CPU陷入spin_lock(&lock2)
的忙等待,持续占用CPU。 -
超过20秒后,watchdog触发: 1
watchdog: BUG: soft lockup - CPU#0 stuck for 22s!
6. 如何避免混淆
- 检查死锁类型:
- 如果任务阻塞(睡眠),不会触发soft lockup。
- 如果任务忙等待(如spinlock),可能触发。
- 日志分析:
- 用
dmesg
查看调用栈,判断是死锁(锁等待)还是单纯的忙循环。
- 用
7. 总结
死锁本身不会直接导致soft lockup,但以下情况可能间接触发: 1. Spinlock死锁:任务忙等待锁,持续占用CPU。 2. 中断上下文死锁:无法让出CPU。 3. 调度器相关死锁:极少数情况下影响调度。
在大多数死锁场景中,任务是阻塞的,不会触发soft lockup。只有当死锁导致CPU忙等待且不调用调度器时,才会与soft lockup关联起来。
为啥死锁会导致hard locup?
死锁(deadlock)本身并不必然直接导致 hard lockup,但在特定条件下,死锁可能引发类似hard lockup的现象,或者间接导致系统进入硬锁死状态。让我详细分析死锁与hard lockup的关系,解释为什么会出现这种关联,以及在什么情况下死锁可能导致hard lockup。
1. 死锁与Hard Lockup的定义
- 死锁(Deadlock):
- 多个任务互相等待对方持有的资源,导致所有相关任务无法继续执行。
- 例如,任务A等待任务B释放锁L2,任务B等待任务A释放锁L1。
- Hard Lockup:
- Hard lockup是指某个CPU核心完全停止响应,甚至无法处理硬件中断(如NMI,非屏蔽中断)。
- Linux通过NMI
watchdog检测,当CPU在一段时间内(通常与
watchdog_thresh
相关)未响应NMI“心跳”时,触发hard lockup警告。 - 日志示例:
1
NMI watchdog: Watchdog detected hard LOCKUP on cpu 0
2. 死锁为什么通常不会直接导致Hard Lockup?
与soft lockup类似,死锁本身并不直接触发hard lockup,因为死锁通常涉及任务阻塞或忙等待,而不是让CPU完全失控。以下是原因:
- 阻塞状态不影响硬件响应:
- 大多数死锁场景中,任务等待资源(如mutex、semaphore)时会进入睡眠状态(调用
schedule()
),让出CPU。 - 睡眠任务不会阻止CPU响应中断(包括NMI),因此不会触发hard lockup。
- 大多数死锁场景中,任务等待资源(如mutex、semaphore)时会进入睡眠状态(调用
- Hard Lockup需要更严重条件:
- Hard lockup通常由以下情况引起:
- 中断被禁用(
local_irq_disable()
)且陷入死循环。 - 硬件故障(如CPU过热、内存错误)。
- 内核bug导致CPU完全无响应。
- 中断被禁用(
- 单纯的死锁(即使是spinlock忙等待)通常不会禁用中断,CPU仍能响应NMI。
- Hard lockup通常由以下情况引起:
3. 死锁导致Hard Lockup的可能情况
尽管死锁本身不直接触发hard lockup,但在某些极端条件下,死锁可能间接导致CPU进入硬锁死状态。以下是具体场景:
a. 死锁发生在禁用中断的上下文中
- 场景:
- 如果死锁发生在禁用了本地中断(
local_irq_disable()
)的代码段中,且涉及忙等待(如spinlock),CPU可能陷入无法响应的状态。 - 示例:
1
2
3
4
5
6
7
8
9spinlock_t lock1, lock2;
static int __init deadlock_init(void) {
unsigned long flags;
local_irq_save(flags); // 禁用中断
spin_lock(&lock1);
spin_lock(&lock2); // 等待另一个任务释放lock2
local_irq_restore(flags);
return 0;
} - 如果另一个CPU持有
lock2
并等待lock1
,当前CPU会在禁用中断的情况下陷入spin_lock(&lock2)
的死循环。
- 如果死锁发生在禁用了本地中断(
- 结果:
- 中断被禁用,NMI watchdog无法通过中断检查CPU状态。
- CPU持续执行死循环,无法响应NMI心跳,触发hard lockup。
- 日志:
1
NMI watchdog: Watchdog detected hard LOCKUP on cpu 0
b. 中断处理程序中的死锁
- 场景:
- 如果死锁发生在中断处理程序中,且中断处理程序禁用了某些关键中断(或硬件配置错误),可能导致CPU失控。
- 示例:
1
2
3
4
5spinlock_t lock;
void irq_handler(void) {
spin_lock(&lock);
spin_lock(&lock); // 自锁死或等待其他中断上下文
} - 中断上下文默认不可抢占,且可能隐式禁用部分中断。
- 结果:
- CPU被困在中断处理程序的死循环中,无法退出。
- 如果NMI也被禁用或无法触发(例如硬件问题),CPU完全无响应,触发hard lockup。
c. 死锁引发内核崩溃
- 场景:
- 如果死锁涉及内核关键组件(如调度器、内存分配器)的资源,可能导致内核进入不可恢复状态。
- 例如,死锁阻塞了所有CPU的调度队列锁(
rq->lock
),所有核心都无法继续执行。
- 结果:
- CPU可能进入未定义状态,甚至停止响应硬件中断。
- NMI watchdog检测到CPU无心跳,触发hard lockup。
d. 硬件交互中的死锁
- 场景:
- 如果死锁发生在与硬件交互的代码中(如驱动程序等待硬件响应),且硬件未正确响应,可能导致CPU挂起。
- 示例:驱动程序禁用中断并等待硬件DMA完成,但硬件由于死锁未完成任务。
- 结果:
- CPU无法退出等待状态,失去对NMI的响应,触发hard lockup。
4. Hard Lockup与Soft Lockup的区别
- Soft Lockup:
- CPU仍在执行代码,但未调用调度器。
- 中断通常未被禁用,watchdog线程可检测。
- 死锁中的spinlock忙等待可能触发。
- Hard Lockup:
- CPU完全无响应,连NMI都无法处理。
- 通常需要禁用中断或硬件故障。
- 死锁只有在极端条件(如禁用中断+忙等待)下才可能触发。
死锁导致hard lockup的关键在于中断被禁用,否则只会停留在soft lockup级别。
5. 示例代码:死锁触发Hard Lockup
以下是一个可能触发hard lockup的死锁示例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
MODULE_LICENSE("GPL");
static spinlock_t lock1, lock2;
static int __init hardlockup_init(void)
{
unsigned long flags;
spin_lock_init(&lock1);
spin_lock_init(&lock2);
printk(KERN_INFO "Starting hard lockup demo...\n");
local_irq_save(flags); // 禁用本地中断
spin_lock(&lock1);
printk(KERN_INFO "Acquired lock1, waiting for lock2...\n");
spin_lock(&lock2); // 假设lock2被其他CPU持有且等待lock1
spin_unlock(&lock2);
spin_unlock(&lock1);
local_irq_restore(flags);
return 0;
}
static void __exit hardlockup_exit(void)
{
printk(KERN_INFO "Exiting hard lockup demo...\n");
}
module_init(hardlockup_init);
module_exit(hardlockup_exit);lock2
并等待lock1
,形成死锁。 -
当前CPU禁用中断并忙等待lock2
。 -
运行结果: - CPU陷入死循环,且无法响应NMI。 - 如果NMI
watchdog启用(nmi_watchdog=1
),会检测到hard lockup:
1
NMI watchdog: Watchdog detected hard LOCKUP on cpu 0
6. 如何区分和排查
- 检查中断状态:
- Hard lockup需要中断被禁用(
local_irq_disable()
或硬件问题)。 - 用
dmesg
查看调用栈,确认是否涉及irq_save
。
- Hard lockup需要中断被禁用(
- 硬件依赖:
- Hard
lockup检测依赖NMI支持,检查
cat /proc/sys/kernel/nmi_watchdog
是否为1。
- Hard
lockup检测依赖NMI支持,检查
- 死锁类型:
- 普通阻塞死锁(如mutex)不会触发hard lockup。
- Spinlock死锁+禁用中断可能触发。
7. 总结
死锁导致hard lockup的条件: 1.
禁用中断:如local_irq_disable()
,阻止NMI介入。
2. 忙等待:如spinlock死锁,CPU持续执行循环。 3.
关键上下文:发生在中断处理或内核核心代码中。
在大多数情况下,死锁只会导致任务阻塞或soft lockup。只有当死锁结合禁用中断和忙等待时,才可能触发hard lockup。
Soft Lockup:
CPU仍在执行代码,但未调用调度器。 中断通常未被禁用,watchdog线程可检测。 死锁中的spinlock忙等待可能触发。
CPU仍在执行代码,但未调用调度器。这句话啥意思?
“CPU仍在执行代码,但未调用调度器” 这句话描述了 soft lockup 的核心特征,是理解soft lockup与系统调度机制关系的钥匙。让我详细解释一下这句话的含义,以及它在soft lockup上下文中的意义。
1. 基本含义
- “CPU仍在执行代码”:
- 表示CPU没有完全停止运行(不像hard lockup那样无响应),它仍在按照指令顺序执行某个任务的代码。
- 例如,CPU可能在运行一个无限循环、忙等待(busy-waiting)或长时间计算的任务。
- “但未调用调度器”:
- 指的是CPU在执行代码的过程中,没有调用Linux内核的调度函数(如
schedule()
),因此无法切换到其他任务。 - 调度器(scheduler)是内核中负责管理任务切换的组件,它决定哪个任务(进程或线程)接下来获得CPU时间。
- 指的是CPU在执行代码的过程中,没有调用Linux内核的调度函数(如
在正常情况下,CPU会周期性地调用调度器(例如通过时钟中断或主动让出),以确保所有任务都能公平共享CPU。但在soft lockup中,这个过程被中断了。
2. 为什么会发生这种情况?
Soft lockup发生时,CPU被某个任务“霸占”,没有机会运行调度器。以下是具体原因:
a. 任务不让出CPU
- 如果一个任务(通常是内核态代码)陷入无限循环或长时间操作,且没有主动调用
schedule()
或cond_resched()
,调度器就无法介入。 - 示例:
1
2
3while (1) {
/* 无限循环,不让出CPU */
}- 这里CPU一直在执行
while
循环的指令,但没有调用调度器。
- 这里CPU一直在执行
b. 忙等待(Busy-Waiting)
- 某些操作(如等待spinlock或用
mdelay()
实现的延迟)会让CPU持续执行代码,但不触发任务切换。 - 示例:
1
2spinlock_t lock;
spin_lock(&lock); // 如果lock不可用,CPU忙等待- CPU在等待锁时循环检查状态,仍在“执行代码”,但不调用调度器。
c. 内核态的默认行为
- 在非抢占式内核(
CONFIG_PREEMPT=n
或CONFIG_PREEMPT_VOLUNTARY=y
)中,内核态代码运行时不会被自动抢占,除非主动让出。 - 如果内核态任务不配合(不调用调度函数),CPU会被它完全占用。
3. 与调度器的关系
- 调度器的作用:
- 调度器通过
schedule()
函数从就绪队列(run queue)中挑选下一个任务,切换CPU的执行上下文。 - 正常情况下,调度器会在以下时机被调用:
- 时钟中断触发,时间片用尽。
- 任务睡眠或阻塞(如
wait_event()
)。 - 显式调用
schedule()
或cond_resched()
。
- 调度器通过
- Soft Lockup的情况:
- CPU执行的任务没有触发上述任何一种情况,导致调度器长时间未运行。
- 例如,一个内核模块的死循环:
1
2
3
4
5
6static int __init demo_init(void) {
while (1) {
printk(KERN_INFO "Looping...\n");
}
return 0;
} - CPU忙于执行
printk
和循环逻辑,没有让出控制权。
4. Watchdog如何检测?
- Linux内核的watchdog机制通过一个高优先级线程(
watchdog/N
,每个CPU一个)监控CPU状态。 - Watchdog线程周期性地检查CPU是否更新了“心跳”(通常由调度器调用
touch_softlockup_watchdog()
实现)。 - 如果CPU在
kernel.watchdog_thresh
时间(默认20秒)内未调用调度器,watchdog线程无法被调度到该CPU上运行,就会触发soft lockup警告:1
watchdog: BUG: soft lockup - CPU#0 stuck for 22s!
- 关键点:
- “CPU仍在执行代码”说明它没死,只是被困在某个任务中。
- “未调用调度器”说明watchdog线程无法获得CPU时间来检查或报告状态。
5. 与Hard Lockup的对比
- Soft Lockup:
- CPU仍在运行代码(例如忙等待或循环)。
- 中断未被禁用,watchdog线程可以检测并报告。
- 示例:spinlock死锁导致的忙等待。
- Hard Lockup:
- CPU完全无响应,连中断(包括NMI)都无法处理。
- 通常由禁用中断(
local_irq_disable()
)+死循环或硬件故障引起。 - 示例:
1
2local_irq_disable();
while (1) {}
Soft lockup的“仍在执行代码”强调CPU还有活动,而“未调用调度器”是问题的核心。
6. 实际例子
假设以下内核模块: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
MODULE_LICENSE("GPL");
static int __init softlockup_init(void) {
printk(KERN_INFO "Starting soft lockup demo...\n");
while (1) {
mdelay(1000); // 忙等待1秒
}
return 0;
}
module_init(softlockup_init);mdelay()
的忙等待循环(本质是一个计数循环)。 -
mdelay()
不会调用schedule()
,调度器无法运行。 -
20秒后,watchdog检测到CPU未调度,触发soft lockup。 -
含义: -
“仍在执行代码”:CPU在执行mdelay()
的循环。 -
“未调用调度器”:没有任务切换,watchdog线程被饿死。
7. 总结
“CPU仍在执行代码,但未调用调度器”意味着: -
CPU被某个任务占用,持续运行其指令(例如循环或等待)。 -
由于任务没有主动让出CPU(通过schedule()
或睡眠),调度器无法接管,任务切换停止。
- 这正是soft lockup的特征:CPU有活动,但系统调度停滞。
如果把这句话想象成现实场景,就像一个工人(CPU)一直在忙着干活(执行代码),但忘了按时休息或换班(调用调度器),最终被监管员(watchdog)发现并报警。
如果CPU在kernel.watchdog_thresh时间(默认20秒)内未调用调度器,watchdog线程无法被调度到该CPU上运行,就会触发soft lockup警告
没有调用调度器,watchdog线程是如何被cpu执行的,soft lockup警告是如何输出的?
你提出了一个非常核心的问题:如果CPU在kernel.watchdog_thresh
时间内未调用调度器,watchdog线程无法被调度到该CPU上运行,那么soft
lockup警告是如何被检测并输出的?这涉及到Linux内核中soft
lockup检测的机制,尤其是watchdog线程和调度器的交互。让我详细解释一下。
1. Watchdog机制的背景
- Watchdog线程:
- Linux内核为每个CPU核心创建一个高优先级的watchdog线程,命名为
watchdog/N
(N是CPU编号,例如watchdog/0
)。 - 这些线程运行在实时调度策略(
SCHED_FIFO
)下,优先级非常高(通常是99),以确保它们能在正常情况下及时执行。
- Linux内核为每个CPU核心创建一个高优先级的watchdog线程,命名为
- 作用:
- Watchdog线程定期检查该CPU是否“活跃”,即是否在规定时间内调用了调度器或更新了“心跳”。
- 心跳机制:
- 内核通过
touch_softlockup_watchdog()
函数更新心跳,通常在调度器(如schedule()
)或某些关键路径中调用。 - 如果CPU正常调度任务,心跳会定期刷新。
- 内核通过
2. Soft Lockup的检测原理
Soft lockup的检测依赖于watchdog线程,但当CPU被某个任务霸占时,watchdog线程确实无法直接在该CPU上运行。关键在于: - 检测不是由被锁死的CPU自己完成,而是由其他CPU或系统机制辅助完成的。 - 触发警告的输出也不一定依赖被锁死CPU的调度器。
以下是具体过程:
a. Watchdog线程的运行环境
- 在多核系统中,每个CPU有自己的watchdog线程(
watchdog/0
、watchdog/1
等),它们默认绑定到对应的CPU上运行。 - 这些线程通过内核的hrtimer(高精度定时器)定期唤醒(默认每4秒检查一次,具体由
watchdog_timer_fn
控制)。 - 在正常情况下,watchdog线程会在其绑定的CPU上运行,检查该CPU的心跳时间戳。
b. CPU未调用调度器的情况
- 当某个CPU(例如CPU0)被一个任务霸占(例如无限循环),未调用
schedule()
超过kernel.watchdog_thresh
时间(默认20秒):- CPU0上的watchdog线程(
watchdog/0
)无法被调度,因为调度器未运行。 - 心跳时间戳(
hrtimer_interrupts
或类似的计数器)不会更新。
- CPU0上的watchdog线程(
c. 检测和警告的触发
- 多核系统的协作:
- 在多核系统中,其他CPU(例如CPU1)的watchdog线程(
watchdog/1
)仍在正常运行。 - 内核的watchdog机制通过共享数据结构(如
watchdog_touch_ts
)监控所有CPU的心跳状态。 - 当CPU1的
watchdog/1
线程运行时,它会检查所有CPU的时间戳,发现CPU0的心跳已过期(超过20秒未更新),从而判断CPU0发生了soft lockup。
- 在多核系统中,其他CPU(例如CPU1)的watchdog线程(
- 单核系统的情况:
- 在单核系统中,没有其他CPU协助检测。这时依赖于时钟中断或hrtimer中断:
- 即使调度器未运行,时钟中断(softirq)仍然可能触发。
- 如果中断处理程序检测到心跳过期,会直接调用soft lockup的报告函数。
- 在单核系统中,没有其他CPU协助检测。这时依赖于时钟中断或hrtimer中断:
- 警告输出:
- 一旦检测到soft lockup,警告信息通过
printk()
输出。 printk()
是异步的,它将消息写入内核日志缓冲区(log buffer),并不依赖被锁死CPU的调度器。- 其他CPU或中断上下文可以接管日志的实际输出(例如通过串口、控制台驱动)。
- 一旦检测到soft lockup,警告信息通过
d. 关键函数
watchdog_timer_fn
:hrtimer的回调函数,检查心跳并触发警告。touch_softlockup_watchdog
:刷新心跳时间戳。trigger_all_cpu_backtrace
:在检测到soft lockup时,请求所有CPU dump调用栈(通过IPI或NMI)。
3. 为什么Watchdog线程“看似”无法运行却能检测?
这是一个常见的困惑点,答案在于检测和报告的分离: -
Watchdog线程未运行不等于无法检测: -
如果CPU0被锁死,watchdog/0
确实无法运行,但检测是由其他CPU的watchdog线程(或中断)完成的。
- 在多核系统中,CPU之间的协作确保了全局监控。 -
中断仍然有效: - Soft
lockup的定义是“中断未被禁用”,因此时钟中断或hrtimer中断仍然会发生。 -
这些中断可以在被锁死的CPU上触发检查逻辑,即使调度器未运行。
- 单核特殊情况:
- 如果是单核,且中断未被禁用,hrtimer中断会调用
watchdog_timer_fn
,直接检测心跳过期并输出警告。 - 如果中断也被禁用(例如
local_irq_disable()
),则升级为hard lockup,依赖NMI检测。
- 如果是单核,且中断未被禁用,hrtimer中断会调用
4. 警告输出的实现
- 日志机制:
printk()
将警告写入内核环形缓冲区(ring buffer),这是一个内存操作,不需要调度器。- 实际输出到控制台(如串口、VGA)由其他CPU或中断驱动的kworker线程完成。
- 调用栈dump:
- 在多核系统中,触发soft lockup后,内核通过IPI(处理器间中断)请求被锁死CPU dump调用栈。
- 如果IPI失败(例如CPU完全无响应),则可能是hard lockup。
- 示例日志:
1
watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [insmod:1234]
- 这条消息由其他CPU或中断上下文生成并输出。
5. 实际例子
假设以下代码在CPU0上运行: 1
2
3
4
5
6static int __init softlockup_init(void) {
while (1) {
/* 无调度,无让出 */
}
return 0;
}schedule()
。 2.
watchdog/0
无法运行,心跳时间戳停滞。 3.
CPU1的watchdog/1
检查全局状态,发现CPU0心跳过期。 4.
CPU1调用printk()
输出警告,并通过IPI请求CPU0 dump调用栈。 5.
用户通过dmesg
看到soft lockup警告。
- 单核情况:
- hrtimer中断触发,检测心跳过期,直接输出警告。
6. 与Hard Lockup的对比
- Soft Lockup:
- 中断未禁用,其他CPU或中断可检测并报告。
- “未调用调度器”是关键,但系统仍有响应能力。
- Hard Lockup:
- 中断被禁用,连NMI都无法触发,检测依赖硬件NMI watchdog。
- CPU完全无响应,警告可能由其他CPU通过NMI生成。
7. 总结
- “未调用调度器”:CPU被任务占用,watchdog线程无法在该CPU上运行。
- 检测如何实现:
- 多核:其他CPU的watchdog线程通过共享时间戳检测。
- 单核:时钟中断或hrtimer直接检查。
- 警告如何输出:
printk()
不依赖调度器,由其他CPU或中断上下文完成。
这个机制利用了多核协作和中断的健壮性,确保即使被锁死的CPU无法自我报告,系统也能发现问题。