1. 问题现象与初步分析
最近在调试i.MX8MM平台上的Android 9.0系统时,遇到了一个棘手的启动问题:系统在启动过程中界面突然卡死,完全失去响应。作为一名嵌入式系统工程师,我立即展开了排查工作。
首先使用top命令查看系统状态,发现iowait指标异常高,占用了大量CPU资源。尝试执行一些基本的文件系统操作命令(如ls /data)时,终端也会立即卡死。这明显指向了I/O子系统的问题。
通过分析进程堆栈,发现包括system_server在内的多个关键进程都阻塞在io_schedule()和wait_on_page_bit()等I/O等待函数上。这种多进程同时因I/O阻塞的现象,通常意味着底层存储设备或文件系统出现了严重问题。
提示:当遇到系统级卡顿时,iowait是最直接的指标之一。高iowait表明CPU在等待I/O操作完成,此时系统响应会明显变慢。
2. 硬件与基础环境排查
2.1 硬件兼容性验证
为了排除硬件故障的可能性,我首先进行了以下验证步骤:
- 将相同的系统镜像烧录到多块不同的核心板上进行测试
- 更换不同批次的eMMC芯片进行交叉验证
- 检查电源供应稳定性,确保没有电压波动问题
测试结果显示,问题在所有硬件上都稳定复现,这基本排除了单一硬件故障的可能性。同时,电源测量数据显示各电压轨都在规格范围内,没有异常波动。
2.2 存储性能基准测试
为了评估eMMC的基本性能,我使用以下工具进行了基准测试:
bash复制# 测试顺序读写性能
fio --name=seqread --rw=read --direct=1 --bs=128k --size=256m --runtime=60
fio --name=seqwrite --rw=write --direct=1 --bs=128k --size=256m --runtime=60
# 测试随机读写性能
fio --name=randread --rw=randread --direct=1 --bs=4k --size=256m --runtime=60
fio --name=randwrite --rw=randwrite --direct=1 --bs=4k --size=256m --runtime=60
测试结果显示,eMMC的基本读写性能符合规格书指标,没有明显的性能异常。这进一步将问题范围缩小到了软件层面。
3. 内核与文件系统深入分析
3.1 内核版本与配置检查
系统使用的是Linux内核4.14.78版本,这是一个长期支持(LTS)版本。检查内核配置发现:
- 启用了dm-verity用于分区完整性校验
- 使用了ext4文件系统
- eMMC驱动启用了Command Queue Engine(CQE)功能
特别值得注意的是,这个内核版本中dm-verity的实现与CQE功能可能存在兼容性问题。dm-verity会在读取时进行哈希校验,而CQE则允许并行处理多个I/O请求,两者结合可能导致死锁情况。
3.2 死锁场景模拟与分析
通过反复测试和日志分析,我重现了问题发生的典型场景:
- 系统启动时,多个进程同时访问/data分区
- dm-verity开始校验数据块
- CQE尝试并行处理这些校验请求
- 某些情况下,请求队列出现竞争条件
- 最终导致I/O调度器死锁,所有依赖该队列的进程都被阻塞
内核日志中可以看到如下关键信息:
code复制[ 12.345678] mmc0: cqhci: timeout waiting for transfer complete
[ 12.345789] mmc0: cqhci: ============ CQHCI REGISTER DUMP ===========
[ 12.345890] mmc0: cqhci: Caps: 0x00000000 | Version: 0x00000000
这表明CQE引擎确实发生了超时和异常。
4. 解决方案与优化
4.1 临时解决方案:禁用CQE
最直接的解决方法是禁用eMMC的CQE功能。可以通过以下方式实现:
- 修改设备树(DTS)文件:
dts复制&usdhc2 {
no-mmc-hs400;
no-mmc-hs200;
disable-cqe;
};
- 或者在启动参数中添加:
code复制mmc_core.use_cqe=0
禁用CQE后,系统启动恢复正常,不再出现卡死现象。不过这会带来约10-15%的存储性能下降,在频繁I/O操作场景下更为明显。
4.2 长期优化方案
为了兼顾性能和稳定性,我建议采取以下优化措施:
- 内核补丁升级:检查是否有针对dm-verity和CQE兼容性的上游补丁
- CPU电压调整:适当提高CPU核心电压,增强信号完整性
dts复制&cpu0 { operating-points = < /* kHz uV */ 1200000 1000000 800000 900000 >; }; - I/O调度器调优:尝试不同的调度器组合
bash复制echo "deadline" > /sys/block/mmcblk0/queue/scheduler
4.3 验证与测试
实施上述修改后,需要进行全面测试:
- 压力测试:模拟高负载I/O场景
bash复制
stress-ng --hdd 4 --hdd-ops 100000 - 启动稳定性测试:连续重启100次,记录失败次数
- 性能基准测试:对比修改前后的存储性能指标
测试结果显示,系统在禁用CQE后稳定性显著提升,虽然顺序读写性能有所下降,但随机读写性能影响较小,在大多数应用场景下可以接受。
5. 经验总结与避坑指南
5.1 关键发现
- dm-verity与CQE的兼容性问题在特定内核版本中存在
- 高iowait不一定表示存储设备故障,可能是软件层面的死锁
- 多进程同时阻塞在I/O操作上是死锁的典型表现
5.2 排查技巧
-
系统状态快速检查清单:
检查项 正常情况 异常情况 iowait% <5% >20% 进程D状态计数 0-2个 >5个 eMMC错误计数器 0 >0 -
实用调试命令:
bash复制# 查看阻塞进程 ps -eo state,pid,cmd | grep "^D" # 检查eMMC状态 cat /sys/kernel/debug/mmc0/ios # 获取进程堆栈 cat /proc/<pid>/stack
5.3 后续优化方向
- 评估升级到更新的内核版本(如4.19或5.x)的可能性
- 考虑使用f2fs文件系统替代ext4,可能获得更好的性能
- 针对特定负载场景优化I/O调度器参数
在实际部署中,我们最终选择了禁用CQE的方案,因为稳定性是首要考虑因素。对于性能敏感的应用,可以考虑分配更大的IO缓冲区或使用更高效的文件系统来补偿性能损失。
这个案例再次证明,嵌入式系统调试往往需要综合考虑硬件特性、驱动实现和上层软件的交互。通过系统化的排查方法和严谨的验证流程,我们能够有效解决这类复杂的系统级问题。