1. 项目概述:从零开始理解嵌入式Linux开发
如果你对“嵌入式Linux开发”这个词感到既熟悉又陌生,觉得它像是单片机开发的升级版,又像是服务器Linux的缩小版,那你的感觉没错,但又不完全对。我干了十多年嵌入式,从8位单片机一路做到复杂的多核ARM+Linux系统,可以明确告诉你,嵌入式Linux开发是一个独特的领域,它融合了硬件底层操作和上层软件生态,既要求你有硬件的严谨思维,又需要你具备软件的系统架构能力。简单来说,它就是在资源受限的专用硬件上,裁剪、定制并运行Linux操作系统,最终实现特定功能的产品,比如智能家居中控、工业网关、车载信息娱乐系统、网络路由器等等。
那么,嵌入式Linux开发和普通的单片机开发、或者和在PC上玩Linux有什么区别?核心区别在于“目标”和“约束”。单片机开发通常面向裸机或RTOS,直接操纵寄存器,对内存和算力锱铢必较。而嵌入式Linux则提供了一个完整的操作系统环境,有进程管理、文件系统、网络协议栈,开发更接近桌面应用,但你必须时刻考虑你的硬件只有几十兆到几百兆的内存,存储可能只有几十到几百兆字节,CPU主频可能只有几百兆赫兹。它绝不是把Ubuntu装进一个小盒子里那么简单,而是一个高度定制化的过程。对于刚入行的朋友,或者是从单片机转向Linux的工程师,理清这条开发主线至关重要,它能帮你避开无数坑,把精力用在刀刃上。
2. 开发环境搭建与工具链选型
嵌入式Linux开发的第一步,不是急着写代码,而是搭建一个顺手的“工作台”。这个环境通常由宿主机(你的开发电脑)、目标板(最终的嵌入式设备)以及连接它们的桥梁(如网络、串口)构成。其中,工具链的选择是基石,它决定了你能否成功编译出能在目标板上运行的代码。
2.1 宿主机Linux环境选择
宿主机强烈推荐使用Linux发行版,Ubuntu LTS版本(如20.04, 22.04)是社区支持最广泛的选择,资料多,坑少。如果你必须用Windows,那么Windows Subsystem for Linux 2 (WSL2) 是目前最可行的方案,它提供了一个近乎原生的Linux内核环境,性能比早期的虚拟机方案好很多。不过,涉及到USB设备直连(如JTAG调试器)时,WSL2的配置会稍显麻烦,有时不如纯Linux或虚拟机(如VMware/VirtualBox)配合USB穿透来得直接。
我的经验是,如果你主要做应用开发,WSL2完全够用;如果涉及底层驱动、Bootloader调试,需要频繁连接硬件调试器,那么一台物理机安装的Ubuntu,或者配置完善的虚拟机是更稳妥的选择。记住,稳定、可复现的开发环境能极大提升效率,避免在环境问题上浪费生命。
2.2 交叉编译工具链的获取与配置
这是嵌入式开发的核心概念。你的宿主机(比如x86_64架构)和你的目标板(比如ARM Cortex-A53架构)指令集不同,无法直接运行彼此的程序。因此,你需要一个在x86上运行、却能生成ARM代码的编译器集合,这就是交叉编译工具链。
获取方式主要有三种:
- 芯片厂商提供:最推荐的方式。比如你使用NXP的i.MX系列芯片,就去NXP官网下载Yocto Project或他们定制好的工具链;用瑞芯微的RK芯片,就去他们的资源中心找。厂商提供的工具链通常优化得最好,包含了该芯片特有的库和头文件。
- 从源码构建:使用crosstool-ng等工具自己编译。这种方式最灵活,可以精细控制版本和选项,但耗时漫长,且容易出错,适合深度定制或学习工具链原理,不适合项目初期快速启动。
- 第三方预编译:如Linaro发布的工具链。通用性较好,更新也及时,当你使用的芯片比较通用(如Cortex-A系列),且厂商未提供时,这是一个很好的备选。
安装后,关键一步是配置环境变量,将工具链的bin目录加入PATH,并设置CROSS_COMPILE前缀。例如,对于arm-linux-gnueabihf-工具链,你需要在~/.bashrc中添加:
export PATH=/opt/gcc-linaro-arm-linux-gnueabihf/bin:$PATH export CROSS_COMPILE=arm-linux-gnueabihf-然后执行source ~/.bashrc。验证是否成功:在终端输入arm-linux-gnueabihf-gcc -v,应能正确输出编译器版本信息。
注意:不同工具链的命名规范不同,
CROSS_COMPILE变量的值通常是工具链前缀。例如,arm-linux-gnueabihf-gcc对应的前缀就是arm-linux-gnueabihf-。在编译内核或U-Boot时,这个变量至关重要。
2.3 必备辅助工具清单
除了工具链,以下工具能让你的开发过程如虎添翼:
- 代码编辑/IDE:VSCode + 远程开发插件是当前的主流,配合C/C++、CMake等插件,体验很好。传统一点的Vim/Emacs配置好也不错。对于大型项目,CLion等专业IDE也支持远程开发和交叉编译。
- 版本控制:Git是绝对标准。从第一天开始就用Git管理你的代码、内核配置、设备树修改,你会感谢这个决定。
- 终端与文件传输:
- 串口工具:
minicom,picocom,screen。用于连接目标板的调试串口,这是查看系统启动信息、进行基础调试的生命线。 - 网络工具:
ssh。这是你与目标板交互的主要方式,比串口快得多。确保目标板内核支持网络并运行了sshd服务。 - 文件传输:
scp,rsync。用于在宿主机和目标板之间同步文件。rsync在增量同步时效率极高。
- 串口工具:
- 调试工具:
gdb+gdbserver:远程调试的黄金组合。在目标板运行gdbserver,在宿主机用交叉编译版本的gdb连接上去,可以进行源码级调试。strace/ltrace:跟踪进程的系统调用或库函数调用,分析程序行为的神器。top/htop:查看目标板系统资源(CPU、内存)使用情况。
3. 系统构建三部曲:Bootloader、内核与根文件系统
一个可运行的嵌入式Linux系统,通常由Bootloader、Linux内核和根文件系统(Rootfs)三大部分组成。理解这三者的关系和构建方法是嵌入式Linux开发的核心技能。
3.1 Bootloader:硬件世界的引路人
Bootloader是系统上电后运行的第一段代码,它的使命简单而艰巨:初始化最基本的硬件(如时钟、内存、串口),然后将操作系统内核从存储设备(Flash、eMMC、SD卡)加载到内存中,并跳转到内核入口点开始执行。
U-Boot是绝对的主流选择。它是一个功能强大、支持芯片种类繁多的开源Bootloader。对于新手,你通常不需要从头编写,而是基于芯片厂商提供的U-Boot源码进行配置和编译。
U-Boot的典型工作流程:
- BL1/SPL:对于有些芯片,U-Boot被分为两阶段。SPL是一个极其精简的版本,负责初始化最基础的内存,然后加载完整的U-Boot到内存。
- U-Boot主阶段:完成更全面的硬件初始化,建立命令行环境,最后通过
bootcmd环境变量指定的命令(如load kernel; load dtb; bootz)来加载并启动内核。
关键操作:
- 配置:
make <board_name>_defconfig。这个defconfig文件通常由芯片厂商提供,包含了该开发板的基础配置。 - 编译:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-。编译后会生成u-boot.bin(可能还有SPL)等镜像文件。 - 烧写:根据板子支持的方式,可以通过SD卡、USB OTG、或者芯片内置的ROM Loader模式,使用厂商工具(如
uuufor NXP,upgrade_toolfor Rockchip)将U-Boot烧写到存储设备的特定偏移地址。
实操心得:U-Boot的环境变量是个好东西。你可以把常用的启动命令(如内核地址、设备树地址、启动参数)保存在环境变量中,避免每次手动输入。使用
saveenv命令可以将其保存到持久化存储(如Flash)。但要注意,错误的启动参数可能导致内核无法启动,此时可以通过串口中断U-Boot启动过程,进入命令行进行修改。
3.2 Linux内核:系统的核心引擎
Linux内核管理着所有硬件资源,为应用程序提供运行环境。嵌入式开发中,我们面对的是一个需要深度定制的内核。
内核源码获取与配置:
- 获取源码:同样,优先从芯片厂商的Git仓库获取他们长期维护的内核分支(如
linux-imxfor NXP)。这包含了该系列芯片的必要驱动和补丁。也可以从kernel.org获取主线内核,但可能需要自己移植驱动,工作量较大。 - 内核配置:这是关键且容易让人困惑的一步。内核有成千上万个配置选项。
- 基础配置:
make ARCH=arm <board_name>_defconfig。这会加载一个针对你开发板的基础配置。 - 精细调整:
make ARCH=arm menuconfig。这是一个基于ncurses的图形化配置界面。你需要在这里进行裁剪(去掉用不到的功能以减小体积)和添加(启用你需要的驱动和功能)。- 裁剪原则:对于嵌入式系统,遵循“按需启用”。例如,如果你的设备没有音频功能,就关掉所有声音子系统;如果只有有线网络,就关掉无线驱动。这能显著减小内核大小和内存占用。
- 驱动启用:找到你板上硬件的对应驱动,将其编译进内核(
*)或编译为模块(M)。通常,关键驱动(如网络、存储)建议直接编译进内核,确保系统能正常启动;不常用或可插拔的驱动可以编译为模块,在需要时动态加载。
- 基础配置:
设备树(Device Tree)详解: 这是现代ARM Linux内核的“硬件描述文件”。它取代了过去内核中大量的板级硬编码信息,以.dts文本文件的形式描述CPU、内存、总线、外设等硬件拓扑和资源分配。内核在启动时会解析这个文件,从而知道“我这个板子上有什么硬件,它们在哪里”。
.dts:设备树源文件,对应一个具体的板子。.dtsi:设备树包含文件,描述SoC级别的通用硬件信息,可以被多个.dts文件包含。- 编译:内核编译过程会使用
dtc工具将.dts编译成二进制的.dtb文件。 - 修改:当你添加了一个新的外设(比如一个GPIO连接的传感器),你通常需要修改对应的设备树文件,添加这个设备的节点,描述它使用的GPIO引脚、中断号、寄存器地址等信息。这是嵌入式Linux驱动开发中非常常见的工作。
内核编译与输出:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)-j选项指定并行编译的作业数,$(nproc)会自动获取你CPU的核心数,加快编译速度。编译完成后,在arch/arm/boot/目录下你会得到压缩的内核镜像zImage,在arch/arm/boot/dts/目录下得到对应的.dtb文件。这两个文件需要被部署到目标板的存储设备上。
3.3 根文件系统:用户空间的基石
内核启动后,需要挂载一个根文件系统(Rootfs),这里包含了系统运行所需的所有用户空间应用程序、库、配置文件和目录结构(如/bin,/sbin,/etc,/lib)。
构建根文件系统的几种方式:
- 使用Buildroot:这是对于初学者和大多数项目我最推荐的方式。Buildroot是一个集成的构建系统,通过简单的配置(
make menuconfig),它可以自动下载、交叉编译工具链、内核(可选)、以及你选择的各类应用软件包(如busybox, dropbear, iperf等),并打包生成一个完整的根文件系统镜像(如rootfs.tar或ext4格式镜像)。它自动化程度高,配置清晰,非常适合快速构建一个精简、定制的嵌入式系统。 - 使用Yocto Project/OpenEmbedded:功能更强大、更灵活,用于构建复杂的、产品级的Linux发行版。它通过“层”的概念来组织配置,可以精确控制每个软件包的版本和编译选项。但学习曲线陡峭,构建速度较慢,适合大型团队和需要严格版本控制与复现性的产品。
- 使用Debootstrap:在宿主机上模拟目标架构,安装一个Debian或Ubuntu的基础系统。这种方式得到的根文件系统比较“胖”,包含了包管理器(apt),方便后续安装软件,但体积较大,适合对空间不敏感、需要丰富软件生态的应用。
- 手工制作:最原始的方式,手动创建目录结构,交叉编译BusyBox(一个集成了上百个常用Unix命令的瑞士军刀)和必要的库(如glibc)。这种方式让你对根文件系统的组成有最深刻的理解,但效率最低,容易出错。
以Buildroot为例的简要流程:
# 1. 获取Buildroot git clone https://git.buildroot.net/buildroot cd buildroot # 2. 配置 make menuconfig # 在此界面选择目标架构(Target Architecture)、工具链、内核(可选)、需要安装的软件包等。 # 3. 编译(这会花费较长时间,因为它会从头编译工具链和所有选中的包) make -j$(nproc) # 4. 输出 # 编译完成后,输出文件在 `output/images/` 目录下,通常包括根文件系统镜像。生成的根文件系统镜像,需要和内核、设备树一起,通过适当的方式(如SD卡分区、通过U-Boot的tftp网络加载、或直接烧写到eMMC/NAND Flash的对应分区)部署到目标板。
4. 系统部署、启动与调试实战
当Bootloader、内核和根文件系统都准备好后,下一步就是将它们放到目标板上,并让系统成功跑起来。
4.1 存储介质规划与镜像烧写
嵌入式设备常见的存储介质有SD卡、eMMC、NAND Flash等。我们需要对存储空间进行分区规划。一个典型的分区表可能如下:
| 分区序号 | 偏移量 | 大小 | 文件系统 | 内容 | 说明 |
|---|---|---|---|---|---|
| 1 | 1MB | 1-2MB | 无 (RAW) | SPL | Bootloader第一阶段 |
| 2 | 2MB | 2-4MB | 无 (RAW) | U-Boot | Bootloader主镜像 |
| 3 | 4MB | 4-8MB | FAT32 | 内核(zImage)、设备树(.dtb) | 便于U-Boot通过fatload命令读取 |
| 4 | 8MB | 剩余空间 | EXT4 | 根文件系统 | 系统主要存储空间 |
你可以使用fdisk或parted工具在宿主机上对SD卡进行分区和格式化。然后,使用dd命令将SPL和U-Boot烧写到精确的偏移地址,将内核和dtb拷贝到FAT32分区,将根文件系统解压或直接dd到EXT4分区。
注意事项:
dd命令非常强大但也非常危险,务必确认of=参数是你的SD卡设备(如/dev/sdb),而不是你的系统硬盘!一个错误的dd命令可以瞬间清空你的硬盘数据。建议先使用lsblk命令确认设备名。
4.2 U-Boot引导参数设置
系统能否启动,U-Boot传递给内核的参数(bootargs)至关重要。这是一个典型的使用SD卡启动的bootargs示例:
console=ttymxc0,115200 earlyprintk root=/dev/mmcblk1p2 rootwait rwconsole=ttymxc0,115200:指定内核控制台为串口0,波特率115200。ttymxc0是NXP i.MX系列芯片串口的设备名,其他平台可能是ttyS0,ttyAMA0等。root=/dev/mmcblk1p2:指定根文件系统位于第二个SD卡(mmcblk1)的第二个分区(p2)。mmcblk0通常是eMMC。rootwait:等待根设备就绪后再挂载。rw:以读写方式挂载根文件系统。
你可以在U-Boot命令行中通过setenv bootargs ...来设置,并用saveenv保存。更常见的做法是将这些命令写入U-Boot的启动脚本(bootcmd环境变量)中,实现上电自动启动。
4.3 系统启动过程观察与问题定位
连接串口,上电,你将看到信息如瀑布般涌出。学会阅读这些启动信息是调试的必修课。
- U-Boot信息:会显示CPU型号、DRAM初始化大小、存储设备初始化情况。如果卡在这里,可能是DDR初始化失败或存储设备访问有问题。
- 内核解压与启动:看到“Uncompressing Linux...”字样。之后内核开始初始化,打印版本号,解析设备树,初始化CPU、中断、内存管理。
- 外设驱动初始化:会显示各个平台设备、总线、驱动匹配和初始化的信息。例如
[OK]或[FAILED]。 - 挂载根文件系统:寻找
“VFS: Mounted root (ext4 filesystem)”类似信息。如果这里失败,最常见的原因是root=参数指定错误,或者根文件系统镜像损坏、格式不对。 - 用户空间启动:内核最后会尝试执行根文件系统中的第一个用户进程,默认是
/sbin/init。之后就是用户空间的初始化(如通过/etc/inittab或systemd启动服务)。
4.4 开启SSH服务实现远程登录
串口调试是基础,但网络登录才是高效开发的常态。这需要在根文件系统中包含SSH服务器(通常用轻量级的dropbear或功能更全的openssh-server),并确保其随系统启动。
以Buildroot集成dropbear为例:
- 在
make menuconfig中,勾选Target packages -> Networking applications -> dropbear。 - 重新编译Buildroot,生成的根文件系统中就会包含
dropbear。 - 系统启动后,
dropbear服务会自动运行。你需要为目标板设置一个IP地址(可以通过DHCP自动获取,或在文件系统中静态配置)。 - 在宿主机上,使用
ssh root@<板子IP地址>即可登录。首次连接会提示保存主机密钥。
实操心得:为了开发方便,我通常会在Buildroot中配置一个静态IP,并禁用root用户的密码登录,改用公钥认证。这样既安全又方便。具体做法是:在
/etc/network/interfaces中配置静态IP,将宿主机~/.ssh/id_rsa.pub的内容追加到目标板的/root/.ssh/authorized_keys文件中。
5. 驱动开发入门与应用程序调试
当系统稳定运行后,真正的设备开发工作就开始了:让外设工作起来,并编写跑在上面的应用程序。
5.1 Linux驱动开发基础概念
Linux驱动运行在内核空间,充当硬件和用户空间应用程序之间的桥梁。它遵循“一切皆文件”的哲学,通过文件操作接口(open,read,write,ioctl,close)向应用程序提供服务。
驱动类型:
- 字符设备:以字节流形式访问的设备,如按键、LED、传感器。是最常见、最适合入门的驱动类型。
- 块设备:以数据块为单位访问的设备,如硬盘、SD卡、eMMC。
- 网络设备:面向数据包的设备,如以太网、Wi-Fi芯片。
一个最简单的字符设备驱动框架:
#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> static int mydev_open(struct inode *inode, struct file *filp) { printk(KERN_INFO "Device opened\n"); return 0; } static ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { // 将数据从内核空间拷贝到用户空间buf return 0; } static struct file_operations mydev_fops = { .owner = THIS_MODULE, .open = mydev_open, .read = mydev_read, // ... 其他操作函数 }; static int __init mydev_init(void) { // 1. 申请设备号 // 2. 创建cdev结构并初始化,关联fops // 3. 将cdev添加到内核 // 4. 在/sys/class/下创建类设备节点(可选,方便udev自动创建设备文件) printk(KERN_INFO "My device driver loaded\n"); return 0; } static void __exit mydev_exit(void) { // 释放所有资源 printk(KERN_INFO "My device driver unloaded\n"); } module_init(mydev_init); module_exit(mydev_exit); MODULE_LICENSE("GPL");编写驱动后,需要编写一个Makefile,使用内核的Kbuild系统进行交叉编译,生成.ko文件。然后通过insmod命令加载到目标板的内核中,使用mknod命令(或依靠udev自动)在/dev/目录下创建对应的设备节点(如/dev/mydevice)。应用程序就可以像操作普通文件一样,通过open("/dev/mydevice", O_RDWR)来访问这个设备了。
5.2 应用程序的交叉编译与部署
应用程序在宿主机上使用交叉编译工具链进行编译。
# 假设一个简单的Hello World程序 hello.c arm-linux-gnueabihf-gcc -o hello hello.c -static # 静态链接,避免依赖目标板上的库 # 或者动态链接,但需要将所需的库也放到目标板的/lib或/usr/lib下 # arm-linux-gnueabihf-gcc -o hello hello.c # 将编译好的可执行文件拷贝到目标板 scp hello root@<板子IP>:/home/root/ # 在目标板上执行 ./hello对于复杂的项目,使用CMake管理编译是更好的选择。你需要编写一个toolchain.cmake文件来指定交叉编译工具链,然后在CMake命令中引用它。
5.3 高级调试技巧
- 内核日志:
dmesg命令可以查看内核环形缓冲区中的消息。驱动中使用的printk输出就在这里。通过/proc/sys/kernel/printk可以调整内核日志级别。 - GDB远程调试:
- 目标板:
gdbserver :2345 ./my_app(在2345端口启动调试服务,等待连接) - 宿主机:
arm-linux-gnueabihf-gdb ./my_app- 在gdb中:
target remote <板子IP>:2345 - 然后就可以像调试本地程序一样设置断点、单步执行、查看变量了。
- 在gdb中:
- 目标板:
- 性能与资源分析:
top/htop:实时查看进程资源占用。free:查看内存使用情况。iostat,vmstat:查看IO和虚拟内存统计。perf:强大的性能分析工具,可以分析CPU热点、缓存命中率等,但需要内核支持。
6. 常见问题排查与避坑指南
这条路我踩过太多坑,这里总结几个最常见的问题和解决思路。
6.1 系统启动类问题
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| U-Boot无法启动,无串口输出 | 1. 供电问题 2. 启动介质选择错误(如拨码开关) 3. Bootloader镜像损坏或烧写位置错误 4. DDR初始化失败 | 1. 检查电源电压、电流是否达标。 2. 确认板子的启动模式设置正确(SD卡/eMMC/NOR Flash)。 3. 使用示波器或逻辑分析仪检查晶振、复位信号。确认烧写工具和地址偏移正确。 4. 查看芯片手册,确认U-Boot中DDR配置参数与板载内存型号匹配。 |
| U-Boot能启动,但加载内核时失败 | 1. 内核镜像或dtb文件损坏 2. 加载地址错误 3. 存储设备读取失败(如SD卡接触不良) | 1. 在U-Boot中使用fatls mmc 0:1等命令确认文件存在,或用md命令查看文件头是否正常。2. 检查 loadaddr、fdt_addr等环境变量设置是否正确,确保加载地址不与U-Boot、内核本身占用内存冲突。3. 尝试更换SD卡或重新插拔。 |
| 内核panic,提示“VFS: Unable to mount root fs” | 1.root=参数指定的设备节点不对2. 根文件系统镜像格式不对或损坏 3. 内核缺少对应文件系统的驱动(如ext4) 4. 根文件系统分区未识别 | 1. 仔细核对bootargs中的root=参数,是mmcblk0p2还是mmcblk1p2?可以在内核启动信息里搜索“mmc”查看识别到的设备。2. 在宿主机上检查根文件系统镜像是否能正常挂载。 3. 在内核 menuconfig中确认已编译进对应文件系统支持(如File systems -> <*> The Extended 4 (ext4) filesystem)。4. 检查内核是否识别到了存储设备的分区表。 |
| 内核启动后卡住,无任何输出 | 1. 控制台参数console=设置错误2. 内核崩溃过早,来不及输出信息 3. 根文件系统中的 /sbin/init不存在或无法执行 | 1. 确认串口设备名和波特率是否正确。尝试在U-Boot中修改bootargs的console参数。2. 尝试在内核命令行添加 earlyprintk参数,让内核更早打印信息。3. 检查根文件系统中 /sbin/init的链接是否正确(通常指向/lib/systemd/systemd或/bin/busybox init),以及其是否有可执行权限。 |
6.2 驱动与应用类问题
- 驱动加载失败,
insmod报错:使用dmesg | tail查看详细错误信息。常见原因:内核版本不匹配(驱动是用不同版本内核编译的)、符号未找到(依赖其他内核模块)、资源冲突(如IO地址、中断号已被占用)。 - 应用程序运行时提示“No such file or directory”:即使路径正确,也可能是动态链接器或库的问题。使用
file命令查看程序架构是否正确(应为ARM),使用ldd命令(在目标板上,或用交叉编译版本的arm-linux-gnueabihf-ldd在宿主机上)查看缺失的库。确保所有依赖的.so库都存在于目标板的/lib或/usr/lib目录下。 - 系统运行一段时间后卡死或重启:可能是内存泄漏、死锁或硬件不稳定。使用
top观察内存增长;检查内核日志dmesg是否有OOM(Out-Of-Memory)杀手信息或错误打印;对于死锁,可以尝试在驱动中增加调试信息,或使用内核的锁检测功能。
6.3 避坑经验谈
- 版本一致性是生命线:工具链、内核、Bootloader、库的版本尽量保持与芯片厂商提供的参考版本一致。随意混用高版本可能导致各种诡异的兼容性问题。
- 善用初始化脚本:在根文件系统的
/etc/init.d/或/etc/rc.local中放置你的启动脚本,用于设置IP、加载驱动、启动你的主应用程序。确保脚本有可执行权限(chmod +x)。 - 文件系统只读挂载:对于产品,在完成所有配置后,可以考虑将根文件系统以只读(
ro)方式挂载,提高系统的抗损坏能力。需要写的数据可以挂载到单独的可读写分区(如/data)。 - 备份!备份!备份!:任何重要的修改(内核配置
.config、设备树文件、Buildroot配置)之前,先进行备份或提交到Git。一个错误的配置可能让你花费数小时甚至数天来回溯。 - 理解硬件:再好的软件也绕不开硬件。手边备好原理图、芯片数据手册、板级硬件指南。当软件行为异常时,首先怀疑硬件连接和电源稳定性,用万用表、示波器验证。