拓冰建站拓冰建站
首页 / 资讯中心 / 正文

手把手教你Linux设备驱动开发:从字符设备到中断调试实战

去年年底就有出版社编辑找我约书稿说现在市面上讲Linux应用编程的书多真正能把设备驱动讲透的实战书太少。当时我其实有点犹豫因为驱动开发的门槛摆在那里写浅了没价值写深了又怕读者啃不动。结果拖了大半年这本《手把手教你学Linux设备驱动开发》终于正式出版了。拿到样书翻了一遍我反而松了一口气——它没走那种“贴满内核源码然后逐行念注释”的路子而是真的在教你怎么把驱动跑起来、怎么调通、怎么应对开发板上五花八门的硬件问题我觉得这才是大家缺的东西。这本书适合谁说实话定位非常清晰你已经会C语言和Linux基本操作比如编译内核模块、用makefile但是面对具体的硬件芯片、寄存器、中断、DMA时心里没底翻开代码不知道从哪里下手。写应用层的程序员想拓宽底层能力做单片机的工程师想切入Linux平台也都很合适。它不假设你懂硬件原理图但也不会把你当完全没写过代码的新手。为什么说驱动开发是Linux学习中“最难啃但最值钱”的部分很多人Linux学了半年shell命令玩得溜进程、线程、网络编程都摸过一遍然后卡在驱动这一关。原因很简单应用编程面对的是系统调用和库函数抽象层级高出错了有报错信息有core dump可以分析。驱动不一样你面对的是具体硬件一个寄存器配置错了系统可能什么都不显示、不打印、不报错就是个死连问搜索引擎都不知道该怎么描述问题。我当年第一次写GPIO按键驱动自以为照着书上的框架抄就够了结果模块加载之后按下按键系统直接重启。没有任何日志没有任何提示当时整个人是懵的。后面花了整整两天把设备树、中断号、pinmux配置全部翻了一遍才发现是寄存器地址写错了把另一组引脚的地址当成目标引脚的地址用了。这种错应用开发里很难遇到但驱动开发里天天都有。这本书的价值就在于它提前把这些坑给你标出来了。它不是告诉你“寄存器地址要写对”这种废话而是带着你从芯片手册里查到寄存器手把手解析每一位的含义再对应到代码里的宏定义和写值操作。这个能力才是驱动工程师和应用工程师真正的分水岭。从字符设备到内核调试书里的核心技术主线我看完整本书的目录觉得它的章节安排比较符合实际开发的学习曲线。全文花了大量篇幅讲字符设备驱动这是所有Linux驱动的根基因为它最直观open、read、write、ioctl和应用层的open、read、write一一对应理解成本最低。很多刚从应用转驱动的人在这里能很快建立信心。2.1 字符设备驱动的完整骨架搭建字符设备那几章写得比较踏实从一个最简单的hello设备开始然后一步步加上file_operations、设备号申请、cdev注册、class创建、设备节点自动生成。每一步都能跑每一步都能看到效果。比如设备号这块作者讲了两种分配方式动态分配和静态指定。静态指定适合你知道设备号没被占用的场景动态分配则更安全。书中代码用了alloc_chrdev_region这在内核里会帮你自动找一个未使用的设备号避免冲突。然后通过MKDEV和device_create在/dev下生成节点。整个过程不是光贴代码他会把mknod命令为什么需要主设备号和次设备号讲清楚然后告诉你现在为什么大多数驱动可以不用手敲mknod了因为devtmpfs和udev会自动搞定。我记得里面还有一个小的对比表把字符设备、块设备、网络设备的差异列得很清楚字符设备按字节流访问块设备按块访问且有缓冲区网络设备走socket接口。别看这个表简单很多人在面试的时候说半天说不清楚背概念和真理解是两回事。2.2 并发与竞争的处理思路驱动开发里最容易被初学者忽略的就是并发控制。应用层的多线程编程你已经知道要加锁驱动里面对的并发问题比这更复杂因为除了多个进程同时访问还有中断上下文和内核线程的并发访问。书里把互斥锁、自旋锁、原子变量、读写锁的使用场景和差异都做了对比。核心区分点在于锁保护的临界区会不会睡眠。如果在临界区里调用了一个可能睡眠的函数比如copy_to_user那就只能使用互斥锁或信号量。如果是在中断上下文或者临界区很短、不能睡眠的场景就要用自旋锁或者原子操作。这个选择一旦搞错轻则死锁重则系统崩溃而且问题还极难复现。作者给了一个很实在的建议写驱动的时候先把并发模型画出来搞清楚哪些路径会被同时执行、哪些路径会睡眠再决定用哪种锁。先画图再写代码看着保守实际能省下大量调试时间。2.3 中断机制和下半部机制中断可能是整本书里最劝退新手的一章因为它涉及到与硬件时序相关的概念跑出来不如字符设备那样有成就感。但作者处理得比较巧妙直接从一个按键中断的例子切入注册irq、编写中断处理函数、处理按键抖动、使用工作队列把业务逻辑放到下半部。中断处理函数的要求极其苛刻尽量短小、不能睡眠、不能调用可能导致阻塞的函数。但是真实驱动里中断来了往往要处理很多事。怎么解决这个矛盾就是把耗时长的操作推迟到“下半部”。书中主要讲了workqueue和tasklet两种机制并且说明了它们在新时代内核中的一些变化。比如tasklet现在在新版本内核里正逐步被更推荐的延时机制替代作者很贴心地标注了在内核哪个版本之后发生了变化。这种对内核版本演进敏感性的把握我觉得是很多旧书上找不到的。不知道怎么搭开发环境书里这部分讲得很透学驱动最大的拦路虎不是代码难写而是很多操作本身是“环环相扣”的你手上有一块开发板但是板子上跑的内核和你电脑上编译的内核版本不一致导致模块加载报version magic错误然后你一头雾水不知道问题出在哪。书里用一整章专门讲环境准备从宿主机Linux环境搭建、交叉编译工具链配置到内核源码获取与编译再到通过TFTP/NFS方式启动开发板每一步都有详细命令和预期输出。我看到有些读者反馈说光是照着书把编译环境搭建好、让第一个“hello module”在板子上跑起来就已经觉得这本书值回票价了。这话听着夸张但真实。因为很多驱动学习者不是死在知识点上而是死在环境上。环境不通后面的所有代码都是纸上谈兵。这里特别要说一下作者对Ubuntu下交叉编译工具链的讲解。不同开发板厂商提供的工具链版本不同、命名规则不同初学者经常在设置环境变量那边就卡死。作者不仅给了arm-linux-gnueabihf-gcc这类常见工具链的安装方法还解释了为什么需要交叉编译——因为开发板的CPU架构和PC不同PC的gcc编译出来的程序无法在ARM上运行。这些底层逻辑一旦想清楚工具链再换什么版本你都不会害怕了。3.1 内核模块的加载与调试技巧模块加载看着简单其实就是insmod一个文件的事但实际遇到的问题远不止version magic。书里专门列了一个“模块加载失败排查清单”我看了一下都是实打实的案例module verification failed内核开启了强制模块签名验证需要关闭或者签名disagrees about version of symbol xxx内核符号版本不匹配常见于内核升级后没有重新编译模块Unknown symbol xxx函数未导出需要查看内核源码里的EXPORT_SYMBOLInvalid module format架构不对或者内核源码路径不对编译时用了错误版本的头文件这几类问题每一条都配了错误log和排查步骤。这种“看日志找问题”的能力恰恰是自学者最缺的。很多人拿到错误以后会慌因为看的书里没讲过这个错该怎么处理。作者能把错误信息逐行拆开指出关键字在哪、对应的原因是什么这就是实战经验带来的底气。从看懂代码到自己写实操建议和避坑指南我发现很多自学者有一种误区觉得把书上的驱动代码抄一遍跑通了就算学会了。其实这只是第一步离自己写驱动还差得远。这本书的好处是每个章节的例程都留了“改造”空间你需要按照后面的思考题自己修改功能比如改IO口的编号、换一种实现方式、增加一个功能这样才能真正把代码变成自己的能力。4.1 建议带着硬件手册一起读我强烈建议读这本书的时候手上一定要有一份对应的芯片参考手册。书里讲GPIO子系统的时候会提到某个寄存器的bit3是方向控制位bit4到bit6是电气属性配置。你光看代码可能觉得云里雾里但翻开芯片手册找到那一页你会突然明白代码里的每一个数字是从哪里来的原来驱动和硬件之间就是通过这一位一位的寄存器值联系起来的。这种打通抽象概念和具体硬件的过程可以说是驱动开发中最有成就感的一刻。4.2 不要忽略设备树的学习设备树可能是这本书里另一块重点内容。现在ARM Linux内核几乎全面转向设备树来描述硬件如果你不会看设备树后面基本寸步难行因为内核不知道你的板子上有哪些设备、中断号是多少、GPIO怎么用全靠设备树告诉它。书中对设备树的讲解不会一上来就铺开全部语法而是从“让一个LED能亮起来”这种最小目标切入教你写节点、配置属性、编译设备树、烧录到板子。然后再逐步深入中断控制器、pinctrl、时钟等等。我尤其喜欢它把设备树编译和内核编译分开讲的思路——编译设备树就是一条简单的dtc命令不要跟内核源码编译混在一起。很细节但真的能避免很多新手困惑。4.3 调试手段的多样性调试驱动的方式和应用层完全不同。printf不好使因为内核日志有自己的打印机制printk并且级别分明KERN_EMERG到KERN_DEBUG哪个级别的日志会显示在控制台取决于内核参数loglevel。书里把这些参数和命令都说了你通过dmesg查看的时候遇到某个级别的日志没显示就知道不是代码没执行而是日志级别被过滤掉了。我特别看重这本书对于/proc和/sysfs接口的讲解。驱动开发不能光靠printk很多信息要通过这些虚拟文件系统暴露给用户空间这样你才能观察驱动的状态、修改驱动参数。作者用一个led驱动当例子教你通过sysfs导出属性文件然后在用户态使用echo命令控制LED闪烁这个演示我试了一下整个过程和书里描述完全一致。这种感觉特别好因为你清楚地知道自己写的驱动和用户态是怎么关联起来的而不是黑盒操作。内核版本一直变这本书的知识会不会过时这是个很现实的问题。Linux内核迭代速度极快大概每两三个月就发布一个大版本API变化也确实频繁。经常有人问学驱动开发是不是得等内核稳定了再学我觉得恰恰相反正因为内核变化快你才更应该掌握那些不变的东西字符设备模型、中断处理流程、并发管理思想、设备模型架构。这些设计理念是几十年来Linux内核开发者逐步沉淀下来的虽然函数细节在变但框架和思路很稳定。这本书在写作时也是把握住了这个度核心的框架讲得比较稳对易变的部分则标注了版本信息并给出了在新版本中推荐的替代方式。所以即使你用的内核版本比书里新上许多也不会出现完全看不懂的情况。我在学习过程中刻意用了一个比较新的内核版本实测书中大部分示例代码修改最小依赖后都能正常编译运行。坚持手动把代码敲一遍而不是复制粘贴最后再说一个我个人的体会。现在开发资源非常丰富代码可以从网上下载或者直接把书里的配套源码拿过来用。但我还是建议每个例程都要自己手动敲一遍。哪怕你是照着书敲这个过程也是在训练你的肌肉记忆。驱动代码不像应用代码那样逻辑外显它大量使用宏、函数指针、结构体嵌套如果你只是在脑子里过一遍很容易产生“我懂了”的错觉实际一写就卡住。手动敲代码时遇到编译报错比如某个结构体成员不存在你才会去查内核版本对应的源码才会去搜这个字段是不是被改名了。这个过程虽然痛苦但积累的排查经验非常宝贵。这就像练武术你看了十年招式不上手打一遍永远不知道自己哪里会露破绽。这本书配合手动实操一起来效果最佳既是入门地图也是答疑手册。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门