2. 内核开发环境搭建:交叉编译工具链配置、内核源码树获取与切换(git checkout)、内核编译系统(Kbuild/Makefile)基础
嵌入式内核驱动移植的第一步,是搭建一个与目标硬件匹配的、可复现的开发环境。本章聚焦三个核心环节:交叉编译工具链的配置、内核源码树的获取与版本切换、以及Kbuild/Makefile编译系统的基本原理。只有正确理解并操作这三部分,后续的驱动移植工作才能在一个稳定的基础上展开。
2.1 交叉编译工具链配置
嵌入式设备通常资源受限(如ARM Cortex-A系列、RISC-V SoC),无法在目标板上直接运行GCC进行本地编译。因此,需要在性能强大的宿主机(Host)上使用交叉编译工具链(Cross Toolchain),生成目标板(Target)可执行的二进制代码。
2.1.1 工具链命名规则
交叉工具链的命名遵循 arch-vendor-os-abi 格式,例如:
| 工具链前缀 | 架构 | 厂商/系统 | ABI/环境 | 典型应用 |
|---|---|---|---|---|
arm-linux-gnueabihf- |
arm | linux | gnueabihf (硬浮点) | ARM Cortex-A 系列,如 i.MX6、AM335x |
aarch64-linux-gnu- |
aarch64 | linux | gnu (LP64) | ARMv8 64位,如 RK3399、树莓派4 |
riscv64-linux-gnu- |
riscv64 | linux | gnu | RISC-V 64位开发板 |
arm-linux-gnueabi-)逐渐被硬浮点(arm-linux-gnueabihf-)取代。移植驱动时,务必确认目标板内核配置中的 CONFIG_AEABI 和 CONFIG_VFP 选项,选择匹配的工具链。
2.1.2 工具链安装与环境变量
推荐使用 Linaro 或 ARM 官方提供的预编译工具链,避免从源码构建的复杂性。安装后,需要将工具链的 bin 目录加入 PATH,并设置核心环境变量:
# 以 arm-linux-gnueabihf 为例,解压到 /opt/toolchains/
export PATH=/opt/toolchains/gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf/bin:$PATH
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 验证工具链
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-objdump --version
ARCH 和 CROSS_COMPILE 是内核编译系统识别目标架构和工具链前缀的两个核心变量。在后续所有内核编译、模块编译命令中,这两个变量必须正确传递。
2.2 内核源码树获取与切换(git checkout)
驱动移植的核心工作是在不同版本的内核源码树之间进行代码比对、修改和适配。因此,熟练使用 Git 管理内核源码是必备技能。
2.2.1 获取内核源码
从官方仓库克隆完整的历史记录,便于后续切换任意标签或分支:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux
# 查看所有可用的稳定版本标签
git tag -l 'v3.*' | head -20
git tag -l 'v5.*' | head -20
2.2.2 版本切换与工作区管理
驱动移植通常需要同时维护多个版本的内核源码树。推荐使用 独立目录 + 远程分支 的方式,而非在单仓库内频繁切换:
| 操作场景 | Git 命令 | 说明 |
|---|---|---|
| 切换到指定稳定版 | git checkout v5.10.100 |
进入 detached HEAD,适合只读分析 |
| 基于旧版创建移植分支 | git checkout -b migrate-v5.10 v5.10.100 |
从标签创建分支,用于修改 |
| 查看两个版本间的差异 | git diff v4.19.200..v5.10.100 -- drivers/i2c/ |
对比子系统级改动,指导移植 |
| 保存未完成的修改 | git stash / git stash pop |
切换版本前暂存工作区 |
git clone --depth=1 --branch v5.10.100 进行浅克隆,只获取单个版本的文件。但浅克隆无法使用 git diff 跨版本比对,仅适合编译验证。
2.3 内核编译系统(Kbuild/Makefile)基础
Linux 内核使用 Kbuild(Kernel Build) 系统,它是一套基于 GNU Make 的递归编译框架。理解 Kbuild 的工作流程,是编写和移植驱动 Makefile 的前提。
2.3.1 Kbuild 核心架构
下图展示了从顶层 Makefile 到驱动模块编译的完整数据流:
2.3.2 驱动模块的 Makefile 编写规则
在 Kbuild 系统中,驱动开发者通常只需要编写一个简短的 Kbuild 风格 Makefile(或直接命名为 Makefile),核心语法如下:
# 单个源文件编译为模块
obj-m := my_driver.o
# 多个源文件编译为一个模块
obj-m := my_driver.o
my_driver-objs := main.o ioctl.o platform.o
# 条件编译(依赖内核配置)
obj-$(CONFIG_MY_DRIVER) += my_driver.o
# 编译进内核(built-in)
obj-y := my_driver.o
obj-m 表示编译为可动态加载的模块(.ko),obj-y 表示静态编译进内核镜像。移植驱动时,建议先用 obj-m 方式独立编译验证,确认无误后再改为 obj-y 集成进内核。
2.3.3 外部模块编译命令
当驱动源码独立于内核源码树之外时(例如在驱动移植过程中),使用以下命令进行外部模块编译:
# 假设内核源码在 ~/linux-5.10,驱动源码在 ~/my_driver/
cd ~/my_driver/
make -C ~/linux-5.10 M=$(pwd) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
# 清理
make -C ~/linux-5.10 M=$(pwd) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean
| 参数 | 含义 | 说明 |
|---|---|---|
-C |
Change directory | 切换到内核源码顶层目录,读取其 Makefile |
M=$(pwd) |
Module directory | 告诉 Kbuild 去指定目录查找驱动源码和 Makefile |
ARCH=arm |
目标架构 | 必须与目标板一致 |
CROSS_COMPILE=... |
交叉编译前缀 | 指定工具链前缀,末尾必须带 - |
2.4 环境验证清单
在开始驱动移植之前,建议按以下步骤验证开发环境是否就绪:
- 工具链验证: 运行
${CROSS_COMPILE}gcc -v,确认版本和 target 输出正确。 - 内核源码验证: 进入内核目录,运行
make ARCH=arm defconfig,确认 Kbuild 系统能正常生成.config。 - 编译测试: 选择一个内核自带的驱动(如
drivers/misc/下的简单模块),执行make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_prepare,确认无报错。 - 外部模块编译测试: 编写一个空的
hello.c和对应的 Makefile,使用M=方式编译,生成.ko文件。
env_check.sh),每次在新环境或新版本内核上开始移植工作前,先运行脚本进行快速检查,避免在工具链或源码路径这类基础问题上浪费时间。