从零构建一个嵌入式 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

十四、推荐开发板

十五、复盘

完成本练习,你应该掌握:
- 工具链、U-Boot、内核、rootfs 的关系
- 设备树、内核模块、字符设备驱动
- QEMU 模拟 vs 实物调试
- 完整嵌入式 Linux 系统的搭建流程