Linux 内核 x86-64 机器检查(Machine Check)可配置 sysfs 参数完全指南
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
机器检查(Machine Check)是 x86 体系结构内置的硬件错误报告机制:CPU 检测到内部硬件错误后,通过 MCA(Machine Check Architecture)记录到各银行(bank)的状态寄存器中。本文以 Linux 内核源码树中的 Documentation/arch/x86/x86_64/machinecheck.rst 为骨架,结合 arch/x86/kernel/cpu/mce/core.c、Documentation/ABI/testing/sysfs-mce 等内核实现,系统讲解/sys/devices/system/machinecheck/machinecheckN/下每个可配置项的含义、默认值、生效机制与实战用法。读完本文,你将掌握:如何读懂"Machine check errors logged"日志并驱动 mcelog 解码;如何通过 sysfs 细粒度开关各银行的子事件;如何调优纠正错误(CE)轮询间隔、配置错误触发脚本与 monarch 超时;以及 ignore_ce、dont_log_ce、cmci_disabled 三个"关闭纠正错误处理"选项之间的本质区别。
机器检查与 MCE 银行(bank)体系
什么是机器检查
机器检查报告的是CPU 检测到的内部硬件错误条件。根据错误的严重程度,其处理方式截然不同:
- 不可纠正错误(Uncorrected errors):通常会触发一次机器检查异常(Machine Check Exception,MCE),往往伴随内核 panic(在可恢复场景下也可能触发系统软离线或进程杀死);
- 可纠正错误(Corrected errors):不会引发异常,只会产生一条机器检查日志条目,由内核周期轮询或 CMCI(Corrected Machine Check Interrupt,可纠正机器检查中断)机制收集。
这些错误被组织在银行(bank)中。每个银行通常对应一个硬件子系统(如指令缓存、数据缓存、总线、内存控制器等),一个银行内部又细分为多个子事件(subevent)。需要特别强调的是:银行与子事件的确切含义是 CPU 厂商相关的——Intel 与 AMD 的银行布局、编号和事件位定义各不相同,甚至同厂商不同微架构之间也有差异。这也是为什么内核不做统一解码,而是把解码工作交给用户态工具。
mcelog:解码 MCE 记录的标准工具
mcelog是解码这些机器检查记录的事实标准工具。当你在系统日志中看到"Machine check errors logged"消息时,就应该运行 mcelog 从/dev/mcelog字符设备收集并解码机器检查条目(该设备由内核的 arch/x86/kernel/cpu/mce/dev-mcelog.c 提供)。
通常的做法是从cron定期运行 mcelog。但正如后文将要介绍的trigger属性,sysfs 还提供了一种比 cron 更快的事件触发机制,二者可以互相替代或配合使用。
/sys/devices/system/machinecheck/machinecheckN/ 目录结构
内核为每个 CPU在 sysfs 中创建一个目录:
/sys/devices/system/machinecheck/machinecheckN/其中N为 CPU 编号。该目录的创建逻辑位于 arch/x86/kernel/cpu/mce/core.c,内核将 machinecheck 注册为一个总线子系统(mce_subsys),总线名与设备名均为machinecheck:
static const struct bus_type mce_subsys = { .name = "machinecheck", .dev_name = "machinecheck", };该目录下包含以下可配置条目,本文逐一展开:
| 条目 | 权限 | 作用 | 默认值 |
|---|---|---|---|
bank<Y> | 0644 | 64 位十六进制位掩码,开关银行 Y 的特定子事件 | 全 1(全部使能) |
check_interval | 0644 | 轮询纠正错误的间隔(秒),轮询发现/停止发现错误时指数加速/退避 | 5 分钟(300s) |
trigger | 0644 | 检测到 MCE 事件时运行的程序路径 | 空(不触发) |
monarch_timeout | 0644 | 异常时等待其他 CPU 上报 MCE 的超时时间(微秒) | 依平台而定 |
ignore_ce | 0644 | 关闭纠正错误的轮询与 CMCI | 0 |
dont_log_ce | 0644 | 关闭纠正错误的日志记录(静默清除) | 0 |
cmci_disabled | 0644 | 关闭 CMCI 特性 | 0 |
这些属性的 sysfs 定义同样在 arch/x86/kernel/cpu/mce/core.c 中:
static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout); static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce); static DEVICE_BOOL_ATTR(print_all, 0644, mca_cfg.print_all); static struct dev_ext_attribute dev_attr_check_interval = { __ATTR(check_interval, 0644, device_show_int, store_int_with_restart), ... };注意事项:部分条目是"全局共享"的
文档明确提醒:check_interval、trigger等条目虽然在每个 CPU 目录下都出现,但它们实际上是所有 CPU 之间共享的全局变量。修改任意一个 CPU 目录下的值,等价于修改全局配置(这也解释了为什么mce_restart()需要持有全局的mce_sysfs_mutex互斥锁来重启轮询定时器)。从源码看,check_interval是一个文件级静态全局变量(core.c),trigger对应的mce_helper同样是全局缓冲区(dev-mcelog.c)。
bank :按子事件粒度开关错误报告
语义与默认值
bank<Y>是一个64 位十六进制位掩码,用于开关银行 Y 内的特定子事件:
- 位掩码中某一位为0:对应的子事件不再上报;
- 位掩码中某一位为1:对应子事件正常上报;
- 默认所有位均为 1,即所有事件都使能。
注意:BIOS 还会维护另一套按银行屏蔽事件的掩码,这套掩码不会在此处显示,因此 sysfs 中看到的全 1 不代表 BIOS 没有屏蔽某些事件。
内核实现:读写路径
bank<Y>的读写实现在 arch/x86/kernel/cpu/mce/core.c 中。读取时直接输出该 CPU 上银行 Y 的控制值b->ctl;写入时通过kstrtou64()解析用户输入并更新b->ctl,随后在mce_sysfs_mutex保护下调用mce_restart()重启相关机制使配置生效:
static ssize_t set_bank(struct device *s, struct device_attribute *attr, const char *buf, size_t size) { ... if (kstrtou64(buf, 0, &new) < 0) return -EINVAL; ... b->ctl = new; mutex_lock(&mce_sysfs_mutex); mce_restart(); mutex_unlock(&mce_sysfs_mutex); return size; }从内核初始化路径看,银行初始默认值在 core.c 中为b->ctl = -1ULL(即全 1),与文档所述"默认全部使能"完全一致。此外,不同厂商/微架构会通过 quirk 覆盖个别银行——例如源码中针对某些 CPU 的 IFU(指令取指单元)银行错误做了特殊处理(core.c),这说明银行与事件编号确实高度依赖具体 CPU。
实战:屏蔽/恢复某个银行
# 查看 CPU0 上银行 1 当前的子事件掩码 cat /sys/devices/system/machinecheck/machinecheck0/bank1 # 屏蔽银行 1 的 bit 3 子事件(其余位保持不变) OLD=$(cat /sys/devices/system/machinecheck/machinecheck0/bank1) NEW=$(( OLD & ~(1 << 3) )) printf '%llx\n' "$NEW" > /sys/devices/system/machinecheck/machinecheck0/bank1 # 恢复全部事件 echo ffffffffffffffff > /sys/devices/system/machinecheck/machinecheck0/bank1提示:改银行掩码属于调试/规避手段,建议先结合 mcelog 解码确认具体子事件含义,再决定是否屏蔽,避免掩盖真实的硬件故障信号。
check_interval:纠正错误轮询间隔与指数退避
语义与默认值
check_interval表示多久轮询一次纠正的机器检查错误,单位为秒。有两个关键细节:
- 输出是十六进制(源码中用
device_show_int显示,注意0x12c表示 300 秒); - 默认值为 5 分钟(300 秒),在 arch/x86/kernel/cpu/mce/internal.h 中定义为
#define INITIAL_CHECK_INTERVAL 5 * 60 /* 5 minutes */。
动态调整:指数加速与退避
轮询间隔并非固定不变。内核实现了自适应的指数调整策略:
- 当轮询器发现 MCE时,触发指数加速——更频繁地轮询(最多每 10ms 一次,即
HZ/100); - 当轮询器不再发现 MCE时,触发指数退避——逐渐降低轮询频率,直到恢复到
check_interval; check_interval既是初始轮询间隔,也是轮询间隔的上限;- 设为0表示完全不轮询纠正错误(但部分纠正错误仍可能通过其他途径上报,例如 CMCI)。
该逻辑实现在 arch/x86/kernel/cpu/mce/core.c 的mce_timer_fn()中:
if (!mce_gen_pool_empty()) iv = max(iv / 2, (unsigned long) HZ/100); /* 指数加速 */ else iv = min(iv * 2, round_jiffies_relative(check_interval * HZ)); /* 指数退避 */此外,当某个银行进入错误风暴(storm)模式时,内核会切换到每秒轮询一次(__start_timer(t, HZ)),风暴结束后再恢复默认间隔(core.c)。
实战:修改轮询间隔
# 查看当前轮询间隔(注意:十六进制输出,0x12c = 300 秒 = 5 分钟) cat /sys/devices/system/machinecheck/machinecheck0/check_interval # 设置为 60 秒 echo 60 > /sys/devices/system/machinecheck/machinecheck0/check_interval # 完全关闭纠正错误轮询 echo 0 > /sys/devices/system/machinecheck/machinecheck0/check_interval写入后,store_int_with_restart()(core.c)会检测到check_interval变化,并调用mce_restart()重新初始化各 CPU 的轮询定时器。由于该变量是全局共享的,修改任意 CPU 目录下的值即可。
trigger:MCE 事件触发程序
trigger属性用于设置一个在检测到机器检查事件时运行的程序。它是"定期从 cron 运行 mcelog"的替代方案,优点是检测事件更快(事件发生后立即触发,无需等待下一个 cron 周期)。
内核实现
实现在 arch/x86/kernel/cpu/mce/dev-mcelog.c。内核将用户写入的路径保存在全局mce_helper缓冲区中;当有 MCE 事件产生时,mce_work_trigger()会调度一个工作队列,最终通过call_usermodehelper()执行该程序:
static void mce_do_trigger(struct work_struct *work) { call_usermodehelper(mce_helper, mce_helper_argv, NULL, UMH_NO_WAIT); } void mce_work_trigger(void) { if (mce_helper[0]) schedule_work(&mce_trigger_work); }实战:设置触发脚本
# 让 MCE 事件触发 mcelog echo /usr/sbin/mcelog > /sys/devices/system/machinecheck/machinecheck0/trigger # 查看当前配置 cat /sys/devices/system/machinecheck/machinecheck0/trigger # 取消触发(写入空串) echo > /sys/devices/system/machinecheck/machinecheck0/trigger脚本被call_usermodehelper以异步方式(UMH_NO_WAIT)执行,因此应为可执行程序或脚本,并注意其运行环境(环境变量极少、无终端)。同样地,trigger也是全局共享的。
monarch_timeout:异常时等待其他 CPU 的超时
当 CPU 发生机器检查异常时,x86 的 MCA 协议要求多个 CPU 之间协作:一个 CPU 作为monarch(主控者),其他 CPU 作为slave(从属者)向它汇报。monarch_timeout指定monarch 等待其他 CPU 也发生机器检查的最长时间:
- 单位为微秒(us);
- 设为0表示不等待其他 CPU(禁用等待)。
内核通过mca_cfg.monarch_timeout配置该值(core.c 初始为 -1,表示"未显式设置"),在异常处理路径中将其换算为纳秒超时(timeout = (u64)mca_cfg.monarch_timeout * NSEC_PER_USEC,见 core.c)。平台相关代码会在初始化时给出默认值:例如对较新的 Intel CPU 默认设为 1 秒(USEC_PER_SEC,见 core.c);在部分不支持该机制的配置下会回退为 0(core.c)。
# 查看当前超时(微秒) cat /sys/devices/system/machinecheck/machinecheck0/monarch_timeout # 设置为 2 秒 echo 2000000 > /sys/devices/system/machinecheck/machinecheck0/monarch_timeout # 禁用等待其他 CPU echo 0 > /sys/devices/system/machinecheck/machinecheck0/monarch_timeout该值也可通过内核命令行参数mce=monarch_timeout=<us>设置,见后文。
ignore_ce、dont_log_ce 与 cmci_disabled:三者的区别
这三个属性都用于关闭纠正错误(CE)的处理,但粒度完全不同,务必区分清楚。它们在 arch/x86/kernel/cpu/mce/core.c 中均有独立的 setter 实现,修改时会在mce_sysfs_mutex保护下对每个 CPU 执行使能/禁用动作。
ignore_ce:整体关闭纠正错误的收集
- 作用:关闭纠正错误的轮询与 CMCI(两种收集途径全部关闭);
- 副作用:所有纠正事件不会被清除,而是保留在银行 MSR 中;
- 适用场景:希望彻底停止纠正错误处理、且允许错误状态寄存器保持原样时。
源码中开启该选项的路径为:删除所有轮询定时器(mce_timer_delete_all())+ 在每个 CPU 上禁用 CMCI(mce_disable_cmci),并置mca_cfg.ignore_ce = true(core.c)。
dont_log_ce:只关日志、仍清除状态
- 作用:仅关闭纠正错误的日志记录;
- 行为:所有上报的纠正错误都会被静默清除(不产生日志);
- 适用场景:完全不在乎纠正错误、希望避免日志噪音、又不想让错误状态寄存器一直挂着时。
在轮询路径machine_check_poll()中可以看到该选项的实际作用:if (mca_cfg.dont_log_ce && !mce_usable_address(m)) ...跳过日志记录但继续clear_bank(m)清除银行状态(core.c)。
cmci_disabled:只关 CMCI
- 作用:仅关闭CMCI(Corrected Machine Check Interrupt)特性;
- 行为:关闭后纠正错误不再通过中断方式上报,但仍可通过轮询机制收集;
- 适用场景:CMCI 中断风暴或与虚拟化等环境冲突时,保留轮询兜底。
对比小结
| 属性 | 关闭轮询 | 关闭 CMCI | 关闭日志 | 是否清除银行状态 |
|---|---|---|---|---|
ignore_ce | ✅ | ✅ | — | ❌ 保留在 MSR |
dont_log_ce | ❌ | ❌ | ✅ | ✅ 静默清除 |
cmci_disabled | ❌ | ✅ | ❌ | 依轮询结果 |
# 例:整体忽略纠正错误 echo 1 > /sys/devices/system/machinecheck/machinecheck0/ignore_ce # 例:只静默纠正错误日志 echo 1 > /sys/devices/system/machinecheck/machinecheck0/dont_log_ce # 例:只关闭 CMCI(保留轮询) echo 1 > /sys/devices/system/machinecheck/machinecheck0/cmci_disabled这三个属性同样可等价地通过内核命令行参数设置:mce=ignore_ce、mce=dont_log_ce、mce=cmci_disabled(见 core.c 中的解析代码,其中cmci_disabled在启用 CMCI 的 CPU 上默认关闭)。
内核命令行参数:mce= 引导选项
上述部分配置还可以通过内核命令行(/proc/cmdline或引导加载器)在启动时设置。从 arch/x86/kernel/cpu/mce/core.c 的解析逻辑可以看出:
| 引导参数 | 等价 sysfs 操作 |
|---|---|
mce=ignore_ce | 启动即置ignore_ce |
mce=dont_log_ce | 启动即置dont_log_ce |
mce=cmci_disabled | 启动即关闭 CMCI |
mce=monarch_timeout=<us> | 启动即设置 monarch 超时(微秒) |
# 示例:GRUB 内核命令行 mce=ignore_ce mce=monarch_timeout=2000000引导参数在系统早期(sysfs 尚不可用时)就能生效,适合需要在启动阶段就改变 MCE 行为的场景;sysfs 写入则适合运行期动态调整,无需重启。
故障排查与诊断流程
综合文档与源码,一套完整的机器检查诊断工作流如下:
- 观察日志:关注 dmesg/系统日志中的
Machine check errors logged消息; - 运行 mcelog:从
/dev/mcelog收集并解码 MCE 记录(定期 cron 或配置trigger事件触发); - 查看银行状态:
cat /sys/devices/system/machinecheck/machinecheckN/bank*,了解各银行子事件掩码与默认使能情况; - 按需调整:
- 轮询过快/过慢 → 调整
check_interval; - 纠正错误日志刷屏 →
dont_log_ce=1(静默清除)或ignore_ce=1(完全停止收集); - CMCI 中断异常 →
cmci_disabled=1; - 多 CPU 上报协作异常 → 调整
monarch_timeout;
- 轮询过快/过慢 → 调整
- 回到硬件层面:由于银行与子事件含义是 CPU 厂商相关的,遇到难以理解的条目时,应查阅 Intel/AMD 架构手册中的 MCA 章节。此外,架构细节还可参考 mcelog 作者 Andi Kleen 撰写的机器检查架构介绍文档
http://one.firstfloor.org/~andi/mce.pdf(该链接同时被 machinecheck.rst 与 sysfs-mce 引用)。
相关内核文档与源码索引
- 本文主题文档:Documentation/arch/x86/x86_64/machinecheck.rst
- sysfs 接口 ABI 规范:Documentation/ABI/testing/sysfs-mce
- MCE 核心实现(银行、轮询、sysfs 属性、引导参数解析):arch/x86/kernel/cpu/mce/core.c
- /dev/mcelog 字符设备与 trigger 触发逻辑:arch/x86/kernel/cpu/mce/dev-mcelog.c
- 轮询默认间隔定义:arch/x86/kernel/cpu/mce/internal.h
- 相关子系统:AMD 阈值中断(arch/x86/kernel/cpu/mce/threshold.c)、Intel CMCI 实现(arch/x86/kernel/cpu/mce/intel.c)、MCE 错误注入调试工具(arch/x86/kernel/cpu/mce/inject.c)
说明:
Documentation/arch/x86/x86_64/machinecheck.rst中提到的"AMD threshold interrupt configuration"(AMD 阈值中断配置)条目在文档中标注为 TBD(待补充),对应实现可参考 arch/x86/kernel/cpu/mce/threshold.c 中的阈值中断(threshold interrupt)逻辑。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考