Linux 内核开发指南
编制日期 2026-10-14
BSP 总文档 · 覆盖环境搭建 / 配置裁剪 / 设备树 / 驱动开发 / 调试调优 / 测试发布全流程 · 驱动专项文档为子文档挂在第 6 章下
这份文档解决的是「一个 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 电路设计指南 |
| 市场 / 选型报告 | 容量档位、应用场景、厂商格局 | 存储芯片应用市场分析 |
第 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 等 | 以项目选型为准,不写死本文档 |
| Bootloader | U-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 术语、缩写与参考文档
| 缩写 | 全称 / 含义 | 备注 |
|---|---|---|
| BSP | Board Support Package,板级支持包 | 本文档主题 |
| DTS / DTB / DTC | 设备树源码 / 二进制 / 编译器 | 第 3.3、5 章 |
| DTBO | Device Tree Overlay 二进制 | 第 5.4 节 |
| MTD | Memory Technology Device,裸闪存子系统 | 第 6.6 节 |
| UBI / UBIFS | 闪存卷管理层 / 闪存文件系统 | 第 7 章 |
| rootfs | 根文件系统 | 第 7 章 |
| bootargs | U-Boot 传给内核的启动参数 | 第 7.3 节 |
| Oops / panic | 内核异常 / 致命错误 | 第 8.3 节 |
| ko | 可动态加载的内核模块 | 第 3.5、6.5 节 |
| LTS | Long Term Support,长期支持版本 | 第 1.2 节 |
参考文档(详见附录 E):内核源码树内 Documentation/、
README、各子系统 *-api.rst;芯片厂商 SDK Release Note 与 TRM;
器件 datasheet(以正式版本为准)。
1.4 内核源码来源与分支策略
内核源码只有两种来源,必须先定性再开发——它决定了后续所有补丁与升级方式。
kernel.org / 镜像站拉取官方 LTS 分支。优点:可长期维护、社区补丁可直接合入、安全修复及时。代价:芯片平台支持可能不完整,需自行补齐驱动与 DTS。分支管理策略(建议)
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 权限的动作前加 sudo。以 root 编译会导致源码树文件属主混乱,后期 git clean、补丁生成都会踩坑;内核构建本身也不推荐用 sudo make。
2.2 交叉编译工具链选型
工具链必须与内核版本和目标架构同时匹配,尤其注意浮点 ABI
(ARM32 的 gnueabihf 与 gnueabi 不兼容)。
| 目标架构 | 典型前缀 | 说明 |
|---|---|---|
| ARM32 | arm-linux-gnueabihf- | 带硬浮点,i.MX6ULL / STM32MP1 等常用 |
| ARM64 | aarch64-linux-gnu- | RK3568 及多数 64 位平台 |
| RISC-V | riscv64-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
三者不是互斥关系,而是不同规模产品的不同选择。
| 维度 | Buildroot | Yocto / 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 / ctags | make cscope 生成索引,查函数调用极快 |
| 版本管理 | git + gitk / tig | 分支、补丁、bisect 定位引入问题的提交 |
| 静态检查 | make checkstack / sparse / coccinelle | 编译期发现隐患 |
| 动态调试 | trace-cmd / kernelshark / perf | 第 8.5 节 |
| 内核调试 | gdb-multiarch + KGDB | 第 8.4 节 |
| 串口终端 | picocom / minicom | picocom -b 115200 /dev/ttyUSB0 |
| 压力测试 | stress-ng / fio / iperf3 | 第 13 章 |
第 3 章 内核基础原理
这一章是给新人的理论底座:搞清楚内核怎么启动、设备树起什么作用、子系统怎么分层、模块怎么加载。理解了这些,后面调驱动才知道自己在改哪一层。
3.1 内核空间与用户空间、系统调用
CPU 以特权级区分两种运行态:用户空间跑应用程序,各自拥有独立虚拟地址空间, 一旦越界访问或执行特权指令会被 MMU / CPU 拦截;内核空间所有进程共享同一份地址空间, 驱动代码就运行在这里,一处出错会影响整机。
用户态进入内核态的合法途径只有三类:系统调用(open/read/write/ioctl 等)、
异常(缺页、未定义指令)、中断(外设触发)。
驱动开发者打交道最多的是 ioctl 与 read/write 在内核侧的
file_operations 实现。
内核态没有内存保护:一个野指针写坏的是整个内核,现象往往是随机的死机而不是立刻崩溃。因此第 6.7 节的资源自动释放(devm_*)与第 8.6 节的内存排查不是可选项,而是必备动作。
3.2 内核启动全流程
启动链路是排障的总地图。拿到一块不启动的板子,第一件事永远是看串口停在哪一步, 而不是猜。下图给出七阶段与每一步的日志特征。
| 阶段 | 关键动作 | 常见失败点 |
|---|---|---|
| ROM / SPL | 片内 ROM 加载 SPL 到 SRAM | 启动引脚电平、Boot 介质焊接、SPL 镜像不对 |
| U-Boot SPL | 初始化 DDR / 时钟 / 串口 | DDR 参数不匹配 → 卡死无输出 |
| U-Boot 主体 | 加载 zImage + dtb,装配 bootargs | bootargs 写错、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()。
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 |
| 电源管理 PM | suspend/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 包含上千项(绝大多数是默认值),提交它会导致:① 评审时看不出真正改了什么;② 升级内核时冲突一大片;③ 别人 make oldconfig 后产生隐性差异。正确做法:只提交 make savedefconfig 产出的 defconfig(通常几十行),并在 arch/<arch>/configs/ 或项目 build/ 目录下管理。
4.2 配置基线管理规范
- 选起点:以最接近目标硬件的
*_defconfig为起点,不要从零开始。 - 调配置:用
menuconfig调整,每改一批就记录「改了什么、为什么」。 - 存基线:
make savedefconfig产出 defconfig,与源码同仓提交。 - 编译验证:
make -j$(nproc)全量编译,确认无警告转错误。 - 度量收益:记录镜像体积、启动时间、内核内存占用三项指标。
- 写 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 裁剪原则与收益
裁剪的三条原则:没有对应硬件的驱动全删;量产版本关闭调试组件; 按需保留文件系统与协议栈。收益体现在体积、启动时间、内存占用与攻击面四个方面。
| 裁剪对象 | 典型 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、.config 与源码 commit id 一起归档。没有它们,后面出现 Oops 时无法用 addr2line 还原调用栈,只能靠猜。归档成本几乎为零,缺了它排查成本极高。
4.5 镜像烧录与启动介质
烧录方式取决于介质与阶段:开发期用 TFTP/NFS 避免擦写; 小批量用 U-Boot 命令或 USB 烧写工具;量产用编程器或产线烧录工装。
# 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 会导致板子变砖,只能重新用编程器救。