3. 内核编译环境搭建:交叉编译工具链安装、环境变量配置、Makefile基础

在 Android 驱动开发中,宿主机(x86_64)与目标设备(ARM64)架构不同,必须使用交叉编译。本章将系统讲解交叉编译工具链的安装、环境变量配置以及驱动 Makefile 的核心编写方法,为后续内核模块编译打下坚实基础。

核心概念:交叉编译是指在 x86_64 架构的 PC 上,使用针对 ARM64 架构的编译器、链接器和头文件,生成可在 Android 设备上运行的二进制内核模块(.ko)。

3.1 交叉编译工具链安装

Android 内核编译推荐使用 Google 官方提供的 Android NDK 中预编译的独立工具链,或使用 Linaro GCC 工具链。以下是两种主流安装方式。

3.1.1 使用 Android NDK 生成独立工具链

  1. 下载 Android NDK(推荐 r25c 或更高版本)并解压。
  2. 使用 NDK 内置的 make_standalone_toolchain.py 脚本生成独立工具链。
  3. 将工具链路径添加到系统 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 系统的协作关系。

交叉编译流程与 Makefile 调用关系 环境变量设置 ARCH / CROSS_COMPILE / PATH 驱动 Makefile obj-m / ccflags-y / KDIR 内核 Kbuild 系统 -C $(KDIR) M=$(PWD) 交叉编译过程 aarch64-gcc → .o → .ko 生成 .ko 模块 hello_driver.ko 关键路径说明 1. 环境变量决定目标架构 2. Makefile 指定源文件和选项 3. Kbuild 调用交叉编译器 4. 链接生成 ARM64 的 .ko 5. 模块只能在 Android 设备加载 输出文件清单 .ko / .o / .mod.c / .mod.o / Module.symvers

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 工具对模块进行签名。