尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

LDD3中英文高清电子书:嵌入式Linux驱动开发案头必备

发布时间:2026/9/26 1:45:44

资讯中心
01
ARTICLE

LDD3中英文高清电子书:嵌入式Linux驱动开发案头必备

LDD3中英文高清电子书:嵌入式Linux驱动开发案头必备
1. 为什么一本二十年前的驱动开发书至今仍是嵌入式Linux的硬通货第一次翻到《LINUX设备驱动程序》第3版业内习惯叫它LDD3的时候我正被一块网卡的DMA描述符环搞得焦头烂额。那会儿手头只有一份芯片手册和满屏的oops日志同事丢过来一个PDF说“你先看第15章”。那一章讲的是内存映射与DMA把dma_alloc_coherent、流式映射、一致性映射的区别掰开揉碎讲了一遍连“为什么不能在中断上下文里睡眠”这种新手最容易踩的坑都单独拎出来说了。看完之后我回头改了三行代码网卡就通了。这就是LDD3的典型使用场景它不是一本从头读到尾的教材而是一本放在手边随时翻的案头书。标题里说的“中英文版 高清电子书”本质上解决的是一个很现实的问题——原版英文书在国内不好买影印版字小纸薄而驱动开发又恰恰是那种需要反复对照代码和文字描述的活儿。电子版能搜索、能复制代码片段、能双屏对照着看效率完全不一样。这本书的作者是Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman。Greg后来成了Linux内核稳定分支的维护者Jonathan是LWN.net的主编都是实打实写过内核代码的人。书里的例子基于2.6.10内核虽然现在的内核已经跑到6.x了但驱动模型的核心骨架——字符设备、块设备、网络设备三大分类file_operations结构体并发控制中断处理内存分配——这些东西的底层逻辑没变过。变的只是API的名字和参数比如早期的init_timer后来变成了timer_setupcreate_proc_entry被proc_create取代但“为什么要这么设计”的思路是一脉相承的。适合读这本书的人我大致分三类。第一类是刚入行的嵌入式Linux工程师手上有块开发板想从“会写应用程序”跨到“能改驱动”这一步。第二类是运维或者系统管理员平时用linux常用命令大全解决日常问题但遇到内核模块加载失败、设备节点权限不对这类问题时想往下挖一层。第三类是做国产化替代方案的开发者现在linux国产化的趋势很明显很多外设厂商提供的驱动质量参差不齐你得有能力看懂它到底在干什么才能判断能不能用、要不要改。提示LDD3的代码示例基于2.6内核直接编译大概率报错。正确的用法是把它当“原理参考书”配合当前内核的Documentation目录和头文件一起看而不是当“代码模板”直接抄。2. 这本书到底讲了什么从“写一个能加载的模块”到“写出能进主线的驱动”2.1 全书结构拆解与阅读路线图LDD3一共18章外加几个附录。从内容组织上看它其实分成了四个层次。第一层是入门层第1到第4章。第1章讲设备驱动程序的角色把“驱动是内核和硬件之间的翻译官”这个定位说清楚了。第2章是“建立和运行模块”手把手教你写第一个hello world模块讲insmod、rmmod、lsmod、dmesg这一套流程。第3章是字符设备驱动核心是file_operations结构体和主次设备号。第4章讲调试技术包括printk的日志级别、oops消息的解读、strace和gdb的使用。第二层是并发与内核机制层第5到第8章。第5章讲并发和竞态信号量、自旋锁、原子操作、完成量都在这里。第6章是高级字符设备操作ioctl、阻塞I/O、poll和select、异步通知。第7章是时间、延迟和延后工作内核定时器、tasklet、工作队列。第8章是分配内存kmalloc、vmalloc、lookup_free_pages、内存池。第三层是硬件交互层第9到第12章。第9章讲与硬件通信I/O端口和I/O内存、内存屏障。第10章是中断处理上半部/下半部的划分、中断共享、中断处理程序的注册与释放。第11章是内核的数据类型这章看起来简单但极其重要端口ability问题、字节序、时间类型、链表。第12章是PCI驱动程序讲PCI总线的配置空间、DMA映射。第四层是高级主题层第13到第18章。第13章是USB驱动程序第14章是Linux设备模型第15章是内存映射和DMA第16章是块设备驱动第17章是网络设备驱动第18章是TTY驱动程序。我的建议阅读路线是如果你完全没写过驱动从第2章开始把hello world模块编译加载一遍感受一下内核模块的生命周期。然后跳到第3章写一个最简单的字符设备实现open、read、write、release四个方法。这两个步骤走完你对驱动的基本框架就有感觉了。之后再回头按顺序读第5、6、7、8章把并发和内存这两块硬骨头啃下来。第9到第12章可以结合你手头的硬件来选读比如你做的是PCIe采集卡就重点看第12章做的是USB外设就重点看第13章。2.2 核心概念设备号、file_operations与设备模型设备号是驱动开发的第一个门槛。主设备号标识驱动次设备号标识具体设备。在2.6内核里设备号是16位的主次各8位。到了现代内核dev_t扩展到了32位主设备号12位次设备号20位。这个变化带来的直接影响是以前主设备号最多256个现在有4096个次设备号从256个扩展到一百多万个。如果你在代码里硬编码主设备号比如#define MY_MAJOR 240在新内核上可能就会和别的驱动冲突。正确的做法是用alloc_chrdev_region动态申请或者用register_chrdev_region指定一个范围。file_operations结构体是字符设备驱动的灵魂。LDD3第3章给出的scull例子实现了一个内存模拟设备支持open、read、write、llseek、ioctl等操作。这个例子的价值在于它把“用户空间的一次read调用在内核里经历了什么”完整地展示出来了。用户调用read(fd, buf, count)VFS层根据fd找到对应的file结构体再通过file-f_op-read调用到你的驱动函数。你的驱动函数需要完成两件事从硬件或内核缓冲区取数据然后用copy_to_user把数据拷贝到用户空间。这里有个经典错误直接对用户空间指针做解引用比如*(char *)buf data这在x86上可能侥幸能跑但在ARM上大概率直接oops因为用户空间和内核空间的地址映射不一样。设备模型是LDD3里比较难啃但又绕不过去的一块。第14章讲kobject、kset、subsystem这些概念后来演化成了现在的device、driver、bus、class结构。理解设备模型的意义在于现代内核里驱动不再是“一个孤立的模块”而是挂载在总线上的一个设备节点。你写一个I2C驱动内核会自动在/sys/bus/i2c/devices/下面创建对应的目录和属性文件。用户空间通过sysfs就能查看设备状态、修改驱动参数不需要再自己实现ioctl。这也是为什么现在很多驱动都推荐用sysfs属性代替proc文件的原因——procfs是给进程信息用的sysfs才是设备信息的正规入口。2.3 中英文版对照阅读的实操价值中英文版对照最大的价值在于术语的精确理解。举个例子英文版里“race condition”翻译成“竞态条件”但“race”这个词本身有“竞争、赛跑”的意思理解成“多个执行路径在赛跑谁先到谁说了算”就比“竞态”两个字更直观。再比如“sleeping”在驱动语境下不是“睡觉”而是“当前进程主动让出CPU等待某个条件满足后被唤醒”。中文版翻译成“睡眠”新手容易和“延时”混淆。对照着看英文版帮你建立准确的概念映射中文版帮你快速过一遍内容。另一个实际用途是搜索。电子版的好处是全文检索。比如你想查“如何判断一个指针是用户空间指针还是内核空间指针”直接搜access_ok英文版和中文版都能定位到相关段落。纸质书做不到这一点。注意网上流传的所谓“高清电子书”版本质量参差不齐。有些是扫描版文字不可复制有些是OCR版代码里的下划线和连字符经常识别错比如copy_to_user被识别成copy—to—user。下载后先翻到第3章的代码清单看看代码格式是否正常再决定要不要用。3. 从LDD3到现代内核哪些API变了哪些思想没变3.1 内核API的演进与替代方案速查LDD3基于2.6.10内核现在是6.x时代中间隔了将近二十年。很多API已经废弃或者改名了。我整理了一张常用API的对照表方便你在读LDD3的时候知道“这个函数现在该用什么”。LDD3中的API现代内核替代方案变化原因init_timertimer_setup类型安全避免强制转换create_proc_entryproc_create简化参数支持权限控制class_device_createdevice_create设备模型统一struct class_devicestruct device类设备概念合并register_chrdevalloc_chrdev_regioncdev_add支持多设备避免主设备号浪费interruptible_sleep_onwait_event_interruptible消除竞态窗口sleep_on已删除同上check_regionrequest_region资源申请与检查合并pci_module_initpci_register_driver命名规范化SA_INTERRUPTIRQF_DISABLED后又被移除中断处理模型简化这张表的使用方法是读LDD3的时候看到左边这一列心里知道“这个现在不用了”然后去查右边对应的新API。但更重要的是理解“为什么变”。比如interruptible_sleep_on被废弃是因为它内部实现有一个无法消除的竞态窗口——在判断条件为假和进入睡眠之间如果有中断到来并唤醒了等待队列这个唤醒会丢失导致进程永久睡眠。wait_event_interruptible把条件判断和睡眠放在一个宏里用自旋锁保护从根本上杜绝了这个问题。3.2 并发控制从信号量到互斥锁的取舍LDD3第5章讲并发控制的时候重点讲了信号量semaphore和自旋锁spinlock。信号量用于可能睡眠的场景自旋锁用于不能睡眠的场景。这个划分到现在依然成立但具体实现上有了变化。信号量在早期内核里是struct semaphore用init_MUTEX初始化为1down获取up释放。后来内核引入了互斥锁mutexstruct mutex用mutex_init初始化mutex_lock获取mutex_unlock释放。互斥锁比信号量更轻量而且有所有权概念——谁加锁谁解锁不能跨线程释放。信号量没有这个限制但正因为没有限制容易写出难以调试的代码。自旋锁的变化不大spin_lock、spin_unlock、spin_lock_irqsave、spin_unlock_irqrestore这些还在用。但有一个重要的补充spin_lock_bh用于处理软中断上下文和进程上下文的竞争。如果你在进程上下文里访问一个会被软中断修改的数据结构就需要用spin_lock_bh同时禁用软中断。还有一个新手容易忽略的点读写锁rwlock和顺序锁seqlock。读写锁允许多个读者同时进入但写者独占。顺序锁更进一步读者不加锁只在读取前后检查版本号如果版本号变了就重读。顺序锁适合读多写少且写操作很快的场景比如jiffies的读取。实操心得在驱动里用锁第一原则是“锁的粒度尽量小”。我见过一个驱动在中断处理程序里拿了一个全局互斥锁结果用户空间一调用read就死锁——因为read路径也拿同一个锁而中断上下文不能睡眠互斥锁在中断里根本不能用。正确的做法是中断里用自旋锁进程上下文里用互斥锁两者通过工作队列或者tasklet来衔接。3.3 内存管理kmalloc、vmalloc与DMA的边界LDD3第8章和第15章讲内存这两章的内容到现在依然有效但有一些细节需要更新。kmalloc分配的内存在物理上是连续的大小有限制通常不能超过128KB取决于内核配置和架构。vmalloc分配的内存在虚拟地址上连续物理上可以不连续适合分配大块内存但不能用于DMA因为DMA控制器需要物理地址。__get_free_pages直接分配页返回虚拟地址物理上连续。DMA映射分两种一致性映射coherent mapping和流式映射streaming mapping。一致性映射用dma_alloc_coherent分配的内存在驱动生命周期内一直有效CPU和设备看到的数据是一致的不需要手动同步。流式映射用dma_map_single或dma_map_sg用于一次性的数据传输传输前后需要调用dma_sync_single_for_cpu或dma_sync_single_for_device来同步。这里有一个常见的误区以为dma_alloc_coherent返回的虚拟地址可以直接做DMA。实际上DMA需要的是总线地址dma_addr_t不是虚拟地址。在x86上总线地址和物理地址通常一样但在ARM上总线地址可能经过IOMMU映射和物理地址不同。所以正确的用法是dma_alloc_coherent(dev, size, dma_handle, GFP_KERNEL)返回的虚拟地址给CPU用dma_handle给设备用。LDD3里讲DMA的时候用的是pci_map_single这些PCI专用的API。现代内核推荐用通用DMA API也就是dma_map_single它内部会根据设备的总线类型自动处理。这样写出来的驱动可以同时支持PCI、PCIe、平台设备等多种总线。4. 把LDD3用起来从电子书到实际驱动开发的转化路径4.1 搭建一个可编译的LDD3实验环境LDD3的代码示例可以从书附带的源码包获取但直接编译会报一堆错。我的做法是找一个长期支持的内核版本比如5.10或者5.15把LDD3的代码移植过去。移植的过程本身就是最好的学习。第一步准备内核头文件。在Ubuntu上apt install linux-headers-$(uname -r)。在开发板上需要内核源码树make modules_prepare生成必要的头文件。第二步写一个最简单的Makefile。LDD3的Makefile是给2.6内核用的现代内核的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第三步从hello world模块开始。LDD3第2章的hello world是这样的#include linux/init.h #include linux/module.h MODULE_LICENSE(Dual BSD/GPL); static int hello_init(void) { printk(KERN_ALERT Hello, world\n); return 0; } static void hello_exit(void) { printk(KERN_ALERT Goodbye, cruel world\n); } module_init(hello_init); module_exit(hello_exit);这个代码在现代内核上可以直接编译。MODULE_LICENSE是必须的否则内核会报“tainted”警告。printk的KERN_ALERT是日志级别数字越小优先级越高。module_init和module_exit指定了模块加载和卸载时调用的函数。编译用make加载用sudo insmod hello.ko查看日志用dmesg | tail卸载用sudo rmmod hello。这一套流程走通你就有了一个可以反复实验的基础环境。4.2 从scull到真实硬件字符设备驱动的完整实现LDD3第3章的scull是一个内存模拟设备不涉及真实硬件。但它的框架和真实字符设备驱动是一样的。我以一块简单的GPIO控制卡为例说明怎么把scull的框架套到真实硬件上。首先定义设备结构体。scull用的是struct scull_dev里面包含struct cdev、数据缓冲区、信号量等。对于GPIO卡结构体里需要加上IO端口基地址、寄存器映射指针、中断号等。struct gpio_card_dev { struct cdev cdev; void __iomem *reg_base; int irq; struct mutex lock; wait_queue_head_t readq; int data_ready; };cdev是字符设备的内核表示reg_base是ioremap之后的寄存器虚拟地址irq是中断号lock保护并发访问readq是等待队列data_ready标志数据是否就绪。然后实现file_operationsstatic int gpio_card_open(struct inode *inode, struct file *filp) { struct gpio_card_dev *dev; dev container_of(inode-i_cdev, struct gpio_card_dev, cdev); filp-private_data dev; return 0; } static ssize_t gpio_card_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct gpio_card_dev *dev filp-private_data; int value; int ret; if (mutex_lock_interruptible(dev-lock)) return -ERESTARTSYS; while (!dev-data_ready) { mutex_unlock(dev-lock); if (filp-f_flags O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible(dev-readq, dev-data_ready)) return -ERESTARTSYS; if (mutex_lock_interruptible(dev-lock)) return -ERESTARTSYS; } value ioread32(dev-reg_base DATA_REG); if (copy_to_user(buf, value, sizeof(value))) { mutex_unlock(dev-lock); return -EFAULT; } dev-data_ready 0; mutex_unlock(dev-lock); return sizeof(value); }这段代码里有几个关键点。container_of从inode-i_cdev反推出设备结构体这是内核里常用的技巧。mutex_lock_interruptible允许在等待锁的时候被信号打断返回-ERESTARTSYS让内核重新执行系统调用。wait_event_interruptible在条件不满足时睡眠条件满足时被唤醒。ioread32从内存映射的寄存器读取32位数据。copy_to_user把数据拷贝到用户空间返回非零表示拷贝失败。中断处理程序里当硬件产生中断时读取数据设置data_ready然后wake_up_interruptible(dev-readq)唤醒等待的读进程。这个框架和scull的区别在于scull的数据来自内核缓冲区gpio_card的数据来自硬件寄存器。但file_operations的调用时机、并发控制的逻辑、用户空间和内核空间的数据拷贝方式完全一样。4.3 调试技巧从oops到ftrace的排查链路LDD3第4章讲的调试技术核心是printk和oops分析。现代内核增加了ftrace、perf、kprobe等更强大的工具但printk依然是最直接的手段。oops消息的解读关键是看三样东西出错的指令指针EIP或PC、调用栈Call Trace、出错原因比如“Unable to handle kernel NULL pointer dereference”。调用栈从下往上读最下面的是最外层的函数最上面的是出错点。比如Call Trace: [c0103f5c] dump_stack0x1c/0x20 [c0123456] my_driver_read0x36/0x80 [c0156789] vfs_read0x99/0x120 [c0156abc] sys_read0x3d/0x70这说明错误发生在my_driver_read函数的偏移0x36处。用objdump -d my_driver.ko反汇编找到偏移0x36对应的指令就能定位到具体哪一行代码。ftrace是内核自带的跟踪工具可以跟踪函数调用、中断、调度等事件。开启方法cd /sys/kernel/debug/tracing echo function current_tracer echo my_driver_read set_ftrace_filter echo 1 tracing_on # 执行操作 echo 0 tracing_on cat trace这会输出my_driver_read被调用的次数和调用栈。对于分析“为什么这个函数没被调用”或者“这个函数被调用了多少次”这类问题ftrace比printk高效得多因为不需要重新编译内核模块。避坑技巧printk的日志级别如果设置得太低比如KERN_DEBUG默认不会输出到控制台只在dmesg里能看到。如果调试的是启动阶段的驱动控制台还没初始化printk的输出会丢失。这时候可以用early_printk或者CONFIG_DEBUG_LLARM平台来输出。5. 常见问题与排查技巧实录5.1 模块加载失败的五种典型原因“insmod: ERROR: could not insert module xxx.ko: Invalid module format”是最常见的错误。原因通常是模块编译时用的内核版本和当前运行的内核版本不一致。用uname -r查看当前内核版本用modinfo xxx.ko查看模块的vermagic两者必须匹配。如果开发板上的内核是你自己编译的记得make modules_install之后depmod -a。“Unknown symbol in module”表示模块引用了内核没有导出的符号。用nm xxx.ko | grep U列出所有未定义的符号然后去内核源码里查这个符号有没有EXPORT_SYMBOL。如果没有说明这个函数不打算给模块用你需要换一种实现方式。“Operation not permitted”通常是权限问题。加载模块需要root权限或者CAP_SYS_MODULE能力。在容器里加载模块还需要额外的权限配置。“Resource temporarily unavailable”可能是设备号冲突。用cat /proc/devices查看已注册的主设备号确保你的驱动没有和别的驱动抢同一个号。“Killed”或者系统直接重启说明模块初始化函数里发生了严重的错误比如空指针解引用或者栈溢出。这时候dmesg里通常有oops消息按上一节的方法分析。5.2 设备节点创建与权限管理的坑加载模块之后/dev下面不会自动出现设备节点。需要手动创建mknod /dev/mydev c 240 0c表示字符设备240是主设备号0是次设备号。现代内核推荐用udev自动创建驱动里调用device_createudev会根据sysfs信息自动在/dev下创建设备节点。权限问题也很常见。默认创建的设备节点权限是crw-rw----普通用户无法访问。可以在udev规则里设置权限或者在驱动里用class_create时指定devnode回调函数来修改权限。还有一个容易忽略的点设备节点的所有者。如果驱动在device_create时传入了dev指针udev会把设备节点的所有者设置为dev对应的用户。如果没有传入默认是root。5.3 并发问题的排查思路并发问题最难排查因为它的表现往往是不确定的——有时候正常有时候崩溃压力大的时候更容易出问题。排查的第一步是确认有没有并发。在驱动里加计数器记录同时进入临界区的执行路径数量。如果计数器超过1说明有并发。第二步是确认并发的来源。可能是多个用户进程同时调用read可能是中断处理程序和进程上下文竞争可能是软中断和进程上下文竞争也可能是多核CPU上的并行执行。第三步是选择合适的保护机制。进程上下文之间的竞争用互斥锁进程和中断之间的竞争用自旋锁加禁用中断进程和软中断之间的竞争用自旋锁加禁用软中断多核之间的竞争用自旋锁。第四步是检查锁的获取和释放是否配对。我见过一个驱动在错误处理路径上忘记释放锁导致后续所有调用都死锁。用lockdep可以检测这类问题内核配置里打开CONFIG_PROVE_LOCKING运行时会自动报告锁的获取顺序问题。问题现象可能原因排查方法系统随机死锁锁获取顺序不一致开启lockdep检查锁的嵌套关系数据偶尔出错缺少内存屏障检查DMA缓冲区的同步操作中断丢失中断处理程序执行时间过长用ftrace测量中断处理时间模块无法卸载有引用计数未释放检查module_put和try_module_get是否配对设备节点权限不对udev规则未生效检查/etc/udev/rules.d/下的规则文件独家经验调试并发问题的时候我习惯在临界区的入口和出口各加一条printk打印当前进程的PID和CPU号。如果看到两条入口日志之间夹着另一条入口日志说明有并发。这个方法虽然原始但比任何工具都直观。6. 电子书的使用技巧与延伸学习路径6.1 高效检索与笔记整理方法电子版最大的优势是检索。我常用的检索关键词分三类。第一类是API名称比如copy_to_user、request_irq、dma_alloc_coherent直接搜函数名看它在书里是怎么用的。第二类是错误信息比如Unknown symbol、Invalid module format搜错误信息能找到对应的解释。第三类是概念关键词比如race condition、memory barrier、interrupt context搜概念能定位到原理讲解。笔记整理我推荐用“问题-答案”格式。比如“问题为什么不能在中断上下文里调用kmalloc”“答案kmalloc可能睡眠中断上下文不允许睡眠。替代方案是用kmalloc的GFP_ATOMIC标志或者用预先分配的内存池。”这样整理出来的笔记下次遇到同样的问题直接搜问题就能找到答案。中英文对照阅读的时候我习惯把英文版放在左边中文版放在右边遇到翻译不通顺的地方对照英文原文理解。比如“bottom half”翻译成“下半部”但英文原意是“中断处理中可以被推迟执行的部分”理解了这个意思再看代码里tasklet_schedule和workqueue的区别就清楚了。6.2 从LDD3到内核源码的进阶路线LDD3是“怎么用”内核源码是“为什么这么实现”。读完LDD3之后下一步是读内核源码里对应的实现。比如LDD3讲了file_operations的read方法内核源码里fs/read_write.c的vfs_read函数展示了从系统调用到驱动方法的完整路径。再比如LDD3讲了中断处理内核源码里kernel/irq/目录下的代码展示了中断描述符的注册、中断控制器的抽象、中断线程化的实现。读源码的方法是从入口函数开始顺着调用链往下读。比如从sys_open开始经过do_sys_open、do_filp_open、path_openat最终到vfs_open再到file-f_op-open。这条路径上的每一个函数都值得花时间理解它在做什么。另一个进阶方向是看当前内核的Documentation目录。Documentation/driver-api/下面有各种驱动框架的文档Documentation/devicetree/下面是设备树的绑定文档。这些文档比LDD3更贴近现代内核但LDD3帮你建立了基本概念之后看这些文档会轻松很多。6.3 嵌入式Linux项目中的驱动开发实践嵌入式Linux项目和PC上的驱动开发有一个显著区别硬件资源受限而且往往没有标准的总线枚举机制。在PC上PCI设备有配置空间内核可以自动发现设备并加载对应的驱动。在嵌入式系统里设备是通过设备树Device Tree描述的内核根据设备树的compatible属性来匹配驱动。设备树里的一个I2C设备节点长这样i2c1 { status okay; clock-frequency 100000; sensor48 { compatible ti,tmp102; reg 0x48; interrupt-parent gpio1; interrupts 12 IRQ_TYPE_EDGE_FALLING; }; };驱动里对应的of_device_id表static const struct of_device_id tmp102_of_match[] { { .compatible ti,tmp102 }, { } }; MODULE_DEVICE_TABLE(of, tmp102_of_match);内核启动时I2C控制器驱动会解析设备树为每个子节点创建i2c_client然后根据compatible属性匹配到tmp102驱动调用驱动的probe函数。probe函数里完成硬件初始化、注册字符设备或hwmon设备、申请中断等操作。这个流程和LDD3里讲的“模块加载时注册驱动”有所不同但本质是一样的都是把驱动和设备的关联建立起来然后执行初始化。理解了LDD3里的驱动注册模型再看设备树和platform driver的匹配机制就很容易上手。嵌入式开发还有一个特点是交叉编译。在x86主机上编译ARM目标板的模块需要指定交叉编译器和目标内核的源码路径make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -C /path/to/arm/kernel M$PWD modules编译出来的.ko文件拷贝到目标板上用目标板的insmod加载。注意目标板的内核版本和编译时用的内核源码版本必须一致否则会出现“Invalid module format”错误。我个人在实际操作中的体会是LDD3最大的价值不是教会你某个具体的API而是帮你建立“内核视角”——知道内核是怎么看待一个设备的知道用户空间的一次系统调用在内核里经历了什么知道并发和内存这些底层机制是怎么运作的。有了这个视角再看任何新的驱动框架都能很快找到切入点。电子书的形式让这本书可以随时查阅中英文对照让概念理解更准确这两点结合起来就是它至今仍然值得放在手边的原因。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。