2、开发环境搭建:交叉编译工具链安装、内核源码树配置、最小系统制作与NFS挂载

说实话,做字符设备驱动开发,环境搭建这一步要是没弄好,后面全是坑。我见过太多新手,代码写得挺漂亮,结果编译都过不了,或者烧进去系统直接崩了。今天咱们就把这四件事儿捋清楚:交叉编译工具链、内核源码树、最小系统、NFS挂载。

2.1 交叉编译工具链安装

先说说交叉编译。你想想看,咱们的PC是x86架构,但嵌入式设备通常是ARM、RISC-V或者MIPS。在PC上编译出能在ARM上跑的程序,这就是交叉编译。

我个人习惯用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/

# 配置环境变量
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin

这里有个坑,我曾经遇到过:解压后直接编译,报错说找不到库。后来发现是64位系统上缺少32位兼容库。解决办法很简单:

注意: 在Ubuntu 20.04及以上系统,需要安装32位库支持:
sudo apt install lib32z1 lib32ncurses5 lib32stdc++6

验证工具链是否安装成功,跑一下这个命令:

arm-linux-gnueabihf-gcc --version

如果能看到版本信息,说明工具链已经就绪。嗯,这一步其实很简单,但很多人会忽略环境变量的持久化配置。我建议你把它写到 ~/.bashrc 里:

echo 'export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin' >> ~/.bashrc
source ~/.bashrc

2.2 内核源码树配置

驱动开发离不开内核源码树。说白了,你写的驱动模块需要依赖内核的头文件和编译规则。我刚开始做驱动时,以为随便下一个内核源码就能用,结果编译出来的模块一加载就报版本不匹配。

为什么会这样?因为内核模块必须与运行中的内核版本完全一致。所以正确的做法是:

  1. 获取内核源码:从你的开发板厂商获取,或者从kernel.org下载对应版本
  2. 配置内核:使用开发板提供的默认配置
  3. 编译内核:生成 vmlinux 和模块
# 以树莓派3B+为例
git clone --depth=1 https://github.com/raspberrypi/linux
cd linux
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage modules dtbs

这里有个小技巧:第一次编译内核时,别急着加 -j 参数,先单线程跑一遍,确认没有语法错误。我吃过这个亏,多线程编译时错误信息被冲掉了,排查了半天。

编译完成后,你会得到几个关键文件:

文件 说明
arch/arm/boot/zImage 压缩后的内核镜像
arch/arm/boot/dts/*.dtb 设备树二进制文件
vmlinux 未压缩的内核,用于调试
modules 内核模块,需要安装到根文件系统
个人经验: 我习惯把内核源码树放在 /home/developer/kernel/ 下,然后创建一个软链接:
ln -s /home/developer/kernel /home/developer/linux-5.10
这样在写驱动Makefile时,路径更清晰。

2.3 最小系统制作

最小系统,说白了就是能让内核跑起来的最简根文件系统。你想想看,一个嵌入式设备不需要桌面环境,只需要基本的shell、库文件和驱动模块。

我推荐用Buildroot来制作,它比Yocto轻量得多,适合快速验证。当然,你也可以用Busybox手动搭建,但步骤会繁琐一些。

用Buildroot的步骤:

# 下载Buildroot
git clone https://github.com/buildroot/buildroot.git
cd buildroot

# 配置(以树莓派3为例)
make raspberrypi3_defconfig

# 自定义配置
make menuconfig

在menuconfig里,你需要关注几个关键选项:

  • Target options:选择ARM架构和具体的CPU型号
  • Toolchain:选择外部工具链(就是我们刚才安装的那个)
  • System configuration:设置主机名、root密码等
  • Filesystem images:选择ext4格式,方便后续挂载

配置完成后,直接编译:

make -j4

编译完成后,在 output/images/ 下会生成 rootfs.ext4 文件。这就是我们的最小系统。

核心要点: 最小系统必须包含以下内容:
  • /dev/console 和 /dev/null 设备节点
  • /etc/inittab 初始化脚本
  • /lib 下的动态链接库
  • /sbin/init 或 busybox
缺少任何一个,内核启动时都会panic。

2.4 NFS挂载

开发阶段,每次修改驱动都要重新烧写根文件系统,那效率太低了。NFS挂载可以让你在PC上共享一个目录,开发板通过网络挂载它作为根文件系统。

我当年第一次用NFS时,折腾了一整天,最后发现是防火墙没关。嗯,这里把步骤说清楚:

PC端配置(Ubuntu):

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

# 创建共享目录
sudo mkdir -p /srv/nfs/rootfs

# 把最小系统解压到共享目录
sudo tar -xvf rootfs.ext4 -C /srv/nfs/rootfs/

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

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

开发板端配置(U-Boot):

# 设置内核启动参数
setenv bootargs 'console=ttyAMA0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs rw ip=192.168.1.101:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'
saveenv
boot

这里有几个参数需要解释一下:

  • nfsroot:PC的IP和共享目录路径
  • ip:开发板的IP、服务器IP、网关、子网掩码
  • no_root_squash:允许root用户访问,否则权限会出问题
避坑指南: 我曾经遇到过NFS挂载后,开发板上的驱动模块无法加载,提示"Exec format error"。排查了半天,发现是交叉编译工具链和内核版本不匹配。记住:工具链的gcc版本必须与编译内核时使用的gcc版本一致。

2.5 本章知识体系

为了让你更直观地理解整个环境搭建的流程,我画了一张图:

开发环境搭建核心流程 交叉编译工具链 arm-linux-gnueabihf-gcc 内核源码树 配置 + 编译 最小系统 Buildroot/Busybox NFS挂载 网络文件系统 依赖关系 工具链用于编译内核和最小系统 最终目标:在开发板上通过NFS挂载最小系统,加载驱动模块

这张图展示了四个模块之间的依赖关系。说白了,交叉编译工具链是基础,它用来编译内核源码树和最小系统。而NFS挂载则是开发阶段的加速器,让你不用反复烧写SD卡。

好了,环境搭建这部分就讲到这里。记住:工具链、内核、根文件系统、NFS,这四个东西配齐了,你就能舒舒服服地开始写驱动了。下一章咱们开始写第一个字符设备驱动——"hello world"。


可可精品资料库