从零构建一个嵌入式 Linux 系统
完整实操指南:以 QEMU 模拟 vexpress-a9 + Buildroot 为例,从无到有构建一个可启动的 Linux 系统。
一、目标
在 QEMU 上启动一个完整的嵌入式 Linux 系统:
- 交叉编译工具链
- U-Boot bootloader
- Linux 内核
- 最小根文件系统(BusyBox)
- 自定义应用 + 字符设备驱动
二、准备环境
# 主机
sudo apt install -y \
build-essential git libncurses5-dev flex bison \
libssl-dev device-tree-compiler \
qemu-system-arm qemu-user-static \
u-boot-tools
mkdir ~/myboard && cd ~/myboard
mkdir -p src output
三、构建工具链
1. 用 Buildroot 自动生成
wget https://buildroot.org/downloads/buildroot-2024.02.tar.gz
tar xf buildroot-2024.02.tar.gz
cd buildroot-2024.02
make qemu_arm_vexpress_defconfig
make menuconfig
# Toolchain → C library: glibc / musl (用 musl 更小)
make -j$(nproc)
输出:
output/host/bin/arm-linux-gcc
output/images/rootfs.ext4
工具链示例:
output/host/bin/arm-buildroot-linux-uclibc-gcc # uclibc
output/host/bin/arm-buildroot-linux-musl-gcc # musl
2. 或手动用 crosstool-ng
git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng
./bootstrap && ./configure && make && sudo make install
ct-ng arm-cortex_a9-linux-gnueabihf
ct-ng build
四、编译 U-Boot
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout v2024.01
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_ca9x4_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
五、编译内核
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux
git checkout v6.1
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# 打开需要的选项
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
六、构建 Rootfs
1. 安装 BusyBox
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# Settings → Build Options → [*] Build as a static binary
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=/path/to/rootfs install
2. 准备 rootfs 目录
ROOTFS=/path/to/rootfs
mkdir -p $ROOTFS/{bin,sbin,etc,proc,sys,dev,run,tmp,usr/{bin,sbin,lib},var,lib,home,root}
# 必备 etc
cat > $ROOTFS/etc/fstab <<EOF
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devtmpfs /dev devtmpfs mode=0755 0 0
EOF
cat > $ROOTFS/etc/inittab <<EOF
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyAMA0 115200 vt100
::ctrlaltdel:/sbin/reboot
::shutdown:/etc/init.d/rcK
EOF
mkdir -p $ROOTFS/etc/init.d
cat > $ROOTFS/etc/init.d/rcS <<EOF
#!/bin/sh
mount -a
echo "Welcome to MyBoard!"
EOF
chmod +x $ROOTFS/etc/init.d/rcS
cat > $ROOTFS/etc/init.d/rcK <<EOF
#!/bin/sh
echo "Stopping all..."
EOF
chmod +x $ROOTFS/etc/init.d/rcK
# passwd
cat > $ROOTFS/etc/passwd <<EOF
root:x:0:0:root:/root:/bin/sh
EOF
cat > $ROOTFS/etc/group <<EOF
root:x:0:
EOF
# hostname
echo "myboard" > $ROOTFS/etc/hostname
# 添加用户应用
mkdir -p $ROOTFS/usr/bin
cp myapp $ROOTFS/usr/bin/
3. 创建设备节点
sudo mknod -m 600 $ROOTFS/dev/console c 5 1
sudo mknod -m 666 $ROOTFS/dev/null c 1 3
sudo mknod -m 666 $ROOTFS/dev/tty c 5 0
sudo mknod -m 600 $ROOTFS/dev/ttyAMA0 c 204 64
sudo mknod -m 666 $ROOTFS/dev/zero c 1 5
sudo mknod -m 666 $ROOTFS/dev/random c 1 8
sudo mknod -m 666 $ROOTFS/dev/urandom c 1 9
七、启动测试(QEMU)
1. 用 U-Boot 引导
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel u-boot \
-nographic \
-sd rootfs.ext4
U-Boot 命令行:
=> setenv bootargs 'console=ttyAMA0,115200 root=/dev/mmcblk0 rootfstype=ext4 rootwait'
=> setenv bootcmd 'mmc dev 0; ext4load mmc 0:1 0x60008000 zImage; ext4load mmc 0:1 0x61000000 vexpress-v2p-ca9.dtb; bootz 0x60008000 - 0x61000000'
=> saveenv
=> boot
2. 或直接内核启动
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel arch/arm/boot/zImage \
-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-drive file=rootfs.ext4,if=sd,format=raw \
-append "console=ttyAMA0,115200 root=/dev/mmcblk0 rootfstype=ext4 rootwait" \
-nographic \
-sd rootfs.ext4
八、串口登录
Welcome to MyBoard!
myboard login: root
#
九、添加自编字符设备驱动
1. 驱动源码 mychardev.c(见 04-kernel-driver/09-char-device.md)
2. 编译进内核
cp mychardev.c drivers/char/
echo "obj-y += mychardev.o" >> drivers/char/Makefile
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# Device Drivers → Character devices → MISC devices → My char device
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
3. 加载
# 内核命令行
insmod mychardev.ko
echo "test" > /dev/mychardev
cat /dev/mychardev
十、制作完整 SD 卡镜像
dd if=/dev/zero of=sdcard.img bs=1M count=128
# 分区(fdisk / sfdisk)
cat > sfdisk-script <<EOF
label: dos
unit: sectors
start=2048, type=6, name="boot"
start=526336, type=83, name="rootfs"
EOF
sfdisk sdcard.img < sfdisk-script
# 写 boot 分区
losetup -o 1048576 --sizelimit 268435456 sdcard.img
mkfs.ext4 /dev/loop0
mount /dev/loop0 /mnt
cp -r $ROOTFS/* /mnt/
umount /mnt
losetup -d /dev/loop0
十一、NFS 根(开发神器)
主机:
apt install nfs-kernel-server
echo "/path/to/rootfs 192.168.1.0/24(rw,sync,no_subtree_check)" >> /etc/exports
exportfs -a
QEMU:
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-append "console=ttyAMA0,115200 root=/dev/nfs ip=dhcp nfsroot=192.168.1.10:/path/to/rootfs,tcp,v3" \
-net nic,model=lan9118 -net tap,ifname=tap0 \
-nographic
十二、自动化(Buildroot 一键)
把上述过程写到 Buildroot:
board/myvendor/myboard/
├── linux.config # 内核配置
├── busybox.config # busybox 配置
├── readme.txt
├── rootfs_overlay/ # 直接覆盖
│ └── etc/inittab
└── post_build.sh # 编译后脚本
make myboard_defconfig
make -j$(nproc)
ls output/images/
十三、实物运行
替换 QEMU 为真板:
1. 把 sdcard.img 用 dd 写到 SD 卡
2. 插入开发板
3. 连接串口(115200 8N1)
4. 上电
5. 修改设备树为自己的 myboard.dtb
6. 重新烧写 U-Boot 到板载 Flash
十四、推荐开发板
- i.MX 6ULL(恩智浦,正点原子 / 野火 / 韦东山教程丰富)
- STM32MP157(ST,Cortex-A7 + M4 异构)
- 全志 V3S / H616(国产、低成本)
- RK3568(瑞芯微,性能强)
十五、复盘
完成本练习,你应该掌握:
- 工具链、U-Boot、内核、rootfs 的关系
- 设备树、内核模块、字符设备驱动
- QEMU 模拟 vs 实物调试
- 完整嵌入式 Linux 系统的搭建流程