Skip to content

第五层:嵌入式 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 系统可以抽象成如下结构:

text
[Bootloader] -> [Linux Kernel] -> [Root File System] -> [User Application]

各层职责:

  • Bootloader:完成最早期硬件初始化并加载内核
  • Kernel:管理 CPU、内存、驱动、网络、进程和系统调用
  • Root File System:提供用户空间目录结构、库和命令
  • User Application:承载具体业务逻辑

如果把 MCU 开发比作“一个程序控制一整块硬件”,Embedded Linux 更像是“一个完整的软件平台运行在硬件之上”。


启动流程详解

通用启动流程

Embedded Linux 的启动链路通常比 MCU 复杂得多,但核心思路是一致的:逐级初始化、逐级交接。

典型顺序:

  1. ROM Code:芯片内部固化启动逻辑
  2. 第一阶段 Bootloader:初始化最小运行环境
  3. 第二阶段 Bootloader:加载内核与设备树
  4. Linux Kernel:初始化驱动、挂载根文件系统
  5. init / systemd:启动用户空间服务和应用

学习这一部分时,最重要的是理解每个阶段分别负责什么,而不是死记每个平台的细节。

U-Boot(主流 Bootloader)

U-Boot 是最常见的嵌入式 Linux Bootloader。

它常见的职责包括:

  • 初始化 DDR、串口、时钟、存储设备
  • 提供命令行和下载功能
  • 选择启动介质(eMMC、SPI Flash、SD 卡、网络)
  • 加载内核镜像、设备树和 initramfs
  • 在升级、回滚和恢复场景中承担控制逻辑

Bootloader 在实际项目中的价值远不止“启动内核”,它还常常承担:

  • 工厂烧录
  • OTA 升级
  • 双分区切换
  • 故障恢复

设备树(Device Tree)

基本概念

设备树的作用,是用数据结构描述硬件,而不是把硬件信息硬编码在内核里。

它主要解决的问题是:

  • 同一套内核如何适配不同板卡
  • 哪个外设启用、哪个引脚复用、哪个中断号和地址空间有效

设备树让“内核代码”和“板级硬件描述”分离,这对嵌入式 Linux 非常重要。

示例结构

设备树通常以树形节点表达硬件:

dts
/ {
    model = "my-board";

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;
    };

    uart1: serial@40011000 {
        compatible = "vendor,uart";
        reg = <0x40011000 0x400>;
        interrupts = <5>;
        status = "okay";
    };
};

阅读设备树时,建议先看这些字段:

  • compatible
  • reg
  • interrupts
  • clocks
  • pinctrl
  • status

编译设备树

设备树源文件通常是 .dts / .dtsi,最终会编译成 .dtb

常见命令:

bash
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 运行”。

进程管理

常用命令:

  • ps
  • top
  • kill
  • htop(如果系统有)

重点关注:

  • 哪些进程在启动
  • 谁占用 CPU / 内存
  • 某个服务是否异常退出

网络调试

常见命令包括:

  • ifconfig / ip addr
  • ping
  • netstat / ss
  • route
  • tcpdump

这些工具对定位“设备连不上网”“服务端口没起来”“丢包严重”等问题非常关键。

设备与文件系统

常见命令:

  • mount
  • umount
  • df
  • du
  • lsblk
  • dmesg

学习要点:

  • 块设备与字符设备的区别
  • /dev/proc/sys 的作用
  • 文件系统挂载点和启动脚本之间的关系

软件包管理

在桌面 Linux 中软件包管理是安装软件,在 Embedded Linux 中更重要的是理解“系统内容来自哪里”。

常见方式:

  • 发行版包管理(如 aptopkg
  • Buildroot 打包
  • Yocto 镜像构建

对嵌入式开发者来说,重点不是记包命令,而是清楚:

  • 软件如何进入镜像
  • 依赖如何管理
  • 升级如何控制

Shell 脚本与自动化

Shell 脚本常用于:

  • 启动业务服务
  • 网络初始化
  • 系统自检
  • 产测脚本
  • 升级脚本

常见建议:

  • 关键步骤输出清晰日志
  • 带返回值检查
  • 对文件路径、权限和挂载状态做显式判断

交叉编译相关命令

嵌入式 Linux 开发中最重要的不是“在板子上编译”,而是“在主机上交叉编译后部署到板子”。

常见命令和环境变量:

bash
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
  • strace
  • perf
  • tcpdump

调试 Embedded Linux 时,最好同时具备三种视角:

  1. 启动阶段视角:Bootloader、内核日志、挂载过程
  2. 用户态视角:进程、服务、文件系统、网络
  3. 驱动视角:中断、寄存器、probe 过程、内核日志

常见开发平台

典型平台包括:

  • STM32MP1:MCU + MPU 结合,适合工业和 HMI
  • NXP i.MX 系列:常见于图形与多媒体终端
  • Rockchip:适合高性能嵌入式和显示场景
  • Allwinner:消费类和轻量终端较常见
  • Raspberry Pi:学习和原型验证方便

学习时不必急着覆盖所有平台,先选一个成熟开发板打通完整流程更重要。


嵌入式系统安全基础

安全启动(Secure Boot)

安全启动的核心是:只允许运行可信的固件。

基本思路:

  1. 建立硬件信任根
  2. 验证 Bootloader
  3. 验证内核和系统镜像
  4. 验证应用或升级包

它解决的是“设备启动的第一步就被篡改”的风险。

固件加密与防逆向

常见做法:

  • 固件加密存储
  • 读保护和调试口限制
  • 敏感算法放在安全区域
  • 对升级包做签名和校验

权限隔离与防护

Embedded Linux 的优势之一是可以利用成熟的权限体系:

  • 用户权限隔离
  • 文件权限限制
  • 容器或沙箱
  • SELinux / AppArmor(在较复杂系统中)

Bootloader 开发建议

Bootloader 设计上建议尽量考虑:

  • 下载失败后的恢复能力
  • 双分区升级
  • 升级标志位和回滚逻辑
  • 日志与错误码

本章小结

嵌入式 Linux 的难点在于系统层次更多、边界更复杂。把启动流程、设备树、驱动模型和根文件系统这几条主线串起来之后,后续学习 BSP、Yocto、内核移植会容易很多。

最后更新于:

以 GitHub Pages 发布,使用 VitePress 构建。