3. 开发环境搭建:交叉编译工具链安装、QEMU模拟器配置、目标板准备与网络引导环境
实时内核 PREEMPT_RT 的移植与调优离不开一套可靠、可复现的开发环境。本章将指导你从零搭建完整的嵌入式 Linux 实时开发栈,涵盖交叉编译工具链、QEMU 模拟器、物理目标板(Raspberry Pi / BeagleBone)以及 TFTP/NFS 网络引导环境。每个环节都经过实测验证,确保后续章节的例程可以无缝运行。
环境对齐建议: 推荐使用 Ubuntu 22.04 LTS 作为宿主机。所有工具链和依赖包均通过 apt 或源码编译安装,避免使用容器化环境带来的内核模块加载限制。
3.1 交叉编译工具链安装
嵌入式目标板资源受限,无法本地编译大型内核源码,必须在宿主机上使用交叉编译工具链生成目标架构的二进制文件。本节以 aarch64(Raspberry Pi 3B+)和 armv7l(BeagleBone Black)为例。
3.1.1 工具链选择对比
| 工具链 | 目标架构 | C库 | 适用场景 | 安装方式 |
|---|---|---|---|---|
| gcc-arm-10.3-2021.07 | armv7l / aarch64 | glibc | 通用 Linux 内核 + 应用 | tar.xz 解压 |
| Linaro GCC 7.5 | armv7l | glibc | BeagleBone 传统 BSP | apt / 源码 |
| Buildroot 工具链 | armv7l / aarch64 | glibc / uclibc | 定制根文件系统 | Buildroot 编译 |
| Yocto SDK | aarch64 | glibc | 企业级 BSP 开发 | Yocto 构建 |
推荐选择: 对于 PREEMPT_RT 内核移植,使用 ARM 官方发布的
gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu 工具链,兼容性最好,且支持 C11/C++17 标准。
3.1.2 安装步骤(以 aarch64 为例)
- 下载工具链:从 ARM 开发者网站获取预编译包。
- 解压到指定目录:建议统一放在
/opt/toolchains/下。 - 设置环境变量:将工具链的
bin目录加入PATH。 - 验证安装:运行
aarch64-none-linux-gnu-gcc --version。
# 下载 aarch64 工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz
# 解压到 /opt
sudo tar -xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/toolchains/
# 设置环境变量(建议写入 ~/.bashrc)
export PATH=/opt/toolchains/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
export CROSS_COMPILE=aarch64-none-linux-gnu-
export ARCH=arm64
# 验证
aarch64-none-linux-gnu-gcc --version
# 输出示例:aarch64-none-linux-gnu-gcc (GCC) 10.3.1 20210621
避坑提示: 不要使用 Ubuntu 自带
gcc-arm-linux-gnueabihf 编译内核,其版本通常较旧(7.5),且缺少 __atomic 支持,会导致 PREEMPT_RT 的 futex 相关代码编译失败。
3.2 QEMU 模拟器配置
QEMU 可以在没有物理开发板的情况下验证 PREEMPT_RT 内核的启动和实时性。本节配置 qemu-system-aarch64 模拟 virt 平台,支持 GICv3 中断控制器,适合测试 PREEMPT_RT 的抢占延迟。
3.2.1 安装 QEMU 及相关依赖
# 安装 QEMU 完整包
sudo apt update
sudo apt install qemu-system-arm qemu-efi-aarch64 qemu-utils libvirt-daemon-system
# 验证安装
qemu-system-aarch64 --version
# 输出示例:QEMU emulator version 7.2.0
3.2.2 启动脚本模板
以下脚本启动一个带有 2 个 CPU 核心、2GB 内存的 virt 机器,使用 PREEMPT_RT 内核和 initramfs 根文件系统。
#!/bin/bash
# qemu_rt.sh - 启动 PREEMPT_RT 内核
KERNEL=./arch/arm64/boot/Image
DTB=./arch/arm64/boot/dts/arm/virt.dtb
INITRAMFS=./rootfs.cpio.gz
qemu-system-aarch64 \
-machine virt,gic-version=3 \
-cpu cortex-a72 \
-smp 2 \
-m 2G \
-kernel ${KERNEL} \
-dtb ${DTB} \
-initrd ${INITRAMFS} \
-append "console=ttyAMA0 root=/dev/ram0 rw earlycon" \
-nographic \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0
调试技巧: 添加
-s -S 参数可让 QEMU 在启动时暂停,等待 GDB 连接(端口 1234),便于单步调试内核启动流程。
3.3 目标板准备:Raspberry Pi 3B+ 与 BeagleBone Black
物理目标板用于最终验证实时性能。以下步骤确保板卡可以从 SD 卡启动自定义 PREEMPT_RT 内核。
3.3.1 Raspberry Pi 3B+ 准备
| 组件 | 要求 | 备注 |
|---|---|---|
| SD 卡 | ≥ 16GB,Class 10 | 建议使用 SanDisk Extreme |
| 固件 | raspberrypi-firmware | 包含 bootcode.bin / start4.elf |
| 串口线 | 3.3V USB-TTL | 连接 GPIO 14/15 (UART0) |
| 电源 | 5V / 2.5A | USB-C 或 MicroUSB |
3.3.2 制作可启动 SD 卡
# 1. 分区(假设 SD 卡为 /dev/sdb)
sudo fdisk /dev/sdb
# 创建 FAT32 分区(100MB),剩余为 ext4 分区
# 2. 格式化
sudo mkfs.vfat -F 32 /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
# 3. 挂载并复制固件
sudo mount /dev/sdb1 /mnt/boot
sudo mount /dev/sdb2 /mnt/rootfs
# 4. 复制 Raspberry Pi 固件
git clone --depth=1 https://github.com/raspberrypi/firmware
sudo cp -r firmware/boot/* /mnt/boot/
# 5. 复制 PREEMPT_RT 内核
sudo cp arch/arm64/boot/Image /mnt/boot/kernel8.img
sudo cp arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dtb /mnt/boot/
# 6. 配置 config.txt
echo "kernel=kernel8.img" | sudo tee /mnt/boot/config.txt
echo "arm_64bit=1" | sudo tee -a /mnt/boot/config.txt
# 7. 卸载
sudo umount /mnt/boot /mnt/rootfs
注意: Raspberry Pi 3B+ 的 GPU 固件要求
kernel8.img 为 64 位内核。如果使用 32 位内核,需命名为 kernel7.img 并设置 arm_64bit=0。
3.4 TFTP/NFS 网络引导环境搭建
网络引导(Netboot)允许目标板从宿主机加载内核和根文件系统,极大加速开发迭代。本节搭建 DHCP + TFTP + NFS 三件套。
3.4.1 服务端配置(宿主机)
| 服务 | 端口 | 配置文件 | 作用 |
|---|---|---|---|
| dnsmasq | 67/68/69 | /etc/dnsmasq.conf | DHCP + TFTP 一体化 |
| nfs-kernel-server | 2049 | /etc/exports | 导出根文件系统 |
3.4.2 dnsmasq 配置
# 安装 dnsmasq
sudo apt install dnsmasq
# 编辑 /etc/dnsmasq.conf
interface=eth0
dhcp-range=192.168.1.100,192.168.1.200,12h
dhcp-option=3,192.168.1.1
dhcp-option=6,192.168.1.1
enable-tftp
tftp-root=/srv/tftp
dhcp-boot=Image
# 创建 TFTP 根目录并复制内核
sudo mkdir -p /srv/tftp
sudo cp arch/arm64/boot/Image /srv/tftp/
sudo cp arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dtb /srv/tftp/
# 重启服务
sudo systemctl restart dnsmasq
3.4.3 NFS 根文件系统导出
# 安装 NFS 服务器
sudo apt install nfs-kernel-server
# 编辑 /etc/exports
/srv/nfs/rootfs 192.168.1.0/24(rw,no_root_squash,no_subtree_check,sync)
# 创建根文件系统目录(使用 debootstrap 或 Buildroot)
sudo mkdir -p /srv/nfs/rootfs
sudo debootstrap --arch=arm64 bullseye /srv/nfs/rootfs
# 导出并重启 NFS
sudo exportfs -a
sudo systemctl restart nfs-kernel-server
3.4.4 目标板 U-Boot 引导参数
在目标板的 U-Boot 提示符下设置以下环境变量,实现网络引导:
setenv serverip 192.168.1.10
setenv ipaddr 192.168.1.101
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1
setenv bootfile Image
setenv fdtfile bcm2710-rpi-3-b-plus.dtb
setenv rootpath /srv/nfs/rootfs
# 从 TFTP 加载内核和设备树
tftp ${kernel_addr_r} ${bootfile}
tftp ${fdt_addr_r} ${fdtfile}
# 设置内核命令行
setenv bootargs console=ttyAMA0,115200 root=/dev/nfs nfsroot=${serverip}:${rootpath},v3,tcp rw ip=${ipaddr}:::${gatewayip}:${netmask}::eth0:off
# 启动
booti ${kernel_addr_r} - ${fdt_addr_r}
快速验证: 在宿主机上使用
tftp 192.168.1.10 命令,然后执行 get Image,确认 TFTP 服务正常。NFS 可用 showmount -e 192.168.1.10 检查导出目录。
3.5 环境验证清单
完成上述搭建后,执行以下验证步骤,确保所有组件正常工作:
- 工具链验证:编译一个简单的 Hello World 程序,在目标板上运行。
- QEMU 验证:启动 PREEMPT_RT 内核,检查
/proc/sys/kernel/sched_rt_runtime_us是否存在。 - 目标板启动:从 SD 卡启动,确认内核日志包含
PREEMPT_RT字样。 - 网络引导验证:拔掉 SD 卡,目标板通过 TFTP/NFS 成功进入系统。
# 在目标板上验证实时内核
uname -a
# 输出应包含:PREEMPT RT
# 检查实时调度器参数
cat /proc/sys/kernel/sched_rt_runtime_us
# 输出:950000 (表示 95% 的 CPU 时间可分配给实时任务)
# 测试 cyclictest(需提前编译)
cyclictest -l 1000 -m -n -p 99
# 输出最大延迟应 < 50us(QEMU)或 < 20us(物理板)
环境就绪标志: 当你在目标板上执行
cyclictest 并看到稳定的微秒级延迟数据时,说明整个开发环境已成功搭建,可以进入下一阶段的实时内核配置与编译。