1. 嵌入式Linux系统概述
嵌入式Linux,说白了就是把Linux这套操作系统塞进那些不是电脑的设备里。你家的路由器、智能电视、汽车中控台,甚至医院里的CT机,里面跑的很可能就是嵌入式Linux。
我刚开始接触这行的时候,也觉得不就是把Linux装上去嘛。后来踩了不少坑才明白——嵌入式Linux和桌面Linux完全是两码事。桌面Linux是通用平台,而嵌入式Linux是为特定硬件量身定做的。
什么是嵌入式Linux
嵌入式Linux,就是经过裁剪、定制,运行在嵌入式设备上的Linux操作系统。它保留了Linux内核的核心能力——进程管理、内存管理、文件系统、网络协议栈——但去掉了桌面系统那些花里胡哨的东西。
举个例子,你电脑上的Ubuntu安装完占好几个G,但一个嵌入式Linux系统,可能整个系统才几兆。为什么?因为不需要图形桌面、不需要办公软件、不需要那些你用不上的驱动。
核心特点:
- 可裁剪——按需配置,要什么功能就加什么
- 实时性——通过RT补丁或PREEMPT_RT实现硬实时
- 资源受限——可能只有几MB的Flash和几十MB的内存
- 稳定性要求高——设备可能几年不关机
我记得有一次给一个工业控制器做系统,Flash只有8MB,还得塞进内核、文件系统和应用程序。那段时间天天跟配置选项较劲,删一个模块省几百K,加一个功能多几十K。嗯,这就是嵌入式Linux的日常。
系统三大件:Bootloader、内核、根文件系统
一个完整的嵌入式Linux系统,说白了就三样东西:引导程序、内核、根文件系统。少了哪个都跑不起来。
| 组件 | 作用 | 常见实现 | 存储位置 |
|---|---|---|---|
| Bootloader | 硬件初始化,加载内核到内存 | U-Boot、Barebox | Flash起始扇区 |
| 内核 | 管理硬件资源,提供系统调用 | Linux Kernel | Flash或eMMC |
| 根文件系统 | 提供目录结构、应用程序、库文件 | BusyBox、Buildroot、Yocto | Flash、RAM或网络 |
Bootloader
设备上电后,CPU首先执行的就是Bootloader。它负责初始化时钟、内存、串口这些最基本的硬件,然后把内核从Flash拷贝到内存,跳转过去执行。
U-Boot是目前最主流的Bootloader。我习惯在U-Boot里加一个倒计时,按任意键进入命令行,不按就自动启动。调试阶段特别有用——万一内核起不来,还能在U-Boot里改启动参数。
我的经验:U-Boot的环境变量保存在Flash里,可以设置bootargs传给内核。比如指定console=ttyS0,115200,这样内核启动信息就能从串口输出了。调试时这个参数救过我很多次。
内核
内核是系统的核心。它管理CPU、内存、中断、设备驱动、网络协议栈……几乎所有硬件资源都由内核统一调度。
嵌入式Linux内核和桌面Linux内核用的是同一套源码,区别在于配置。桌面系统会编译进大量驱动和功能模块,而嵌入式系统只选需要的。你想想看,一个路由器需要显卡驱动吗?不需要。所以配置内核就是做减法。
根文件系统
内核启动完成后,需要挂载一个根文件系统。根文件系统里放着init进程、shell、各种命令、库文件、配置文件。
嵌入式系统常用的根文件系统有几种:
- initramfs——和内核打包在一起,启动快,适合小系统
- JFFS2/UBIFS——针对Flash优化的文件系统,支持磨损均衡
- NFS——网络挂载,开发调试阶段用,不用反复烧写Flash
我曾经在一个项目里用initramfs,整个根文件系统才2MB,启动时间不到3秒。客户要求上电即用,这个方案正好满足。
系统启动流程
从按下电源到进入shell,整个流程是这样的:
- 上电——CPU从复位向量执行,通常是Flash的0地址
- Bootloader第一阶段——初始化CPU、时钟、内存控制器
- Bootloader第二阶段——初始化串口、网口、Flash,加载内核
- 内核解压——如果内核是压缩的,先解压到内存
- 内核初始化——初始化中断、调度器、内存管理、驱动
- 挂载根文件系统——根据bootargs指定的root参数挂载
- 启动init进程——通常是/sbin/init,读取inittab
- 启动shell或应用程序——进入用户态
这个流程我背得滚瓜烂熟。为什么?因为每次系统起不来,就得顺着这个链条排查——是Bootloader没加载成功?内核解压出错?还是根文件系统挂载失败?
开发环境搭建
做嵌入式Linux开发,首先得把环境搭好。说白了就是两件事:交叉编译工具链和虚拟机。
交叉编译工具链
什么是交叉编译?你在x86的电脑上写代码,编译出ARM架构的可执行文件,这就叫交叉编译。因为目标设备(ARM)性能太弱,没法在上面直接编译。
工具链包含三部分:
- 编译器——arm-linux-gnueabihf-gcc
- 链接器——arm-linux-gnueabihf-ld
- 调试器——arm-linux-gnueabihf-gdb
获取工具链有几种方式:
| 方式 | 说明 | 推荐场景 |
|---|---|---|
| Linaro预编译工具链 | ARM官方维护,直接下载解压 | 快速上手,初学者首选 |
| Buildroot构建 | 自动下载源码编译工具链 | 需要定制glibc版本 |
| Yocto/OpenEmbedded | 完整构建系统,生成工具链 | 大型项目,商业产品 |
| 手动交叉编译 | 从源码编译gcc、binutils、glibc | 学习目的,特殊架构 |
⚠️ 避坑指南:我曾经在Linaro官网下载了一个armv7的工具链,结果目标芯片是armv5。编译出来的程序一运行就报非法指令。所以一定要确认工具链的架构版本和你的CPU匹配。
安装工具链其实很简单:
# 下载Linaro工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
# 解压到/opt
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt
# 添加环境变量
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
# 验证
arm-linux-gnueabihf-gcc --version
验证通过后,你就可以编译第一个嵌入式程序了:
arm-linux-gnueabihf-gcc -o hello hello.c
file hello
# 输出: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)
虚拟机配置
我习惯用Ubuntu作为开发主机。为什么?因为嵌入式Linux社区的工具链、文档、示例大多基于Ubuntu/Debian。你用其他发行版也行,但遇到问题查资料时,Ubuntu的解决方案最多。
虚拟机配置建议:
- 内存——至少4GB,编译内核时内存越大越快
- 磁盘——至少40GB,内核源码、工具链、编译产物都很占空间
- CPU——分配2-4核,make -j参数可以并行编译
- 网络——桥接模式,方便NFS挂载和TFTP下载
我的习惯:在虚拟机里装好Ubuntu后,第一件事就是安装必备软件包:build-essential、git、flex、bison、libncurses-dev。这些是编译内核的必需品。我一般写个脚本一键安装,省得每次重装虚拟机再折腾一遍。
sudo apt update
sudo apt install -y build-essential git flex bison \
libncurses-dev libssl-dev device-tree-compiler \
u-boot-tools qemu-system-arm
嗯,环境搭好了,后面就可以开始真正的内核配置和编译了。记住,开发环境是地基,地基不稳后面全是坑。