1. 这本书到底解决了谁的痛点
嵌入式Linux这个方向,坑多、链条长、入门曲线陡,几乎是所有从单片机转过来的开发者共同的感受。我见过太多人抱着《Linux设备驱动开发详解》啃了三个月,结果连一块开发板都没跑起来;也见过培训班出来的学员,面试时能把字符设备驱动的框架背得滚瓜烂熟,真给他一个I2C传感器,却不知道从设备树哪个节点开始改。飞凌嵌入式这次联合北京大学出版社推出《嵌入式Linux系统开发21天速成》,从书名就能看出来,它瞄准的不是“学院派”的完整知识体系,而是“上手干活”这条最短路径。
这本书的核心价值在于把嵌入式Linux开发拆成了一条有时间刻度的学习路线。21天不是噱头,它对应的是三个完整的工程阶段:环境搭建与系统认知、驱动开发与外设调试、系统集成与项目实战。每个阶段7天,每天一个明确的产出目标。这种结构对于自学者来说非常友好,因为嵌入式Linux最大的问题不是知识点难,而是知识点之间的依赖关系太乱——你先学内核还是先学驱动?先搞根文件系统还是先搞uboot?没有一条清晰的路径,很容易在某个环节卡死然后放弃。
适合读这本书的人,我梳理了一下,大概有三类。第一类是有C语言和单片机基础、想转嵌入式Linux的开发者,这类人最需要的就是把“裸机思维”切换成“操作系统思维”,书里前7天的内容基本就是干这个的。第二类是在校学生或者刚入行的新人,需要一套能跑通的完整流程来建立信心,飞凌的板子加这本书的组合,相当于把“实验环境”和“实验指导”打包了。第三类是有一定经验但知识碎片化的工程师,比如会写应用但不懂驱动,或者会调驱动但不懂系统启动流程,这本书的章节编排可以当手册来查漏补缺。
需要提前说明的是,这本书大概率是以飞凌自家的开发板(比如OK系列)为硬件平台来写的,这意味着书中的例程、设备树配置、交叉编译工具链都是针对特定板子验证过的。好处是“抄作业”成功率高,坏处是换一块板子可能需要做移植。但嵌入式Linux的学习本来就是这样,先在一块确定的板上把全流程跑通,比在十块不同的板上各跑一半要有价值得多。
2. 21天速成的底层逻辑:拆解嵌入式Linux的知识依赖链
2.1 为什么是21天而不是30天或100天
市面上嵌入式Linux的教材动辄五六百页,从操作系统原理讲到内核源码分析,学完一遍半年过去了,回头一看开发板还在吃灰。这种“先学完再动手”的模式,对于已经工作的开发者来说几乎不可行。21天的设定,本质上是把“最小可运行知识集”抽出来,砍掉了大量在入门阶段用不上的理论细节。
我对比过几本主流教材的目录结构,大多数是按照“计算机组成原理→操作系统→Linux命令→Shell编程→内核→驱动→应用”这种自底向上的顺序组织的。这种顺序在逻辑上没问题,但在学习体验上很糟糕,因为前两周你都在跟命令行和理论打交道,看不到任何“嵌入式”的东西。飞凌这本书的编排逻辑更像是“自顶向下、按需下钻”:先让你把板子跑起来,看到串口输出,登录进系统,然后再告诉你这个系统是怎么来的,最后才深入到驱动层。
21天的时间分配,我推测大概是这样的:第1-7天,搞定开发环境(虚拟机、交叉编译工具链、串口终端、网络配置),理解启动流程(BootROM→SPL→uboot→kernel→rootfs),能烧录系统并登录;第8-14天,从最简单的字符设备驱动开始,逐步过渡到GPIO、中断、I2C、SPI等常用外设,同时讲解设备树的基本语法和修改方法;第15-21天,做一个综合项目,比如通过I2C读取传感器数据并通过网络上报,把驱动、应用、系统配置串起来。这个节奏对于每天能投入3-4小时的人来说是合理的,如果每天只有1小时,可能需要拉长到6-8周,但路径本身是清晰的。
2.2 根文件系统挂载:NFS v3为什么是调试利器
热搜词里出现了“嵌入式linux 根文件系统挂载 使用nfs v3”,这恰恰是嵌入式开发中一个极其关键但又容易被教材一笔带过的环节。我单独把它拎出来讲,因为在实际开发中,NFS挂载根文件系统的能力直接决定了你的调试效率。
先解释一下为什么需要NFS挂载根文件系统。在嵌入式开发中,根文件系统(rootfs)包含了系统启动后需要的所有目录、库、配置文件和应用程序。传统的做法是把rootfs打包成镜像烧写到板子的Flash里,但这样每次修改一个配置文件或者重新编译一个应用程序,都要重新打包、烧录、重启,一轮下来至少五分钟。而通过NFS(Network File System)把主机上的一个目录挂载为板子的根文件系统,修改主机上的文件后,板子重启就能生效,甚至有些文件可以热更新,调试效率提升十倍不止。
那为什么特别强调NFS v3?因为NFS v4在嵌入式环境中有几个坑。第一,NFS v4引入了伪文件系统和复杂的状态管理,很多嵌入式内核的NFS客户端配置默认只支持v3,用v4挂载会直接报错。第二,v4对用户ID映射和权限管理更严格,而嵌入式系统往往用root跑一切,v3的宽松策略反而更方便。第三,uboot的NFS引导参数对v3的支持最成熟,nfsroot=192.168.1.100:/home/rootfs,v3,tcp这种写法几乎是标配。
具体操作上,主机端需要安装NFS服务,编辑/etc/exports文件,加入类似/home/rootfs *(rw,sync,no_root_squash)的配置,然后重启NFS服务。板子端在uboot中设置bootargs,指定root=/dev/nfs nfsroot=主机IP:路径,v3,tcp ip=板子IP:主机IP:网关:掩码::eth0:off。这里有个细节:no_root_squash是必须的,否则板子以root身份访问NFS时会被映射成nobody,导致权限拒绝。另一个细节是内核配置中要确保CONFIG_ROOT_NFS和CONFIG_NFS_V3是打开的,很多默认配置只开了v4,需要手动改。
注意:NFS挂载根文件系统只适合开发阶段,量产产品绝对不能这么干,因为网络断了系统就起不来。另外,主机和板子必须在同一网段,且防火墙要放行NFS相关的端口(2049、111等)。
2.3 驱动开发在21天里能学到什么程度
“嵌入式linux驱动开发”是热搜词里另一个高频词。很多人对驱动开发有畏难情绪,觉得要懂内核源码、要会写复杂的回调函数。但实际上,入门级的驱动开发是有固定套路的,21天里完全可以把常用外设的驱动框架摸清楚。
书里大概率会从字符设备驱动入手,因为这是最简单的驱动模型。字符设备驱动的核心就是实现open、read、write、ioctl、release这几个文件操作函数,然后通过register_chrdev或者更现代的alloc_chrdev_region+cdev_add注册到内核。这个过程中会涉及到file_operations结构体、copy_to_user/copy_from_user、ioremap等基础概念。把这些搞明白,大概需要2-3天。
接下来是设备树。现代Linux内核(3.x以后)已经全面采用设备树来描述硬件,驱动代码里不再硬编码寄存器地址,而是通过of_match_table匹配设备树节点,用platform_get_resource获取资源。设备树的语法本身不难,难的是理解“设备树描述硬件、驱动匹配设备树、两者通过compatible属性关联”这个机制。书里应该会以GPIO驱动为例,展示如何在设备树中添加一个节点,然后在驱动中解析这个节点。
再往后是中断和并发控制。中断处理涉及request_irq、中断上半部和下半部(tasklet、工作队列)、中断共享等概念。并发控制则涉及自旋锁、互斥锁、原子操作,这些在驱动中无处不在,因为内核是多线程环境,驱动代码可能被多个进程同时调用。这部分内容比较抽象,但书里如果有实际的按键中断例程,理解起来会容易很多。
最后是I2C和SPI子系统。这两个是嵌入式最常用的外设总线,内核已经提供了完整的子系统框架,驱动开发者只需要实现i2c_driver或spi_driver结构体,填充probe、remove等函数,然后通过设备树匹配。以I2C为例,一个典型的传感器驱动大概100-200行代码,核心就是i2c_smbus_read_byte_data和i2c_smbus_write_byte_data这两个函数。21天里能把I2C传感器驱动跑通,已经算是入门了。
3. 从零搭建开发环境:虚拟机、工具链与串口调试
3.1 主机环境的选择与配置
嵌入式Linux开发的第一步是搭建主机环境。虽然理论上可以在Windows下用WSL或者直接装双系统,但最稳妥的方案还是用虚拟机跑Ubuntu。我推荐Ubuntu 18.04或20.04,因为这两个版本的软件源里交叉编译工具链比较全,而且很多嵌入式SDK的官方支持也是基于这两个版本。
虚拟机的配置有几个关键点。第一,磁盘至少给80GB,因为交叉编译工具链、内核源码、根文件系统加起来很容易超过30GB,再加上虚拟机快照,空间不够会很痛苦。第二,内存至少4GB,如果主机有16GB,建议给虚拟机8GB,因为编译内核的时候内存不足会直接报错。第三,网络模式建议用桥接,这样虚拟机会获得一个独立的IP,和开发板通信更方便。如果用NAT模式,需要额外配置端口转发,比较麻烦。
安装完Ubuntu后,需要安装一些基础工具:build-essential、git、vim、minicom、nfs-kernel-server、tftp-hpa、libncurses5-dev、flex、bison等。这些工具在后续的编译和调试中都会用到。特别是minicom,它是Linux下最常用的串口终端工具,配置好之后可以通过串口线和开发板交互。
提示:安装完Ubuntu后第一件事是换软件源,国内用户建议换成清华或阿里的镜像源,否则下载速度会让你怀疑人生。换源之后执行
sudo apt update && sudo apt upgrade,确保系统是最新的。
3.2 交叉编译工具链的安装与验证
交叉编译是嵌入式开发的核心概念。简单来说,就是在x86的PC上编译出能在ARM架构的开发板上运行的程序。这需要一套专门的工具链,通常由芯片厂商或者第三方提供,比如arm-linux-gnueabihf-前缀的gcc、g++、ld等。
安装工具链的方式有两种:一种是直接用apt安装,比如sudo apt install gcc-arm-linux-gnueabihf,这种方式简单但版本可能比较旧;另一种是从厂商官网下载工具链压缩包,解压后添加到PATH环境变量中。飞凌的SDK通常会附带推荐的工具链版本,建议按照书中的说明来,因为工具链版本和内核版本、库版本之间有兼容性要求。
验证工具链是否安装成功,可以执行arm-linux-gnueabihf-gcc -v,如果能看到版本信息就说明OK了。然后写一个最简单的hello.c,用交叉编译器编译,再用file命令查看生成的可执行文件,如果显示“ARM aarch64”或“ARM, EABI5”之类的信息,就说明交叉编译成功了。把这个可执行文件拷贝到开发板上运行,如果能打印出“Hello, Embedded Linux”,那整个工具链和开发板的连通性就验证完毕了。
这里有个常见的坑:工具链的浮点ABI要和开发板的根文件系统匹配。比如gnueabihf表示硬件浮点,gnueabi表示软件浮点,如果编译出来的程序在板子上跑不起来,报“No such file or directory”或者“Illegal instruction”,大概率就是ABI不匹配。解决办法是查看开发板rootfs中/lib目录下的动态链接器名称,比如ld-linux-armhf.so.3对应硬件浮点,ld-linux.so.3对应软件浮点。
3.3 串口终端与网络调试通道的建立
串口是嵌入式开发中最重要的调试通道,因为系统启动阶段网络还没起来,只能通过串口看打印信息。连接串口需要一根USB转TTL线,开发板上通常有调试串口的排针,一般是3针或4针(GND、TX、RX、VCC),只需要接GND、TX、RX三根线,VCC不要接,因为板子自己供电。
在Ubuntu下,插入USB转TTL线后,用dmesg | tail可以看到识别出的串口设备,通常是/dev/ttyUSB0。然后用minicom配置:sudo minicom -s,进入串口设置,波特率一般选115200,数据位8,停止位1,无校验,流控选None。配置好后保存退出,就能看到开发板的串口输出了。如果乱码,检查波特率是否正确;如果没有任何输出,检查TX/RX是否接反了(TX接RX,RX接TX)。
网络通道的建立有两种方式:TFTP用于传输文件,NFS用于挂载根文件系统。TFTP的配置比较简单,安装tftpd-hpa后,编辑/etc/default/tftpd-hpa,设置TFTP_DIRECTORY为你的共享目录,然后重启服务。在uboot中可以用tftp命令从主机下载内核镜像或设备树文件。NFS的配置前面已经讲过,这里不再重复。这两个通道建立好之后,开发效率会有质的提升,因为你可以随时更新内核、设备树、应用程序,而不需要反复插拔SD卡或烧录Flash。
4. 系统启动流程与根文件系统构建
4.1 从上电到登录:嵌入式Linux的启动链路
理解启动流程是嵌入式Linux开发的基本功。很多人调驱动调了半天没反应,最后发现是设备树没编译进去,或者内核根本没加载这个驱动,根源就在于对启动链路不清楚。
嵌入式Linux的启动大致分为四个阶段。第一阶段是BootROM,这是芯片内部固化的一段代码,上电后首先运行,它会根据启动引脚的电平决定从哪个介质启动(SD卡、eMMC、NAND等),然后把SPL(Secondary Program Loader)加载到内部SRAM中运行。第二阶段是SPL,它负责初始化DDR内存,然后把uboot加载到DDR中运行。第三阶段是uboot,这是功能最丰富的引导程序,它会初始化网络、显示、存储等外设,然后根据环境变量加载内核镜像和设备树到DDR中,最后跳转到内核入口。第四阶段是内核启动,内核会打印大量信息,初始化各个子系统,最后挂载根文件系统,执行/sbin/init,进入用户空间。
这个过程中,串口打印是唯一的观测手段。uboot阶段会打印版本号、DRAM大小、环境变量等信息;内核阶段会打印CPU信息、内存布局、驱动加载情况等。如果启动卡在某个阶段,可以根据最后的打印信息定位问题。比如卡在“Starting kernel ...”之后没有任何输出,可能是内核镜像格式不对、加载地址不对、或者串口波特率不对;如果内核打印到“VFS: Cannot open root device”就停了,说明根文件系统挂载失败,需要检查bootargs中的root=参数。
4.2 BusyBox构建最小根文件系统
根文件系统是嵌入式Linux的“灵魂”,没有它系统起不来。构建根文件系统最常用的工具是BusyBox,它把ls、cd、cp、mv、ifconfig等常用命令集成到一个可执行文件中,通过符号链接的方式提供各种命令,非常适合资源受限的嵌入式环境。
构建BusyBox的流程大概是:下载源码、配置(make menuconfig)、编译(make)、安装(make install)。配置的时候要注意选择交叉编译器,在Settings→Build Options中设置Cross Compiler prefix为你的工具链前缀,比如arm-linux-gnueabihf-。编译完成后,_install目录下会生成bin、sbin、usr等目录,这就是根文件系统的雏形。
但光有BusyBox还不够,还需要添加一些必要的目录和文件。比如/dev目录下需要有console和null设备节点,/etc目录下需要有inittab、fstab、profile等配置文件,/lib目录下需要放动态链接库(如果BusyBox是动态编译的)。这些内容书里应该会有详细的步骤,我在这里只强调一个容易忽略的点:/etc/inittab文件决定了系统启动后执行哪些脚本,如果这个文件配置错了,系统可能起不来或者起来后没有shell。
注意:BusyBox编译时建议选择静态编译(
Settings→Build Options→Build static binary),这样就不需要拷贝动态链接库,根文件系统会更简单。代价是可执行文件会大一些,但对于入门学习来说,简单比省空间更重要。
4.3 使用NFS挂载根文件系统的完整配置
前面讲了NFS挂载的原理,这里给出一个完整的配置流程。主机端:安装nfs-kernel-server,创建/home/rootfs目录,把BusyBox构建的根文件系统拷贝进去,编辑/etc/exports加入/home/rootfs *(rw,sync,no_root_squash,no_subtree_check),然后执行sudo exportfs -ra和sudo systemctl restart nfs-kernel-server。
板子端:在uboot中设置bootargs,格式为root=/dev/nfs rw nfsroot=192.168.1.100:/home/rootfs,v3,tcp ip=192.168.1.200:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off console=ttyS0,115200。这里的IP地址要根据实际网络环境修改,192.168.1.100是主机IP,192.168.1.200是板子IP,192.168.1.1是网关。console=ttyS0,115200指定串口控制台,确保启动信息能打印出来。
内核配置方面,需要确保以下选项是打开的:CONFIG_NFS_FS=y、CONFIG_NFS_V3=y、CONFIG_ROOT_NFS=y、CONFIG_IP_PNP=y、CONFIG_IP_PNP_DHCP=y(如果用DHCP)或CONFIG_IP_PNP_BOOTP=y。如果内核不支持NFS根文件系统,启动时会报“VFS: Cannot open root device”错误。
配置完成后,重启板子,如果一切正常,你应该能看到内核启动日志,最后出现BusyBox的shell提示符。这时候你在主机上修改/home/rootfs中的文件,板子重启后就能看到变化。这个流程跑通之后,后续的驱动调试和应用开发都会方便很多。
5. 驱动开发实战:从字符设备到I2C传感器
5.1 字符设备驱动的最小框架
字符设备驱动是Linux驱动开发的“Hello World”。一个最小的字符设备驱动包含以下几个部分:定义file_operations结构体,实现open、read、write、release等函数;在模块初始化函数中注册设备号,添加cdev;在模块退出函数中注销设备号和cdev。
设备号的注册有两种方式:静态指定和动态分配。静态指定用register_chrdev_region,需要自己选一个未被占用的主设备号,容易冲突;动态分配用alloc_chrdev_region,内核会自动分配一个可用的主设备号,更推荐。注册完设备号后,需要初始化cdev结构体,用cdev_init绑定file_operations,然后用cdev_add添加到内核。
file_operations中的函数是驱动和用户空间交互的接口。open函数在用户调用open("/dev/xxx", ...)时被调用,通常用来初始化硬件或增加引用计数;read函数在用户调用read时被调用,需要用copy_to_user把内核空间的数据拷贝到用户空间;write函数相反,用copy_from_user把用户空间的数据拷贝到内核空间;release函数在用户调用close时被调用,用来释放资源。
编译这个驱动需要编写Makefile,指定内核源码路径和交叉编译器。编译生成.ko文件后,通过TFTP或NFS拷贝到板子上,用insmod加载,用rmmod卸载。加载成功后,用dmesg可以看到驱动的打印信息,用cat /proc/devices可以看到注册的设备号,然后用mknod创建设备节点,就可以用用户空间的程序来读写这个设备了。
5.2 设备树语法与GPIO驱动实例
设备树是理解现代Linux驱动的关键。它的本质是一棵描述硬件拓扑的树,每个节点代表一个设备或总线,节点中的属性描述了这个设备的地址、中断号、时钟、引脚等信息。驱动代码通过compatible属性来匹配设备树节点,匹配成功后调用probe函数。
一个典型的GPIO设备树节点长这样:
my_led { compatible = "mycompany,my-led"; led-gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>; status = "okay"; };这个节点定义了一个LED设备,使用gpio1控制器的第3号引脚,高电平有效。驱动代码中,通过of_match_table匹配"mycompany,my-led",然后在probe函数中用gpio_get_value或gpiod_get获取GPIO,用gpio_direction_output设置为输出,用gpio_set_value控制电平。
设备树的编译需要用内核源码中的dtc工具,把.dts文件编译成.dtb文件,然后由uboot加载到内核。修改设备树后,需要重新编译.dtb并更新到板子上,通常放在TFTP目录下,在uboot中用tftp命令下载。这里有个容易犯的错误:设备树节点的status属性默认是"disabled",如果忘记改成"okay",驱动不会匹配成功。
5.3 I2C传感器驱动开发与调试
I2C是嵌入式最常用的传感器接口,很多温湿度传感器、加速度计、EEPROM都是I2C接口。Linux内核提供了完整的I2C子系统,驱动开发者只需要实现i2c_driver结构体,填充probe、remove、id_table等字段。
以一款常见的I2C温度传感器为例,驱动的大致流程是:在设备树中定义传感器节点,指定I2C总线号和从机地址;驱动中定义of_device_id和i2c_device_id匹配表;在probe函数中用i2c_smbus_read_word_data读取温度寄存器,用i2c_smbus_write_byte_data配置传感器;把读取到的温度值通过字符设备或sysfs接口暴露给用户空间。
调试I2C驱动时,最常用的工具是i2cdetect和i2cget/i2cset。i2cdetect -y 1可以扫描I2C总线1上的所有设备,如果传感器的地址出现在列表中,说明硬件连接和总线驱动是正常的。i2cget -y 1 0x48 0x00 w可以读取地址为0x48的传感器的0x00寄存器的值。如果i2cdetect扫不到设备,检查SDA/SCL是否接反、上拉电阻是否焊接、从机地址是否正确。
提示:I2C从机地址通常是7位,但在
i2cget等工具中需要左移一位变成8位地址。比如传感器手册上写地址是0x48,实际使用时是0x90(0x48<<1)。这个坑我踩过不止一次,明明地址没错但就是读不到数据,最后发现是移位问题。
6. 常见问题排查与避坑经验
6.1 启动类问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 串口无任何输出 | 串口线接反、波特率不对、板子没供电 | 检查TX/RX是否交叉连接,确认波特率115200,测量板子电压 |
| uboot启动后卡住 | 环境变量错误、存储介质损坏 | 进入uboot命令行,用printenv查看环境变量,用mmc info检查存储 |
| 内核启动后无输出 | 内核镜像格式不对、加载地址错误 | 确认内核是zImage还是Image,检查bootargs中的console=参数 |
| 根文件系统挂载失败 | NFS配置错误、内核不支持NFS | 检查/etc/exports、防火墙、内核CONFIG_ROOT_NFS选项 |
| 驱动加载失败 | 设备树未匹配、依赖模块未加载 | 用dmesg查看错误信息,用lsmod查看模块依赖 |
这张表里的问题,我在实际开发中至少遇到过一半。特别是“内核启动后无输出”这一条,有一次我调了一下午,最后发现是设备树的加载地址和内核重叠了,导致内核被覆盖。这种问题没有捷径,只能根据串口打印的信息一步步缩小范围。
6.2 驱动调试中的典型坑与解决思路
驱动调试最让人头疼的是“没有报错但就是不工作”。比如加载了GPIO驱动,insmod成功,dmesg也有打印,但LED就是不亮。这时候需要分步排查:首先确认设备树节点是否被内核解析,可以用ls /proc/device-tree/查看节点是否存在;其次确认probe函数是否被调用,可以在probe函数入口加printk;然后确认GPIO号是否正确,用cat /sys/kernel/debug/gpio查看GPIO状态;最后确认硬件连接,用万用表测量引脚电平。
另一个常见的坑是内核版本和设备树语法的兼容性。比如旧版本内核用gpio = <&gpio1 3 0>这种写法,新版本内核推荐用led-gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>。如果语法用错了,编译可能不报错,但驱动匹配不上。解决办法是参考内核源码中Documentation/devicetree/bindings/目录下的示例,或者直接看同类型驱动的设备树节点。
还有一点容易被忽略:内核配置。很多驱动依赖特定的内核配置选项,比如I2C驱动需要CONFIG_I2C=y和CONFIG_I2C_CHARDEV=y,SPI驱动需要CONFIG_SPI=y和CONFIG_SPI_SPIDEV=y。如果这些选项没打开,驱动编译都过不了。建议在make menuconfig之后,用make savedefconfig保存一个精简配置,方便后续对比。
6.3 交叉编译与部署环节的避坑清单
交叉编译环节的坑主要集中在工具链和库的匹配上。我整理了一个清单,供参考:
- 工具链前缀要和开发板架构匹配,ARMv7用
arm-linux-gnueabihf-,ARMv8用aarch64-linux-gnu-。 - 动态链接库要从工具链的
sysroot中拷贝,不能直接用主机的/lib目录。 - 编译应用程序时,
-I和-L参数要指向工具链的include和lib目录,避免误用主机的头文件和库。 - 部署到板子上的可执行文件,用
file命令确认架构正确,用ldd确认依赖库都能找到。 - 如果程序在板子上报“Permission denied”,检查文件是否有可执行权限,用
chmod +x添加。 - 如果报“No such file or directory”,但文件明明存在,大概率是动态链接器路径不对,检查
readelf -l中的interpreter字段。
这些经验都是我在实际项目中一点点积累的,书里可能不会全部覆盖,但对于初学者来说,提前知道这些坑可以省下大量时间。嵌入式Linux开发就是这样,理论知识只是基础,真正的能力是在一次次排查问题的过程中锻炼出来的。飞凌这本书的价值在于提供了一条经过验证的路径和一个可运行的平台,剩下的就是动手实践了。