1. 驱动开发环境搭建:Linux内核源码获取、交叉编译工具链配置、最小系统编译与烧录

做嵌入式驱动开发,第一件事不是写代码,而是把环境搭好。我见过太多新手卡在环境上,代码写好了却编译不过,或者烧进去系统起不来。说白了,环境搭建就像打地基,地基不稳,后面全是坑。

这一章,我带你把整个流程走一遍。从内核源码怎么拿,到交叉编译工具链怎么配,再到最小系统怎么编译、怎么烧到板子上。每一步我都会说说我踩过的坑,你照着做就行。

1.1 整体流程概览

先看一张图,把整件事串起来。你心里有个谱,后面每一步都知道自己在干什么。

驱动开发环境搭建流程 获取内核源码 git clone / 下载tar包 配置交叉编译工具链 arm-linux-gnueabihf- 配置内核 make menuconfig 编译内核与设备树 make zImage / dtbs 制作根文件系统 Busybox / Buildroot 烧录与启动验证 SD卡 / TFTP / NFS 💡 核心思路 先搭好交叉编译环境 → 再编译内核和设备树 → 最后制作根文件系统并烧录 每一步验证通过,再走下一步,不要一口气全做完再调试

1.2 获取Linux内核源码

内核源码怎么拿?两种主流方式:

  • 从官方仓库git clone:适合长期开发,方便切换分支和打补丁
  • 下载稳定版tar包:适合快速上手,不用等git拉取历史

我个人习惯用git clone,因为后面调试驱动时经常需要查内核源码的历史提交。你想想看,一个bug查了两天,最后发现是内核某次提交引入的——这时候git blame就救命了。

我的建议:初学者先用稳定版LTS内核。比如Linux 5.10.y或6.1.y,社区支持时间长,资料也多。别追最新版,最新版往往有未稳定的驱动接口,你踩坑的概率会大很多。

具体操作:

# 方式一:git clone(推荐)
git clone --depth=1 -b linux-5.10.y \
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

# 方式二:下载tar包
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.200.tar.xz
tar -xf linux-5.10.200.tar.xz

--depth=1 这个参数我特别提一下。它只拉取最新一次提交,不下载完整历史。我第一次没加这个参数,等了快半小时才下完。加上它,几秒钟就搞定了。

1.3 配置交叉编译工具链

交叉编译,说白了就是在你的x86电脑上编译出ARM架构能运行的二进制文件。你不可能在板子上直接编译内核——那得等到天荒地老。

工具链的选择,我列个表给你参考:

工具链 适用平台 安装方式 我的评价
arm-linux-gnueabihf- Cortex-A系列(如IMX6ULL、STM32MP1) apt install / 源码编译 最常用,资料最多
aarch64-linux-gnu- ARM64位(如树莓派3B+、RK3399) apt install / 官方下载 64位首选
arm-none-eabi- Cortex-M裸机(如STM32F4) ARM官网下载 不带Linux,别搞混
我曾经踩过的坑:一开始我用的 arm-linux-gnueabi(没有hf后缀),编译出来的内核在浮点运算时直接崩溃。后来才发现,我的目标芯片支持硬件浮点,必须用 arm-linux-gnueabihf。这个hf就是 hard-float 的意思。你配工具链前,一定先查清楚芯片的浮点单元类型。

安装步骤(以Ubuntu 22.04为例):

# 安装ARM32位交叉编译工具链
sudo apt update
sudo apt install gcc-arm-linux-gnueabihf

# 验证安装
arm-linux-gnueabihf-gcc --version

# 设置环境变量(写入 ~/.bashrc 永久生效)
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

环境变量设好之后,你编译内核时就不用每次都敲一长串参数了。我习惯把这两行写到 ~/.bashrc 里,source一下,一劳永逸。

1.4 编译最小系统

环境搭好了,接下来编译三样东西:内核、设备树、根文件系统。这三样凑齐了,你的板子就能跑起来。

1.4.1 编译内核与设备树

先进入内核源码目录,配置一下:

cd linux-5.10.200

# 使用默认配置(以IMX6ULL为例)
make imx_v7_defconfig

# 或者手动配置
make menuconfig

make menuconfig 这个界面你以后会经常打交道。它是个图形化的配置菜单,你可以在这里打开或关闭内核功能。比如你要加一个I2C驱动,就在 Device Drivers → I2C support 里勾上。

配置好之后,开始编译:

# 编译内核镜像
make zImage -j4

# 编译设备树
make dtbs -j4

-j4 表示用4个线程并行编译。我建议你设成CPU核心数的1.5倍左右。比如4核CPU就用 -j6,编译速度能快不少。

编译完成后,在 arch/arm/boot/ 下会生成 zImage,在 arch/arm/boot/dts/ 下会生成 .dtb 文件。这两个就是你要烧录的东西。

关键点:设备树文件必须和你的板子匹配。比如IMX6ULL开发板,一般用 imx6ull-14x14-evk.dtb。如果你用错了设备树,内核启动时会卡在"Unsupported device tree"或者直接黑屏。我刚开始就犯过这个错,折腾了一下午才发现是dtb文件搞错了。

1.4.2 制作根文件系统

根文件系统,说白了就是板子启动后能看到的 / 目录。里面要有 /bin、/sbin、/etc 这些基本目录,还要有 init 程序。

最快的方式是用Busybox:

# 下载Busybox
wget https://busybox.net/downloads/busybox-1.36.0.tar.bz2
tar -xf busybox-1.36.0.tar.bz2
cd busybox-1.36.0

# 配置
make menuconfig
# 在 Settings → Build static binary (no shared libs) 勾上
# 这样编译出来的二进制是静态链接的,不依赖动态库

# 编译并安装
make -j4
make install

# 安装后会生成 _install 目录
# 把它做成根文件系统镜像
cd _install
mkdir proc sys dev etc etc/init.d

# 创建最简单的 init 脚本
cat > etc/init.d/rcS << EOF
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Hello from embedded Linux!"
EOF
chmod +x etc/init.d/rcS

然后打包成镜像:

# 制作ext4格式的根文件系统镜像
dd if=/dev/zero of=rootfs.ext4 bs=1M count=64
mkfs.ext4 rootfs.ext4
mkdir /tmp/rootfs
sudo mount -o loop rootfs.ext4 /tmp/rootfs
sudo cp -r _install/* /tmp/rootfs/
sudo umount /tmp/rootfs

这样你就得到了一个64MB的 rootfs.ext4 镜像。虽然简陋,但足够让板子启动到shell了。

1.5 烧录与启动验证

烧录方式取决于你的板子。我以最常用的SD卡启动为例:

# 假设SD卡设备是 /dev/sdb(用 lsblk 确认)
# 先分区:一个FAT分区放内核和设备树,一个ext4分区放根文件系统

# 格式化FAT分区
sudo mkfs.vfat /dev/sdb1

# 复制内核和设备树
sudo mount /dev/sdb1 /mnt
sudo cp arch/arm/boot/zImage /mnt/
sudo cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /mnt/
sudo umount /mnt

# 烧录根文件系统到第二个分区
sudo dd if=rootfs.ext4 of=/dev/sdb2 bs=1M status=progress
注意:dd命令会直接覆盖目标设备,搞错了分区你的硬盘数据就没了。我建议你每次执行dd前,先用 lsblk 确认三次设备名。我曾经有一次把 /dev/sda(我的系统盘)当成SD卡写了进去……嗯,那次之后我养成了拔掉所有不相关USB设备的习惯。

烧录完成后,把SD卡插到板子上,上电。串口终端应该能看到内核启动日志:

U-Boot 2020.04
...
Starting kernel ...

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 5.10.200
...
[    1.234567] VFS: Mounted root (ext4 filesystem) on device 179:2.
[    1.345678] Freeing unused kernel memory: 1024K
[    1.456789] Run /sbin/init as init process
Hello from embedded Linux!
/ #

看到这个 / # 提示符,恭喜你,最小系统跑起来了。你现在可以敲 ls、cat /proc/cpuinfo 这些命令,验证系统是否正常。

1.6 常见问题与避坑

  • 编译报错"arm-linux-gnueabihf-gcc: not found":工具链没装或者环境变量没设。检查 echo $CROSS_COMPILE 是否为空。
  • 启动卡在"Starting kernel ...":设备树不匹配或者内核配置不对。先确认dtb文件名和板子型号一致。
  • 根文件系统挂载失败"VFS: Unable to mount root fs":内核没有编译对应文件系统的支持。比如你用ext4,但内核没开 CONFIG_EXT4_FS。
  • 串口没有输出:检查串口波特率、接线、以及内核的早期串口打印是否开启(CONFIG_DEBUG_LL 和 CONFIG_EARLY_PRINTK)。

环境搭建这一步,说白了就是耐心活。我第一次搭环境用了整整两天,各种报错。但只要你每一步都验证通过,后面写驱动的时候就会特别顺畅。别急,慢慢来。