第 17 篇 安卓驱动调试神器,全流程问题排查指南
目录开篇先讲驱动调试的核心原则小白必须牢记一、基础中的基础内核日志查看驱动调试的眼睛1. 查看内核日志的常用命令2. printk 日志级别小白必懂3. 日志查看的核心技巧二、驱动调试必备工具每一个都能帮你解决大问题1. 设备树与 GPIO 调试工具2. 总线调试工具I2C/SPI3. 输入设备调试工具4. 音频调试工具5. 视频 / 摄像头调试工具6. 系统与进程调试工具7. 内核崩溃调试工具三、驱动开发常见问题万能排查流程问题 1驱动加载失败probe 函数根本不执行问题 2驱动加载成功但是硬件操作没反应问题 3内核崩溃 Oops/Panic系统重启问题 4驱动能工作但是安卓 App 调用不了四、小白调试避坑指南这些错误绝对不能犯结尾说两句大家好我是黒漂技术佬。前面我们已经完成了 GPIO、中断、PWM、I2C、SPI、输入、显示、音频、摄像头的全系列驱动开发后台很多兄弟说“佬我写驱动的时候经常遇到各种问题驱动加载失败、内核崩溃、功能不生效根本不知道从哪里查起有没有一套万能的排查方法”必须有驱动开发三分写七分调调试能力才是驱动工程师的核心竞争力。很多新手写驱动遇到问题只会瞎改代码没有系统的排查思路结果越改越乱小问题改成大问题。今天这篇我就把我多年的 RK 安卓驱动调试经验全部分享给你从基础的日志查看、常用调试工具到内核崩溃、驱动不加载、功能不生效的全场景排查流程给你一套万能的驱动问题排查方法论小白也能快速定位问题解决问题。开篇先讲驱动调试的核心原则小白必须牢记在讲具体的工具和方法之前先给大家讲 3 条驱动调试的核心原则这是我多年踩坑总结出来的能让你少走 90% 的弯路先看日志再改代码遇到问题第一件事绝对不是改代码而是看内核日志90% 的问题日志里已经明确告诉你哪里错了比如 GPIO 申请失败、中断注册失败、设备树匹配失败看日志就能直接定位问题不用瞎猜最小化验证分步排查不要一次性写一大堆代码然后编译烧录结果出了问题根本不知道是哪一行代码导致的。正确的做法是写一点验证一点分步调试比如写 GPIO 驱动先验证设备树配置再验证 GPIO 申请再验证中断一步一步来出了问题立刻就能定位先硬件后软件嵌入式驱动开发和硬件强相关很多问题根本不是软件的问题而是硬件接线错了、供电不对、芯片坏了。遇到问题先测量硬件确认硬件没问题再查软件不然你对着代码改一天也解决不了硬件接线错了的问题。一、基础中的基础内核日志查看驱动调试的眼睛内核日志是驱动调试最核心、最基础的工具没有之一。我们写的驱动里的printk()打印内核里的报错、警告、启动信息全都会输出到内核日志里遇到问题第一件事就是看内核日志。1. 查看内核日志的常用命令表格命令作用高频使用场景dmesg查看内核的全部日志从开机到当前的所有内核打印驱动加载后查看驱动的打印和报错最常用dmesg -w实时查看内核日志新的日志会实时打印出来调试驱动的时候实时看操作对应的日志比如按键按下、中断触发的日志dmesggrep 关键词 过滤日志只看和关键词相关的内容只看我们自己的驱动日志比如 dmesggrep hello_drv不用在一堆日志里瞎找dmesg -c清空当前的内核日志调试前清空日志只看本次操作产生的新日志更清晰cat /proc/kmsg实时查看内核的原始日志和 dmesg -w 类似dmesg 看不了的时候用这个命令cat /proc/kmsg /data/kmsg.log把内核日志保存到文件里日志太多屏幕显示不下保存到文件里慢慢分析2. printk 日志级别小白必懂很多新手会问“我在驱动里写了 printk ()但是 dmesg 里看不到打印是怎么回事”答案是printk 有日志级别只有日志级别高于控制台当前设置的级别才会打印出来。printk 的日志级别分为 8 级数字越小级别越高越重要表格级别宏定义含义使用场景0KERN_EMERG紧急事件系统崩溃系统崩溃前的紧急打印1KERN_ALERT必须立即处理的警告严重的硬件错误2KERN_CRIT严重错误硬件故障、驱动初始化失败3KERN_ERR错误普通的驱动错误比如 GPIO 申请失败4KERN_WARNING警告不影响功能的警告信息5KERN_NOTICE注意正常的、但需要注意的信息6KERN_INFO信息驱动正常的信息打印比如加载成功7KERN_DEBUG调试信息调试用的打印开发阶段用我们平时调试驱动用printk(KERN_DEBUG xxx)或者直接printk(xxx)默认级别是 4有时候控制台的级别设置的比较高就不会打印出来。解决方法临时修改控制台日志级别让所有日志都打印出来bash运行echo 8 /proc/sys/kernel/printk执行这个命令后所有级别的日志都会打印出来再也不会出现 printk 不显示的问题了调试的时候用高一点的级别比如printk(KERN_ERR 调试信息%d\n, val)肯定会打印出来调试完再改回去。3. 日志查看的核心技巧驱动加载失败先搜驱动名比如你的驱动叫 key_irq_drv加载失败了先执行dmesg | grep key_irq_drv看驱动的 probe 函数有没有执行有没有报错比如设备树匹配失败、GPIO 申请失败、中断注册失败日志里都会明确告诉你内核崩溃先搜 Oops/Panic内核崩溃的时候会打印 Oops 信息里面有崩溃的地址、调用栈、出错的代码行先搜 Oops 关键词找到崩溃的原因硬件操作没反应先搜相关子系统比如 I2C 通信失败搜i2cSPI 通信失败搜spi屏幕不亮搜dsi、vop看子系统的报错信息。二、驱动调试必备工具每一个都能帮你解决大问题除了 dmesg还有很多专门的调试工具能帮我们快速定位问题我把最常用的、新手必学的工具按使用场景分类给你整理好了每个都讲清楚作用和高频用法。1. 设备树与 GPIO 调试工具表格工具作用高频用法ls /proc/device-tree/查看设备树的节点和属性确认设备树修改是否生效1. 查看节点是否存在ls /proc/device-tree/key_irq02. 查看节点属性cat /proc/device-tree/key_irq0/statuscat /sys/kernel/debug/gpio查看系统里所有 GPIO 的状态包括所属驱动、方向、电平调试 GPIO 驱动的时候看 GPIO 有没有被正确申请方向是输入还是输出当前电平是高还是低一眼就能看出来gpioinfo更直观的查看 GPIO 信息和上面的命令类似新版内核里用比 /sys/kernel/debug/gpio 更直观2. 总线调试工具I2C/SPI表格工具作用高频用法i2cdetect -y 总线号扫描 I2C 总线上的所有设备看能不能扫到从机地址调试 I2C 设备的时候先执行这个命令比如i2cdetect -y 1扫 I2C1 总线上的设备扫不到就是硬件接线错了或者设备没上电i2cget -y 总线号 设备地址 寄存器地址读取 I2C 设备的指定寄存器的值确认能不能正常读写 I2C 设备比如i2cget -y 1 0x3c 0x00读取 0x3C 设备的 0x00 寄存器i2cset -y 总线号 设备地址 寄存器地址 值向 I2C 设备的指定寄存器写入值调试 I2C 设备的时候手动写寄存器测试设备是否正常spidev_testSPI 总线测试工具测试 SPI 通信是否正常调试 SPI 设备的时候用这个工具发送和接收数据确认 SPI 总线通信正常3. 输入设备调试工具表格工具作用高频用法cat /proc/bus/input/devices查看系统里所有的 input 输入设备确认设备是否注册成功调试按键、触摸屏驱动的时候看设备有没有正常注册支持哪些事件getevent安卓系统自带的 input 事件查看工具实时查看输入设备的事件调试按键、触摸屏的时候执行这个命令按下按键、触摸屏幕会实时打印对应的事件确认驱动有没有正常上报事件evtestLinux 标准的 input 事件测试工具比 getevent 更详细非安卓系统用功能和 getevent 类似4. 音频调试工具表格工具作用高频用法tinyplay简易的音频播放工具直接操作 ALSA 设备播放 wav 音频调试音频驱动的时候先不用管安卓系统用这个工具测试播放能播放说明驱动没问题问题在安卓 HAL 层tinycap简易的录音工具直接操作 ALSA 设备录音保存为 wav 文件测试录音功能能录音说明 ADC 和驱动没问题amixerALSA 混音器控制工具设置音量、切换输入输出通道调试音频的时候用这个工具设置 DAC/ADC 的音量切换耳机 / 喇叭输出aplay -l查看系统里的声卡和播放设备确认声卡有没有正常注册arecord -l查看系统里的录音设备确认录音设备有没有正常注册5. 视频 / 摄像头调试工具表格工具作用高频用法v4l2-ctlV4L2 设备的核心控制工具功能极其强大1. 查看视频设备v4l2-ctl --list-devices2. 查看设备支持的分辨率、格式v4l2-ctl -d /dev/video0 --list-formats-ext3. 抓取图像v4l2-ctl -d /dev/video0 --stream-mmap3 --stream-count1 --stream-to/data/test.yuvmedia-ctl媒体控制器工具查看和配置媒体链路调试摄像头、ISP 的时候用media-ctl -p查看整个数据流链路确认链路有没有打通6. 系统与进程调试工具表格工具作用高频用法cat /proc/devices查看系统里注册的字符设备、块设备确认设备号有没有正常注册调试字符设备驱动的时候看主设备号有没有正常注册lsmod查看系统里加载的内核模块动态加载模块的时候看模块有没有正常加载insmod xxx.ko加载内核模块动态加载驱动模块调试的时候用不用每次都编译内核rmmod xxx卸载内核模块卸载驱动模块重新加载新的模块strace跟踪进程的系统调用看用户空间程序调用了哪些系统调用哪里失败了调试上层应用和驱动的交互的时候看 open、read、write、ioctl 调用有没有成功返回值是什么快速定位问题lsof /dev/xxx查看哪个进程打开了设备文件看设备文件被哪个进程占用了7. 内核崩溃调试工具表格工具作用高频用法addr2line把内核崩溃的地址转换成对应的代码文件和行号内核 Oops 崩溃的时候用这个工具把崩溃的指令地址转换成对应的代码行一眼就能看到是哪一行代码导致的崩溃objdump反汇编内核模块查看汇编代码调试内核崩溃的时候用这个工具反汇编模块看崩溃地址对应的汇编代码GDB内核调试工具支持断点、单步调试高级调试用 JTAG 调试器配合 GDB单步调试内核驱动三、驱动开发常见问题万能排查流程我把驱动开发里最常见的 4 类问题整理了一套万能的排查流程遇到问题按这个流程走99% 都能解决。问题 1驱动加载失败probe 函数根本不执行这是新手最常遇到的问题按这个顺序排查第一步确认驱动有没有编译进内核或者有没有正常加载静态编译进内核看内核的.config文件对应的驱动配置是不是y没开的话驱动根本不会编译进内核动态加载模块用lsmod看模块有没有加载insmod的时候有没有报错有没有提示module verification failed内核版本不匹配。第二步确认设备树和驱动的 compatible 属性完全一致90% 的 probe 不执行都是这个原因设备树里的compatible xxx,xxx必须和驱动里的of_match_table里的字符串完全一致包括大小写、逗号、空格差一个字符都匹配不上probe 函数根本不会执行。第三步确认设备树节点的 status 是 okay设备树里的节点必须设置status okay如果是status disabled内核会忽略这个节点根本不会去匹配驱动。第四步看内核日志有没有匹配失败的报错执行dmesg | grep 驱动名看有没有驱动注册失败、匹配失败的报错比如Driver xxx was unable to register with bus_type platform看具体的报错信息。第五步确认驱动的注册函数有没有执行在驱动的module_init入口函数里加一句printk(KERN_ERR 驱动入口函数执行了\n)看内核日志里有没有这句打印确认驱动的注册函数有没有执行没执行的话就是驱动根本没编译进内核。问题 2驱动加载成功但是硬件操作没反应按这个顺序排查第一步先查硬件确认硬件没问题测量供电硬件有没有正常供电电压对不对测量引脚GPIO 引脚的电平有没有变化I2C/SPI 总线有没有信号输出测量时钟需要时钟的设备有没有正常的时钟输入确认接线有没有接错线、接反了比如 I2C 的 SCL 和 SDA 接反了GPIO 引脚接错了。第二步确认硬件资源申请成功没有被占用执行dmesg | grep 驱动名看 GPIO、中断、I2C、时钟申请有没有成功有没有报错request GPIO failed、request_irq failed。用cat /sys/kernel/debug/gpio看 GPIO 有没有被其他驱动占用用cat /proc/interrupts看中断号有没有被占用硬件资源被占用了你的驱动操作当然没反应。第三步确认寄存器配置正确时序符合要求I2C/SPI 设备确认寄存器地址、值、读写时序正确用 i2cget/i2cset 手动读写寄存器看设备有没有反应时序设备确认 PWM、I2S、MIPI 的时序参数、频率、极性正确和设备规格书一致。第四步确认驱动里的操作逻辑正确在驱动的关键位置加 printk 打印比如 GPIO 设置电平前后打印 GPIO 的电平值看有没有设置成功I2C 读写前后打印读写的值看有没有读写成功确认代码逻辑有没有问题。问题 3内核崩溃 Oops/Panic系统重启内核崩溃是新手最头疼的问题按这个流程排查一眼就能定位问题第一步保存完整的 Oops 日志内核崩溃的时候会在串口里打印完整的 Oops 信息第一时间把完整的日志保存下来里面有所有的崩溃信息。第二步找到崩溃的指令地址和调用栈Oops 日志里会有PC is at xxx这就是崩溃的指令地址还有Call Trace:这就是崩溃前的函数调用栈这两个是定位问题的核心。第三步用 addr2line 工具把地址转换成代码行用 addr2line 工具把崩溃的地址转换成对应的代码文件和行号命令格式bash运行aarch64-linux-gnu-addr2line -f -e vmlinux 崩溃地址其中 vmlinux 是编译内核生成的未压缩的内核文件在 kernel 目录下。执行这个命令会直接输出崩溃的函数名、代码文件和行号一眼就能看到是哪一行代码导致的崩溃。第四步分析崩溃的常见原因内核崩溃99% 都是这几个原因按优先级排查空指针解引用访问了 NULL 指针是最常见的原因数组越界访问了数组超出范围的下标导致内存越界中断上下文睡眠在中断处理函数里调用了会睡眠的函数比如 msleep、copy_from_user释放后使用使用了已经释放的内存野指针死锁自旋锁死锁导致内核卡死。问题 4驱动能工作但是安卓 App 调用不了按这个顺序排查第一步确认设备文件权限正确执行ls -l /dev/xxx看设备文件的权限是不是 777有没有读写权限安卓 App 没有权限的话根本打不开设备文件。临时解决chmod 777 /dev/xxx永久解决在驱动里用 udev 规则设置权限。第二步确认 SELinux 没有拦截安卓系统开启了 SELinux会拦截 App 对设备文件的访问临时关闭 SELinux 测试setenforce 0如果关闭后能正常访问说明是 SELinux 的问题需要给 App 添加 SELinux 权限。第三步用 strace 工具跟踪 App 的系统调用用strace -p 进程号跟踪 App 的进程看 App 调用 open、ioctl 的时候返回值是什么有没有报错比如Permission denied权限不足No such device or address设备号不对一眼就能看到问题出在哪里。第四步确认 HAL 层、JNI 层的代码正确先在串口里用命令行测试驱动确认驱动本身没问题问题就在上层的 HAL 层、JNI 层检查 HAL 层的 open、ioctl 调用有没有成功JNI 的函数名、参数类型是不是正确。四、小白调试避坑指南这些错误绝对不能犯绝对不要在驱动里用 printf ()printf 是用户空间的 C 库函数内核里不能用内核里用 printk ()很多新手会犯这个低级错误绝对不要直接操作用户空间的指针必须用 copy_from_user ()/copy_to_user () 拷贝数据不然内核直接 Oops 崩溃绝对不要在中断上下文里睡眠中断处理函数里绝对不能调用 msleep、kmalloc (GFP_KERNEL)、copy_from_user () 这些会导致睡眠的函数不然内核直接崩溃绝对不要忘记释放申请的资源GPIO、中断、内存、设备号驱动卸载的时候必须释放不然会导致内存泄漏、资源占用下次加载驱动会失败绝对不要关闭串口日志调试驱动的时候串口日志是你唯一的眼睛绝对不要关闭出了问题第一时间看日志绝对不要一次性写一大堆代码不做分步验证写一点验证一点不然出了问题根本不知道是哪一行代码导致的。结尾说两句这篇文章我们把安卓驱动开发的全套调试工具、万能排查流程、常见问题解决方案全部分享给你了。驱动开发从来都不是一次性写对代码而是通过调试一步步定位问题、解决问题的过程掌握了这套调试方法你遇到任何驱动问题都不会再手足无措。到这里我们的第五卷「进阶实战与系统调试篇」就全部完成了从输入设备、显示、音频、摄像头驱动到全套的调试排查方法你已经掌握了 RK 安卓驱动开发的所有核心技能能独立完成绝大多数外设的驱动适配和系统调试了。下一篇我们进入第六卷「项目实战与行业落地篇」的开篇综合项目实战基于 RK3568 的安卓智能门禁系统全栈开发把我们前面所有学到的知识整合到一个完整的工业级项目里从需求分析、硬件方案、驱动开发、HAL 层适配到安卓 App 开发全流程打通让你真正具备独立完成项目的能力。我是黒漂技术佬关注我带你零基础入门 RK 安卓驱动开发不踩坑。有任何驱动调试的问题评论区留言我都会一一回复。