
清空记录
历史记录
取消
清空记录
历史记录

本文基于触觉智能RK3576开发板Purple Pi OH2的linux SDK讲解,内核出问题,线索从「日志」开始:启动日志、崩溃日志、设备树运行时状态、引脚复用、时钟与IO域,一层层往下。
本文为上篇,聚焦内核排障的完整链路串起来——先读 Panic Call Trace 定位肇事函数,再用启动日志时间戳分段找到耗时大户,最后通过设备树运行时对照法确认配置是否真的生效。下篇将深入pinctrl引脚、时钟与 IO 域,并讲透 dynamic_debug/ftrace/perf 三件套的打开方式。
先看日志(启动时间线、panic)确定大致方向,再按设备树→引脚→时钟逐层核对,最后用调试开关深挖。每层都有debugfs或日志作为「最终真相」。
Debian/Buildroot/Yocto等系统分别跑对应的check-*.sh。报错不是"玄学",而是这套检查脚本吐出来的结构化信息。报错信息里一般就带着解法:装哪个包、换哪个版本、改哪个目录权限。
panic只有5秒窗口:三步读出call trace
panic不可怕,可怕的是看不懂。call trace是内核崩溃时的"事故现场",读懂它就能把问题定位到具体驱动、具体函数甚至具体那一行。读call trace就三步:
①看崩溃原因(第一行"Kernel panic - not syncing"/"Unable to handle ...");
②找崩溃现场(Call trace最靠前、属于某个驱动/[模块]的那条,就是肇事函数);
③对照源码与Code:行确认崩在哪条指令。另外注意:本SDK内核开了 CONFIG_PANIC_TIMEOUT=5,panic后5秒自动重启——抓日志要快,或先改成0便于取证。
崩溃原因写在开头,常见两类:
原因行往往就是结论:rootfs 挂不上、某地址空指针、指令未对齐、栈溢出……先读它,别急着往下翻。
以下为ARM64内核标准崩溃输出格式示例:
[ 12.345678] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000020[ 12.345679] Mem abort info:[ 12.345680] ESR = 0x96000004[ 12.345681] EC = 0x25: DABT (current EL), IL = 32 bits[ 12.345682] SET = 0, FnV = 0, EA = 0, S1PTW = 0, FSC = 0x04: level 0 translation fault[ 12.345683] Data abort info:[ 12.345684] ISV = 0, ISS = 0x00000004[ 12.345685] CM = 0, WnR = 0[ 12.345686] user pgtable: 4k pages, 48-bit VAs, pgdp=0000000000abcd00[ 12.345687] [0000000000000020] pgd=0000000000000000, pte=0000000000000000[ 12.345688] CPU: 3 PID: 123 Comm: kworker/u8:2 Not tainted 6.1.99[ 12.345689] Hardware name: Rockchip (Device Tree)[ 12.345690] pstate: 60000005 (nZCv daif -PAN -UAO -TCO BTYPE=--)[ 12.345691] pc : my_driver_probe+0x30/0x100 [my_driver][ 12.345692] lr : platform_probe+0x48/0x90[ 12.345693] sp : ffff8000103fbd00[ 12.345694] x29 : ffff8000103fbd10[ 12.345695] x28 : ffff0000c1234567[ 12.345696] ...[ 12.345697] Call trace:[ 12.345698] my_driver_probe+0x30/0x100 [my_driver][ 12.345699] platform_probe+0x48/0x90[ 12.345700] really_probe+0xa4/0x2e0[ 12.345701] __driver_probe_device+0x78/0xd0[ 12.345702] driver_probe_device+0x40/0x170[ 12.345703] __device_attach+0xb8/0x1e0[ 12.345704] bus_probe_device+0xbc/0xc0[ 12.345705] ...[ 12.345706] Code: f9400a60 f9400001 91001020 f9400024 (f9400080)[ 12.345707] ---[ end trace 00000000abcdef01 ]---
逐字段看:
内核6.1.99(kernel-6.1/arch/arm64/configs/rockchip_linux_rk3576_defconfig)默认已开:
# 1. 确保串口全日志(见 01.05),把崩溃日志完整存下# 2. 临时关掉自动重启,便于慢慢取证(调试期建议)# 内核命令行加 panic=-1(无限等待),或改 CONFIG_PANIC_TIMEOUT# 3. 定位:trace 第一条带模块名的函数 → 对应驱动源码grep -n "Call trace:" crash.logsed -n '/Call trace:/,/end trace/p' crash.log# 4. 需要精确到行(有 vmlinux 时)kernel-6.1/scripts/faddr2line vmlinux my_driver_probe+0x30
读panic:先看原因行,再看 Call trace 第一条带 [模块] 的函数(肇事点),结合pc与Code:括号里的指令精确定位,最后去 drivers/ 源码核对。别忘了 CONFIG_PANIC_TIMEOUT=5 会自动重启,抓日志要快。
开机日志按时间线,拆6个阶段的标志性打印
系统能起来不等于启动没问题。这份日志自带时间戳,把它当"时间线"读:每个子系统该在什么时候出现、时间花在了哪里,一目了然。
本SDK内核开了CONFIG_PRINTK_TIME=y,每条日志自带[ 秒.微秒 ] 时间戳。相邻两条日志的时间戳之差,就是这段时间"花在哪":哪两行之间间隔大,哪里就是耗时大户或卡点。按"启动顺序地图"对照,几秒就能看懂一大屏日志。
下表为ARM64/Linux6.1内核启动的标准顺序(通用形态),各子系统会按这个次序出现:
关键技巧:相邻日志的时间差=这段代码花的墙钟时间。典型用法:
内核段看完,rootfs 用 systemd 的板子(本 SDK Debian/Bookworm)可以用 systemd-analyze 直接量化用户态启动:
systemd-analyze # 内核/initrd/用户态各自耗时systemd-analyze blame # 每个 unit 耗时从高到低systemd-analyze critical-chain # 关键启动链路(最慢的那条依赖链)
这样"开机慢"就能拆成:内核段慢(看 dmesg 时间戳)还是用户态慢(看 systemd-analyze)。
dmesg -T # 人类可读时间(相对启动秒 + 墙钟)dmesg | grep -iE "error|fail|oops|panic" # 先扫异常dmesg | grep -i "mmc\|ufs\|eth\|drm\|clk" # 看指定子系统dmesg | awk '$1 ~ /^\[[0-9]+\.[0-9]+\]/ {split($1,a,"[.]"); print a[1]"_"$0}' # 把日志按整秒分组看节奏① 按启动顺序地图,早期→中断/时钟/内存→存储→网络/显示→挂rootfs→init定位日志在哪个阶段;
② 用 [ 秒.微秒 ] 时间戳找出"间隔大"的两行=时间/卡点所在;
③ 用户态用 systemd-analyze blame 量化。这样"启动慢/卡"就能精确定位到具体子系统。
设备树改了却没生效?节点找不到的5个排查点
改了dts却没反应,要么编译/烧录没跟上,要么节点被继承链里的某个文件覆盖,要么 status/compatible不对。用运行时对照法逐一验证。
设备树是从dts编译成dtb、随boot.img 烧录、内核启动时加载的。
①内核加载的dtb对吗?
②新dtb烧上去了吗?
③节点 status开没开?
④compatible与驱动匹配吗?
⑤属性值是不是被继承链覆盖了?其中第 ⑤ 点最容易被忽略。
RK3576板级dts不是单文件,而是一棵include树。以触觉智能Purple Pi OH2的RK3576为例(kernel-6.1/arch/arm64/boot/dts/rockchip/):
上层文件用&节点{ ... }引用并覆盖下层节点属性。后 include 的同名覆盖会生效,覆盖顺序取决于include 顺序。所以"我改了 rk3576.dtsi 里的属性"却常被板级dtsi覆盖——这正是排查第 ⑤ 点的由来。
本板ido-evb7609-v1a-mipi.dts顶部#define HDMI 0,用预处理宏把 &hdmi/&dsi 等节点的 status 设为 okay/disabled:
#define HDMI 0...// MIPI 分支(HDMI=0)&hdmi { status = "disabled"; };&dsi { status = "okay"; };&dsi_in_vp1 { status = "okay"; };&route_dsi { status = "okay"; };这告诉我们:节点"存在"不等于"启用",调试时先看 status。/proc/device-tree/<节点>/status 是运行时最直接的证据。
设备树编译成 dtb 后,内核把它挂载到 /sys/firmware/devicetree/base(别名 /proc/device-tree)。这里看到的就是内核实际使用的、最终生效的设备树:
cat /proc/device-tree/model # 确认加载了哪块板的 dtbls /proc/device-tree/ # 顶层节点cat /proc/device-tree/<节点>/compatible # 驱动匹配用cat /proc/device-tree/<节点>/status # 是否启用xxd /proc/device-tree/<节点>/<属性> # 属性值(属性是二进制,用 xxd 看十六进制)# 对照源码:看实际生效值是否和你改的一致# 若不一致 → 被继承链覆盖(排查点⑤),去上层 .dtsi 找 <节点>{} 覆盖设备树"改了没生效"按 5 步查:① model 确认 dtb 加载对 → ② 重编重烧 boot.img → ③ 看 status → ④ 看 compatible 匹配 → ⑤ 用 /proc/device-tree 对照实际值、怀疑被继承链覆盖。前两步是流程问题,后三步是内容问题——而 /proc/device-tree 是唯一可信的"最终真相"。
定位到具体驱动之后,问题往往不在代码,而在引脚、时钟和电压域。下篇我们深入pinctrl引脚三件套、时钟树与IO-Domain,最后讲透 dynamic_debug/ftrace/perf三件套的正确打开方式。
触觉智能Purple Pi OH2开发板,基于触觉智能RK3576核心板SOM7609系列+底板架构,搭载4核A72+4 核A53+M0多核异构处理器,主频2.2GHz,集成 6Tops NPU,轻松应对 AI 推理、边缘计算与轻量级深度学习。配套完善SDK、丰富资料与Demo,适用于嵌入式工程师项目选型、预研开发。
触觉智能SOM7609系列核心板仅40.5×40.5mm超小尺寸,专为空间受限场景打造。基于RK3576/RK3576J SoC,集成高性能GPU与VPU,支持4K视频编解码与多屏异显;提供PCIe、USB3.2、双路千兆以太网、MIPI-DSI/CSI、eDP、CAN、SPI、I2C 等丰富接口,灵活扩展。支持Linux、Android、开源鸿蒙OpenHarmony等丰富系统,配套硬件设计指南、驱动源码与技术支持,助力产品快速落地。
