40.交叉编译
如果你想在 ARM、RISC-V、MIPS 等平台上运行程序,但又在 x86 主机(PC) 上开发,那么你必须掌握交叉编译。
别担心,我们会从 零开始、逐步深入,让你彻底明白:
“为什么要交叉编译、交叉编译器是什么、怎么用、怎么配置、怎么调试。”
🧩 一、什么是交叉编译(Cross Compilation)
✅ 通俗解释:
在一台电脑上编译出在另一种架构上运行的程序。
例如:
- 你的开发主机:Ubuntu x86_64
- 目标设备:ARM Cortex-A7
- 你希望在 Ubuntu 上写好代码,然后生成能在 ARM 板子上运行的
.elf或.bin文件。
这时,就需要 交叉编译器(cross compiler)。
🧠 区分三个概念
| 概念 | 含义 | 示例 |
|---|---|---|
| 主机(Host) | 编译器运行的平台 | x86 Ubuntu |
| 目标(Target) | 程序运行的平台 | ARM Cortex-A7 |
| 构建机(Build) | 编译器本身被构建的平台(通常与主机相同) | x86 Ubuntu |
👉 我们的任务:在 Host 上,为 Target 编译程序。
🧠 二、为什么需要交叉编译
在嵌入式开发中:
- 目标设备(如 ARM 板)性能弱;
- 资源有限(无完整 Linux、无编译器);
- 无法在目标上本地编译。
因此:
我们在 PC 上交叉编译 → 生成目标平台能运行的可执行文件 → 通过串口、网口或 SD 卡传到板子运行。
⚙️ 三、交叉编译器的组成
交叉编译器不是一个命令,而是一套工具链(toolchain):
| 工具 | 作用 |
|---|---|
gcc | 编译器 |
as | 汇编器 |
ld | 链接器 |
ar | 归档工具(打包静态库) |
objcopy | 格式转换(如 .elf → .bin) |
objdump | 反汇编 |
strip | 去除符号表(减小体积) |
🧰 四、交叉编译器命名规则
交叉编译器通常以目标平台为前缀:
| 工具链前缀 | 目标架构 | 示例命令 |
|---|---|---|
arm-linux-gnueabi- | ARM(Linux EABI) | arm-linux-gnueabi-gcc |
arm-none-eabi- | ARM 裸机(无OS) | arm-none-eabi-gcc |
aarch64-linux-gnu- | ARM64 | aarch64-linux-gnu-gcc |
riscv64-unknown-elf- | RISC-V | riscv64-unknown-elf-gcc |
🔧 五、安装交叉编译工具链
✅ Ubuntu 下安装 ARM 工具链
sudo apt update
sudo apt install gcc-arm-linux-gnueabi
或 64 位 ARM:
sudo apt install gcc-aarch64-linux-gnu
✅ 裸机开发工具链(无操作系统):
sudo apt install gcc-arm-none-eabi
安装完成后可查看:
arm-linux-gnueabi-gcc -v
🧱 六、第一次交叉编译实例
示例程序(hello.c)
#include <stdio.h>int main(void)
{printf("Hello ARM world!\n");return 0;
}
编译(目标为 ARM)
arm-linux-gnueabi-gcc hello.c -o hello_arm
查看可执行文件架构
file hello_arm
输出类似:
hello_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1, dynamically linked
💡 表示:这是 ARM 平台程序,不是 x86。
⚙️ 七、与普通 gcc 的区别
| 操作 | 普通编译(本机) | 交叉编译(目标机) |
|---|---|---|
| 编译器 | gcc | arm-linux-gnueabi-gcc |
| 运行平台 | x86 | ARM |
| 执行结果 | 可在 PC 上运行 | 必须传到 ARM 板上运行 |
| 库文件路径 | /usr/lib/x86_64-linux-gnu | /usr/arm-linux-gnueabi/lib |
💡 八、交叉编译链接库
编译时常常要指定库和头文件路径:
arm-linux-gnueabi-gcc main.c \-I/home/user/arm/include \-L/home/user/arm/lib \-lmylib -o main_arm
解释:
-I:头文件路径(include)-L:库路径(lib)-l:链接库名(不含前缀lib和后缀.so)
🔍 九、动态库 / 静态库的交叉编译
1️⃣ 交叉编译静态库
arm-linux-gnueabi-gcc -c add.c sub.c
arm-linux-gnueabi-ar rcs libmymath.a add.o sub.o
2️⃣ 使用静态库
arm-linux-gnueabi-gcc main.c -L. -lmymath -o main_arm
3️⃣ 动态库
arm-linux-gnueabi-gcc -fPIC -shared add.c sub.c -o libmymath.so
⚙️ 十、验证与运行
- 将生成的
main_arm上传到 ARM 板; - 通过串口、ssh 或 NFS 执行:
./main_arm
输出:
Hello ARM world!
🧩 十一、常见问题与排错
| 问题 | 原因 | 解决 |
|---|---|---|
cannot execute binary file: Exec format error | 程序架构不匹配 | 用 file 命令检查 ELF 类型 |
No such file or directory | 缺少运行时库 | 将对应 .so 拷贝到目标机 /lib |
undefined reference | 链接库缺失 | 加上 -L、-l 参数 |
🧠 十二、交叉编译系统(多层级)
复杂系统中(如 Linux 内核、U-Boot、BusyBox),会分为多级交叉编译:
| 层级 | 示例 | 编译内容 |
|---|---|---|
| Bootloader | U-Boot | 启动程序 |
| Kernel | Linux kernel | 驱动与系统 |
| RootFS | BusyBox, lib, app | 用户层应用 |
| Application | 自定义程序 | 业务逻辑 |
这些都是用交叉编译器编译的。
🚀 十三、实战建议
-
熟悉 file 命令
file xxx.elf→ 快速确认架构。
-
学会指定 sysroot
arm-linux-gnueabi-gcc main.c --sysroot=/opt/arm/sysroot让编译器自动从目标系统的根文件系统查找库。
-
Makefile 集成
CC = arm-linux-gnueabi-gcc CFLAGS = -O2 main: main.c$(CC) $(CFLAGS) -o $@ $<
✅ 总结:交叉编译流程全图
[源代码] │▼
[交叉编译器 (arm-linux-gnueabi-gcc)]│├── 编译(.o)├── 链接(.elf)└── 转换(.bin/.so)▼
[目标程序] → 上传至嵌入式设备 → 运行
下一步带你做一个实操演练:
💡 目标:
用 arm-none-eabi-gcc 为 STM32 微控制器交叉编译一个裸机程序(LED闪烁),
从 .c → .elf → .bin → 烧录 → 运行。
现在要进入嵌入式开发的真正核心——交叉编译 + 裸机(Bare-metal)开发实战。
我们这节的目标是:
🧠 从零开始,在 PC 上用
arm-none-eabi-gcc编译一个 ARM 裸机程序(LED 闪烁),
并彻底理解.c → .o → .elf → .bin → 烧录 → 运行的全过程。
🧩 一、什么是“裸机程序”(Bare-metal Program)
- “裸机” = 没有操作系统的程序。
- 在嵌入式开发中(如 STM32、NXP、TI DSP 等),
MCU 上没有 Linux/RTOS,而是直接运行一段 C/汇编写的代码。
💡 所以你要自己写:
- 启动代码(startup.s)
- 中断向量表
- 主函数(main.c)
- 链接脚本(linker script)
- 最后用交叉编译器生成
.bin或.elf
⚙️ 二、准备工具链
在 Ubuntu 下安装裸机交叉编译工具链:
sudo apt update
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi gdb-arm-none-eabi
验证:
arm-none-eabi-gcc -v
输出类似:
gcc version 11.2.1 (GNU Arm Embedded Toolchain)
🧱 三、目录结构示例
我们建一个简单工程:
led_demo/
├── main.c
├── startup_stm32.s
├── linker.ld
├── Makefile
💡 四、main.c — 主程序
// main.c
#define RCC_AHB1ENR (*(volatile unsigned int*)0x40023830)
#define GPIOA_MODER (*(volatile unsigned int*)0x40020000)
#define GPIOA_ODR (*(volatile unsigned int*)0x40020014)void delay(void) {for (volatile int i = 0; i < 100000; i++);
}int main(void) {RCC_AHB1ENR |= (1 << 0); // 使能 GPIOA 时钟GPIOA_MODER &= ~(3 << (5 * 2)); // 清除模式位GPIOA_MODER |= (1 << (5 * 2)); // 设置 PA5 为输出while (1) {GPIOA_ODR ^= (1 << 5); // 翻转 LEDdelay();}
}
📘 说明:
- 使用 STM32F4 寄存器地址;
- PA5 接板上 LED;
volatile避免编译器优化寄存器操作。
⚙️ 五、startup_stm32.s — 启动文件
这是裸机最关键的文件,负责程序从上电到 main() 的跳转。
.section .isr_vector, "a", %progbits.word _estack /* 栈顶地址 */.word Reset_Handler /* 复位中断向量 */.text.globl Reset_Handler
Reset_Handler:/* 初始化堆栈和数据段 */bl mainb .
📜 六、linker.ld — 链接脚本
告诉链接器代码和数据放在 MCU 的哪段地址空间。
ENTRY(Reset_Handler)MEMORY
{FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512KRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}SECTIONS
{.text : {KEEP(*(.isr_vector))*(.text*)*(.rodata*)} > FLASH.data : {*(.data*)} > RAM AT > FLASH.bss : {*(.bss*)} > RAM
}
🧰 七、Makefile — 自动化编译规则
CC = arm-none-eabi-gcc
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
CFLAGS = -mcpu=cortex-m4 -mthumb -O2 -Wall
LDFLAGS = -T linker.ldall: led.binled.elf: startup_stm32.o main.o$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^%.o: %.c$(CC) $(CFLAGS) -c $< -o $@%.o: %.s$(CC) $(CFLAGS) -c $< -o $@led.bin: led.elf$(OBJCOPY) -O binary $< $@clean:rm -f *.o *.elf *.bin
🧮 八、编译 & 生成
在项目根目录执行:
make
输出:
arm-none-eabi-gcc -c startup_stm32.s -o startup_stm32.o
arm-none-eabi-gcc -c main.c -o main.o
arm-none-eabi-gcc -T linker.ld -o led.elf startup_stm32.o main.o
arm-none-eabi-objcopy -O binary led.elf led.bin
查看文件:
ls -lh
-rwxr-xr-x led.elf # 可调试的ELF文件
-rw-r--r-- led.bin # 烧录用二进制文件
🔥 九、烧录与运行
✅ 方式1:使用 STM32CubeProgrammer(或 st-flash)
st-flash write led.bin 0x8000000
✅ 方式2:使用 JLink 命令
JLinkExe
loadfile led.bin 0x8000000
r
g
LED 闪烁即代表成功 🎉!
🧠 十、编译输出文件解释
| 文件 | 含义 |
|---|---|
.o | 单个编译单元的目标文件 |
.elf | 链接后的可执行文件(含调试符号) |
.bin | 纯二进制文件,烧录到 Flash |
.map | 地址映射文件(可选) |
🧩 十一、调试(gdb + OpenOCD)
1️⃣ 启动 OpenOCD:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
2️⃣ 另开终端,启动 GDB:
arm-none-eabi-gdb led.elf
3️⃣ 连接调试器:
target remote localhost:3333
load
monitor reset halt
break main
continue
🧠 十二、宏与寄存器映射复习
在上面 main.c 里:
#define GPIOA_ODR (*(volatile unsigned int*)0x40020014)
- 它让你可以直接通过宏访问硬件寄存器;
volatile告诉编译器:“不要优化这个变量,我要直接读写内存地址!”
这是 嵌入式编程的本质:C语言操作寄存器。
🧩 十三、总结图解
main.c + startup.s + linker.ld│▼[arm-none-eabi-gcc]│▼├─> main.o├─> startup.o├─> link (ld)▼led.elf▼[objcopy → .bin]▼led.bin▼[烧录 → Flash → 运行]
✅ 十四、延伸:交叉编译系统工程链
| 阶段 | 工具 | 输出 | 用途 |
|---|---|---|---|
| 编译 | arm-none-eabi-gcc | .o | 目标文件 |
| 链接 | arm-none-eabi-ld | .elf | 可调试可执行文件 |
| 转换 | objcopy | .bin | 烧录用 |
| 下载 | st-flash / openocd | Flash | 写入 MCU |
| 调试 | gdb | 单步、断点 |
接下来我可以教你更高级的交叉编译技巧:
- ✅ 多文件项目的 Makefile 架构
- ✅ 使用
CMake + Toolchain File构建 ARM 工程 - ✅ 交叉编译 Linux 内核 / BusyBox
是否继续深入到 Makefile 多模块与交叉编译工程构建?
非常棒!👏
你现在已经完全掌握了交叉编译的基础与裸机程序构建流程。
接下来我们将进入嵌入式系统工程级交叉编译实战 ——
这一步会让你理解:
“真正的嵌入式项目是怎么组织、编译、管理和移植的。”
🧩 一、为什么要使用 Makefile 管理大型交叉编译项目?
在嵌入式工程中:
- 项目往往由几十、上百个
.c/.h文件组成; - 不同模块需要不同的编译选项;
- 可能存在多种硬件平台(ARM、RISC-V、MIPS…);
- 手动输入
arm-none-eabi-gcc ...十分低效。
💡 解决方案:
使用 Makefile + 交叉编译器前缀 实现模块化构建。
🧱 二、项目结构示例
我们构建一个典型嵌入式工程结构:
project/
├── Makefile
├── linker.ld
├── src/
│ ├── main.c
│ ├── led.c
│ ├── uart.c
│ └── delay.c
├── include/
│ ├── led.h
│ ├── uart.h
│ └── delay.h
└── startup/└── startup_stm32.s
⚙️ 三、Makefile 设计思想
我们用三个逻辑区块:
1️⃣ 环境定义区
设置编译器、目标名、目录、参数等。
2️⃣ 编译规则区
定义如何把 .c 编译成 .o、如何链接成 .elf。
3️⃣ 伪目标区
make clean、make flash、make debug 等命令。
💡 四、Makefile 示例(完整 + 可运行)
###################################
# 工程基础配置
###################################
TARGET = led_demo
BUILD_DIR = build# 交叉编译器
CROSS_COMPILE = arm-none-eabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)as
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump# 目录结构
SRC_DIR = src
INC_DIR = include
STARTUP_DIR = startup# 编译选项
CFLAGS = -mcpu=cortex-m4 -mthumb -O2 -Wall -I$(INC_DIR)
LDFLAGS = -T linker.ld -nostartfiles###################################
# 自动收集源文件
###################################
SRCS = $(wildcard $(SRC_DIR)/*.c) $(wildcard $(STARTUP_DIR)/*.s)
OBJS = $(SRCS:%.c=$(BUILD_DIR)/%.o)
OBJS := $(OBJS:%.s=$(BUILD_DIR)/%.o)###################################
# 目标规则
###################################
all: $(BUILD_DIR)/$(TARGET).bin# 链接
$(BUILD_DIR)/$(TARGET).elf: $(OBJS)@echo "[LD] Linking..."$(LD) $(CFLAGS) $(LDFLAGS) -o $@ $^# 生成二进制文件
$(BUILD_DIR)/$(TARGET).bin: $(BUILD_DIR)/$(TARGET).elf$(OBJCOPY) -O binary $< $@@echo "[OK] Build completed: $@"# 编译 C 源文件
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR)@mkdir -p $(dir $@)$(CC) $(CFLAGS) -c $< -o $@# 编译汇编文件
$(BUILD_DIR)/%.o: %.s | $(BUILD_DIR)@mkdir -p $(dir $@)$(AS) -mcpu=cortex-m4 -mthumb $< -o $@###################################
# 清理
###################################
clean:rm -rf $(BUILD_DIR)@echo "[CLEAN] Done"###################################
# 烧录
###################################
flash: $(BUILD_DIR)/$(TARGET).binst-flash write $< 0x8000000###################################
# 调试
###################################
debug: $(BUILD_DIR)/$(TARGET).elfarm-none-eabi-gdb $<
🔍 五、执行流程说明
执行 make 时:
-
自动扫描
src/与startup/下的.c、.s文件; -
生成对应
.o到build/; -
链接所有
.o→ 生成.elf; -
转换为
.bin(用于烧录); -
输出结果:
build/led_demo.elf build/led_demo.bin
🧠 六、Makefile 的智能依赖机制
make 会根据文件修改时间决定是否重新编译。
比如你只修改了 uart.c:
- 它只会重新编译
uart.o; - 其他
.o文件不会重新编译; - 节省大量时间 ⏱️。
⚙️ 七、交叉编译宏与平台切换
你可以用宏定义区分不同目标平台。
# 在 Makefile 顶部添加
PLATFORM ?= STM32ifeq ($(PLATFORM),STM32)
CFLAGS += -DSTM32
CROSS_COMPILE = arm-none-eabi-
else ifeq ($(PLATFORM),RISCV)
CFLAGS += -DRISCV
CROSS_COMPILE = riscv64-unknown-elf-
endif
调用时:
make PLATFORM=RISCV
👉 自动切换编译器和宏。
🧩 八、在代码中使用条件编译适配平台
#ifdef STM32
#include "stm32f4xx.h"
#define LED_PIN 5
#elif defined(RISCV)
#include "riscv_soc.h"
#define LED_PIN 3
#endif
⚡ 九、使用 CMake 构建交叉编译工程(现代方法)
CMake 比 Makefile 更可移植,特别适合大型项目。
1️⃣ 工程结构
CMakeLists.txt
src/main.cled.c
include/led.h
2️⃣ CMake 配置文件
cmake_minimum_required(VERSION 3.13)
project(led_demo C)set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)set(CMAKE_C_FLAGS "-mcpu=cortex-m4 -mthumb -O2 -Wall")
include_directories(include)
add_executable(led_demo.elf src/main.c src/led.c)
3️⃣ 构建命令
mkdir build && cd build
cmake ..
make
输出:
[100%] Built target led_demo.elf
🧠 十、嵌入式大项目交叉编译架构图
┌─────────────────────────┐│ Host (x86 Ubuntu) ││ ┌────────────────────┐ ││ │Makefile / CMake │ ││ │arm-none-eabi-gcc │ ││ │linker.ld │ ││ └────────────────────┘ ││ │ │└──────────┼───────────────┘▼┌─────────────────────────┐│ Output: led.elf/.bin ││ ↓ ││ [st-flash] or [OpenOCD] ││ ↓ ││ Target (STM32 Board) │└─────────────────────────┘
🧰 十一、实战练习建议
-
尝试将上面工程移植到 RISC-V 工具链;
-
修改
Makefile支持DEBUG=1模式:make DEBUG=1自动增加
-g编译调试信息; -
练习在
gdb中设置断点、查看寄存器值; -
将工程结构分为:
drivers/(硬件驱动)core/(内核或HAL)app/(应用层)- 观察依赖关系如何通过 Makefile 组织。
✅ 十二、总结表格
| 阶段 | 内容 | 工具 | 输出 |
|---|---|---|---|
| 编译 | 多文件编译、宏控制 | arm-none-eabi-gcc | .o |
| 链接 | 指定 linker.ld | arm-none-eabi-ld | .elf |
| 转换 | 格式转换 | objcopy | .bin |
| 烧录 | 下载到 Flash | st-flash / JLinkExe | 运行在 MCU |
| 调试 | 查看变量、寄存器 | arm-none-eabi-gdb | 调试分析 |
接下来我可以教你:
- 🚀 交叉编译 Linux 内核 + BusyBox 根文件系统
- 🧠 如何构建完整嵌入式 Linux 工程(含 bootloader → kernel → rootfs → app)
- 🔍 从 Makefile 到 Yocto/CMake 的自动化构建体系演进
是否继续学习“嵌入式 Linux 的交叉编译系统工程”?
太好了 👍!你现在的学习进度已经进入 嵌入式工程师的核心能力区间。
前面我们讲了交叉编译在“裸机”场景(如 STM32)下的应用,
接下来进入更高级的工程实践 ——
在 Linux 系统层面做交叉编译:构建内核 + 根文件系统 + 应用程序
🧩 一、嵌入式 Linux 系统的构成
在嵌入式开发中,我们的目标往往是为 ARM、RISC-V 等架构设备(非 x86)构建 Linux 系统。
整个系统通常包含以下四个部分:
| 组成部分 | 作用 | 示例文件 |
|---|---|---|
| Bootloader | 启动引导,初始化硬件、加载内核 | U-Boot |
| Kernel | 操作系统内核,管理硬件 | zImage / uImage |
| Root File System | 根文件系统,提供用户空间命令与库 | rootfs.ext4 |
| Application | 用户程序(你写的 app) | hello |
这些部分 都要用交叉编译工具链编译,因为目标设备不是你的电脑架构。
⚙️ 二、交叉编译工具链的组成
常见的工具链有:
| 工具名 | 功能 |
|---|---|
arm-none-eabi-gcc | 无操作系统(裸机)编译器 |
arm-linux-gnueabihf-gcc | 编译 Linux 应用(ARM 平台) |
aarch64-linux-gnu-gcc | 编译 64 位 ARM 程序 |
riscv64-unknown-linux-gnu-gcc | RISC-V 平台 Linux 应用 |
这些工具链其实是 x86 主机 上编译出来的“交叉版本 GCC”。
🧱 三、交叉编译一个 Linux 应用程序示例
假设我们要编译一个简单的 hello.c,运行在 ARM Linux 板上。
🔹 源代码:
#include <stdio.h>
int main() {printf("Hello from ARM Linux!\n");return 0;
}
🔹 用交叉编译器编译:
arm-linux-gnueabihf-gcc hello.c -o hello_arm
输出:
file hello_arm
hello_arm: ELF 32-bit LSB executable, ARM, ...
将这个可执行文件放进 ARM 板(或 QEMU 模拟器)中运行:
./hello_arm
# 输出:
Hello from ARM Linux!
这说明:
你的编译环境(x86) → 交叉工具链 → ARM 设备 的完整通路已经打通 ✅。
🧠 四、交叉编译 Linux 内核(kernel)
1️⃣ 获取源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.xz
tar -xvf linux-5.10.tar.xz
cd linux-5.10
2️⃣ 配置交叉编译环境
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
3️⃣ 配置内核(选择适配的开发板)
make vexpress_defconfig
这会生成一个适用于 ARM Cortex-A 的默认配置。
4️⃣ 编译内核
make -j$(nproc)
输出的文件:
arch/arm/boot/zImage
arch/arm/boot/dts/vexpress-v2p-ca9.dtb
这两个文件就是最终运行在板上的 内核镜像 与 设备树文件。
🧰 五、构建 BusyBox 根文件系统
BusyBox 是一个“微型 Linux 命令合集”,非常适合嵌入式。
1️⃣ 下载源码
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
2️⃣ 设置交叉编译器
make menuconfig
# 在 Settings → Cross Compiler prefix 设置 arm-linux-gnueabihf-
3️⃣ 编译并安装到 rootfs
make -j$(nproc)
make install
默认会生成 _install/ 目录,里面就是最小根文件系统:
_install/
├── bin/
├── sbin/
├── etc/
├── usr/
└── linuxrc
🧩 六、组装最小系统镜像(手工 rootfs)
1️⃣ 创建根文件系统结构:
mkdir -p rootfs/{dev,proc,sys,tmp,etc,lib}
cp -r busybox-1.36.1/_install/* rootfs/
2️⃣ 拷贝库文件(glibc 动态库):
cp /usr/arm-linux-gnueabihf/lib/* rootfs/lib/ -a
3️⃣ 制作成镜像:
dd if=/dev/zero of=rootfs.ext4 bs=1M count=64
mkfs.ext4 rootfs.ext4
sudo mount -o loop rootfs.ext4 /mnt
sudo cp -r rootfs/* /mnt
sudo umount /mnt
最终生成:
rootfs.ext4
🚀 七、运行你的嵌入式 Linux 系统(使用 QEMU 模拟)
QEMU 可以模拟 ARM 平台,无需真实开发板。
qemu-system-arm \-M vexpress-a9 \-m 256M \-kernel arch/arm/boot/zImage \-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \-drive if=sd,format=raw,file=rootfs.ext4 \-append "root=/dev/mmcblk0 rw console=ttyAMA0" \-nographic
启动后进入 shell:
/ #
BusyBox v1.36.1
/ # ls
bin dev etc lib proc sbin tmp usr
恭喜你 🎉 ——
你刚刚在自己的电脑上用 交叉编译 从零构建了一个能启动的 ARM Linux 系统!
🧩 八、交叉编译应用放入根文件系统
现在把你之前编译的 hello_arm 放进 rootfs:
cp hello_arm rootfs/usr/bin/
重新生成 rootfs.ext4 后,在 QEMU 启动系统中执行:
/ # hello_arm
Hello from ARM Linux!
这就是完整的:
应用 → 文件系统 → 内核 → 平台 的嵌入式交叉编译闭环 ✅
📊 九、从裸机到嵌入式 Linux 的知识对比
| 项目 | 裸机交叉编译 | Linux 交叉编译 |
|---|---|---|
| 工具链 | arm-none-eabi-gcc | arm-linux-gnueabihf-gcc |
| 链接 | 手动指定 linker.ld | 内核自带链接脚本 |
| 文件系统 | 无(直接访问寄存器) | 必须有 rootfs |
| 调试 | JTAG / gdb | gdbserver / ssh |
| 目标 | MCU(如 STM32) | SoC(如 Allwinner, NXP) |
🧠 十、后续学习路线建议
你现在已经具备:
- ✅ 编译器基础(gcc)
- ✅ 交叉编译原理
- ✅ Makefile/CMake 管理工程
- ✅ 嵌入式 Linux 系统结构
接下来建议学习:
| 阶段 | 目标 | 学习内容 |
|---|---|---|
| 阶段 1 | 深入 Bootloader | U-Boot 启动过程与移植 |
| 阶段 2 | Linux Kernel | 驱动框架、设备树、内核裁剪 |
| 阶段 3 | RootFS 构建 | BusyBox、Buildroot、Yocto |
| 阶段 4 | 应用层开发 | 使用交叉编译开发 Qt / C++ 程序 |
| 阶段 5 | 系统集成 | 烧录、调试、OTA、优化 |
