嵌入式Linux系统搭建:交叉编译环境配置、Bootloader与内核编译、根文件系统制作、系统烧录与启动验证
嵌入式Linux开发,说白了就是「在PC上写代码,在板子上跑程序」。你想想看,我们的开发板资源有限,不可能在上面装个完整的Linux发行版来编译代码。所以,交叉编译就成了嵌入式开发的必修课。
我个人习惯把整个系统搭建过程分成四个阶段:环境准备、引导加载、内核编译、根文件系统制作。每一步都有坑,我踩过的坑比走过的路还多。今天咱们就一步步来,把这块硬骨头啃下来。
1. 交叉编译环境配置
交叉编译环境,就是让你的PC能编译出ARM架构的可执行文件。我刚开始做的时候,直接在Ubuntu上apt-get安装gcc,结果编译出来的程序在板子上跑不起来——架构不对嘛。
工具链的选择,我建议用Linaro提供的预编译工具链,省心。以ARM Cortex-A7为例:
# 下载ARM交叉编译工具链
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
# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 配置环境变量(建议写入~/.bashrc)
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
验证工具链是否安装成功:
arm-linux-gnueabihf-gcc --version
# 输出类似:arm-linux-gnueabihf-gcc (Linaro GCC 7.5-2019.12) 7.5.0
嗯,看到版本号就说明环境搭好了。这里要注意,CROSS_COMPILE这个变量名不能写错,后面编译U-Boot和内核都要用到它。
2. Bootloader编译——以U-Boot为例
Bootloader是板子上电后第一个跑的程序。它负责初始化硬件、加载内核。U-Boot是目前最主流的方案。
获取源码:
git clone https://github.com/u-boot/u-boot.git
cd u-boot
git checkout v2023.04 # 选一个稳定版本
配置与编译:
# 以某款Cortex-A7开发板为例
make distclean
make mx6ull_14x14_evk_defconfig # 选择对应板型的默认配置
make -j4
关键输出文件:
u-boot.bin—— 原始的U-Boot二进制文件u-boot.imx—— 带i.MX头部格式的镜像(NXP系列专用)u-boot.dtb—— 设备树文件
我曾经犯过一个低级错误:编译U-Boot时忘了设置ARCH=arm环境变量,结果编译出来的东西在板子上直接黑屏。排查了整整一天,最后发现是架构没指定。你想想看,这种低级错误多耽误事。
3. Linux内核编译
内核编译是整个系统搭建的核心环节。我建议先搞清楚你的硬件外设,再配置内核,不然编译出来的内核要么太大,要么缺驱动。
获取内核源码:
git clone https://github.com/torvalds/linux.git
cd linux
git checkout v6.1 # LTS版本,稳定
配置内核:
make distclean
make imx_v7_defconfig # 选择厂商提供的默认配置
make menuconfig # 图形化界面微调
在menuconfig里,我一般会做这几件事:
- 去掉不需要的驱动(比如Wi-Fi、蓝牙,如果板子没有)
- 开启自己外设的驱动(比如SPI屏、摄像头)
- 配置内核调试选项(方便后续排查问题)
编译内核与设备树:
make -j4 zImage
make -j4 dtbs
make -j4 modules
make modules_install INSTALL_MOD_PATH=./modules_install
编译完成后,你会得到:
| 文件 | 说明 |
|---|---|
| arch/arm/boot/zImage | 压缩后的内核镜像 |
| arch/arm/boot/dts/你的板子.dtb | 设备树二进制文件 |
| modules_install/lib/modules/ | 内核模块,需要复制到根文件系统 |
4. 根文件系统制作
根文件系统,就是Linux启动后挂载的根目录/。它包含了所有用户空间的程序、库、配置文件。我习惯用BusyBox来制作最小根文件系统。
编译BusyBox:
git clone https://github.com/mirror/busybox.git
cd busybox
make distclean
make defconfig
make menuconfig # 选择静态编译(Settings -> Build static binary)
make -j4
make install CONFIG_PREFIX=./rootfs
静态编译的好处是,生成的二进制文件不依赖动态库,拷贝到任何ARM板子上都能跑。但缺点是文件体积大一些。我个人偏向于动态编译,因为可以节省Flash空间,但需要把交叉编译器的库也拷贝过去。
构建根文件系统目录结构:
cd rootfs
mkdir -p dev etc init.d lib proc sys tmp var/log
创建必要的设备节点:
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
编写初始化脚本etc/inittab:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
编写etc/init.d/rcS:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Welcome to Embedded Linux!"
根文件系统制作完成后,用以下命令打包:
cd rootfs
find . | cpio -o -H newc | gzip > ../rootfs.cpio.gz
这个rootfs.cpio.gz就是最终的内核initramfs,可以直接嵌入到内核中,或者由U-Boot加载。
5. 系统烧录与启动验证
所有东西都编译好了,接下来就是烧录到板子上。以SD卡启动为例:
准备SD卡:
sudo fdisk /dev/sdb # 假设SD卡是/dev/sdb
# 创建两个分区:
# 分区1: 64MB, FAT32格式,存放内核和设备树
# 分区2: 剩余空间, ext4格式,存放根文件系统
sudo mkfs.vfat /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
烧录U-Boot到SD卡:
sudo dd if=u-boot.imx of=/dev/sdb bs=1K seek=1 conv=fsync
复制内核与设备树:
sudo mount /dev/sdb1 /mnt/boot
sudo cp zImage /mnt/boot/
sudo cp 你的板子.dtb /mnt/boot/
sudo umount /mnt/boot
复制根文件系统:
sudo mount /dev/sdb2 /mnt/rootfs
sudo cp -a rootfs/* /mnt/rootfs/
sudo umount /mnt/rootfs
插上SD卡,上电。如果一切顺利,你会看到串口输出类似这样的信息:
U-Boot 2023.04 (Jan 01 2024 - 12:00:00 +0800)
CPU: i.MX6ULL rev1.1 at 792 MHz
...
Starting kernel ...
...
Welcome to Embedded Linux!
/ #
- U-Boot烧录位置不对(偏移量错误)
- 内核设备树与板子不匹配(比如GPIO引脚定义错了)
- 根文件系统缺少关键库(比如/lib下没有ld-linux)
- 串口波特率设置不一致(U-Boot和终端软件要匹配)
我记得有一次,板子启动到一半卡在VFS: Cannot open root device。查了半天,发现是内核启动参数里root=指定的设备名不对。我用的SD卡是/dev/mmcblk0p2,但内核参数写成了/dev/sda2。这种小错误,排查起来真要命。
系统搭建完成后,我建议你做一个完整的备份。把整个SD卡用dd命令镜像出来,下次换板子或者搞坏了,直接恢复就行:
sudo dd if=/dev/sdb of=./my_embedded_system.img bs=4M status=progress
嗯,到这里,嵌入式Linux系统搭建的核心流程就走通了。从交叉编译环境,到U-Boot、内核、根文件系统,再到烧录验证,每一步都有它的门道。你动手做一遍,遇到问题再回来翻翻这篇文章,应该能少走不少弯路。
公众号:蓝海资料掘金营,微信deep3321