1. 嵌入式系统概述:从一块板子到完整系统
大家好,我是你们这门课的老朋友。做了十几年嵌入式,从最早的 8 位单片机一路折腾到现在的多核 ARM,踩过的坑比走过的路还多。今天咱们先聊聊最基础的东西——到底什么是嵌入式 Linux 系统。
你想想看,我们平时用的电脑,装个 Windows 或者 Ubuntu,插上键盘鼠标就能用。但嵌入式设备不一样。它没有显示器,没有标准键盘,甚至可能连个像样的外壳都没有。它被“嵌入”到某个设备里,默默干活。
嵌入式 Linux 系统 = Linux 内核 + 根文件系统 + 应用软件,运行在资源受限的专用硬件上。
1.1 什么是嵌入式 Linux 系统
说白了,就是把 Linux 这套东西,裁剪到能在小存储、低功耗的板子上跑起来。我最早接触嵌入式 Linux 是在 2008 年,那时候用的还是 2.6 内核,Flash 只有 8MB,RAM 才 32MB。现在想想,那点资源连个微信都装不下。
嵌入式 Linux 和桌面 Linux 最大的区别在哪?
- 资源受限:CPU 主频低,内存小,存储空间有限
- 硬件固定:不像 PC 可以随便换显卡、加内存,嵌入式硬件是定死的
- 功能专一:一个设备只干一件事,比如路由器只管转发数据包
- 实时性要求:很多场景需要确定性的响应时间,比如工业控制
嗯,这里要注意。嵌入式 Linux 不等于“小 Linux”。它不是一个缩水版,而是一个定制版。你不需要的东西,统统砍掉。我见过有人把内核裁剪到不到 1MB,照样跑得稳稳的。
1.2 交叉编译的概念
这个概念,新手最容易搞混。什么是交叉编译?
你在你的电脑上写代码、编译,生成的可执行文件却要在另一台设备上运行。这台设备可能是个 ARM 架构的开发板,或者是个 MIPS 架构的路由器。你的电脑是 x86 架构,目标板是 ARM 架构,指令集都不一样,当然不能直接编译。
我的习惯:在宿主机上搭建交叉编译工具链,编译出目标板能运行的二进制文件。这个工具链包含交叉编译器、交叉链接器、交叉调试器等。
举个例子:
# 宿主机上编译一个简单的 hello.c
# 使用交叉编译器,目标架构是 ARM
arm-linux-gnueabihf-gcc -o hello hello.c
# 查看生成的文件类型
file hello
# 输出:ELF 32-bit LSB executable, ARM, version 1 (SYSV)
看到没?这个 hello 文件是 ARM 格式的,在 x86 电脑上跑不了。你得把它拷贝到开发板上才能执行。
我曾经犯过一个低级错误——在宿主机上编译了一个程序,直接拷贝到板子上,然后报错“Exec format error”。查了半天才发现,我用的编译器是 x86 的 gcc,不是交叉编译器。嗯,从那以后,我每次编译前都会确认一下工具链前缀。
3 目标板与宿主机的关系
这两个概念,我建议你从一开始就分清楚。
| 角色 | 定义 | 典型设备 | 主要任务 |
|---|---|---|---|
| 宿主机 | 你用来开发的电脑 | PC、笔记本(x86) | 编写代码、交叉编译、调试 |
| 目标板 | 最终运行系统的硬件 | ARM 开发板、路由器、工控机 | 运行编译好的内核和应用程序 |
它们之间怎么通信?最常见的方式有几种:
- 串口:最基础的方式,通过 UART 连接,用 minicom 或 putty 登录
- 网络:通过以太网或 WiFi,用 SSH 登录,或者用 NFS 挂载根文件系统
- JTAG/SWD:硬件调试接口,用来烧录 Bootloader 或调试内核
- USB:有些板子支持 USB gadget,模拟成网卡或串口
避坑指南:我曾经在调试一块新板子时,串口线接反了 TX 和 RX,折腾了半小时没输出。后来用万用表一量,才发现线序不对。记住,串口线要交叉连接——TX 接 RX,RX 接 TX。
1.4 嵌入式系统的典型组成
一个完整的嵌入式 Linux 系统,从底层到上层,通常由三大部分组成。我画了一张图,帮你理清它们的关系。
从上图可以看得很清楚,系统启动是有顺序的。我一个个来讲。
1.4.1 Bootloader
板子上电后,CPU 会从某个固定地址开始执行代码。这个地址上放的就是 Bootloader。它的任务很简单:初始化硬件,加载内核到内存,然后跳转到内核入口。
最常见的 Bootloader 是 U-Boot。我习惯用 U-Boot 2018 以后的版本,支持设备树,配置起来方便很多。
# U-Boot 常用命令示例
# 查看内存信息
bdinfo
# 从网络加载内核
tftp 0x42000000 zImage
# 启动内核
bootz 0x42000000 - 0x43000000
我的经验:调试新板子时,先把 U-Boot 跑起来。只要能进 U-Boot 命令行,后面的事情就好办了。如果 U-Boot 都起不来,先检查时钟配置和 DDR 初始化参数。
1.4.2 Linux 内核
内核是系统的核心。它管理硬件资源,提供系统调用接口。嵌入式场景下,内核需要裁剪——去掉不需要的驱动和功能,减小体积,加快启动速度。
我见过有人把内核从 5MB 裁剪到 1.2MB,启动时间从 8 秒降到 2 秒。怎么做到的?
- 去掉不用的文件系统支持(比如 XFS、Btrfs)
- 去掉不用的网络协议(比如 IPX、DECnet)
- 去掉调试选项和打印信息
- 把驱动编译成模块,按需加载
1.4.3 根文件系统
内核启动后,需要挂载一个根文件系统。这里面放着所有用户空间的程序、库文件、配置文件、设备节点等。
根文件系统的选择,取决于你的需求:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| initramfs | 集成在内核中,内存中解压 | 早期启动、调试、临时系统 |
| JFFS2/UBIFS | 支持 NAND Flash,有磨损均衡 | 产品量产,需要持久化存储 |
| ext4 | 标准 Linux 文件系统 | SD 卡、eMMC、硬盘 |
| SquashFS | 只读、高压缩比 | 固件升级、只读根文件系统 |
| NFS | 网络挂载,开发调试方便 | 开发阶段,频繁修改文件 |
避坑指南:我曾经在产品量产时用了 JFFS2,结果发现频繁写入导致 Flash 坏块增多。后来换成 UBIFS,配合 UBI 层的坏块管理,问题才解决。选文件系统时,一定要考虑 Flash 类型和写入频率。
1.5 本章小结
这一章我们聊了嵌入式 Linux 的基本概念。说白了,就是三件事:
- 交叉编译——在宿主机上编译,在目标板上运行
- 系统组成——Bootloader 引导,内核管理硬件,根文件系统提供用户空间
- 开发模式——宿主机开发,目标板运行,通过串口或网络通信
这些概念,后面每一章都会用到。尤其是交叉编译和系统组成,是整门课的基石。我建议你先把这些搞透,再往下走。