Orangepi5 Plus 的 Linux 内核模块编译最佳实践

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_LOCALVERSIONuname -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. 核心原则

  1. 运行内核版本号 (uname -r) 与编译目标版本号 (make kernelrelease) 必须完全一致
  2. /lib/modules/<版本号>/ 必须存在且完整,包含 modules.ordermodules.builtin
  3. 如果仅补充模块,不需重启;若编译了新内核,需切换启动到新内核。

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

:pushpin: 总结
通过沿用运行内核配置、统一 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
1 个赞