本文约2200字,在之前一篇帖子中《系统启动报错?分区识别失败?一份来自项目实战中的SDK移植调试复盘》,有一个现象描述运行了app后,系统无法启动,之前还以为是app与rtos交接数据引起的问题,结果分析不是。本文记录这个问题的修复以及Linux开发中高频踩踏问题汇总以及常规排查方法。
关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。
引言
在嵌入式 Linux 开发中,最隐蔽也最致命的 bug 之一,就是“写对了数据,却写错了地方”。近期我在移植AX615 fastnor型号程序时,就踩中了这个坑——配置分区对应的设备节点忘记同步调整(分区布局调整了,应用代码的配置分区遗漏同步调整),导致本该写入配置分区的数据,径直写入了 rootfs 分区,踩踏了文件系统元数据。重启后系统直接 panic,串口只剩下冷冰冰的mount_block_root调用栈。本文将复盘这次事故的全过程,并梳理 Linux 开发中因“踩踏”导致系统无法启动的常见场景与排查方法论。
运行app后,重启系统,串口打印如下内核 panic 信息:
[ 0.109178] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.19.125 #1
[ 0.115274] Hardware name: Acera_chip (Device Tree)
[ 0.120178] [<c0011f15>] (unwind_backtrace) from [<c000fa73>] (show_stack+0xb/0xc)
[ 0.127579] [<c000fa73>] (show_stack) from [<c03ca6bd>] (dump_stack+0xb/0xc)
[ 0.134993] [<c03ca6bd>] (dump_stack) from [<c05dfcd>] (panic+0xb/0xc)
[ 0.141882] [<c05dfcd>] (panic) from [<c05dfcd>] (mount_block_root+0x17d/0x1fc)
[ 0.149378] [<c05dfcd>] (mount_block_root) from [<c05dfea3>] (prepare_namespace+0xef/0x130)
[ 0.157829] [<c05dfea3>] (prepare_namespace) from [<c05df9f5>] (kernel_init_freeable+0xf5/0x154)
[ 0.166629] [<c05df9f5>] (kernel_init_freeable) from [<c03d5d23>] (kernel_init+0x7/0x9c)
[ 0.174731] [<c03d5d23>] (kernel_init) from [<c00090e9>] (ret_from_fork+0x11/0x28)
[ 0.182303] Exception stack(0xc1415fb0 to 0xc1415ff8)
...
[ 0.210351] Rebooting in 5 seconds.
在深入分析前,还以为是rtos与app之间的交接导致的踩踏事件,实际分析后才知判断错误。关键信息解读:
mount_block_root 处触发 panic → 内核已经完成解压和驱动初始化,但在挂载根文件系统时失败了。在 AX615 fastnor 平台上,配置分区原本对应 /dev/mtdblock6,配置工具通过该节点写入配置参数。但在本次型号移植中,分区表做了调整,配置分区被移动到了 /dev/mtdblock9。
应用层的配置写入代码中,设备节点路径是硬编码的,未同步更新:
// 旧代码(未修改)
int fd = open("/dev/mtdblock6", O_RDWR);
// 实际移植后,/dev/mtdblock3 已变为 rootfs 分区
mount_block_root panic。除了分区写错,嵌入式 Linux 开发中还有以下常见的“踩踏”导致启动失败的场景:
| uboot env 被覆盖 | ||
| 设备树(dtb)被踩 | ||
| 内核镜像被写坏 | Bad Magic Number | |
| kernel cmdline 被改 | root= | |
| initramfs 损坏 | cpio magic 不匹配 | |
| 文件系统 journal 污染 | Journal checksum error |
start_kernel早期 | ||
setup_arch | ||
do_mount_root | root= | |
mount_block_root | ||
init_post |
# 正常启动后备份分区信息
cat /proc/mtd
cat /proc/partitions
lsblk
对比异常板与正常板的分区布局,确认应用层写入的目标节点是否仍对应原分区。
若 rootfs 损坏但内核可启动,可使用 initramfs 进入急救模式:
# 挂载受损 rootfs 为只读,检查文件系统状态
mount -t ext4 -o ro /dev/mmcblk0pX /mnt
dmesg | grep -i "ext4\|error\|corrupt"
若无法挂载,则使用 fsck 尝试修复(有风险,建议先做镜像备份)。
吃一堑长一智,以下措施可有效避免同类踩踏事故:
| 使用分区名而非硬编码节点 | /dev/disk/by-partlabel/config,分区调整后自动映射 |
| 写入前校验分区 label | blkid 或 lsblk 确认目标分区身份 |
| 对关键分区设置只读保护 | |
| 增加写入长度/偏移边界检查 | offset + len 是否越界 |
| uboot 增加 fallback 启动机制 | |
| 烧录后做全分区回读校验 |
本次事故的修复措施如下:
/dev/mtdblock6 修改为 /dev/mtdblock9。config。嵌入式 Linux 开发中,“踩踏”问题往往不是逻辑错误,而是 地址/偏移/节点号 这类“软配置”在代码演进中被人为忽略所致。它们不会在编译期报错,只会在运行时给你一个 panic 栈。最好的防御不是调试技巧,而是 设计上的防御:用符号名代替数字、用边界检查代替盲目写入、用分区只读代替事后修复。
希望这篇复盘能帮到正在嵌入式 Linux 路上踩坑的你。
“谢谢你看到这里”嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计
嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助