3. 内核编译环境搭建
在 Android 驱动开发中,宿主机(x86_64)与目标设备(ARM64)架构不同,必须使用交叉编译。本章将系统讲解交叉编译工具链的安装、环境变量配置以及驱动 Makefile 的核心编写方法,为后续内核模块编译打下坚实基础。
核心概念:交叉编译是指在 x86_64 架构的 PC 上,使用针对 ARM64 架构的编译器、链接器和头文件,生成可在 Android 设备上运行的二进制内核模块(.ko)。
3.1 交叉编译工具链安装
Android 内核编译推荐使用 Google 官方提供的 Android NDK 中预编译的独立工具链,或使用 Linaro GCC 工具链。以下是两种主流安装方式。
3.1.1 使用 Android NDK 生成独立工具链
- 下载 Android NDK(推荐 r25c 或更高版本)并解压。
- 使用 NDK 内置的
make_standalone_toolchain.py脚本生成独立工具链。 - 将工具链路径添加到系统 PATH 中。
# 下载 NDK
wget https://dl.google.com/android/repository/android-ndk-r25c-linux.zip
unzip android-ndk-r25c-linux.zip
# 生成 ARM64 独立工具链
python android-ndk-r25c/build/tools/make_standalone_toolchain.py \
--arch arm64 \
--api 31 \
--install-dir ~/aarch64-linux-android-toolchain
# 验证安装
export PATH=~/aarch64-linux-android-toolchain/bin:$PATH
aarch64-linux-android-gcc --version
3.1.2 使用 Linaro GCC 工具链
Linaro 提供了针对 ARM 架构优化的 GCC 工具链,适合内核编译场景。
# 下载 Linaro GCC 7.5 for ARM64
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
tar -xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
mv gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu ~/aarch64-linux-gnu-toolchain
# 添加至 PATH
export PATH=~/aarch64-linux-gnu-toolchain/bin:$PATH
aarch64-linux-gnu-gcc --version
建议:对于 Android 内核编译,优先使用 NDK 生成的独立工具链,因为它与 Android 系统的 C 库(Bionic)和内核头文件兼容性最好。
3.2 环境变量配置
交叉编译环境需要设置多个关键环境变量,以便 Makefile 能够正确找到编译器、链接器和目标架构参数。下表列出了最核心的环境变量及其作用。
| 环境变量 | 示例值 | 作用说明 |
|---|---|---|
CROSS_COMPILE |
aarch64-linux-android- |
指定交叉编译工具链前缀,Makefile 会自动追加 gcc、ld、as 等后缀 |
ARCH |
arm64 |
指定目标 CPU 架构,影响内核头文件和汇编代码选择 |
PATH |
/home/user/aarch64-toolchain/bin:$PATH |
确保系统能找到交叉编译工具链的可执行文件 |
KERNEL_SRC |
/home/user/android-kernel |
指向已配置的内核源码树路径,驱动编译时需要引用其头文件和 Makefile |
CC |
$(CROSS_COMPILE)gcc |
显式指定 C 编译器(通常由 CROSS_COMPILE 自动推导) |
建议将环境变量写入 ~/.bashrc 或项目根目录的 env.sh 脚本中,避免每次手动设置。
# 创建环境变量设置脚本 env.sh
cat > env.sh << 'EOF'
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-android-
export PATH=~/aarch64-linux-android-toolchain/bin:$PATH
export KERNEL_SRC=~/android-kernel
echo "交叉编译环境已加载: ARCH=$ARCH, CROSS_COMPILE=$CROSS_COMPILE"
EOF
# 每次编译前加载
source env.sh
常见错误:忘记设置
ARCH=arm64 会导致内核 Makefile 使用 x86 架构的头文件,编译出的模块无法在 ARM64 设备上加载。务必在编译前检查 echo $ARCH 输出。
3.3 Makefile 基础
Linux 内核模块的编译依赖内核源码树提供的 Kbuild 系统。驱动开发者只需编写一个简洁的 Makefile,Kbuild 会自动处理编译、链接和模块生成。
3.3.1 最简单的驱动 Makefile
# 最简单的内核模块 Makefile
obj-m := hello_driver.o
KDIR := $(KERNEL_SRC)
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
逐行解释:
obj-m := hello_driver.o:告诉 Kbuild 将hello_driver.c编译成内核模块(.ko)。KDIR := $(KERNEL_SRC):指向已配置的内核源码树路径。PWD := $(shell pwd):获取当前驱动源码目录路径。$(MAKE) -C $(KDIR) M=$(PWD) modules:进入内核源码树,执行模块编译。
3.3.2 多文件模块的 Makefile
当驱动由多个源文件组成时,需要使用 模块名-objs 语法。
# 多文件模块 Makefile 示例
obj-m := my_driver.o
my_driver-objs := main.o utils.o gpio_ops.o
KDIR := $(KERNEL_SRC)
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
3.3.3 带编译选项的 Makefile
可以通过 ccflags-y 和 ldflags-y 添加编译和链接选项。
# 带编译选项的 Makefile
obj-m := sensor_driver.o
ccflags-y := -DDEBUG -O2 -Wall
ldflags-y := --no-warn-rwx-segments
KDIR := $(KERNEL_SRC)
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
调试技巧:在
ccflags-y 中添加 -DDEBUG 可以启用驱动源码中的 pr_debug() 输出,配合 dmesg 查看调试信息。生产版本应移除该选项。
3.4 交叉编译流程与 Makefile 调用关系
下图展示了从环境变量设置到最终生成 .ko 文件的完整编译流程,以及 Makefile 与内核 Kbuild 系统的协作关系。
3.5 完整编译示例
以下是一个完整的编译命令序列,展示了从环境加载到模块生成的全过程。
# 1. 加载交叉编译环境
source ~/env.sh
# 2. 确认环境变量
echo "ARCH=$ARCH"
echo "CROSS_COMPILE=$CROSS_COMPILE"
echo "KERNEL_SRC=$KERNEL_SRC"
# 3. 进入驱动源码目录
cd ~/driver_projects/hello_driver
# 4. 执行编译
make
# 5. 检查生成的模块
ls -lh *.ko
file hello_driver.ko
编译成功后,file 命令应输出类似 ELF 64-bit LSB relocatable, ARM aarch64 的信息,确认是 ARM64 架构的模块。
验证要点:使用
file 命令检查 .ko 文件架构;使用 modinfo 查看模块依赖和参数信息;在 Android 设备上使用 insmod 加载测试。
3.6 常见编译错误与解决方案
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Cannot use CONFIG_CC_STACKPROTECTOR_STRONG |
工具链不支持栈保护选项 | 在内核配置中关闭 CONFIG_CC_STACKPROTECTOR 或更新工具链 |
scripts/Makefile.build: No such file |
KDIR 指向的内核源码未配置 |
确保已执行 make defconfig 或 make ARCH=arm64 defconfig |
aarch64-linux-android-gcc: command not found |
工具链未加入 PATH | 检查 export PATH 路径是否正确,或使用绝对路径 |
multiple definition of `init_module' |
模块入口函数重复定义 | 检查源码中是否同时使用了 module_init() 和 init_module() |
重要提醒:Android 内核通常开启了 GKI(Generic Kernel Image) 和 模块签名验证。如果编译的模块无法加载,请检查内核配置中
CONFIG_MODULE_SIG 和 CONFIG_MODULE_SIG_FORCE 是否启用。若启用,需要使用内核源码树中的 sign-file 工具对模块进行签名。