第五层:嵌入式 Linux 开发基础
这一章帮助你从 MCU 开发视角跨到 Embedded Linux 体系。重点不在“会用命令”,而在理解从 Bootloader、内核、设备树到根文件系统和用户空间之间的完整关系。
建议学习目标:
- 搞清楚 Bootloader、Kernel、RootFS、应用程序的职责边界。
- 理解设备树为什么存在,以及它如何描述硬件。
- 掌握常见 Linux 开发工具、交叉编译流程和调试思路。
- 对系统安全、权限隔离和 Bootloader 设计有基础认知。
阅读建议:先看系统启动链路,再看设备树和驱动模型,最后再学习工具链、文件系统和安全相关内容。
嵌入式 Linux 系统概览
嵌入式 Linux 特点
嵌入式 Linux 不是简单把 Linux 装到板子上,而是围绕特定硬件和产品目标裁剪出来的 Linux 系统。
它的典型特点包括:
- 可裁剪:内核、驱动、文件系统、服务都可按需精简
- 可移植:支持 ARM、RISC-V、MIPS、x86 等多种架构
- 生态强:网络、文件系统、多进程、图形界面、中间件非常丰富
- 资源要求更高:相比裸机和 RTOS,需要更多存储与内存
适合使用 Embedded Linux 的常见场景:
- 复杂网络设备
- 带显示和多媒体能力的终端
- 网关与边缘计算设备
- 需要文件系统、用户态应用和远程运维能力的产品
系统组成
一个典型 Embedded Linux 系统可以抽象成如下结构:
[Bootloader] -> [Linux Kernel] -> [Root File System] -> [User Application]各层职责:
- Bootloader:完成最早期硬件初始化并加载内核
- Kernel:管理 CPU、内存、驱动、网络、进程和系统调用
- Root File System:提供用户空间目录结构、库和命令
- User Application:承载具体业务逻辑
如果把 MCU 开发比作“一个程序控制一整块硬件”,Embedded Linux 更像是“一个完整的软件平台运行在硬件之上”。
启动流程详解
通用启动流程
Embedded Linux 的启动链路通常比 MCU 复杂得多,但核心思路是一致的:逐级初始化、逐级交接。
典型顺序:
- ROM Code:芯片内部固化启动逻辑
- 第一阶段 Bootloader:初始化最小运行环境
- 第二阶段 Bootloader:加载内核与设备树
- Linux Kernel:初始化驱动、挂载根文件系统
- init / systemd:启动用户空间服务和应用
学习这一部分时,最重要的是理解每个阶段分别负责什么,而不是死记每个平台的细节。
U-Boot(主流 Bootloader)
U-Boot 是最常见的嵌入式 Linux Bootloader。
它常见的职责包括:
- 初始化 DDR、串口、时钟、存储设备
- 提供命令行和下载功能
- 选择启动介质(eMMC、SPI Flash、SD 卡、网络)
- 加载内核镜像、设备树和 initramfs
- 在升级、回滚和恢复场景中承担控制逻辑
Bootloader 在实际项目中的价值远不止“启动内核”,它还常常承担:
- 工厂烧录
- OTA 升级
- 双分区切换
- 故障恢复
设备树(Device Tree)
基本概念
设备树的作用,是用数据结构描述硬件,而不是把硬件信息硬编码在内核里。
它主要解决的问题是:
- 同一套内核如何适配不同板卡
- 哪个外设启用、哪个引脚复用、哪个中断号和地址空间有效
设备树让“内核代码”和“板级硬件描述”分离,这对嵌入式 Linux 非常重要。
示例结构
设备树通常以树形节点表达硬件:
/ {
model = "my-board";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
uart1: serial@40011000 {
compatible = "vendor,uart";
reg = <0x40011000 0x400>;
interrupts = <5>;
status = "okay";
};
};阅读设备树时,建议先看这些字段:
compatiblereginterruptsclockspinctrlstatus
编译设备树
设备树源文件通常是 .dts / .dtsi,最终会编译成 .dtb。
常见命令:
dtc -I dts -O dtb -o board.dtb board.dts实际工程里一般由内核构建系统或 Buildroot / Yocto 自动完成编译。
常用 Linux 命令与开发工具
文件与目录管理
最常用的基础命令包括:
ls:查看文件cp/mv/rm:复制、移动、删除mkdir:创建目录cat/less/tail:查看文件内容
嵌入式 Linux 中最常见的实际用途是:
- 检查挂载点
- 查看日志
- 检查配置文件
- 操作脚本和升级包
权限与用户管理
需要重点理解:
- 用户与用户组
- 读写执行权限
chmod/chown- root 权限与最小权限原则
嵌入式产品中,很多安全问题都源于“所有服务都用 root 运行”。
进程管理
常用命令:
pstopkillhtop(如果系统有)
重点关注:
- 哪些进程在启动
- 谁占用 CPU / 内存
- 某个服务是否异常退出
网络调试
常见命令包括:
ifconfig/ip addrpingnetstat/ssroutetcpdump
这些工具对定位“设备连不上网”“服务端口没起来”“丢包严重”等问题非常关键。
设备与文件系统
常见命令:
mountumountdfdulsblkdmesg
学习要点:
- 块设备与字符设备的区别
/dev、/proc、/sys的作用- 文件系统挂载点和启动脚本之间的关系
软件包管理
在桌面 Linux 中软件包管理是安装软件,在 Embedded Linux 中更重要的是理解“系统内容来自哪里”。
常见方式:
- 发行版包管理(如
apt、opkg) - Buildroot 打包
- Yocto 镜像构建
对嵌入式开发者来说,重点不是记包命令,而是清楚:
- 软件如何进入镜像
- 依赖如何管理
- 升级如何控制
Shell 脚本与自动化
Shell 脚本常用于:
- 启动业务服务
- 网络初始化
- 系统自检
- 产测脚本
- 升级脚本
常见建议:
- 关键步骤输出清晰日志
- 带返回值检查
- 对文件路径、权限和挂载状态做显式判断
交叉编译相关命令
嵌入式 Linux 开发中最重要的不是“在板子上编译”,而是“在主机上交叉编译后部署到板子”。
常见命令和环境变量:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make menuconfig
make -j8理解交叉编译时要特别关注:
- 主机架构与目标架构不同
- sysroot 的作用
- 链接的是目标平台库,而不是主机库
Linux 驱动开发模型
Linux 驱动不是简单“写寄存器”,它需要适配内核框架和总线模型。
常见模型包括:
- platform driver
- I2C driver
- SPI driver
- character device
- network driver
驱动开发时通常要理解:
- 设备如何被匹配到驱动
- probe / remove 生命周期
- 中断怎么注册
- 资源如何申请与释放
如果这部分和 MCU 驱动开发相比感觉更复杂,原因就在于 Linux 驱动不仅要操作硬件,还要服从内核的统一模型。
根文件系统构建
根文件系统(RootFS)是用户空间的基础运行环境。
它通常包括:
/bin、/sbin、/usr- 动态库
- 启动脚本
- 配置文件
- 业务程序
常见构建方式:
- BusyBox:提供轻量命令集合
- Buildroot:适合快速构建中小型系统
- Yocto:适合大型产品和复杂供应链管理
选择哪种方式,取决于:
- 项目规模
- 长期维护成本
- 第三方包复杂度
工具链与调试手段
常见调试手段包括:
- 串口日志
dmesg- GDB / gdbserver
straceperftcpdump
调试 Embedded Linux 时,最好同时具备三种视角:
- 启动阶段视角:Bootloader、内核日志、挂载过程
- 用户态视角:进程、服务、文件系统、网络
- 驱动视角:中断、寄存器、probe 过程、内核日志
常见开发平台
典型平台包括:
- STM32MP1:MCU + MPU 结合,适合工业和 HMI
- NXP i.MX 系列:常见于图形与多媒体终端
- Rockchip:适合高性能嵌入式和显示场景
- Allwinner:消费类和轻量终端较常见
- Raspberry Pi:学习和原型验证方便
学习时不必急着覆盖所有平台,先选一个成熟开发板打通完整流程更重要。
嵌入式系统安全基础
安全启动(Secure Boot)
安全启动的核心是:只允许运行可信的固件。
基本思路:
- 建立硬件信任根
- 验证 Bootloader
- 验证内核和系统镜像
- 验证应用或升级包
它解决的是“设备启动的第一步就被篡改”的风险。
固件加密与防逆向
常见做法:
- 固件加密存储
- 读保护和调试口限制
- 敏感算法放在安全区域
- 对升级包做签名和校验
权限隔离与防护
Embedded Linux 的优势之一是可以利用成熟的权限体系:
- 用户权限隔离
- 文件权限限制
- 容器或沙箱
- SELinux / AppArmor(在较复杂系统中)
Bootloader 开发建议
Bootloader 设计上建议尽量考虑:
- 下载失败后的恢复能力
- 双分区升级
- 升级标志位和回滚逻辑
- 日志与错误码
本章小结
嵌入式 Linux 的难点在于系统层次更多、边界更复杂。把启动流程、设备树、驱动模型和根文件系统这几条主线串起来之后,后续学习 BSP、Yocto、内核移植会容易很多。
