只读分享解决方案文档原理方案设计📅 2026-10-05🔖 Rev 1.4⏳ 26天有效(至 2026-11-05)
🔧解决方案&应用市场分析 -> 原理方案设计 -> Linux内核开发指南(BSP Kernel)

Linux内核开发指南(BSP Kernel)

生成时间 2026-10-04 11:49:46 有效期至 2026-11-05 15:39:01(26天有效(至 2026-11-05))
同系列 · 原理方案设计

Linux 内核开发指南

编制日期 2026-10-05

BSP 总文档 · 覆盖环境搭建 / 配置裁剪 / 设备树 / 驱动开发 / 调试调优 / 测试发布全流程 · 驱动专项文档为子文档挂在第 6 章下

23 章 + 6 附录
完整章节体系
第 1–15 章为主流程,第 16 章给方向,第 17–23 章为 P0 / P1 扩展专题
31 张
内联图示
流程图 / 分层图 / 决策树 / 状态机 / 架构图
5.4 / 6.1
内核版本基线(示例)
按项目实际 LTS 版本替换,见 1.2
40+ 项
发布前检查项
附录 F,版本评审逐条过

这份文档解决的是「一个 BSP 团队如何把内核从源码变成可量产固件」的问题: 它不教你怎么写某一个驱动(那是第 6 章下挂的专项子文档),而是把 环境 → 配置 → 设备树 → 驱动 → 文件系统 → 调试 → 调优 → 测试 → 发布串成一条可复用的流水线, 并把每一步的基线(源码基线 / 配置基线 / 交付基线)固化下来。 新人按 1→15 章通读一遍即可上手;老手直接翻第 15 章速查与附录 F 检查清单。 第 17–23 章是从「能跑」到「能量产、能长期维护」的七块拼图: 硬件 Bring-up、存储子系统深度、安全启动、量产支持、CI/CD、可观测性、内核生命周期—— 按项目阶段取用,不必通读。

读者路径

BSP 工程师:第 2、4、5、7、14 章是主战场;附录 A/B/C 是日常手册。

驱动工程师:第 3、5、6 章 + 对应专项子文档;第 8 章决定你定位问题的速度。

测试工程师:第 13 章全套用例 + 第 15 章排查入口;附录 D 的脚本可直接复用。

硬件工程师:第 5.6 节(GPIO 复用 / 电源域 / 中断冲突)与第 10、11、17 章必须看, 多数「驱动调不通」最后都定位到硬件设计。

新板首次点亮:直接翻第 17 章,按六步顺序走,不要凭感觉跳级。

要交付量产 / 要过安全合规:第 19、20、21、23 章是一组,缺一项都会在后期付出代价。

这份文档的三条硬约束

约束具体要求落地位置
配置基线永不直接提交 .config,一律 make savedefconfig 生成 defconfig 入仓第 4.2 节
源码基线内核源码来源、分支、补丁集三要素可追溯,每个发布版本有 tag第 1.4、14.1 节
交付基线发布前过一遍检查清单,产出 changelog + Known Issues + 交付物清单第 14.4、14.5 节、附录 F

与其它文档的分工

文档层级覆盖范围典型例子
本指南(总文档)内核全流程:环境、配置、设备树、驱动、文件系统、调试、调优、测试、发布本文档
驱动专项子文档单一器件的驱动实现细节:寄存器、命令序列、MTD 对接、时序SPI NAND 驱动开发指南
硬件设计指南原理图、电源、封装、信号完整性SD NAND 电路设计指南
市场 / 选型报告容量档位、应用场景、厂商格局存储芯片应用市场分析
BSP 内核开发主流程:从环境搭建到版本发布阶段一环境搭建宿主机 / 工具链源码 / 构建系统第 2 章阶段二配置与裁剪defconfig 基线menuconfig 裁剪第 4 章阶段三内核与驱动开发设备树 DTS驱动 probe 适配第 5–7 章阶段四调试与调优Oops 定位 / ftrace启动与存储调优第 8–11 章阶段五测试与发布压力 / 稳定 / 回归补丁与版本基线第 12–15 章贯穿全程的三条基线源码基线Git 分支策略 + tag上游 / 厂商 SDK 同步第 1.4、14 章配置基线defconfig 入仓不直接提交 .config第 4.2 节交付基线检查清单 + 交付物版本号 Rev 1.4附录 F、第 14.4 节说明:五阶段是主线,三条基线是约束——任何阶段改动都要回到基线上留痕,否则版本无法追溯。图示:阶段顺序不强制串行,实项目中阶段三与阶段四通常多轮迭代。
图 1 · BSP 内核开发主流程与贯穿全程的三条基线
文档体系分层:本指南是总文档,驱动专项是子文档Linux 内核开发指南BSP 总文档 · 覆盖内核全流程环境构建与配置裁剪第 2、4 章设备树与启动流程第 3、5 章驱动开发第 6 章调试 · 调优 · 发布第 8–15 章第 6 章下挂的驱动专项子文档(各自独立成文,接口规范由本指南统一约束)SPI NAND 驱动开发SPI NOR 驱动开发eMMC / 块设备I2C / ADC / 其它外设
图 2 · BSP 总文档与驱动专项子文档的层级关系

第 1 章 文档总览

这一章先把边界划清楚:这份文档给谁用、基于什么平台与版本、用到哪些术语、内核源码从哪来。边界不清是 BSP 项目后期返工的头号原因。

1.1 编写目的、适用范围与读者

本指南用于规范嵌入式 Linux 产品内核层(BSP)开发的全流程动作与交付标准, 目标是让不同背景的工程师在同一套基线上协作,并把「个人经验」沉淀成「团队可复用的流程」。

角色关注重点必读章节
BSP 工程师环境、配置裁剪、设备树、启动、镜像打包、版本发布第 2、4、5、7、14 章
驱动工程师总线模型、驱动框架、资源管理、调试定位第 3、5、6、8 章
测试工程师测试用例、性能基准、稳定性方法、回归范围第 13 章、附录 D
硬件工程师引脚复用、电源域、中断分配、启动介质、功耗第 5.6、10、11 章
项目管理 / 评审基线、交付物、检查清单、Known Issues第 14 章、附录 F

适用范围:基于 Linux 内核的嵌入式产品(ARM32 / ARM64 / RISC-V 均可参照), 存储介质覆盖 SPI NOR、SPI NAND、eMMC、SD / SD NAND 等。 不适用范围:裸机 / RTOS 开发、纯用户态应用开发、芯片 RTL 设计。

1.2 平台与版本基线

版本基线必须写进文档的「第一页」,后续所有命令、补丁、配置都以此为准。 下表为示例基线,项目立项时按实际情况替换并冻结。

项目基线选择说明
内核版本Linux 5.4 / 6.1(示例)优先选 LTS 主线版本;厂商 SDK 内核需在 1.4 说明与主线差异
CPU 架构ARM32(arm)/ ARM64(arm64)决定 ARCH 与工具链前缀
芯片平台i.MX6ULL / RK3568 / 全志 / STM32MP1 等以项目选型为准,不写死本文档
BootloaderU-Boot(版本随 SDK)与内核 DTB、bootargs 强耦合,必须配对升级
构建系统Buildroot / Yocto / 厂商 SDK见 2.4 三者差异
启动介质SPI NOR / SPI NAND / eMMC / SD决定分区表与文件系统选型,见第 7 章
工具链gcc-linaro / arm-gnu-toolchain / SDK 自带版本与内核版本强相关,见 2.2
版本基线冻结纪律

立项评审通过后,内核版本、工具链版本、U-Boot 版本三者必须同时冻结并写入本表;任一变更都要走变更评审,并在 14.5 Known Issues 中记录影响面。中途偷偷升级工具链是「昨天还能编译,今天编不过」最常见的来源。

1.3 术语、缩写与参考文档

缩写全称 / 含义备注
BSPBoard Support Package,板级支持包本文档主题
DTS / DTB / DTC设备树源码 / 二进制 / 编译器第 3.3、5 章
DTBODevice Tree Overlay 二进制第 5.4 节
MTDMemory Technology Device,裸闪存子系统第 6.6 节
UBI / UBIFS闪存卷管理层 / 闪存文件系统第 7 章
rootfs根文件系统第 7 章
bootargsU-Boot 传给内核的启动参数第 7.3 节
Oops / panic内核异常 / 致命错误第 8.3 节
ko可动态加载的内核模块第 3.5、6.5 节
LTSLong Term Support,长期支持版本第 1.2 节

参考文档(详见附录 E):内核源码树内 Documentation/、 README、各子系统 *-api.rst;芯片厂商 SDK Release Note 与 TRM; 器件 datasheet(以正式版本为准)。

1.4 内核源码来源与分支策略

内核源码只有两种来源,必须先定性再开发——它决定了后续所有补丁与升级方式。

来源 A
主线内核 mainline
从 kernel.org / 镜像站拉取官方 LTS 分支。优点:可长期维护、社区补丁可直接合入、安全修复及时。代价:芯片平台支持可能不完整,需自行补齐驱动与 DTS。
适合:平台主线支持好、产品生命周期长、有 upstream 诉求
来源 B
厂商 SDK 内核
芯片原厂提供的内核(如 NXP / Rockchip / 全志 SDK)。优点:外设驱动齐全、开箱即用。代价:版本往往落后、与主线差异大、升级困难、补丁难以上游化。
适合:量产时间紧、强依赖厂商私有驱动
混合策略
厂商 SDK 起步 + 逐步上游化
初期用 SDK 保证进度,同期把通用部分(DTS、存储驱动、通用外设)整理成可提交主线的补丁,长期降低维护成本。
推荐:有 2 年以上生命周期的产品

分支管理策略(建议)

main            # 基线分支,只接受经过评审的合入,每个发布版本打 tag
release/vX.Y    # 发布分支,从 main 切出,只接受 bugfix,不再合入新特性
dev/<feature>   # 特性分支,开发者个人分支,完成后提 MR/PR 合入 main
hotfix/<issue>  # 紧急修复分支,修完同时回流 main 与对应 release 分支
与上游同步的建议节奏

无论选哪种来源,每季度至少同步一次上游 LTS 的安全补丁;厂商 SDK 内核则跟踪原厂 Release Note 的 CVE 修复。同步前先在 dev/ 分支验证编译与启动,再合入 main。

第 2 章 开发环境搭建

环境是后面所有工作的地基。这一章的目标是:任何一个新人在一台干净的机器上,照着命令跑完就能编出可启动的内核。

2.1 宿主机系统与依赖包

推荐 Ubuntu 20.04 / 22.04 LTS(64 位),磁盘预留 100 GB 以上(内核源码 + 构建产物体积可观)。 以下为最小依赖集,已覆盖内核编译、menuconfig、设备树编译、模块签名等常见需求。

sudo apt update
sudo apt install -y build-essential libncurses-dev flex bison libssl-dev libelf-dev \
    bc cpio kmod rsync wget git python3 python3-pip device-tree-compiler \
    u-boot-tools zlib1g-dev liblz4-tool zstd file cscope exuberant-ctags \
    picocom minicom tftpd-hpa nfs-kernel-server openssh-server

# 调试与性能工具(第 8、9 章会用到)
sudo apt install -y trace-cmd kernelshark linux-tools-common linux-tools-generic \
    gdb-multiarch stress stress-ng iperf3 sysstat
不要用 root 直接编译内核

用普通用户编译,需要 root 权限的动作前加 sudo。以 root 编译会导致源码树文件属主混乱,后期 git clean、补丁生成都会踩坑;内核构建本身也不推荐用 sudo make。

2.2 交叉编译工具链选型

工具链必须与内核版本和目标架构同时匹配,尤其注意浮点 ABI (ARM32 的 gnueabihf 与 gnueabi 不兼容)。

目标架构典型前缀说明
ARM32arm-linux-gnueabihf-带硬浮点,i.MX6ULL / STM32MP1 等常用
ARM64aarch64-linux-gnu-RK3568 及多数 64 位平台
RISC-Vriscv64-linux-gnu-注意 ISA 扩展与 -march 设置
SDK 自带以厂商 Release Note 为准与 SDK 内核版本绑定,不建议混用外部工具链
# 方式一:发行版仓库安装(够用,版本较固定)
sudo apt install -y gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu

# 方式二:官方工具链(推荐,版本可控)
wget https://developer.arm.com/-/media/Files/downloads/gnu/.../arm-gnu-toolchain-*.tar.xz
sudo tar -xf arm-gnu-toolchain-*.tar.xz -C /opt
export PATH=/opt/arm-gnu-toolchain-*/bin:$PATH

# 验证(必须看到版本号与正确的 target)
arm-linux-gnueabihf-gcc --version
aarch64-linux-gnu-gcc --version

# 写入环境脚本,避免每次手敲(放在 build/env.sh,团队共用)
cat > build/env.sh <<'EOF'
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=/opt/arm-gnu-toolchain-13.2/bin:$PATH
export KBUILD_OUTPUT=${PWD}/build/out      # 可选:把编译产物放到独立目录
EOF
source build/env.sh
工具链版本不匹配的典型症状

现象:编译报 undefined reference to `__aeabi_...'、unrecognized command line option '-mno-unaligned-access';或者编出来了但内核启动即崩溃(浮点 ABI 不一致)。处置:不要临时换工具链硬编,回到基线表确认版本,必要时连内核一起升/降。

2.3 内核源码获取与补丁管理

# 主线内核(浅克隆加速,够编译即可)
git clone --depth 1 --branch v6.1 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux && git log --oneline -1

# 厂商 SDK:以原厂文档为准,一般提供完整 git 仓库或 tarball + 补丁集
# 补丁管理:所有本地修改一律做成 patch 入仓,禁止直接改源码不留痕
git format-patch -1 <commit> -o patches/       # 生成补丁
git am patches/0001-*.patch                    # 应用补丁
quilt series                                   # 若 SDK 使用 quilt 管理补丁,按其规范操作

2.4 构建系统:Buildroot / Yocto / 厂商 SDK

三者不是互斥关系,而是不同规模产品的不同选择。

维度BuildrootYocto / OpenEmbedded厂商 SDK
定位轻量根文件系统与工具链生成器完整发行版构建框架原厂一键编译脚本
上手成本低(menuconfig 一套配置)高(layer / recipe 概念多)最低(照文档跑脚本)
灵活性中,适合单一产品形态高,适合多产品线与长期演进低,绑定原厂目录结构
可复现性好(defconfig + 版本锁定)最好(recipe 精确到 checksum)差(依赖 SDK 包本身)
构建耗时分钟级到半小时级首次数小时(有 sstate 缓存后快)视 SDK 而定
推荐场景中小项目 / 单一产品 / 快速验证多产品线 / 需要 OTA / 需要长期维护前期验证 / 原厂方案直出
选型建议

做单一产品、团队人手少:选 Buildroot,把 buildroot 的 defconfig、外部树(BR2_EXTERNAL)与内核 defconfig 一起入仓,整机构建可复现。多条产品线、要做 OTA 与长期演进:选 Yocto,把自定义层单独建仓。厂商 SDK 只建议在前期验证阶段用,量产项目应把关键配置抽出来落到 Buildroot/Yocto。

2.5 内核、设备树与模块的独立编译

日常开发中,全量重编太慢,掌握「单独编」能省大量时间。

source build/env.sh

# 1) 配置(首次)
make <soc>_defconfig            # 载入芯片默认配置,例如 imx_v6_v7_defconfig
make menuconfig                 # 交互调整
make savedefconfig              # 生成精简 defconfig(提交这一个,不要提交 .config)
cp defconfig arch/arm/configs/myboard_defconfig

# 2) 编译内核与模块(-j 按 CPU 核数调整)
make -j$(nproc) zImage          # ARM32;ARM64 通常是 Image
make -j$(nproc) modules
make -j$(nproc) dtbs            # 编译全部设备树
make mysoc-myboard.dtb          # 只编指定的一个 dtb(快)

# 3) 单独编译某个外部模块
make -C /path/to/kernel M=$(pwd) modules
make -C /path/to/kernel M=$(pwd) modules_install INSTALL_MOD_PATH=/path/to/rootfs

# 4) 清理(注意别误删 .config)
make clean                      # 清目标文件
make mrproper                   # 连 .config 一起清,慎用

2.6 调试通道:串口 / TFTP / NFS / SSH

开发期强烈建议内核走 TFTP、rootfs 走 NFS,把「改一行代码」到「看到结果」压缩到一分钟内, 同时避免反复擦写 Flash 导致器件损耗。

通道用途关键配置
串口控制台看 U-Boot / 内核启动日志、进 U-Boot 命令行波特率常见 115200 8N1,按板卡确认
TFTP下载 zImage / dtb 到内存运行服务端 /srv/tftp,U-Boot 设 serverip
NFS挂载根文件系统,改文件即时生效内核需开 CONFIG_ROOT_NFS,/etc/exports 配权限
SSH登录后跑性能工具、拷贝日志rootfs 内置 dropbear / openssh
# U-Boot 侧:网络启动内核 + NFS 根文件系统(开发期最常用)
setenv ipaddr 192.168.1.100; setenv serverip 192.168.1.10
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.10:/srv/nfs/rootfs,v3,tcp rw ip=dhcp'
tftpboot 0x80800000 zImage
tftpboot 0x83000000 myboard.dtb
bootz 0x80800000 - 0x83000000        # ARM64 用 booti

# 宿主机:把编译产物同步到 TFTP 目录
cp arch/arm/boot/zImage /srv/tftp/ && cp arch/arm/boot/dts/myboard.dtb /srv/tftp/

2.7 推荐工具集

类别工具用途
编辑 / 浏览vim / VS Code + clangd配合 compile_commands.json 获得跳转补全
代码索引cscope / ctagsmake cscope 生成索引,查函数调用极快
版本管理git + gitk / tig分支、补丁、bisect 定位引入问题的提交
静态检查make checkstack / sparse / coccinelle编译期发现隐患
动态调试trace-cmd / kernelshark / perf第 8.5 节
内核调试gdb-multiarch + KGDB第 8.4 节
串口终端picocom / minicompicocom -b 115200 /dev/ttyUSB0
压力测试stress-ng / fio / iperf3第 13 章
开发环境链路:宿主机编译 → 产物 → 目标板 → 调试通道回传宿主机开发机· Ubuntu 20.04 / 22.04 LTS· 内核源码树 + 补丁· Buildroot / Yocto / SDK· TFTP·NFS·SSH 服务端交叉编译· arm-linux-gnueabihf-· aarch64-linux-gnu-· ARCH=arm / arm64· CROSS_COMPILE= 前缀编译产物· zImage / uImage· *.dtb 设备树· *.ko 内核模块· rootfs 镜像目标板· U-Boot 引导· 内核 + DTB 加载· rootfs 挂载· 业务进程运行调试通道(宿主机 ↔ 目标板)· 串口控制台 UART· TFTP 下载内核 / DTB· NFS 挂载 rootfs· SSH 网络调试建议:开发期一律走 TFTP + NFS,避免反复烧写 Flash;量产前再切回本地 Flash 启动。
图 3 · 宿主机交叉编译到目标板的完整链路与调试通道

第 3 章 内核基础原理

这一章是给新人的理论底座:搞清楚内核怎么启动、设备树起什么作用、子系统怎么分层、模块怎么加载。理解了这些,后面调驱动才知道自己在改哪一层。

3.1 内核空间与用户空间、系统调用

CPU 以特权级区分两种运行态:用户空间跑应用程序,各自拥有独立虚拟地址空间, 一旦越界访问或执行特权指令会被 MMU / CPU 拦截;内核空间所有进程共享同一份地址空间, 驱动代码就运行在这里,一处出错会影响整机。

用户态进入内核态的合法途径只有三类:系统调用(open/read/write/ioctl 等)、 异常(缺页、未定义指令)、中断(外设触发)。 驱动开发者打交道最多的是 ioctl 与 read/write 在内核侧的 file_operations 实现。

内核空间与用户空间的边界:系统调用是唯一合法通道用户空间(User Space,CPU 低特权级,地址空间隔离)应用程序业务进程 / 守护进程C 库 glibc / musl封装系统调用调试工具perf / strace / fio系统调用接口 syscall(svc / swi)· 异常 · 中断 —— 唯一合法进入内核的通道内核空间(Kernel Space,高特权级,共享同一地址空间)系统调用层open / read / write / ioctl 的内核实现核心子系统VFS · 网络协议栈 · 进程调度 · 内存管理设备驱动层字符设备 · 块设备 · MTD · 网络驱动 · 总线驱动体系相关层中断控制器 · 时钟 · GPIO · DMA · 电源域硬件:CPU · 内存 · 存储器件(SPI NOR / SPI NAND / eMMC)· 外设
图 4 · 内核空间与用户空间的分层及系统调用通道
对驱动开发的直接含义

内核态没有内存保护:一个野指针写坏的是整个内核,现象往往是随机的死机而不是立刻崩溃。因此第 6.7 节的资源自动释放(devm_*)与第 8.6 节的内存排查不是可选项,而是必备动作。

3.2 内核启动全流程

启动链路是排障的总地图。拿到一块不启动的板子,第一件事永远是看串口停在哪一步, 而不是猜。下图给出七阶段与每一步的日志特征。

从上电到用户进程:七个阶段与各自的「日志特征」ROM / SPL片内 Boot ROM 加载第一阶段引导程序到 SRAM无串口输出 = 卡在这一步1U-Boot SPL初始化 DDR、时钟、引脚,为加载 U-Boot 主体做准备U-Boot SPL banner2U-Boot 主体加载 zImage / dtb,装配 bootargs 并跳转内核bootargs 在此定型3内核入口自解压(zImage)→ 跳转到内核入口 → 早期汇编初始化Uncompressing Linux...4内核初始化setup_arch 解析 dtb → 各子系统 initcall 分级初始化大量 printk 输出5挂载 rootfs按 bootargs 的 root= 挂载根文件系统VFS: Mounted root6用户态 init启动 init / systemd → 拉起业务进程login / 业务进程日志7排障入口:先看串口停在哪一步,再跳到第 15.1 节的对应分支;第 9 章的启动耗时优化就是逐段量测这七步。注:阶段 1–3 属 Bootloader 范畴,本文只界定接口边界(dtb 传递、bootargs 装配)。
图 5 · 嵌入式 Linux 启动七阶段与日志特征
阶段关键动作常见失败点
ROM / SPL片内 ROM 加载 SPL 到 SRAM启动引脚电平、Boot 介质焊接、SPL 镜像不对
U-Boot SPL初始化 DDR / 时钟 / 串口DDR 参数不匹配 → 卡死无输出
U-Boot 主体加载 zImage + dtb,装配 bootargsbootargs 写错、dtb 地址冲突、网口不通
内核入口自解压并跳转到内核入口镜像损坏、加载地址与链接地址不一致
内核初始化解析 dtb、子系统 initcall 分级初始化dtb 不匹配、时钟/电源域未开导致驱动崩溃
挂载 rootfs按 root= 参数挂载根文件系统root 参数错、文件系统驱动未编译进内核
用户态 init启动 init / systemd 并拉起业务rootfs 缺库、init 路径不存在

3.3 设备树 DTS 原理

设备树解决的是「同一份内核如何描述不同硬件」的问题: 硬件描述(DTS)从内核代码中剥离出来,编译成 DTB 后由 Bootloader 传给内核, 内核展开成 device_node,再按 compatible 与驱动的 of_match_table 做匹配,匹配成功才调用 probe()。

DTS 如何从源文件变成驱动 probe:编译 → 传递 → 展开 → 匹配*.dts / *.dtsi板级与 SoC 公共描述dtc 编译器make dtbs 或手动 dtc*.dtb / *.dtbo扁平二进制设备树U-Boot 加载 dtb传给内核(寄存器/约定)解析为 device_node/proc/device-tree 可查按 compatible 匹配与驱动的 of_match_table 比对调用驱动 probe()拿到资源并注册设备匹配失败的典型后果· 驱动不 probe,设备不出现· dmesg 无相关日志· /sys、/dev 下找不到节点· 排查入口见 5.5 / 15.3compatible 写法(从具体到通用,驱动按最前匹配优先)compatible = "xtx,xt25w1g", "jedec,spi-nor";
图 6 · 设备树从编译到驱动 probe 的完整链路
最容易误解的一点:compatible 不是「驱动名」

compatible 是硬件的标识字符串,格式惯例为 "厂商,型号",可带多个从具体到通用的候选(如 "xtx,xt25w1g", "jedec,spi-nor")。驱动侧在自己的 of_match_table 里声明支持哪些字符串,两边能对上才会 probe。驱动调不通时先核对这一对字符串,比看寄存器快得多。

3.4 内核子系统地图

下表是子系统清单与本文定位——每个子系统单独写专项文档,这里只给出职责边界与常用接口入口。

子系统职责驱动常用接口 / 文档入口
Platform 总线无物理总线的片上外设挂接点platform_driver_register、of_device_id
SPI / I2C / UART串行总线与控制器、从设备驱动spi_driver、i2c_driver、serdev
MTD裸闪存(NOR / NAND)统一抽象mtd_info、nand_controller、spi_nand
块设备eMMC / SD / UBI 之上的块接口gendisk、blk_mq
字符设备按字节流访问的设备cdev、file_operations
时钟 CLK时钟树与门控clk_get / clk_prepare_enable
电源管理 PMsuspend/resume、Runtime PM、电源域dev_pm_ops、pm_runtime_*
中断 IRQ中断号映射、上下半部、线程化中断request_threaded_irq、devm_request_irq
DMA外设与内存之间的直接搬运dmaengine、dma_map_single
GPIO / Pinctrl引脚复用与电平控制gpiod_get、pinctrl

3.5 内核模块:内置 vs 动态加载

方式配置形态优点代价 / 注意
内置编译CONFIG_XXX=y启动即可用,无加载顺序问题;rootfs 挂载前必需的驱动(存储、文件系统)必须内置镜像变大,改动需重编内核
动态模块CONFIG_XXX=m可现场加载调试,迭代快,镜像小需要处理依赖与加载顺序,modules_install 到 rootfs
# 模块常用操作
insmod  demo.ko                 # 加载(不处理依赖)
modprobe demo                   # 加载(自动处理依赖,需先 depmod)
lsmod                           # 查看已加载模块
modinfo demo.ko                 # 查看模块信息(依赖、参数、版本)
rmmod demo                      # 卸载
cat /proc/modules               # 内核视角的模块列表

# 模块参数(驱动内 module_param 声明)
insmod demo.ko debug=1
经验法则

启动链路上必需的驱动一律内置:存储控制器、MTD/块设备、根文件系统类型、以及它们依赖的时钟与 GPIO/pinctrl。调试期才用得上的驱动做成模块,量产后按需关闭。把存储驱动做成模块而文件系统内置(或反之)是「VFS: Unable to mount root fs」的高频成因。

第 4 章 内核配置与裁剪

配置裁剪是 BSP 工程师的核心工作:把几千个 CONFIG 收敛成一份可维护、可追溯、体积与启动时间都达标的基线。

4.1 .config 与 Kconfig 原理

内核配置由 Kconfig(各级目录下的配置描述)生成菜单,选择结果落在源码树根目录的 .config(CONFIG_XXX=y/m/n)。常见命令:

make <soc>_defconfig        # 载入芯片/板级默认配置(起点)
make menuconfig             # 图形化调整(需要 ncurses)
make oldconfig              # 沿用旧 .config,只提示新增项(升级内核时用)
make olddefconfig           # 同上,新增项取默认值,不交互(CI 里用这个)
make savedefconfig          # 生成精简 defconfig(只保留非默认项)→ 入仓
make nconfig / xconfig      # 其它交互前端
不要直接把 .config 提交进仓库

.config 包含上千项(绝大多数是默认值),提交它会导致:① 评审时看不出真正改了什么;② 升级内核时冲突一大片;③ 别人 make oldconfig 后产生隐性差异。正确做法:只提交 make savedefconfig 产出的 defconfig(通常几十行),并在 arch/<arch>/configs/ 或项目 build/ 目录下管理。

4.2 配置基线管理规范

  1. 选起点:以最接近目标硬件的 *_defconfig 为起点,不要从零开始。
  2. 调配置:用 menuconfig 调整,每改一批就记录「改了什么、为什么」。
  3. 存基线:make savedefconfig 产出 defconfig,与源码同仓提交。
  4. 编译验证:make -j$(nproc) 全量编译,确认无警告转错误。
  5. 度量收益:记录镜像体积、启动时间、内核内存占用三项指标。
  6. 写 changelog:配置变更与代码变更一样要有评审与记录(见 14.2)。
# 推荐:把 defconfig 与构建脚本一起放进项目的 build/ 目录
build/
├── env.sh                  # ARCH / CROSS_COMPILE / PATH
├── myboard_defconfig       # 配置基线(唯一来源)
├── build_kernel.sh         # 一键编译:载入 defconfig → 编译 → 拷贝产物
└── patches/                # 内核补丁集

# build_kernel.sh 关键片段
#!/bin/sh
set -e
source "$(dirname "$0")/env.sh"
cp "$1" .config || cp build/myboard_defconfig .config
make olddefconfig
make -j"$(nproc)" zImage dtbs modules
make modules_install INSTALL_MOD_PATH=../rootfs

4.3 裁剪原则与收益

裁剪的三条原则:没有对应硬件的驱动全删;量产版本关闭调试组件; 按需保留文件系统与协议栈。收益体现在体积、启动时间、内存占用与攻击面四个方面。

配置基线的闭环:改配置 → 存基线 → 编译 → 度量 → 回归选基线芯片 defconfig调配置menuconfig存基线savedefconfig 入仓编译验证zImage / dtb / ko度量收益体积 / 启动 / 内存任一环节不满足目标 → 回到「调配置」重来,并把结论记入 changelog裁剪优先级(按「收益 ÷ 风险」排序,从上往下做)第一优先 · 收益高· 用不到的设备驱动· 多余的文件系统· 不用的网络协议栈体积下降最明显第二优先 · 收益中· 调试组件与 tracing· 内核符号表 / kgdb· 多余的字符设备量产前必关第三优先 · 收益低· 调度 / 内存参数微调· initcall 层级调整影响启动时间与内存
图 7 · 内核配置基线闭环与裁剪优先级
裁剪对象典型 CONFIG收益风险提示
无用驱动CONFIG_DRM、CONFIG_SND 等未使用子系统体积下降最明显删错会导致外设不可用,逐项验证
文件系统CONFIG_ISO9660、CONFIG_NFS_FS 等体积 + 启动时间根文件系统类型必须保留且内置
网络协议栈CONFIG_IPV6、无关隧道与过滤模块体积 + 攻击面确认业务是否用到
调试组件CONFIG_DEBUG_KERNEL、CONFIG_KGDB、CONFIG_FTRACE体积 + 性能开发期保留,量产前关闭
模块符号表CONFIG_MODULES 相关体积关掉后无法加载任何 ko
裁剪必须配合回归测试

裁剪后必须跑一遍第 13 章的基础功能用例。「编过了」不等于「能用」——很多配置项编译期无依赖,运行期才发现某个驱动被间接依赖(例如 UBI 依赖 MTD、MTD 依赖特定的 NAND ECC 引擎)。裁剪前后各跑一次 lsmod 与 /sys 节点对比最稳妥。

4.4 内核编译产出

产出路径用途
内核镜像arch/<arch>/boot/zImage 或 Image被 U-Boot 加载运行
uImage由 mkimage 在 zImage 上加 64 字节头老版本 U-Boot 需要
设备树arch/<arch>/boot/dts/*.dtb描述硬件,与内核配对加载
内核模块*.ko,安装到 /lib/modules/<ver>/可动态加载的驱动
符号表System.map、vmlinux定位 Oops 地址(第 8.3 节必备)
一定要保留 vmlinux 与 System.map

发布版本把 vmlinux、System.map、.config 与源码 commit id 一起归档。没有它们,后面出现 Oops 时无法用 addr2line 还原调用栈,只能靠猜。归档成本几乎为零,缺了它排查成本极高。

4.5 镜像烧录与启动介质

烧录方式取决于介质与阶段:开发期用 TFTP/NFS 避免擦写; 小批量用 U-Boot 命令或 USB 烧写工具;量产用编程器或产线烧录工装。

编译产物落到哪里:分区表是产物与介质之间的契约编译产物zImage / uImage压缩内核镜像*.dtb设备树二进制rootfs 镜像rootfs.squashfs / ubi / ext4*.ko 模块已编入 rootfs 的 /lib/modules典型分区表(示例偏移,按器件容量调整)U-Boot0 ~ 1 MBenv(建议冗余双份)2 × 擦除块DTB几十 KBKernel3 ~ 8 MBrootfs视文件系统而定data / OTA剩余空间分区必须与 U-Boot、内核 MTD 分区、OTA 三方一致启动介质差异SPI NOR容量小 · XIP 可行常用 squashfs + jffs2只读 rootfs + 小数据区SPI NAND需坏块与 ECC 管理常用 UBI/UBIFS量产成本低,注意坏块表eMMC / SD有 FTL,块设备接口常用 ext4 / f2fs容量大,适合 A/B 分区 OTA
图 8 · 内核 / DTB / rootfs 产物与分区表、启动介质的对应关系
# U-Boot 下烧录到 SPI NOR(示例,偏移按分区表调整)
sf probe 0
sf erase 0x100000 0x400000
tftpboot 0x80800000 zImage
sf write 0x80800000 0x100000 ${filesize}

# U-Boot 下烧录到 SPI NAND(需先确认坏块与 ECC 布局)
mtd list
nand erase.spread 0x200000 0x600000
nand write 0x80800000 0x200000 ${filesize}

# eMMC:通常在能启动的系统中用 dd 或专用升级工具写入分区
dd if=rootfs.ext4 of=/dev/mmcblk0p3 bs=4M conv=fsync
烧录前必须核对三件事

① 分区偏移与大小与 U-Boot、内核 MTD 分区表一致;② SPI NAND 必须使用 skip-bad-block 的写入方式(nand write.trimffs / erase.spread),否则坏块会推移后续数据;③ 烧录后做一次回读校验(CRC 或 sha256),不要烧完就断电。写错偏移覆盖掉 U-Boot 会导致板子变砖,只能重新用编程器救。

附录 B .config 参考配置片段

以下为常见场景的配置片段示例。写入 defconfig 时注意:defconfig 只保留非默认项,片段中的每一条都要确认与当前内核版本的选项名一致。

B.1 存储相关(裸闪存场景)

CONFIG_MTD=y
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_RAW_NAND=y            # 并行 NAND,SPI NAND 不需要这一项
CONFIG_MTD_SPI_NAND=y            # SPI NAND(走 spi-nand 框架)
CONFIG_MTD_UBI=y                 # UBI 卷管理
CONFIG_UBIFS_FS=y                # UBIFS(根文件系统必须内置)
CONFIG_MTD_BLOCK=y               # 需要 mtdblock 设备时

B.2 存储相关(块设备场景)

CONFIG_MMC=y
CONFIG_MMC_BLOCK=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_OF_ARASAN=y     # 按实际控制器选择
CONFIG_EXT4_FS=y                 # 或 CONFIG_F2FS_FS=y
CONFIG_SQUASHFS=y
CONFIG_OVERLAY_FS=y              # 只读底 + 可写上层的组合需要

B.3 调试相关(开发版开启,量产关闭)

CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y              # addr2line 需要
CONFIG_FRAME_POINTER=y           # 栈回溯更准确
CONFIG_DEBUG_FS=y                # debugfs(动态调试依赖)
CONFIG_DYNAMIC_DEBUG=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_PERF_EVENTS=y
CONFIG_KGDB=y                    # 视调试需求
CONFIG_LOCKDEP=y                 # 锁问题排查
CONFIG_DEBUG_KMEMLEAK=y          # 内存泄漏排查
CONFIG_MAGIC_SYSRQ=y             # 卡死时打印栈,强烈建议保留

B.4 量产版建议关闭 / 收紧

# CONFIG_DEBUG_KERNEL is not set
# CONFIG_KGDB is not set
# CONFIG_DEBUG_FS is not set        # 减小攻击面
# CONFIG_MODULES is not set         # 若无需现场加载模块
CONFIG_DEBUG_RODATA=y               # 只读数据不可写(按架构名可能不同)
CONFIG_STACKPROTECTOR=y
CONFIG_RANDOMIZE_BASE=y
配置片段必须逐项验证

不同内核版本的选项名可能变化(例如部分安全选项名在不同架构下不同)。写入 defconfig 后一定要 make olddefconfig 再编译,并确认没有选项被静默丢弃。

附录 C DTS 代码样例

三份可直接改用的最小样例:SPI 存储器件、固定分区、GPIO 与中断。

C.1 SPI 存储器件 + 固定分区

&spi0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&spi0_pins>;
    #address-cells = <1>;
    #size-cells = <0>;

    flash@0 {
        compatible = "vendor,part", "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <50000000>;
        spi-rx-bus-width = <4>;
        #address-cells = <1>;
        #size-cells = <1>;

        partitions {
            compatible = "fixed-partitions";
            #address-cells = <1>;
            #size-cells = <1>;
            partition@0      { label = "uboot";  reg = <0x000000 0x100000>; read-only; };
            partition@100000 { label = "env";    reg = <0x100000 0x020000>; };
            partition@120000 { label = "kernel"; reg = <0x120000 0x500000>; };
            partition@620000 { label = "rootfs"; reg = <0x620000 0x900000>; };
        };
    };
};

C.2 保留内存(pstore / ramoops)

/ {
    reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        ramoops@80000000 {
            compatible = "ramoops";
            reg = <0x80000000 0x100000>;
            record-size  = <0x20000>;
            console-size = <0x20000>;
            ftrace-size  = <0x20000>;
        };
    };
};

C.3 带中断的 I2C 外设

&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    sensor@48 {
        compatible = "vendor,part";
        reg = <0x48>;
        interrupt-parent = <&gpio1>;
        interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
        vdd-supply = <&reg_3v3>;          /* 电源域/供电引用 */
        wakeup-source;                    /* 允许作为唤醒源 */
    };
};

附录 D 常用调试命令脚本

四个可直接改用的脚本骨架:一键编译、启动耗时量测、存储压力与掉电测试、回归用例。

D.1 一键编译脚本

#!/bin/sh
# build/build_kernel.sh —— 用法:./build_kernel.sh [defconfig]
set -e
cd "$(dirname "$0")/.."
source build/env.sh

CFG=${1:-build/myboard_defconfig}
cp "$CFG" .config
make olddefconfig
make -j"$(nproc)" zImage dtbs modules
make modules_install INSTALL_MOD_PATH=../rootfs

mkdir -p out
cp arch/"$ARCH"/boot/zImage out/
cp arch/"$ARCH"/boot/dts/*.dtb out/
cp .config System.map vmlinux out/          # 定位 Oops 必需
sha256sum out/* > out/manifest.sha256
echo "build ok: $(git rev-parse --short HEAD)"

D.2 启动耗时量测

#!/bin/sh
# 串口时间戳量测(需 grabserial)
grabserial -d /dev/ttyUSB0 -b 115200 -e 60 -m 'login:' -t | tee boot_$(date +%s).log

# 内核侧 initcall 耗时(bootargs 追加 initcall_debug printk.time=1)
dmesg > boot_dmesg.log
scripts/bootgraph.pl boot_dmesg.log > boot.svg

D.3 存储压力与掉电测试

#!/bin/sh
# stress_storage.sh <挂载点> <轮次>
MNT=$1; N=${2:-100}
i=0
while [ $i -lt $N ]; do
    f="$MNT/dd_$i.bin"
    dd if=/dev/urandom of="$f" bs=1M count=8 conv=fsync 2>/dev/null
    sync
    a=$(sha256sum "$f" | cut -d' ' -f1)
    sync
    b=$(sha256sum "$f" | cut -d' ' -f1)
    [ "$a" = "$b" ] || echo "MISMATCH at round $i" | tee -a err.log
    rm -f "$f"
    i=$((i+1))
done
echo "done $N rounds, errors: $(wc -l < err.log 2>/dev/null || echo 0)"
# 掉电测试:配合继电器工装,在随机时刻断电后重新上电,校验 err.log 与文件系统可挂载

D.4 回归用例骨架

#!/bin/sh
# tests/run_all.sh —— 输出 PASS/FAIL 汇总,供 CI 使用
pass=0; fail=0
run() {  # run <用例名> <命令>
    if eval "$2" >/dev/null 2>&1; then echo "PASS  $1"; pass=$((pass+1));
    else echo "FAIL  $1"; fail=$((fail+1)); fi
}

run "boot_ok"        "dmesg | grep -q 'VFS: Mounted root'"
run "storage_mtd"    "cat /proc/mtd | grep -q rootfs"
run "net_ping"       "ping -c1 -W2 192.168.1.1"
run "fs_rw"          "touch /data/.t && rm /data/.t"
run "no_oom"         "! dmesg | grep -q 'Out of memory'"

echo "---- summary: PASS=$pass FAIL=$fail ----"
[ "$fail" -eq 0 ]

附录 E 参考资料与内核文档

类别资料说明
内核自带Documentation/ 目录各子系统的权威说明,优先于任何二手资料
内核自带Documentation/devicetree/设备树 binding 与写法规范
内核自带Documentation/process/编码规范、提交规范、补丁流程
内核自带scripts/checkpatch.pl补丁检查工具
官方站点kernel.org(源码与 LTS 版本状态)版本基线与 CVE 信息以官方为准
厂商资料芯片 TRM / SDK Release Note平台相关寄存器与驱动支持范围
器件资料器件 datasheet一切电气与时序参数以正式 datasheet 为准
工具文档Buildroot / Yocto 官方手册构建系统配置细节
工具文档fio / perf / trace-cmd 手册性能与调试工具的完整参数
查资料的顺序

① 内核源码树内的 Documentation/ 与源码注释(最权威、最贴合当前版本);② 芯片厂商 TRM 与 SDK 文档;③ 器件 datasheet;④ 社区邮件列表与 LWN。博客与问答仅用于「找线索」,不要直接照抄其中的命令与参数。

附录 F 内核发布前检查清单

发布评审逐条过,任一条不满足都要有书面说明才能放行。建议把这份清单接入评审流程并存档。

F.1 配置与编译

  • defconfig 已入仓,源码树内不提交 .config。
  • 全量编译无 error;关键 warning 已确认或修复。
  • 内核版本、工具链版本、U-Boot 版本与 1.2 基线表一致。
  • 裁剪后镜像体积、启动时间、内存占用三项指标已记录并与基线对比。
  • 根文件系统所依赖的存储与文件系统驱动为内置(非模块)。

F.2 设备树与启动

  • DTS 编译无 warning;dtc -I dts -O dtb 单独验证通过。
  • 板级 /proc/device-tree 节点与预期一致。
  • 所有外设的 compatible 与驱动 of_match_table 已逐项核对。
  • 时钟、电源域、GPIO 复用已确认(尤其新改动的节点)。
  • bootargs 的 root / rootfstype / rootwait 已在实际介质上验证。

F.3 驱动与文件系统

  • 所有改动过 checkpatch.pl,ERROR 清零。
  • 驱动 probe / remove 配对,资源用 devm_* 或手工释放完整。
  • suspend / resume(含 Runtime PM)已验证,唤醒后外设功能正常。
  • 文件系统参数(UBIFS / ubinize)与 mtdinfo 实测值一致。
  • 存储读写循环测试通过,无数据不一致。

F.4 调试配置与量产配置

  • 量产版本已关闭 kgdb / ftrace / debugfs 等调试组件(若策略如此)。
  • 保留 CONFIG_MAGIC_SYSRQ 等现场救命项。
  • 看门狗已启用,喂狗周期覆盖最长阻塞操作。
  • pstore / 日志持久化已验证:死机后重启能取到现场。
  • panic 自动重启策略(kernel.panic)已设置。

F.5 OTA 与可靠性

  • A/B 分区(或等效回滚机制)已验证:升级中断电可自愈。
  • 升级包 sha256 与签名校验已验证;写入后回读比对通过。
  • 掉电测试已跑够轮次(数百次),数据完整性通过。
  • 回滚后启动标记清理正确,不会出现双槽反复横跳。
  • 回滚动作不擦除共享 data 区。

F.6 交付与文档

  • 源码 tag / commit id 已记录,构建可复现。
  • vmlinux、System.map、.config 已归档。
  • manifest(版本号 + sha256)随镜像一起交付。
  • changelog 与 Known Issues 已更新,每条 Known Issue 有规避方案或修复计划。
  • 第 13 章测试报告(含性能基准对比)已产出并归档。
  • 烧录与升级说明文档已交付给产线与售后。
清单的使用方式

把这份清单做成评审模板:每项三个状态 —— 通过 / 不适用(需注明原因)/ 带风险放行。带风险放行的项要写进 Known Issues 并指定责任人。清单本身也要随项目迭代,每次事故复盘后回头补一项。

扫码打开本页
微信扫码 · 展会可扫

再分享给同事

同事可直接打开这份资料《Linux内核开发指南(BSP Kernel)》,在线阅读;完整章节请下载芯参谋。