第2章:内核编译环境搭建

好,咱们正式开始动手了。这一章,我带你把编译环境搭起来。说白了,就是给你的电脑装上能编译蓝牙内核的「工具链」,再把内核源码搞到手,配置好,最后敲下编译命令。

很多初学者在这一步就卡住了。为什么?因为环境没配好,编译报错一堆,心态直接崩了。我当年也踩过这个坑,所以这一章我会把每个细节都讲透。

2.1 交叉编译工具链安装

先说说什么是交叉编译。你想想看,你的开发电脑是x86架构,但目标板(比如树莓派、RK3588)是ARM架构。x86的程序不能直接在ARM上跑,对吧?所以我们需要一个能在x86上生成ARM可执行文件的编译器——这就是交叉编译工具链。

我个人习惯用 Linaro 提供的工具链,稳定且社区支持好。当然,你也可以用芯片厂商提供的,比如 Rockchip 的 SDK 里就自带。

安装步骤

  1. 下载工具链
    我建议直接去 Linaro 官网下载,或者用下面这个链接(以 gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf 为例):
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
  1. 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
  1. 配置环境变量
    编辑 ~/.bashrc,在末尾添加:
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
  1. 验证安装
source ~/.bashrc
arm-linux-gnueabihf-gcc --version

如果看到版本信息,说明安装成功了。

注意: 我曾经遇到过 PATH 配置错误导致编译时找不到编译器的情况。建议你配置完后,用 which arm-linux-gnueabihf-gcc 确认一下路径是否正确。

2.2 内核源码获取与配置

工具链装好了,接下来搞内核源码。这里我分两种情况讲:一种是官方主线内核,另一种是芯片厂商定制内核。

2.2.1 获取官方主线内核

如果你用的是通用开发板(比如树莓派、BeagleBone),直接从 kernel.org 拉取就行:

git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux
git checkout v5.15.100  # 选择一个长期支持版本

为什么选 v5.15?嗯,这个版本对蓝牙 5.2 支持比较完善,而且 LTS 维护周期长。我个人做项目时,除非有特殊需求,否则都优先选 LTS 版本。

2.2.2 获取芯片厂商定制内核

如果你用的是全志、瑞芯微这类芯片,建议直接用厂商提供的 SDK。因为厂商会在内核里打上很多硬件相关的补丁,主线内核不一定能直接跑起来。

举个例子,Rockchip 的 SDK 通常包含在内核源码的 arch/arm/boot/dts/ 目录下,有专门针对自家芯片的设备树文件。

2.2.3 内核配置

配置内核,说白了就是告诉编译器:哪些功能要编译进内核,哪些编译成模块,哪些直接砍掉。

常用的配置命令有这几个:

命令 说明
make menuconfig 基于 ncurses 的文本界面,最常用
make defconfig 使用默认配置,适合快速验证
make savedefconfig 生成精简的配置文件

我一般这样操作:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

先跑 defconfig 生成一个基础配置,再用 menuconfig 手动调整。这样效率最高。

小技巧: 在 menuconfig 里按 / 可以搜索配置项。比如你想找蓝牙相关的配置,直接搜 "BLUETOOTH",所有相关选项就都列出来了。

2.3 编译选项详解

配置完内核,接下来就是编译了。但编译选项怎么设,这里头有讲究。

2.3.1 常用编译选项

选项 含义 我的建议
-jN 并行编译线程数 设为 CPU 核心数的 1.5 倍,比如 4 核设 -j6
ARCH=arm 指定目标架构 必须与工具链匹配
CROSS_COMPILE 交叉编译前缀 注意末尾的短横线不能少
O= 指定输出目录 建议用,避免源码目录被污染

2.3.2 编译内核与模块

完整的编译命令长这样:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 dtbs

这里解释一下:

  • zImage:编译生成压缩后的内核镜像
  • modules:编译所有配置为模块的驱动
  • dtbs:编译设备树文件

如果你只想编译蓝牙驱动模块,可以单独指定:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 M=net/bluetooth

这样只编译 net/bluetooth 目录下的代码,速度会快很多。我在调试蓝牙驱动时经常这么干。

核心要点: 编译选项不是越多越好。我见过有人把 -j 设得特别大,结果内存不够直接 OOM。建议根据你的机器配置来,稳一点。

2.3.3 常见编译错误及解决

编译过程中难免遇到错误。我列几个常见的:

  • 找不到头文件:检查 CROSS_COMPILE 是否设置正确,工具链的 sysroot 路径是否包含在内核源码中。
  • 链接错误:通常是某个依赖模块没编译。比如编译蓝牙驱动时,需要先编译 crypto 子系统。
  • 设备树编译失败:检查 dts 文件语法,可以用 make dtbs 单独编译来定位问题。

我曾经遇到过一个问题:编译蓝牙驱动时一直报 undefined reference to 'hci_register_dev'。查了半天,发现是内核配置里把 CONFIG_BT 设成了 m,但 CONFIG_BT_HCIUART 设成了 y。这俩不一致,导致符号找不到。后来统一改成 m 就解决了。

2.4 本章小结

这一章我们做了三件事:装工具链、拿源码、配编译选项。看起来简单,但每一步都可能出问题。我的建议是:

  1. 工具链装好后,先编译一个最简单的 hello world 程序验证一下。
  2. 内核配置时,先跑 defconfig 再手动调整,不要从零开始配。
  3. 编译时用 O= 指定输出目录,方便清理和调试。

下一章,我们就要真正进入蓝牙驱动的代码世界了。到时候我会带你分析蓝牙核心子系统的源码结构,看看那些驱动到底是怎么工作的。

准备好了吗?我们继续。