NVIDIA Jetson Orin 设备出厂预装的官方内核默认未开启 BTF (BPF Type Format) 功能,而 BTF 是 eBPF 相关功能(如 CO-RE)得以正常运行的关键依赖。因此,若想在 Orin 上开发和运行 eBPF 程序,我们需要在 x86_64 的 Ubuntu 主机上,为 ARM64 架构的 Orin 重新编译并烧录一个开启了 BTF 支持的定制化内核。以下为完整的操作步骤,涵盖环境准备、源码获取、配置修改、交叉编译及最终烧录的全流程。
一、烧录支持BTF的内核
1.1 环境准备
- 系统版本:Ubuntu 20.04 或更高版本
- 存储空间:建议预留至少 100GB 可用空间,用于存放源码和编译中间文件。
1.2 下载编译链
首先,安装编译内核所需的基础依赖包,确保编译环境完整:
sudo apt update
sudo apt install -y \
build-essential \
gcc \
make \
binutils \
libncurses-dev \
flex \
bison \
libssl-dev \
libelf-dev \
bc \
dwarves \
cpio \
rsync
1.3 下载内核及相关文件
访问 NVIDIA 官方开发者网站,获取对应版本的内核及配套文件。本文以 R35.1.0 版本为例。
# 进入以下网站下载相关资料
https://developer.nvidia.com/embedded/jetson-linux-r351
需要下载以下四个文件(重要:建议直接在 Ubuntu 编译主机上使用 Firefox 等浏览器下载,避免在 Windows 系统下载后拷贝至 Ubuntu 导致后续解压错误):

1.3.1 解压核心文件
按顺序解压下载的内核包、根文件系统、源码包及显示驱动源码,确保目录结构正确:
# 解压Jetson Linux核心包
tar -xjf Jetson_Linux_R35.1.0_aarch64.tbz2
# 进入rootfs目录,此时目录下为空,将压缩包Tegra_Linux_Sample-Root-Filesystem_R35.1.0_aarch64.tbz2放到此目录下并用命令进行解压
cd Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/rootfs
sudo tar -xjf Tegra_Linux_Sample-Root-Filesystem_R35.1.0_aarch64.tbz2
# 在Jetson_Linux_R35.1.0_aarch64的同级目录下解压public_sources.tbz2 会自动解压到Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public下 然后在解压public下的 kernel_src.tbz2 就能得到kernel.5.10的源码和nvbuild.sh脚本,但是这个脚本会默认强制使用英伟达的tegra_defconfig,导致你后面改menuconfig,然后去跑脚本时候会重置你自己的配置。
tar -xjf public_sources.tbz2
cd Linux_for_Tegra/source/public
tar –xjf kernel_src.tbz2
# 解压NVIDIA显示模块源码
tar -xjvf nvidia_kernel_display_driver_source.tbz2
1.3.2 修改nvbuild.sh脚本(避免重置自定义配置)
原厂提供的 nvbuild.sh 脚本在每次执行时会强制加载 tegra_defconfig,这将导致我们手动定制的内核配置被重置。为了解决这个问题,我们需要用以下脚本内容替换原有的 nvbuild.sh,其逻辑是:如果输出目录中已存在 .config 文件,则直接使用它,否则才加载默认配置。
将以下内容保存为 nvbuild1.sh(或直接覆盖原文件):
#!/bin/bash
# Copyright (c) 2019-2021, NVIDIA CORPORATION.
# All rights reserved.
# This script builds kernel sources in this directory.
# Modified behavior:
# - If .config already exists, DO NOT reset via tegra_defconfig
set -e
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
SCRIPT_NAME="$(basename "$0")"
source "${SCRIPT_DIR}/nvcommon_build.sh"
function usage {
cat <<EOM
Usage: ./${SCRIPT_NAME} [OPTIONS]
This script builds kernel sources in this directory.
OPTIONS:
-h Displays this help
-o <outdir> Creates kernel build output in <outdir>
EOM
}
# parse input parameters
function parse_input_param {
while [ $# -gt 0 ]; do
case ${1} in
-h)
usage
exit 0
;;
-o)
KERNEL_OUT_DIR="${2}"
shift 2
;;
*)
echo "Error: Invalid option ${1}"
usage
exit 1
;;
esac
done
}
function build_arm64_kernel_sources {
kernel_version="${1}"
echo "Building kernel-${kernel_version} sources"
source_dir="${SCRIPT_DIR}/kernel/kernel-${kernel_version}"
config_file="tegra_defconfig"
tegra_kernel_out="${source_dir}"
if [ -n "${KERNEL_OUT_DIR}" ]; then
O_OPT=(O="${KERNEL_OUT_DIR}")
tegra_kernel_out="${KERNEL_OUT_DIR}"
else
O_OPT=()
fi
CONFIG_PATH="${tegra_kernel_out}/.config"
echo "Kernel source dir : ${source_dir}"
echo "Kernel output dir : ${tegra_kernel_out}"
# --------------------------------------------------
# CONFIG stage
# --------------------------------------------------
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}"
"${MAKE_BIN}" -C "${source_dir}" ARCH=arm64 \
LOCALVERSION="-tegra" \
CROSS_COMPILE="${CROSS_COMPILE_AARCH64}" \
"${O_OPT[@]}" \
"${config_file}"
fi
# Ensure config dependencies are resolved
"${MAKE_BIN}" -C "${source_dir}" ARCH=arm64 \
CROSS_COMPILE="${CROSS_COMPILE_AARCH64}" \
"${O_OPT[@]}" \
olddefconfig
# --------------------------------------------------
# Build Image
# --------------------------------------------------
"${MAKE_BIN}" -C "${source_dir}" ARCH=arm64 \
LOCALVERSION="-tegra" \
CROSS_COMPILE="${CROSS_COMPILE_AARCH64}" \
"${O_OPT[@]}" -j"${NPROC}" \
--output-sync=target Image
# --------------------------------------------------
# Build DTBs
# --------------------------------------------------
"${MAKE_BIN}" -C "${source_dir}" ARCH=arm64 \
LOCALVERSION="-tegra" \
CROSS_COMPILE="${CROSS_COMPILE_AARCH64}" \
"${O_OPT[@]}" -j"${NPROC}" \
--output-sync=target dtbs
# --------------------------------------------------
# Build modules
# --------------------------------------------------
"${MAKE_BIN}" -C "${source_dir}" ARCH=arm64 \
LOCALVERSION="-tegra" \
CROSS_COMPILE="${CROSS_COMPILE_AARCH64}" \
"${O_OPT[@]}" -j"${NPROC}" \
--output-sync=target modules
image="${tegra_kernel_out}/arch/arm64/boot/Image"
if [ ! -f "${image}" ]; then
echo "Error: Missing kernel image ${image}"
exit 1
fi
echo "Kernel sources compiled successfully."
echo "Kernel Image: ${image}"
}
parse_input_param "$@"
build_arm64_kernel_sources "5.10"
1.3.3 解压编译工具链
为交叉编译创建独立的工具链目录,避免与主机系统工具冲突:
# 创建工具链目录
mkdir toolchain
# 解压工具链包
tar -xzvf aarch64--glibc--stable-final.tar.gz -C ./toolchain/
1.4 添加BTF模块并编译内核
1.4.1 设置全局环境变量
配置交叉编译工具链路径及架构参数,确保编译时能正确调用工具链(请根据工具链实际存放路径修改):
export CROSS_COMPILE_AARCH64=/home/njxm/Desktop/toolchain/bin/aarch64-buildroot-linux-gnu-
export CROSS_COMPILE=$CROSS_COMPILE_AARCH64
export ARCH=arm64
export CROSS_COMPILE_AARCH64_PATH=/home/njxm/Desktop/toolchain
1.4.2 内核编译前配置
按步骤清理源码树、加载默认配置、开启 BTF 及所有必要依赖项,确保内核配置满足 eBPF 功能需求:
PUBLIC=/mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public
KERNEL=$PUBLIC/kernel/kernel-5.10
OUT=$PUBLIC/kernel_out
# 1) 清理源码树
cd "$KERNEL"
make ARCH=arm64 mrproper
# 2) 准备输出目录
rm -rf "$OUT"
mkdir -p "$OUT"
# 3) 加载官方默认配置
make -C "$KERNEL" ARCH=arm64 O="$OUT" tegra_defconfig
# 4) 开启BTF/BTF相关依赖配置
"$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
# 5) 对齐配置依赖
make -C "$KERNEL" ARCH=arm64 O="$OUT" olddefconfig
# 6) 执行编译
cd "$PUBLIC"
sudo -E env NPROC=$(nproc) ./nvbuild1.sh -o "$OUT"
1.5 替换内核文件并安装模块
1.5.1 执行apply_binaries.sh脚本
回到 Linux_for_Tegra 根目录,运行 apply_binaries.sh 脚本。这个脚本的作用是将 NVIDIA 提供的闭源/预编译用户态组件、驱动、固件、内核模块、内核头文件及配置 等“二进制包”,安装/解包到指定的目标根文件系统目录中。
cd /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra
sudo ./apply_binaries.sh
1.5.2 替换核心内核文件
将我们刚刚编译好的内核镜像、设备树二进制文件(DTBs)以及关键的 GPU 驱动模块,替换到 Linux_for_Tegra 目录下的对应位置,覆盖原厂文件。
sudo cp /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel_out/drivers/gpu/nvgpu/nvgpu.ko /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/rootfs/usr/lib/modules/5.10.104-tegra/kernel/drivers/gpu/nvgpu &&
sudo cp /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel_out/arch/arm64/boot/dts/nvidia/* /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/kernel/dtb &&
sudo cp /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel_out/arch/arm64/boot/Image /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/kernel
1.5.3 安装内核模块
进入编译输出目录,将编译生成的所有内核模块安装到目标根文件系统的模块目录中,确保系统启动时能正确加载这些模块。
cd /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel_out &&
sudo -E make INSTALL_MOD_STRIP=1 LOCALVERSION="-tegra" ARCH=arm64 modules_install INSTALL_MOD_PATH=/mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/rootfs modules_install
1.6 编译NVIDIA可视化模块
1.6.1 补齐内核配置生成文件
为了编译 NVIDIA 的专有显示模块,需要先在 kernel_out 目录中生成完整的配置头文件。在 source/public 目录下执行:
cd /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public
# 重新设置全局变量(若终端会话已重置)
export CROSS_COMPILE_AARCH64=/home/njxm/Desktop/toolchain/bin/aarch64-buildroot-linux-gnu-
export CROSS_COMPILE=$CROSS_COMPILE_AARCH64
export ARCH=arm64
export CROSS_COMPILE_AARCH64_PATH=/home/njxm/Desktop/toolchain
PUBLIC=/mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public
KERNEL=$PUBLIC/kernel/kernel-5.10
OUT=$PUBLIC/kernel_out
# 确认配置文件存在
ls -l $OUT/.config
# 生成配置依赖文件
make -C "$KERNEL" ARCH=arm64 O="$OUT" olddefconfig prepare
执行后可以验证文件是否生成:
ls -l $OUT/include/config/auto.conf $OUT/include/generated/autoconf.h
1.6.2 编译NVIDIA显示/开源模块
指定内核源码路径、编译输出目录及交叉编译工具链,编译 NVIDIA 的开源显示驱动模块。
cd /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/NVIDIA-kernel-module-source-TempVersion
make modules \
SYSSRC=/mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel/kernel-5.10 \
SYSOUT=/mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel_out \
O=/mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/kernel_out \
CC="${CROSS_COMPILE}gcc" \
LD="${CROSS_COMPILE}ld.bfd" \
AR="${CROSS_COMPILE}ar" \
CXX="${CROSS_COMPILE}g++" \
OBJCOPY="${CROSS_COMPILE}objcopy" \
TARGET_ARCH=aarch64 \
ARCH=arm64
1.6.3 替换显示模块ko文件
将编译好的显示驱动模块文件(.ko)复制到根文件系统的指定模块目录。
cd /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/source/public/NVIDIA-kernel-module-source-TempVersion
sudo cp kernel-open/*.ko /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra/rootfs/usr/lib/modules/5.10.104-tegra/extra/opensrc-disp
1.7 进入Recovery模式并烧录内核
1.7.1 进入Recovery模式
将 Jetson Orin 设备进入强制恢复模式(Force Recovery Mode),有两种方法:
- 设备未开机状态:长按设备上的 Force Recovery 键(通常标记为②),然后插入电源线通电,待设备保持黑屏且白色指示灯常亮,即表示进入恢复模式。
- 设备已开机状态:长按 Force Recovery 键(②),接着按下 Reset 键(③),先松开 Reset 键(③),再松开 Force Recovery 键(②)。

1.7.2 烧录内核
使用双头 Type-C 数据线连接 Orin 设备与作为烧录主机的 Ubuntu 电脑。在主机上通过 lsusb 命令确认设备已被识别(应出现 NVIDIA Corp. 相关设备)。然后,在 Linux_for_Tegra 目录下执行烧录命令:
cd /mnt/data/Jetson_Linux_R35.1.0_aarch64/Linux_for_Tegra
sudo ./flash.sh jetson-agx-orin-devkit mmcblk0p1
烧录过程会自动进行,完成后设备将自动重启。此时,Orin 设备运行的就是我们刚刚编译的、支持 BTF 的新内核了。
二、eBPF程序测试
eBPF (Extended Berkeley Packet Filter) 是一种革命性的内核技术,它允许开发者在不修改内核源码、不加载传统内核模块的前提下,动态地向运行中的内核注入安全、高效的自定义监控和分析逻辑。
2.1 下载eBPF编译链及依赖
在已烧录新内核的 Orin 设备上(通过 SSH 或直接操作),安装编译和运行 eBPF 程序所需的工具链和库。
sudo apt update
sudo apt install make
sudo apt install -y libelf-dev pkg-config
sudo apt install -y clang-12 llvm-12 lld-12
# 下载并编译bpftool
git clone --recurse-submodules https://github.com/libbpf/bpftool.git
sudo apt install -y libssl-dev
cd ~/bpftool/src
make -j"$(nproc)"
# 可将生成的bpftool复制到系统路径,例如 /usr/local/bin/
sudo cp bpftool /usr/local/bin/
2.2 安装libbpf-bootstrap测试
libbpf-bootstrap 是 eBPF 程序开发的优秀脚手架项目。通过运行其示例程序,可以快速验证我们的内核 eBPF 环境是否正常工作。
# 递归克隆项目
cd ~
git clone --recurse-submodules https://github.com/libbpf/libbpf-bootstrap.git
# 进入C语言示例目录并编译
cd /home/orin/libbpf-bootstrap/examples/c
make
# 运行示例工具,观察输出
sudo ./bootstrap
运行 ./bootstrap 后,如果能看到类似以下实时打印的进程执行与退出事件,则证明 eBPF 程序已在内核中成功加载并运行,我们的内核编译与烧录工作圆满成功。
orin@orin:~/libbpf-bootstrap/examples/c$ sudo ./bootstrap
[sudo] password for orin:
TIME EVENT COMM PID PPID FILENAME/EXIT CODE
14:27:29 EXEC git 5573 4959 /usr/bin/git
14:27:29 EXIT git 5573 4959 [0] (1ms)
14:27:29 EXEC git 5574 4959 /usr/bin/git
14:27:29 EXEC git 5575 5574 /usr/lib/git-core/git
14:27:29 EXIT git 5575 5574 [0] (1ms)
14:27:29 EXIT git 5574 4959 [0] (3ms)
14:27:29 EXEC git 5576 4959 /usr/bin/git
整个过程涉及对内核源码的深度定制和交叉编译,是嵌入式 Linux 系统开发中一项颇具代表性的开源实战任务。希望这份详细的指南能帮助你在 NVIDIA Jetson Orin 平台上顺利开启 eBPF 开发之旅。如果在实践过程中遇到问题,欢迎在 云栈社区 与更多开发者交流探讨。