第二章 开发环境搭建:交叉编译工具链配置、Linux内核裁剪与编译、根文件系统制作、TFTP/NFS调试环境搭建

好,咱们正式开始动手了。这一章要解决的是「工欲善其事,必先利其器」的问题。嵌入式开发不像你在PC上写个Hello World那么简单,你写的代码最终要跑到ARM、MIPS或者RISC-V的板子上。所以,你得先搭好一座桥——交叉编译环境。

我个人习惯把这一章分成四个部分来讲:工具链、内核、根文件系统、调试环境。咱们一个一个来。

2.1 交叉编译工具链配置

什么是交叉编译?说白了,就是在你的x86电脑上,编译出能在ARM芯片上跑的程序。你想想看,你的开发板可能只有256MB内存,连个完整的GCC都跑不起来,更别说编译Linux内核了。所以,我们得用「交叉编译工具链」。

工具链的选择,我建议你直接用Linaro提供的预编译版本。为什么?因为我自己踩过坑——曾经花了一周时间从源码编译GCC,结果各种依赖问题,最后发现Linaro的版本稳定又好用。

常用工具链下载地址(以ARM Cortex-A系列为例):

  • 32位ARM:gcc-arm-linux-gnueabihf
  • 64位ARM:gcc-aarch64-linux-gnu

安装其实很简单,解压后配置环境变量就行:

# 下载并解压
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-*.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

小技巧: 我习惯把工具链放在 /opt 目录下,然后在 ~/.bashrc 里加一行 source 脚本。这样每次打开终端,环境变量自动生效,不用重复配置。

验证是否安装成功:

arm-linux-gnueabihf-gcc --version
# 如果能显示版本号,说明配置成功

嗯,这里要注意:不同版本的GCC对内核编译的支持不一样。我曾经用GCC 9.x编译Linux 4.14内核,结果报了一堆语法错误。后来换成GCC 7.5就没事了。所以,建议内核版本和工具链版本匹配

2.2 Linux内核裁剪与编译

内核裁剪,说白了就是「减肥」。你的开发板Flash可能只有16MB,而默认的内核编译出来有几十MB,根本放不下。所以,我们要把不需要的功能去掉。

我个人习惯先拿一个默认配置,然后一步步裁剪。以NXP i.MX6ULL为例:

# 下载内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.14.98.tar.xz
tar -xvf linux-4.14.98.tar.xz
cd linux-4.14.98

# 使用默认配置
make imx_v7_defconfig

# 进入菜单配置界面
make menuconfig

在menuconfig界面里,我一般会做以下几件事:

  • 关闭不需要的驱动:比如你的板子没有WiFi模块,就把Wireless相关的全关掉
  • 关闭调试选项:Kernel hacking里的很多调试选项在生产环境可以关掉
  • 精简文件系统支持:只保留你需要的文件系统类型,比如ext4、yaffs2

警告: 裁剪内核时,千万别把启动必须的驱动给关了。比如串口驱动(UART)、中断控制器(GIC)、定时器(Timer)。我曾经有一次把串口驱动关了,结果内核启动后没有任何输出,折腾了半天才发现是这个问题。

配置完成后,开始编译:

make -j4 zImage
make -j4 dtbs
make modules

编译完成后,你会得到:

  • arch/arm/boot/zImage —— 内核镜像
  • arch/arm/boot/dts/imx6ull-14x14-evk.dtb —— 设备树文件
  • 各个内核模块(.ko文件)

我一般会把zImage和dtb文件单独拿出来,放到TFTP目录下,方便调试。

2.3 根文件系统制作

内核启动后,需要挂载根文件系统。根文件系统里有什么?说白了,就是/bin、/sbin、/etc、/lib这些目录,还有你写的应用程序。

制作根文件系统,我推荐用Buildroot。它比Yocto轻量,比BusyBox功能全。我个人在项目里用了好几年Buildroot,稳定性不错。

# 下载Buildroot
wget https://buildroot.org/downloads/buildroot-2020.02.tar.gz
tar -xvf buildroot-2020.02.tar.gz
cd buildroot-2020.02

# 配置
make menuconfig

在配置界面里,你需要设置:

  • Target options —— 选择你的CPU架构(ARM)
  • Toolchain —— 选择外部工具链(就是我们刚才配置的那个)
  • System configuration —— 设置主机名、root密码等
  • Target packages —— 选择你需要的软件包(比如dropbear用于SSH)

我的经验: 第一次用Buildroot时,我建议先选一个最小的配置,只包含BusyBox和基本的库文件。等系统跑起来了,再慢慢加功能。这样出了问题容易定位。

配置完成后,直接编译:

make -j4

编译完成后,在output/images目录下会生成rootfs.tar、rootfs.cpio等文件。我一般用rootfs.tar,解压到NFS目录或者烧录到Flash里。

2.4 TFTP/NFS调试环境搭建

调试环境搭建,是嵌入式开发中最能提升效率的一环。你想想看,每次修改代码都要重新烧录Flash,那得多浪费时间?所以,我们要用TFTP下载内核,用NFS挂载根文件系统。

2.4.1 TFTP服务器配置

TFTP用于从开发板下载内核镜像和设备树文件。配置很简单:

# 安装TFTP服务器
sudo apt-get install tftpd-hpa

# 配置TFTP目录
sudo mkdir -p /tftpboot
sudo chmod 777 /tftpboot

# 修改配置文件 /etc/default/tftpd-hpa
# 内容如下:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure"

# 重启服务
sudo systemctl restart tftpd-hpa

把编译好的zImage和dtb文件复制到/tftpboot目录下:

cp arch/arm/boot/zImage /tftpboot/
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /tftpboot/

2.4.2 NFS服务器配置

NFS用于让开发板通过网络挂载根文件系统。这样你修改了应用程序,直接重新编译,开发板重启后就能看到新程序,不用烧录。

# 安装NFS服务器
sudo apt-get install nfs-kernel-server

# 创建NFS共享目录
sudo mkdir -p /nfs/rootfs
sudo chmod 777 /nfs/rootfs

# 解压根文件系统到NFS目录
sudo tar -xvf output/images/rootfs.tar -C /nfs/rootfs

# 修改配置文件 /etc/exports
# 添加一行:
/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)

# 重启服务
sudo systemctl restart nfs-kernel-server

避坑指南: 我曾经遇到过NFS挂载后,开发板上的应用程序无法写文件的问题。后来发现是no_root_squash没加。这个选项允许root用户以root权限访问NFS目录,不加的话,root会被映射成nobody,权限不够。

2.4.3 U-Boot启动参数设置

最后,在U-Boot里设置启动参数,让开发板从TFTP下载内核,从NFS挂载根文件系统:

setenv serverip 192.168.1.100    # 主机IP
setenv ipaddr 192.168.1.10      # 开发板IP
setenv netmask 255.255.255.0

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-evk.dtb; bootz 80800000 - 83000000'
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/nfs/rootfs rw ip=192.168.1.10'

saveenv
boot

嗯,这里要注意IP地址的设置。我建议把主机的IP设为静态IP,不然每次路由器重启,IP变了,开发板就找不到服务器了。

2.5 验证与测试

环境搭建完成后,我们来验证一下。开发板上电,串口输出应该能看到:

U-Boot 2020.04
...
Net:   FEC
Using FEC device
TFTP from server 192.168.1.100; our IP address is 192.168.1.10
Filename 'zImage'.
Load address: 0x80800000
Loading: #################################################################
         done
Bytes transferred = 4567890 (4.5 MiB)
...
Starting kernel ...

[    0.000000] Booting Linux on physical CPU 0x0
...
VFS: Mounted root (nfs filesystem) on device 0:12
...
Starting network: OK
Starting dropbear: OK

Welcome to Buildroot
buildroot login: root

看到这个登录提示,说明你的开发环境搭建成功了!

总结一下: 这一章我们做了四件事——配置交叉编译工具链、裁剪并编译Linux内核、用Buildroot制作根文件系统、搭建TFTP/NFS调试环境。这套环境搭好了,后面的开发工作就会顺畅很多。我个人建议你把每一步的配置都记录下来,写成脚本,下次换项目时直接复用。

下一章,我们会开始移植安防协议栈的核心模块。到时候,这套调试环境就会派上大用场了。