尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

手把手教你学Linux设备驱动开发:从字符设备到中断实战 《手把手教你学Linux设备驱动开发》正式出版算是我最近收到的最有分量的消息之一。这几年Linux内核人才缺口一直很大尤其是设备驱动方向会写业务代码的人多能真正把驱动层啃下来的人少。这本书从项目实战角度切入把字符设备、中断、并发、设备树这些硬骨头一块块拆开讲正好补上了很多嵌入式工程师从应用层往内核层跨越时的那段空档。不管你是正在做嵌入式Linux项目、想转内核方向的服务器开发还是准备应对Linux面试题里那些高频考点这本书都值得放在手边当工具书翻。1. 内容整体设计与思路拆解1.1 为什么设备驱动开发是Linux学习的分水岭说到Linux学习很多人第一反应是Linux常用命令、系统安装、环境配置这些入门操作。这些当然重要但它们属于“用Linux”的范畴而设备驱动开发属于“写Linux”的范畴这是两个完全不同的层次。会敲命令、能部署服务只能说明你熟悉了Linux的外围操作而当你开始写驱动意味着你要和内核打交道要理解进程调度、内存管理、文件系统、硬件中断这些底层机制这时候才算是真正摸到了操作系统的内核。我见过太多搞了三五年服务器运维的人Linux常用命令倒背如流什么top、grep、awk、sed用得飞起可真让他写一个简单的外设驱动整个人就懵了。原因很简单应用开发和内核开发是两种完全不同的编程思维。应用层程序崩溃了大不了重启一下进程内核模块出了问题可能直接导致整个系统宕机而且连日志都没法正常打印。这种“一脚踩进内核态”的体验是任何用户态编程都给不了的。设备驱动开发之所以是分水岭还在于它要求你同时具备硬件视角和软件视角。你要会看原理图、读芯片手册、理解寄存器的意义同时还得懂内核的框架设计、接口规范、并发模型。这种双重能力在市面上非常稀缺也直接反映在薪资水平上。1.2 这本书的内容架构和编排逻辑《手把手教你学Linux设备驱动开发》这本“硬核宝典”没有走传统教材那种先堆概念再讲例子的老路。它的编排思路非常实战导向从最简单的模块加载开始一步步过渡到真实硬件驱动的编写每一章都对应着实际工作中会遇到的具体问题。全书在章节安排上有一个很明显的梯度设计。第一部分带着你把开发环境搭起来并且是用一种让你理解内核源码组织方式的方法来搭不是简单说“apt install”就完事了。第二部分从字符设备驱动切入这是所有驱动类型里最容易理解、也最适合用来建立信心的入口因为它不依赖具体硬件可以在开发板上直接实验。第三部分深入到中断、内核并发、内存管理这些内核开发者真正天天打交道的机制。最后一部分用真实硬件的综合项目把这些知识点串联起来相当于一次实战演习。这个编排思路的核心逻辑是“每一章都能运行、每一章都有成果”。读者不是在被动地接收知识而是每学完一章就能在自己的板子上看到实际效果。这种即时反馈机制对学习驱动开发特别重要因为内核开发的知识点相对抽象如果没有看得见摸得着的实验结果很容易学着学着就放弃了。1.3 为什么说是“硬核宝典”这本书解决的三个痛点市面上讲Linux的书籍不少但真正称得上“硬核”的并不多。在我看来这本书能配得上“硬核宝典”这四个字是因为它精准地解决了Linux驱动学习路上最让人头疼的三个痛点。第一个痛点是资料碎片化。很多开发者学驱动的方式是今天看一篇博客、明天查一段内核源码、后天翻一个论坛帖子知识结构零零散散形不成体系。这本书通过完整的章节设计把零散的知识点串成了一条清晰的进阶路径。第二个痛点是理论脱离实战。很多教程在讲内核机制的时候只是一味地罗列概念和函数接口你看完了好像都懂了可真到写代码的时候又无从下手。这本书的做法是每个机制都配一个可以真正运行验证的实验让你在动手的过程中理解理论。第三个痛点是硬件门槛高。驱动开发免不了要碰硬件但并不是每个人都有开发板。书中对这个问题做了很好的处理很多实验可以通过QEMU等虚拟化方式完成你甚至不需要真实硬件就能完成大部分入门学习。2. 核心细节解析与实操要点2.1 环境搭建别急着买开发板先把内核跑起来很多初学者一上来就纠结“我要买哪块开发板”这件事其实应该往后放。驱动的本质是操作硬件但硬件只是载体真正核心的是对内核机制的理解。没有开发板照样可以把模块编程、字符设备、并发控制这些基础内容学好。我个人的建议是分两步走。第一步先在你的电脑上跑一个虚拟机或者直接用WSL2编译一个自己的内核哪怕只是改个配置、加一行打印这个过程能帮你理解内核编译的完整流程。第二步再通过QEMU模拟一块开发板比如用qemu-system-arm模拟vexpress开发板这样你不需要买任何硬件就能体验交叉编译的流程。等你把这两步都走顺了再考虑入手一块真实的开发板不迟。这里有一个关键细节内核编译不同于普通的应用程序编译它有几个很特别的步骤。首先是配置阶段内核源码目录下执行make ARCHarm vexpress_defconfig这一步是生成一个默认的.config配置文件。接着如果要修改配置就执行make ARCHarm menuconfig这时候会有个图形化的配置界面。最后才是真正的编译执行make ARCHarm zImage -j4如果是设备树的话还需要编译dtb文件。这个过程走一遍你就能理解内核镜像和设备树的关系了。搭环境的时候还有一个特别容易踩的坑就是开发板和主机之间的文件传输。新手常常会在NFS挂载和TFTP下载这两个方案之间犹豫不决。我的建议是优先用NFS因为它不需要每次修改程序都重新烧写镜像直接在开发板上通过网络挂载主机的目录代码改了立即生效调试效率会高很多。2.2 第一个驱动模块理解module_init和module_exit的真实含义写Linux驱动的人和写应用程序的人第一个思维差异体现在程序的入口函数上。应用程序的入口是main函数一个程序只有一个main干净利落。而内核模块没有main函数它靠的是module_init和module_exit这两个宏来告诉内核“我的初始化和退出函数在哪里”。很多新手会问为什么不直接叫init_module和cleanup_module其实这就是历史演进的问题。早期的内核确实直接用这两个函数名但后来为了让程序员可以自己命名单个模块的初始化函数内核引入了这两个宏。当你写module_init(hello_init)的时候实际上是把这个宏展开成一段链接脚本指令把hello_init这个函数的地址放到内核镜像的一个特定段里。这个过程我用一个生活化的类比来解释。想象一个大型酒店每个客房门口都有一个卡槽客人入住的时候需要把房卡插进卡槽里房间的灯才会亮。module_init就是把“打开灯光”这个动作注册到卡槽上内核启动或者模块加载时就会执行这个动作。module_exit则是离店时把房卡拔出来让房间恢复待客状态。写第一个驱动的时候有几个细节需要特别注意。首先是打印函数要用printk而不是printf而且printk的日志级别很关键比如printk(KERN_INFO Hello World\n)和printk(KERN_ALERT Hello World\n)的显示效果完全不同。其次在模块加载函数里至少要初始化一个设备号并注册一个字符设备否则这个模块即使能加载成功也没有实际意义。另外还有MODULE_LICENSE(GPL)这个声明别看它只是一行代码如果不声明GPL内核会有一些API不让你用因为那些API是导出给GPL模块使用的。2.3 字符设备驱动file_operations结构体是沟通用户和内核的桥梁字符设备驱动是Linux驱动开发的基本功而file_operations结构体则是基本功里的核心。这个结构体定义了驱动提供给用户空间的“接口函数指针”比如open、read、write、release、ioctl等。用户态程序调用open()打开一个设备文件时内核VFS层会找到这个设备对应的file_operations然后调用你实现的open函数。我打个比方file_operations就像餐厅里的菜单菜单上的每道菜都有一个名字和一个对应的厨房做法。顾客点的菜名就是用户态的函数调用厨房做法就是你实现的驱动函数。内核的任务就是把“顾客点的菜”翻译成“厨房的做法”。实操的时候一个容易出问题的点是设备号的管理。Linux中用主设备号来区分设备类型用次设备号来区分同类型的多个设备。注册字符设备有两种方式老内核用register_chrdev新内核推荐用alloc_chrdev_region动态分配设备号。从学习角度两种方式都值得理解但实际写驱动我更推荐动态分配因为可以避免手动分配冲突的问题。还有一点必须强调设备节点的创建。有些驱动测试程序会直接使用mknod手动创建设备节点比如mknod /dev/hello c 240 0这样也能用但不够优雅。推荐的方式是在驱动中自动创建设备节点核心步骤包括用class_create创建一个设备类然后在probe或模块加载函数中调用device_create创建设备。这样当驱动加载成功后/dev目录下会自动出现对应的设备文件用户态程序就能直接操作了。2.4 中断处理和Linux内核并发控制驱动开发的深水区如果前面的内容还算轻松那中断处理和并发控制就是真正考验功力的地方。很多开发者写驱动程序“正常工作时一点问题没有”但一上高负载就各种灵异现象十有八九是中断处理和并发控制没做好。先说中断的注册和处理。注册中断首先需要一个中断号这个中断号可以是写死的也可以从设备树里获取。推荐的做法是从设备树中获取因为不同板子的中断连接方式很可能不一样。在设备树里配置interrupt-parent和interrupts属性然后在驱动代码中用platform_get_irq或者irq_of_parse_and_map来获取中断号最后用request_irq注册中断处理函数。中断处理函数尤其要注意一个原则就是在中断上下文里不能做休眠操作。因为中断处理程序运行在中断上下文中它不能被调度器换出所以不能调用那些可能睡眠的函数比如copy_to_user、kmalloc(GFP_KERNEL)、mutex_lock等。传统的中断处理分顶半部和底半部现在内核更推荐使用线程化中断处理通过request_threaded_irq把一个处理函数放到内核线程中这样就可以睡眠了。并发控制是另一个大坑。Linux内核是典型的多任务并发系统SMP对称多处理环境下多个CPU可能同时进入同一个驱动函数如果不加保护就会出现资源竞争和内存不一致的问题。常用的保护机制有自旋锁和信号量自旋锁用于保护临界区的短期访问它不会睡眠但会忙等待信号量允许睡眠但如果临界区保护不当可能引入调度延迟。我自己写驱动的习惯是优先考虑无锁设计其次才是用锁。很多并发问题可以通过简化数据结构、采用每CPU变量、或者使用原子操作来避免锁虽然简单直接但也可能造成性能瓶颈和死锁风险。理解这些机制的原理然后根据场景选择合适的方案这才是内核开发者的核心能力。3. 实操过程与核心环节实现3.1 搭建一个可复现的驱动开发实验环境驱动开发环境搭建这件事看着简单其实有很多可以优化的细节。如果你的目标只是学驱动我强烈建议直接在当前Linux机器上开一个虚拟机安装Ubuntu Server版本不需要图形界面省下来的资源全部给编译。装好系统之后第一件事不是急着装驱动开发工具而是先确认内核头文件是否安装到位。执行uname -r查看当前内核版本然后安装对应版本的内核头文件。这一步非常关键因为编译内核模块需要用到内核源码树中的头文件、Makefile和生成的配置信息如果内核头文件与当前运行内核版本不一致编译出来的模块会因版本信息不匹配而无法加载。在正式编译一个模块时Makefile的写法也有讲究。一个最精简的模块Makefile看起来像这样obj-m : hello.o KERNELDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) clean这里的核心是-C参数它告诉make先进入KERNELDIR这个内核源码目录读取那里的Makefile然后在M参数指定的目录中编译模块。很多人不理解为什么模块源码在自己的目录却要到内核目录里去执行make。原因是内核模块的编译必须使用内核构建系统提供的复杂规则和变量这些规则不在你的模块目录里而在内核源码树中。模块编译成功后会生成一个.ko文件。加载模块用insmod卸载用rmmod查看模块信息用modinfo。一个常见的调试技巧是先用modinfo查看模块的依赖关系、作者信息、描述信息再用insmod加载然后用dmesg查看内核日志的输出。如果模块加载失败dmesg最后几行通常能告诉你失败的直接原因。3.2 从零实现一个完整的字符设备驱动并编写测试程序纸上得来终觉浅驱动开发一定要自己动手写一个完整可用的字符设备。我建议从一个简单的虚拟设备开始这个设备的逻辑是用户态写入一串数据驱动保存下来用户态再读取时驱动把保存的数据原样返回。先设计数据结构。一个全局缓冲区加上一个表示缓冲区大小的变量再加一个并发控制的互斥锁。三样东西就构成了最简单的设备状态。然后是函数实现open函数里递增一个打开计数器release函数里递减计数器并做清理。read函数的核心是把内核缓冲区的数据拷贝到用户空间用copy_to_user系统调用write函数则用copy_from_user把用户态数据拷贝到内核缓冲区。在实现read和write时有一个很隐蔽的坑是缓冲区越界。因为用户空间传给驱动的buf是提前分配好的用户态缓冲区驱动开发者必须确保copy_to_user不超过这个缓冲区的长度否则会出现内核态内存越界访问轻则数据错误重则直接造成内核崩溃。设备注册方面我建议直接使用设备树描述设备信息在驱动中通过platform_driver_register注册一个平台驱动。设备树里定义兼容字符串和内存映射区域驱动通过of_match_table来匹配设备树中的节点。这种设备模型称为platform总线模型是现代Linux内核中挂载外设的推荐方式。测试程序的写法也有讲究。写一个简单的应用程序用open打开/dev/hello设备write写入一串字符在read读回来通过printf打印到终端。如果一切正常你就能看到写入和读出的数据一致。这个测试程序本身就是一次“用户态到内核态”的完整旅程你在应用层调用一个write最终会穿越VFS、经过内核的系统调用处理、到达驱动函数然后通过硬件操作或内存读写完成数据交换。3.3 通过设备树配置硬件资源并对照验证设备树是嵌入式Linux开发中绕不开的话题。在设备树出现之前内核和板级硬件信息的耦合非常紧密每换一块开发板往往要修改大量C代码来重新描述硬件资源。设备树用数据描述代替代码描述后硬件配置信息从内核源码中剥离出来变成了一个独立的dtb文件。学习设备树我建议直接上手改。以最常用的vexpress开发板为例在设备树源文件里添加一个自定义设备的节点包含reg属性来设置寄存器地址范围、interrupts属性来配置中断资源。然后在驱动中用platform_get_resource和platform_get_irq分别获取地址资源和中断号资源。一个实操中很容易搞混的概念是设备树中的#address-cells和#size-cells。这两个属性决定了reg属性中address和size字段的长度。比如#address-cells 1和#size-cells 1表示地址是32位宽大小也是32位宽。如果配置不对驱动读取到的寄存器地址会完全错乱而且这种错误非常隐蔽因为编译设备树时不会报错运行时的printk打印出来的地址也“好像”是对的但实际访问的却是不存在的地址。设备树的调试手段也需要掌握。最常用的是在设备树中给节点加status okay状态以及通过内核启动参数中加“regulator_use_dummy_regulator”之类来规避某些资源缺失问题。另外编译设备树时用dtc工具生成的dtb文件可以反编译成dts格式方便你验证自己的改动有没有生效。在书中这部分内容的实验案例非常详实值得逐行对照实践。3.4 中断实验按键中断驱动的完整实现中断是驱动开发中体现“硬件思维”最明显的部分。以最常见的按键中断为例按键连接到一个GPIO引脚用户按下按键时GPIO电平发生变化这个变化触发中断CPU暂停当前任务去处理中断处理例程。接线方面按键一端连接到开发板的GPIO另一端接地然后配置GPIO为输入同时使能内部上拉电阻。这样按键未被按下时GPIO为高电平按下后变为低电平产生一个下降沿触发的中断。驱动代码中request_irq函数的参数需要正确理解。第一个参数是中断号这里不是GPIO编号而是gpio_to_irq函数转换出来的IRQ号第二个参数是中断处理函数第三个参数是中断标志这里设置为IRQF_TRIGGER_FALLING表示下降沿触发第四个参数是设备名称第五个参数是传递给中断处理函数的设备标识这是为了让同一个中断处理函数能服务多个设备。中断处理函数内部不能做耗时操作。我的经验是使用一个原子变量或者一个工作队列来延缓中断事件的后续处理。更通用的做法是使用struct work_struct和schedule_work在中断处理函数里只做最核心的硬件确认和状态记录然后安排一个工作队列来执行数据读取、通知用户态等操作。验证按键中断的方法是使用内核的gpio-keys驱动框架或者通过/proc/interrupts文件观察中断触发次数。在终端执行cat /proc/interrupts能看到每个中断号被触发的次数这是排查中断没有触发的最直观手段。4. 常见问题与排查技巧实录4.1 模块加载失败的原因排查汇总内核模块加载失败是最常见的技术门槛我总结了一套排查思路帮助读者快速定位问题。当insmod报“Invalid module format”错误时最常见的原因是模块编译时的内核版本与当前运行内核版本不一致尤其是内核头文件没安装对或者跨越了大版本编译。当insmod报“Unknown symbol in module”错误时说明模块中使用了一个内核没有导出的符号或者依赖的另一个模块没有被加载。用nm命令查看.ko文件中的未定义符号再用grep搜索内核符号表可以快速确认符号是否存在。还有一种常见情况是模块加载时报设备号冲突。如果你用register_chrdev手动指定了主设备号而这个设备号已经被系统占用加载就会失败。解决办法是改用动态分配设备号。用cdev_add的方式注册配合mknod或自动创建节点能有效避免这个坑。调试模块加载问题时dmesg是首选工具但还要配合correct的日志级别。有些printk的日志级别低于系统当前设置的console_loglevel内核日志不会在终端显示但你仍然可以通过dmesg命令查看完整的内核环形缓冲区信息。4.2 内核崩溃后的日志分析方法内核崩溃是驱动开发中的“大事故”。我的经验是如果内核直接panic先不用慌关键是保存并分析崩溃日志。开启kdump和crash工具可以抓取崩溃时的内核转储文件但大多数学习场景下靠dmesg的输出配合objdump反汇编也能定位到出错的大概位置。一个常见的崩溃场景是驱动中使用了一个空指针。比如在probe函数里如果没有正确获取设备资源后续代码访问了无效地址就会触发“Unable to handle kernel NULL pointer dereference”错误。日志中会给出出错地址、指令指针、调用栈等关键信息。通过addr2line工具将指令地址转换为源码行号能大大加快定位速度。另一个常见的崩溃原因是缓冲区溢出尤其是在使用copy_to_user时没有正确计算长度。这类问题的隐蔽性在于它往往不会在当下立即崩溃而是破坏了一小段内核内存然后在某个遥远的时刻引发古怪错误。对付这类问题的办法是定期使用内核提供的调试工具比如KASAN内核地址消毒器和UBSAN未定义行为消毒器它们能在内存被破坏的第一时间捕捉到异常。对于初学者我建议在刚开始学习时花时间熟悉panic日志的格式。哪一个字段代表出错地址哪一个字段是调用栈哪一段是被访问的内存信息这些看懂之后查驱动bug的效率会成倍提升。4.3 驱动调试验证三板斧printk、proc和devicetree内核调试不像应用调试那样有断点器和单步执行它更依赖日志和状态观测。我最常用的三个手段同时也是书里重点讲解的就是printk、/proc文件系统和设备树的配合。printk一定要用对日志级别比如KERN_INFO、KERN_DEBUG、KERN_ERR等。很多人习惯在所有场合都用printk(KERN_INFO ...)但在调试时有些关键错误信息被普通日志淹没导致排查困难。正确的做法是错误信息用KERN_ERR调试信息用KERN_DEBUG并在运行时通过dmesg -n 8设置终端显示的日志级别灵活控制输出。/proc文件系统是一个特别有用的调试接口。在驱动中实现一个procfs的read回调函数把驱动的内部状态比如当前缓冲区大小、打开计数、中断触发次数等信息格式化成文本输出。用户态直接cat /proc/my_driver_info就能看到驱动状态这个调试手段比printk更直接、更结构清晰。我在实际项目中经常用这个方式调试复杂的驱动逻辑效果非常好。再有就是设备树的配合。在设备树中加一些临时属性比如把某个寄存器的地址值放在节点里驱动启动时读取并打印就能验证设备树与驱动的匹配关系是否正确。你也可以在设备树中为同一设备保留多个版本配置通过修改dtb来测试不同参数下的驱动行为不用重编译内核调试效率会高很多。这三个手段配合起来比任何图形化调试工具都实用。5. 从入门到进阶路径规划与行业应用展望5.1 Linux设备驱动开发的学习路径建议书里每章都附带了“学习目标”和“练手项目”这本身就是一条经过打磨的学习路径。如果让我从外部视角给一个补充建议我会说不建议一上来就读内核源码而是要带着问题读。比如先遇到一个“模块无法加载”的问题再去查内核模块加载机制相关源码这时候的理解深度会完全不一样。初学阶段我建议按照以下顺序推进先从“模块编程”开始学会写一个能加载和卸载的内核模块然后过渡到“字符设备驱动”配合一个完整的用户态测试程序理解设备文件和驱动函数的关系接着进入“并发控制”学会用互斥锁、自旋锁保护共享资源再往后是“中断编程”理解中断上下文和延迟处理机制最后用设备树把硬件资源描述和驱动代码对接起来。每个阶段都不要贪多一个实验做透了再进入下一个。驱动开发最忌讳的是“什么都想学但什么都没亲手跑通”。能做到每学完一个知识点都能独立写出可运行代码的人通常三个月左右就能形成内核开发的完整知识框架。遇到不懂的地方怎么办我的经验是“三查一动手”先查内核文档再查内核源码注释其次查网络资料最后一定要自己动手写代码验证。内核源码是最好的教科书很多问题其实现成的答案都藏在源码注释和Documentation目录里只是很多人被源码规模吓住了不敢翻开而已。5.2 驱动开发技能在嵌入式、服务器和行业软件中的迁移价值有人可能会问学设备驱动开发如果不能直接换一份驱动开发的岗位是不是就白学了这个想法大错特错。驱动开发的思维方式在很多技术岗位中都有巨大的迁移价值。在嵌入式Linux领域驱动开发是系统工程师的核心技能之一从智能硬件到工业设备都离不开BSP工程师来解决外设适配、内核裁剪、启动优化问题。在服务器领域很多高性能网络项目的性能瓶颈最终都需要深入内核来优化协议栈和中断负载均衡理解驱动层有助于更好地解决这些底层性能问题。在云计算和虚拟化方向设备直通、虚拟化IO的加速也与设备模型紧密相关。即使在传统应用开发中理解内核的工作原理也能带来很多优势。比如排查应用性能问题、处理内存泄漏、优化IO路径这些能力在招聘市场上都是丰厚加分的。这也是为什么Linux面试题中关于内核、中断、设备模型的题目越来越多——企业真正缺的是能深入底层的工程技术人才。我注意到一个趋势近年来国内在芯片和操作系统基础设施方面投入越来越大而芯片底座的生态适配、操作系统原生的驱动开发人才正是这个链条上最稀缺的短板。学驱动开发不仅是一门技能的提升更是踩在了一个很有前景的行业节奏上。5.3 这本书最大的特色面向真实项目的知识整合最后想聊聊这本书和其他Linux驱动类书籍的一个明显区别。市面上不少教材偏重语法、API、概念说明而这本书更注重“面向真实项目”的知识整合能力。很多章节在讲完特定知识点后会把前面学过的内容综合起来构造一个更接近真实产品场景的项目案例。这种做法的好处是学习的颗粒度从“单个知识点”被提升到“系统设计方案”的高度读者培养的不是孤立技能而是整机思维。比如书中的综合项目不仅仅是写一个驱动模块而是要完成从硬件原理分析、设备树配置、驱动框架选择到用户态调试程序设计的完整闭环。这其实就是一名合格嵌入式系统工程师日常工作的缩影。另外这本书的代码风格和注释习惯也很贴近企业工程实践。所有实验代码都强调可读性和模块化关键函数都有注释关键逻辑都有对应分析读者完全可以一边学一边模仿把规范的内核开发习惯内化成自己的习惯。这比起“能跑就行”的自学路子要少走很多弯路。写在最后的小体会拿到这本书样书的时候我翻了翻里面某些一度让我“睡不着觉”的章节比如中断下半部和并发控制突然有种当年踩坑日记被整理成册的感觉。我自己学驱动开发那会儿最缺的就是这样一本有人把关键坑都提前标出来的书。现在的读者确实幸福可以站在前人的肩膀上少走很多弯路。这本书适合所有对Linux底层机制有好奇心的开发者不管你是刚接触嵌入式Linux的小白还是已经写了一阵子应用代码想往底层多迈一步的工程师按着书上每一章的话题来做动手把代码跑起来遇到问题再回到对应章节排查反复几轮下来你一定会感谢自己现在的选择。
返回列表