1. 项目背景与需求分析
作为一名长期从事嵌入式系统开发的工程师,最近在NVIDIA Orin平台上部署BPF(Berkeley Packet Filter)相关应用时遇到了一个棘手问题:官方提供的默认内核镜像并未开启BTF(BPF Type Format)支持。这直接导致基于BPF的高级功能(如CO-RE可移植性)无法正常运行。
BTF是Linux内核中用于描述数据类型的一种元数据格式,它允许BPF程序在不同内核版本间实现"一次编译,到处运行"的特性。对于Orin这样广泛应用于边缘计算、自动驾驶等场景的AI平台来说,BPF程序的稳定运行至关重要。
经过深入分析,我决定自行编译一个支持BTF的定制化内核。这个过程涉及内核配置、交叉编译、模块替换等多个技术环节,需要特别注意NVIDIA平台的专有配置。下面将完整记录整个操作流程,包括关键步骤的技术原理和实际踩坑经验。
2. 环境准备与工具链配置
2.1 基础系统要求
编译内核是一个资源密集型任务,对开发环境有较高要求:
- 操作系统:Ubuntu 20.04 LTS(推荐使用纯净安装)
- 存储空间:至少100GB可用空间(内核源码+编译中间文件会占用大量空间)
- 内存:建议16GB以上,8GB勉强可用但编译速度会明显下降
- 网络:稳定连接(需要下载大量源码和依赖包)
注意:虽然理论上可以在WSL2中完成编译,但由于文件系统性能问题,实际编译时间会大幅延长。建议使用物理机或性能足够的虚拟机。
2.2 安装编译依赖
执行以下命令安装必要的编译工具链和开发库:
bash复制sudo apt update
sudo apt install -y \
build-essential \
gcc \
make \
binutils \
libncurses-dev \
flex \
bison \
libssl-dev \
libelf-dev \
bc \
dwarves \
cpio \
rsync
这里有几个关键包需要特别说明:
- dwarves:包含pahole工具,用于生成BTF调试信息
- libelf-dev:处理ELF格式文件的开发库,BPF工具链依赖
- flex/bison:语法分析器生成工具,内核配置需要
2.3 获取交叉编译工具链
Orin采用ARM64架构,需要在x86主机上使用交叉编译工具链:
bash复制mkdir -p ~/toolchain
wget https://developer.nvidia.com/embedded/jetson-linux/bootlin-toolchain-gcc-93 -O gcc-9.3.tar.gz
tar -xzvf gcc-9.3.tar.gz -C ~/toolchain/
工具链路径需要记下,后续编译会用到:
code复制/home/yourname/toolchain/gcc-9.3/bin/aarch64-buildroot-linux-gnu-
3. 内核源码获取与预处理
3.1 下载官方源码包
从NVIDIA开发者网站获取Orin专用内核源码:
- 访问 https://developer.nvidia.com/embedded/jetson-linux-r351
- 下载以下四个核心文件:
- Jetson_Linux_R35.1.0_aarch64.tbz2(基础包)
- Tegra_Linux_Sample-Root-Filesystem_R35.1.0_aarch64.tbz2(根文件系统)
- public_sources.tbz2(公开源码)
- nvidia_kernel_display_driver_source.tbz2(显示驱动源码)
经验提示:建议直接在Ubuntu系统用Firefox下载,避免Windows下载后拷贝导致的解压错误。
3.2 源码解压与目录结构准备
按顺序解压各压缩包:
bash复制# 解压基础包
tar -xjf Jetson_Linux_R35.1.0_aarch64.tbz2
# 进入rootfs目录解压根文件系统
cd Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/rootfs
sudo tar -xjf ../../Tegra_Linux_Sample-Root-Filesystem_R35.1.0_aarch64.tbz2
# 解压公开源码
cd ../..
tar -xjf public_sources.tbz2
cd Linux_for_Tegra/source/public
tar -xjf kernel_src.tbz2
# 解压显示驱动源码
tar -xjf ../nvidia_kernel_display_driver_source.tbz2
解压完成后,目录结构应如下:
code复制Jetson_Linux_R35.1.0_aarch64/
├── Linux_for_Tegra
│ ├── rootfs # 根文件系统
│ ├── source
│ │ └── public
│ │ ├── kernel
│ │ │ └── kernel-5.10 # 内核源码
│ │ └── NVIDIA-kernel-module-source-TempVersion # 显示驱动源码
3.3 修改编译脚本行为
原厂提供的nvbuild.sh脚本会强制加载tegra_defconfig,这会覆盖我们的自定义配置。需要修改为保留现有配置的逻辑:
bash复制cd Linux_for_Tegra/source/public
cp kernel/kernel-5.10/scripts/nvbuild.sh .
chmod +x nvbuild.sh
用编辑器打开nvbuild.sh,找到配置加载部分,修改为:
bash复制if [ -f "${CONFIG_PATH}" ]; then
echo "[nvbuild] Existing .config found"
echo "[nvbuild] >>> Using user-provided config (NOT running tegra_defconfig)"
else
echo "[nvbuild] No .config found"
echo "[nvbuild] >>> Generating default config: ${config_file}"
# 原有加载默认配置的代码...
fi
4. 内核配置与编译
4.1 设置环境变量
配置交叉编译相关环境变量:
bash复制export CROSS_COMPILE_AARCH64=~/toolchain/gcc-9.3/bin/aarch64-buildroot-linux-gnu-
export CROSS_COMPILE=$CROSS_COMPILE_AARCH64
export ARCH=arm64
export CROSS_COMPILE_AARCH64_PATH=~/toolchain/gcc-9.3
4.2 初始化编译配置
创建编译输出目录并加载默认配置:
bash复制PUBLIC=$(pwd)
KERNEL=$PUBLIC/kernel/kernel-5.10
OUT=$PUBLIC/kernel_out
# 清理源码树
cd "$KERNEL"
make ARCH=arm64 mrproper
# 准备输出目录
rm -rf "$OUT"
mkdir -p "$OUT"
# 加载默认配置
make -C "$KERNEL" ARCH=arm64 O="$OUT" tegra_defconfig
4.3 启用BTF及相关功能
使用内核提供的config工具开启必要选项:
bash复制"$KERNEL/scripts/config" --file "$OUT/.config" \
-e BPF \
-e BPF_SYSCALL \
-e BPF_JIT \
-e BPF_EVENTS \
-e BPF_TRAMPOLINE \
-e DEBUG_INFO \
-e DEBUG_INFO_DWARF4 \
-e DEBUG_INFO_BTF \
-e FTRACE \
-e FUNCTION_TRACER \
-e TRACEPOINTS \
-e FTRACE_SYSCALLS \
-e KPROBES \
-e KPROBE_EVENTS \
-e UPROBE_EVENTS \
-e KALLSYMS \
-e KALLSYMS_ALL
关键选项说明:
- DEBUG_INFO_BTF:启用BTF调试信息生成
- BPF_TRAMPOLINE:支持BPF程序挂钩内核函数
- KALLSYMS_ALL:导出所有内核符号,便于BPF程序访问
4.4 解决编译错误
编译过程中可能会遇到unresolved symbol udp6_sock错误,这是内核符号导出问题导致的。解决方法:
- 打开链接脚本:
bash复制vim "$KERNEL/scripts/link-vmlinux.sh"
- 注释掉第346行附近的
resolve_symbols()调用:
bash复制# resolve_symbols()
4.5 执行完整编译
使用修改后的脚本启动编译:
bash复制cd "$PUBLIC"
sudo -E env NPROC=$(nproc) ./nvbuild.sh -o "$OUT"
编译时间视机器性能而定(i7-11800H约30分钟)。完成后检查产物:
bash复制ls -lh "$OUT/arch/arm64/boot/Image"
file "$OUT/vmlinux" | grep "with debug_info"
5. 模块安装与系统整合
5.1 安装内核模块
将编译好的模块安装到根文件系统:
bash复制cd "$OUT"
sudo -E make INSTALL_MOD_STRIP=1 LOCALVERSION="-tegra" ARCH=arm64 \
modules_install INSTALL_MOD_PATH="$PUBLIC/../../rootfs"
5.2 替换核心内核文件
复制内核镜像和设备树文件:
bash复制sudo cp "$OUT/arch/arm64/boot/Image" "$PUBLIC/../../kernel/"
sudo cp "$OUT/arch/arm64/boot/dts/nvidia/"*.dtb "$PUBLIC/../../kernel/dtb/"
5.3 编译NVIDIA专有模块
显示驱动等专有模块需要单独编译:
bash复制cd "$PUBLIC/NVIDIA-kernel-module-source-TempVersion"
make modules \
SYSSRC="$KERNEL" \
SYSOUT="$OUT" \
O="$OUT" \
CC="${CROSS_COMPILE}gcc" \
LD="${CROSS_COMPILE}ld.bfd" \
TARGET_ARCH=aarch64 \
ARCH=arm64
# 安装显示驱动模块
sudo cp kernel-open/*.ko "$PUBLIC/../../rootfs/usr/lib/modules/5.10.104-tegra/extra/opensrc-disp/"
6. 烧录与验证
6.1 进入Recovery模式
- 断开Orin电源
- 按住Recovery按钮(通常标记为"FORCE RECOVERY")
- 连接电源线,保持按住按钮约3秒
- 松开按钮,设备应进入Recovery模式
6.2 执行烧录
连接主机后执行:
bash复制cd "$PUBLIC/../.."
sudo ./flash.sh jetson-agx-orin-devkit mmcblk0p1
烧录完成后系统会自动重启。登录后验证BTF支持:
bash复制cat /proc/config.gz | gunzip | grep CONFIG_DEBUG_INFO_BTF
ls -lh /sys/kernel/btf/vmlinux
7. 常见问题与解决方案
7.1 编译失败:内存不足
现象:编译过程中被OOM Killer终止
解决:
- 增加swap空间:
bash复制sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
- 或减少并行编译线程:
make -j4
7.2 模块版本不匹配
现象:insmod报错"version magic mismatch"
解决:
- 确保所有模块使用相同源码树编译
- 检查
/lib/modules/$(uname -r)/build链接是否正确
7.3 BTF信息未生成
现象:/sys/kernel/btf/vmlinux不存在
解决:
- 确认.config中
CONFIG_DEBUG_INFO_BTF=y - 检查编译日志是否有pahole错误
- 确保dwarves版本≥1.22
整个流程走下来,最大的体会是:嵌入式平台的内核定制需要特别注意厂商提供的专有配置和脚本。直接使用标准Linux内核的编译方法往往会遇到各种兼容性问题。建议在每次修改配置前都做好备份,并保留完整的编译日志以便排查问题。