Orangepi5 Plus 的 Linux 内核模块编译最佳实践
Author: zengkai
Email: zengkai001@qq.com
Date: 2025-09-12
本文旨在帮助开发者在 Orangepi5 Plus (RK3588) 上快速、稳定地编译和加载自定义内核模块,避免常见的 ABI/版本兼容问题 以及OpenKylin SP1 Arm64 驱动模块缺失的问题。
1. 获取内核源码
OrangePi 官方维护了适配 RK3588 的内核树。推荐从官方仓库克隆,指定合适的分支:
git clone --depth=1 -b orange-pi-5.10-rk35xx https://github.com/orangepi-xunlong/linux-orangepi
cd linux-orangepi
2. 安装编译依赖
编译内核与模块需要完整的工具链与依赖库:
sudo apt update
sudo apt install -y \
build-essential bc bison flex libssl-dev libelf-dev libncurses-dev \
dwarves pahole rsync kmod cpio zstd binutils openssl \
fakeroot dpkg-dev
# 确保 python 链接存在
sudo ln -s /usr/bin/python3 /usr/bin/python
3. 配置内核(避免 version-magic 不匹配)
内核模块能否正确加载,关键在于 运行内核版本号 与 编译时版本号 一致。
3.1 导出当前运行内核配置
zcat /proc/config.gz | tee ~/running-kernel.config 2>/dev/null || \
cat /boot/config-$(uname -r) | tee ~/running-kernel.config
复制到源码目录:
cp ~/running-kernel.config .config
make olddefconfig
3.2 统一内核版本号
uname -r # 示例: 5.10.160-rockchip-rk3588
避免自动追加 +
后缀:
printf '' > .scmversion
# 或在 make 命令后加 LOCALVERSION=
3.3 仅编译所需模块(示例:USB 串口全集)
make LOCALVERSION= -j$(nproc) M=drivers/usb/serial
sudo make LOCALVERSION= M=drivers/usb/serial modules_install
验证版本:
make LOCALVERSION= kernelrelease
# 应输出: 5.10.160-rockchip-rk3588
如果系统已有 5.10.160-rockchip-rk3588+
目录,可直接改名:
sudo mv /lib/modules/5.10.160-rockchip-rk3588+ /lib/modules/5.10.160-rockchip-rk3588
sudo depmod -a 5.10.160-rockchip-rk3588
4. 验证模块
以 CH341 USB 串口驱动为例:
sudo modprobe ch341
lsmod | grep ch341
dmesg | tail -n 30
5. 常见错误与解决方法
A. invalid module format
-
绝大多数情况与 LOCALVERSION / MODVERSIONS / Module.symvers 不匹配 有关。
-
确保:
.config
中的CONFIG_LOCALVERSION
与uname -r
后缀一致;- 使用运行内核导出的
.config
。
B. 模块签名报错
日志包含:
scripts/sign-file ... openssl: not found
Can't load private key
原因:启用了 CONFIG_MODULE_SIG_ALL=y
却没有私钥。
解决:
./scripts/config --disable MODULE_SIG
./scripts/config --undefine MODULE_SIG_ALL
make olddefconfig
C. BTF 错误
日志包含:
pahole: command not found
FAILED: ... BTF
解决方法:
sudo apt install dwarves
./scripts/config --disable DEBUG_INFO_BTF
D. 磁盘/内存不足
-
解决存储不足:
df -h
确保 >6~8GB 可用空间。
-
解决内存不足:
sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab sudo swapon -a
并限制并行度:
make -j2
6. 核心原则
- 运行内核版本号 (uname -r) 与编译目标版本号 (make kernelrelease) 必须完全一致。
- /lib/modules/<版本号>/ 必须存在且完整,包含
modules.order
与modules.builtin
。 - 如果仅补充模块,不需重启;若编译了新内核,需切换启动到新内核。
7. 一键自动化脚本
以下脚本将自动执行源码获取、配置同步、编译并安装指定模块:
#!/bin/bash
set -e
# === 配置 ===
KERNEL_BRANCH="orange-pi-5.10-rk35xx"
REPO_URL="https://github.com/orangepi-xunlong/linux-orangepi"
MODULE_PATH="drivers/usb/serial" # 需要编译的模块路径
# === 步骤 1: 获取源码 ===
if [ ! -d linux-orangepi ]; then
git clone --depth=1 -b $KERNEL_BRANCH $REPO_URL
fi
cd linux-orangepi
# === 步骤 2: 导出运行内核配置 ===
zcat /proc/config.gz > .config 2>/dev/null || \
cp /boot/config-$(uname -r) .config
make olddefconfig
# === 步骤 3: 确保版本一致 ===
printf '' > .scmversion
# === 步骤 4: 编译并安装模块 ===
make LOCALVERSION= -j$(nproc) M=$MODULE_PATH
sudo make LOCALVERSION= M=$MODULE_PATH modules_install
# === 步骤 5: 修复模块路径 ===
VER=$(make LOCALVERSION= kernelrelease)
if [ -d /lib/modules/${VER}+ ]; then
sudo mv /lib/modules/${VER}+ /lib/modules/${VER}
fi
sudo depmod -a $VER
echo "✅ 模块编译并安装完成,版本: $VER"
保存为 build-module.sh
,赋予执行权限并运行:
chmod +x build-module.sh
./build-module.sh
总结:
通过沿用运行内核配置、统一 LOCALVERSION
、禁用 BTF/签名等“坑点”,可以大幅提升编译成功率,确保模块与当前运行内核 ABI 完全匹配。推荐使用自动化脚本,避免重复人工操作。
Tips
- 如果你只想给当前运行内核补模块,务必保证脚本打印的
构建目标版本 (BUILD_VER)
与uname -r
完全一致。 - 若遇到
invalid module format
,先确认.scmversion
已清空、LOCALVERSION=
生效,以及depmod -a <版本>
已执行。 - 若出现
BTF
或模块签名相关错误,脚本已尝试关闭相应选项;仍有问题时,按文档里的“常见错误”对症处理即可。
下面是 一键脚本:
只需传入模块目录(相对于内核源树的路径),即可自动同步当前运行内核配置、确保版本号一致、编译并安装该模块。
传第二个参数为模块名(不含 .ko
),脚本会在安装后自动尝试 modprobe
载入并打印最近日志。
把下面内容保存为 op5p-build-module.sh
(已包含之前的全部功能 + 新增测试):
#!/usr/bin/env bash
set -euo pipefail
# Orangepi5 Plus 简化版模块编译安装 + 可选自动测试
# 用法:
# ./op5p-build-module.sh <模块相对路径> [模块名]
# 例:
# ./op5p-build-module.sh drivers/usb/serial ch341
#
# 可选环境变量:
# KERNEL_REPO=https://github.com/orangepi-xunlong/linux-orangepi
# KERNEL_BRANCH=orange-pi-5.10-rk35xx
# SRC_DIR=linux-orangepi
# JOBS=<并行度,默认 nproc>
# DMESG_LINES=<打印多少行 dmesg,默认 80>
MODULE_PATH="${1:-}"
MOD_NAME="${2:-}" # 传入则进行自动 modprobe 测试
if [[ -z "${MODULE_PATH}" ]]; then
echo "用法: $0 <模块相对路径> [模块名] 例如: $0 drivers/usb/serial ch341"
exit 1
fi
KERNEL_REPO="${KERNEL_REPO:-https://github.com/orangepi-xunlong/linux-orangepi}"
KERNEL_BRANCH="${KERNEL_BRANCH:-orange-pi-5.10-rk35xx}"
SRC_DIR="${SRC_DIR:-linux-orangepi}"
JOBS="${JOBS:-$(nproc)}"
DMESG_LINES="${DMESG_LINES:-80}"
need_bins=(make gcc git bc flex bison openssl ld ar strip)
for b in "${need_bins[@]}"; do
command -v "$b" >/dev/null 2>&1 || { echo "缺少依赖: $b"; MISSING=1; }
done
if [[ "${MISSING:-0}" == "1" ]]; then
echo "提示: Debian/Ubuntu 可先执行:sudo apt update && sudo apt install -y build-essential bc bison flex libssl-dev libelf-dev libncurses-dev dwarves pahole kmod cpio zstd binutils openssl fakeroot dpkg-dev"
exit 2
fi
# 获取源码
if [[ ! -d "${SRC_DIR}" ]]; then
echo "克隆内核源码:${KERNEL_REPO} (${KERNEL_BRANCH})"
git clone --depth=1 -b "${KERNEL_BRANCH}" "${KERNEL_REPO}" "${SRC_DIR}"
fi
cd "${SRC_DIR}"
# 同步运行内核配置
if zcat /proc/config.gz >/dev/null 2>&1; then
echo "导出运行内核配置:/proc/config.gz"
zcat /proc/config.gz > .config
else
cfg="/boot/config-$(uname -r)"
echo "导出运行内核配置:${cfg}"
cp "${cfg}" .config
fi
echo "应用 olddefconfig 并准备内核头文件"
make olddefconfig
: > .scmversion
make LOCALVERSION= modules_prepare
RUNNING_VER="$(uname -r)"
BUILD_VER="$(make LOCALVERSION= kernelrelease)"
if [[ "${BUILD_VER}" != "${RUNNING_VER}" ]]; then
echo "⚠️ 警告:运行内核版本 ${RUNNING_VER} ≠ 构建版本 ${BUILD_VER}"
echo " 若要给当前内核“补模块”,应确保两者完全一致(包括后缀)。"
fi
# 可选:关掉 BTF / 模块签名,减少常见报错
if [[ -x ./scripts/config ]]; then
./scripts/config --disable DEBUG_INFO_BTF || true
./scripts/config --disable MODULE_SIG || true
./scripts/config --undefine MODULE_SIG_ALL || true
make olddefconfig
fi
# 编译并安装目标模块
echo "开始编译模块:${MODULE_PATH}"
make -j"${JOBS}" LOCALVERSION= M="${MODULE_PATH}"
echo "安装模块到 /lib/modules/${BUILD_VER}"
sudo make LOCALVERSION= M="${MODULE_PATH}" modules_install
# 修正 '+' 目录并重建索引
if [[ -d "/lib/modules/${BUILD_VER}+" ]]; then
echo "发现 /lib/modules/${BUILD_VER}+ ,改名为 /lib/modules/${BUILD_VER}"
sudo mv "/lib/modules/${BUILD_VER}+" "/lib/modules/${BUILD_VER}"
fi
echo "重建模块索引:depmod -a ${BUILD_VER}"
sudo depmod -a "${BUILD_VER}"
echo "✅ 安装完成:已安装模块版本 ${BUILD_VER};当前运行内核 ${RUNNING_VER}"
# === 自动 modprobe 测试(可选) ===
if [[ -n "${MOD_NAME}" ]]; then
echo "——— 自动测试:尝试加载模块 '${MOD_NAME}' ———"
# 尝试先卸载旧模块,忽略失败
if lsmod | awk '{print $1}' | grep -q "^${MOD_NAME}$"; then
echo "检测到模块已加载,尝试卸载:sudo modprobe -r ${MOD_NAME}"
if ! sudo modprobe -r "${MOD_NAME}"; then
echo "⚠️ 卸载旧模块失败(可能正在使用中),继续尝试加载同名模块。"
fi
fi
echo "加载模块:sudo modprobe ${MOD_NAME}"
if sudo modprobe "${MOD_NAME}"; then
echo "✅ modprobe 成功:${MOD_NAME}"
else
echo "❌ modprobe 失败:${MOD_NAME}"
fi
echo "——— 最近 ${DMESG_LINES} 行内核日志 ———"
dmesg | tail -n "${DMESG_LINES}"
fi
echo "完成。你可以使用:sudo modprobe <模块名> 进行加载;用 'dmesg | tail' 查看日志。"
使用示例
chmod +x op5p-build-module.sh
# 仅编译并安装 USB 串口全集
./op5p-build-module.sh drivers/usb/serial
# 编译安装并自动测试加载 ch341
./op5p-build-module.sh drivers/usb/serial ch341
# 低内存设备:降低并行度 + 指定 dmesg 打印行数
JOBS=2 DMESG_LINES=120 ./op5p-build-module.sh drivers/usb/serial ch341