
Linux 内核裁剪与移植作者Leilei Sun日期2026-07-23适用读者嵌入式 Linux 开发者、BSP 工程师、驱动工程师目录为什么要做内核裁剪与移植内核裁剪与移植的原理依据内核裁剪的具体步骤内核移植的具体步骤验证方法最佳实践与常见陷阱1. 为什么要做内核裁剪与移植1.1 资源约束 —— 嵌入式设备的内存和存储非常有限Linux 主线内核的全量编译产物allyesconfigvmlinux 镜像超过500MB即使defconfig也通常有8~15MB。而嵌入式设备的 Flash 和 RAM 都非常紧张注嵌入式设备中 Flash 和 RAM 的大小关系取决于 Flash 类型。NOR Flash支持 XIP 片上执行价格昂贵、容量小通常 ≤ 256MB通常 RAM FlashNAND / eMMC块读写价格便宜、容量大GB 级通常 Flash RAM类似 PC 的硬盘/内存关系┌────────────────────────────────────────────────────────────────┐ │ 嵌入式设备典型资源 —— NOR Flash 场景 │ ├──────────────┬────────────┬──────────┬──────────┬─────────────┤ │ 设备类型 │ Flash (NOR)│ RAM │ 允许内核 │ Flash vs RAM │ ├──────────────┼────────────┼──────────┼──────────┼─────────────┤ │ IoT 传感器 │ 8MB │ 32MB │ 2MB │ Flash RAM │ │ 家用路由器 │ 16MB │ 64MB │ 3MB │ Flash RAM │ │ 工业控制板 │ 64MB │ 256MB │ 5MB │ Flash RAM │ │ 高端交换机 │ 256MB │ 2GB │ 8MB │ Flash RAM │ └──────────────┴────────────┴──────────┴──────────┴─────────────┘ ┌────────────────────────────────────────────────────────────────┐ │ 嵌入式设备典型资源 —— eMMC / NAND Flash 场景 │ ├──────────────┬────────────────┬──────────┬──────────┬─────────┤ │ 设备类型 │ Flash (eMMC) │ RAM │ 允许内核 │ 关系 │ ├──────────────┼────────────────┼──────────┼──────────┼─────────┤ │ 智能手机 │ 128GB │ 12GB │ 20MB │ Flash RAM │ │ 车载信息娱乐 │ 32GB │ 4GB │ 12MB │ Flash RAM │ │ ARM 开发板 │ 8GB (SD 卡) │ 512MB │ 8MB │ Flash RAM │ │ x86 PC (对比) │ 256GB │ 16GB │ 15MB │ Flash RAM │ └──────────────┴────────────────┴──────────┴──────────┴─────────┘无论哪种场景留给内核的存储空间都极其有限最后一列必须裁剪。必须裁剪否则内核放不进 Flash或者占满了存储导致 rootfs 没空间。1.2 按需取舍 —— 不需要的功能不该编译进去一块 ARM Cortex-A7 的工业控制板上有: ✅ UART (调试串口) ✅ Ethernet (工业以太网) ✅ SPI NOR Flash (存储) ✅ GPIO (控制继电器) ❌ SATA (没有硬盘) ❌ USB Host (不需要) ❌ WiFi (有线就够了) ❌ Sound Card (没声卡) ❌ GPU (没显示器) ❌ Bluetooth (不需要)不裁剪的后果浪费 Flash 空间、增加启动时间、引入不必要的安全漏洞面、增加内存占用某些子系统启动时会申请内存。1.3 启动速度 —— 内核越小启动越快内核镜像 2MB: 解压 启动 ≈ 0.5 秒 内核镜像 8MB: 解压 启动 ≈ 2.0 秒 内核镜像 15MB: 解压 启动 ≈ 4.0 秒对于要求上电 2 秒内进入工作状态的工业设备必须裁剪。1.4 安全性 —— 减少攻击面每个编译进内核的子系统都是一条潜在的攻击路径。裁剪掉不需要的网络协议、文件系统、驱动模块就消除了这些模块可能存在的漏洞。1.5 移植的必要性 —— 换一块 SoC所有东西都变了旧板子: NXP i.MX6ULL (ARM Cortex-A7) 新板子: Allwinner T113 (ARM Cortex-A7 双核 RISC-V) 虽然都是 ARM但: - 内存映射完全不同 (DDR 起始地址变了) - 中断控制器不同 (GICv2 → GICv2 但寄存器布局不同) - 时钟树完全不同 (CCM 寄存器全变了) - 外设 IP 不同 (UART 驱动能复用, 但 base address 变了) - 启动流程不同 (BootROM → SPL → U-Boot → kernel)移植 让同一套 Linux 内核在新的硬件平台上跑起来。2. 内核裁剪与移植的原理依据2.1 Kconfig / Kbuild 体系 —— 裁剪的核心机制Linux 内核的编译系统是一个三层结构┌──────────────────────────────────────────────────────┐ │ 用户执行: make menuconfig │ │ ↓ │ │ Kconfig 文件 (分散在 ~1800 个目录中) │ │ 定义每个选项的: 名称、类型、依赖、帮助文本 │ │ ↓ │ │ .config 文件 (最终输出) │ │ CONFIG_NETy │ │ CONFIG_USBm │ │ # CONFIG_SOUND is not set │ │ ↓ │ │ Makefile (顶层 各子目录) │ │ obj-$(CONFIG_NET) net/ │ │ obj-$(CONFIG_USB) usb/ │ │ ↓ │ │ 编译产物: vmlinux / zImage / modules │ └──────────────────────────────────────────────────────┘例如drivers/usb/Makefileobj-$(CONFIG_USB) usb.o obj-$(CONFIG_USB_EHCI_HCD) host/ehci-hcd.o当.config中CONFIG_USBy变量展开为obj-yusb.o 编入内核。当.config中# CONFIG_USB is not set变量展开为obj-n不编译。2.2 设备树Device Tree—— 移植的核心机制设备树是将硬件描述从内核代码中分离出来的机制┌──────────── 没有设备树的时代 (ARM Linux 3.x) ────────────┐ │ arch/arm/mach-imx/board-mx6q-sabresd.c │ │ → 硬编码: UART 基址 0x021E8000, 寄存器布局, 引脚配置 │ │ → 每块板子一个 C 文件内核里塞了几百个板级文件 │ │ → 换块板子 改 C 代码 重新编译内核 │ └────────────────────────────────────────────────────────────┘ ┌──────────── 设备树时代 (ARM Linux ≥ 3.x) ──────────────────┐ │ arch/arm/boot/dts/imx6q-sabresd.dts │ │ → 描述硬件: UART 基址 0x021E8000, 引脚 pinctrl, 时钟 │ │ → 内核只包含驱动代码不包含硬件数据 │ │ → 换块板子 换个 .dtb 文件 内核代码不用改! │ └────────────────────────────────────────────────────────────┘设备树源码.dts的结构/dts-v1/; / { compatible fsl,imx6q-sabresd, fsl,imx6q; #address-cells 1; #size-cells 1; memory10000000 { device_type memory; reg 0x10000000 0x40000000; /* 1GB DDR3 */ }; soc { uart1: serial021e8000 { compatible fsl,imx6q-uart, fsl,imx21-uart; reg 0x021e8000 0x4000; /* 寄存器基址 大小 */ interrupts 0 27 IRQ_TYPE_LEVEL_HIGH; clocks clks 160, clks 161; status okay; }; }; };内核启动时bootloader 把.dtb传给内核内核动态解析设备树并初始化对应的硬件。2.3 内核模块化机制 —— y / m / n 三元选择┌──────────────────────────────────────────────────────┐ │ CONFIG_XXXy → 编入内核镜像 (built-in) │ │ 占用 Flash, 不可卸载, 启动即加载 │ │ 适用: 串口驱动, 根文件系统驱动 │ ├──────────────────────────────────────────────────────┤ │ CONFIG_XXXm → 编译为独立模块 (.ko) │ │ 在 rootfs 中存储, 按需 insmod 加载 │ │ 适用: 非必需驱动, 调试用 │ ├──────────────────────────────────────────────────────┤ │ # CONFIG_XXX is not set │ │ → 完全不编译 │ │ 适用: 这个板子没有的硬件 │ └──────────────────────────────────────────────────────┘裁剪策略能裁掉就裁掉n必须开机就用的选 y偶尔用的选 m。2.4 架构抽象层arch/—— 跨 CPU 的隔离linux/ ├── arch/ │ ├── arm/ ← ARM 32bit (Cortex-A7, A9, etc.) │ │ ├── boot/dts/ ← 板级设备树 │ │ ├── mach-*/ ← 历史遗留的板级代码 (逐步被 DTS 替代) │ │ ├── kernel/ ← ARM 特有的异常处理、上下文切换 │ │ └── Kconfig ← ARM 架构特有的配置选项 │ ├── arm64/ ← ARM 64bit (Cortex-A53, A72, etc.) │ ├── riscv/ ← RISC-V │ ├── x86/ │ └── ... ├── drivers/ ← 驱动代码 (跨架构复用) ├── kernel/ ← 核心调度、信号、定时器等 └── include/ ← 头文件移植时主要改动在arch//boot/dts/和drivers/中新增驱动。常见疑问arch/arm64/、arch/riscv/、arch/x86/等其他架构目录可以删除吗编译层面不需要。设置ARCHarm后Kbuild 只编译arch/arm/下的代码其他架构目录完全不会被编译进内核镜像。内核镜像的大小取决于编译了什么而不是源码树里有多少文件。源码层面技术上可以删不会影响ARCHarm的编译但不推荐——会破坏git status、丢失未来移植到其他架构的能力且节省的磁盘空间有限arch/ 目录在完整源码中占比不大。3. 内核裁剪的具体步骤3.1 获取内核源码# 方式一: kernel.org 主线wgethttps://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xztarxf linux-6.6.tar.xzcdlinux-6.6# 方式二: SoC 厂商 SDK (推荐, 厂商已打好基础补丁)# 例如 NXP: git clone https://github.com/nxp-imx/linux-imx.git# 方式三: 你的 Ubuntu 虚拟机上的当前内核源码aptsourcelinux-image-$(uname-r)3.2 选择基准配置文件不需要自己写脚本。内核源码树的arch/arch/configs/目录下已经自带了大量参考配置文件# 看看 ARM 架构有哪些现成的配置文件lsarch/arm/configs/# 输出示例:# multi_v7_defconfig ← ARMv7 多平台通用配置# imx_v6_v7_defconfig ← NXP i.MX6/7 专用# sunxi_defconfig ← Allwinner 专用# exynos_defconfig ← Samsung Exynos 专用# ...几十个...操作步骤一步到位不需要脚本# 1. 设置环境变量 (本机编译可省略 ARCH 和 CROSS_COMPILE)exportARCHarmexportCROSS_COMPILEarm-linux-gnueabihf-# 2. 选择合适的 defconfig 生成 .configmakemulti_v7_defconfig# 方式一: 架构通用配置# 或makeimx_v6_v7_defconfig# 方式二: SoC 厂商提供的配置# 或 (在 x86 虚拟机上做裁剪实验)cp/boot/config-$(uname-r).config# 方式三: 复制当前运行内核的配置makeolddefconfig# 把旧配置适配到当前内核版本可选创建一个build.sh避免每次手输环境变量。这不是必须的但可以方便反复编译#!/bin/bashexportARCHarmexportCROSS_COMPILEarm-linux-gnueabihf-make-j$(nproc)$之后用./build.sh zImage替代make zImage。3.3make menuconfig逐项裁剪# 需要安装 ncursessudoaptinstalllibncurses5-dev flex bison# 启动图形化配置界面makemenuconfig┌────────────────── Linux/arm 6.6.0 Kernel Configuration ──────────────────┐ │ Arrow keys navigate the menu. Enter selects submenus --- │ │ Press Y to include, M to module, N to exclude. │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ General setup --- │ │ │ │ [*] Enable loadable module support --- │ │ │ │ -*- Enable the block layer --- │ │ │ │ Processor type and features --- │ │ │ │ Power management options --- │ │ │ │ Bus support --- │ │ │ │ Executable file formats --- │ │ │ │ Memory Management options --- │ │ │ │ [*] Networking support --- │ │ │ │ Device Drivers --- │ │ │ │ File systems --- │ │ │ │ Security options --- │ │ │ │ -*- Cryptographic API --- │ │ │ │ Library routines --- │ │ │ │ Kernel hacking --- │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────┘3.4 裁剪策略 —— 从大到小五轮进行第 1 轮 — 子系统级 (收益最大, 一轮可裁掉 30%~50%) ├── General Setup: 关闭 cgroups (如果不用容器)、关闭 namespace ├── Networking: 关闭 Hamradio / NFC / CAN / Wireless / Bluetooth ├── File Systems: 关闭 NFS / CIFS / XFS / Btrfs / FUSE (只留 ext4 和 tmpfs) ├── Device Drivers: 关闭 SATA / SCSI / USB / Sound / GPU / Infiniband └── Security: 关闭 SELinux / AppArmor / IMA 第 2 轮 — 驱动级 ├── Network device support: 只保留目标板子的网卡驱动 ├── Input device support: 关闭触摸屏、游戏手柄 (嵌入式通常没有) ├── MTD (Memory Technology Device): 只保留目标 Flash 类型 └── HID: 关闭 USB HID (如果没有 USB) 第 3 轮 — 特性级 ├── Kernel hacking: 关闭 KGDB / KDB / debug info ├── Profiling: 关闭 OProfile / perf events (不对客户开放) └── Printk: 只保留 KERN_ERR 以上级别 (CONFIG_CONSOLE_LOGLEVEL) 第 4 轮 — 模块化 ├── 不常用的驱动: m → 编为 .ko, insmod 时加载 └── 根文件系统驱动: 保持 y (否则无法挂载 rootfs) 第 5 轮 — 验证 ├── 编译 → 烧录 → 启动 → 看日志 → 看镜像大小 └── 如果裁剪过度: 加回来, 重复3.5 编译不需要自己写脚本。内核自带的顶层 Makefile 就是完整的构建系统。裁剪完成后几条make命令即可# 编译内核镜像 # 前提: 已完成 ARCH 和 CROSS_COMPILE 的设置 (同 3.2 节)# 在 x86 虚拟机本机编译时不需要设置这两个变量make-j$(nproc)# 编译所有 (vmlinux modules dtbs)# 或分步编译:make-j$(nproc)zImage# ARM 32bit: 生成 arch/arm/boot/zImagemake-j$(nproc)Image.gz# ARM 64bit: 生成 arch/arm64/boot/Image.gz# 编译内核模块 make-j$(nproc)modules# 编译所有 m 的内核模块 (.ko 文件)# 安装模块到 rootfs makemodules_installINSTALL_MOD_PATH/path/to/rootfs# 模块会被安装到 /path/to/rootfs/lib/modules/kernel-version/# 编译设备树 makedtbs# 编译所有设备树 (.dts → .dtb)# 产物在 arch/arm/boot/dts/*.dtb产物在哪里架构内核镜像设备树ARM 32bitarch/arm/boot/zImagearch/arm/boot/dts/your-board.dtbARM 64bitarch/arm64/boot/Image(或Image.gz)arch/arm64/boot/dts/vendor/your-board.dtb编译时间参考全量编译defconfig在 8 核 CPU 上约 5~15 分钟。裁剪后大量 n可缩短到 1~3 分钟。3.6 常见裁剪项速查表裁剪项Kconfig 选项典型收益无线协议栈CONFIG_WIRELESSCONFIG_BT~500KBUSB 子系统CONFIG_USB_SUPPORT~1.5MB声卡CONFIG_SOUND~800KBGPU/DRMCONFIG_DRM~3MB调试信息CONFIG_DEBUG_INFO~100MB(vmlinux)内核调试CONFIG_KGDBCONFIG_KDB~200KB文件系统 (XFS/Btrfs/NFS)CONFIG_XFS_FS等~1MB/个SELinuxCONFIG_SECURITY_SELINUX~400KBIPv6CONFIG_IPV6~300KB模块签名CONFIG_MODULE_SIG~100KB内核压缩CONFIG_KERNEL_GZIP/LZMA/XZXZ 比 gzip 小 ~20%3.7 裁剪效果示例┌────────────────┬──────────┬──────────┬──────────────┐ │ 配置类型 │ zImage │ modules │ 说明 │ ├────────────────┼──────────┼──────────┼──────────────┤ │ multi_v7_defconfig │ 6.1MB │ 15MB │ 全功能 ARM │ │ NXP i.MX6 参考 │ 4.2MB │ 8MB │ 厂商裁剪后 │ │ 手工裁剪后 │ 2.1MB │ 2MB │ 按需裁剪 │ │ 极致裁剪 (IoT) │ 0.8MB │ 0KB │ 单功能设备 │ └────────────────┴──────────┴──────────┴──────────────┘4. 内核移植的具体步骤4.1 移植前的准备工作在动手之前必须拿到以下资料┌─────────────────────────────────────────────────────┐ │ 必备资料 │ ├──────────────────────────────┬──────────────────────┤ │ SoC Reference Manual │ 寄存器地址、时钟、中断 │ │ Board Schematic │ 引脚连接、外设型号 │ │ DDR Datasheet │ 内存起始地址和大小 │ │ 交叉编译工具链 │ arm/arm64/riscv gcc │ │ Bootloader (U-Boot) 支持 │ 至少能加载 kernel │ └──────────────────────────────┴──────────────────────┘4.2 编写设备树 (.dts)这是移植中最重要的一步。一个典型的新板级设备树/dts-v1/; #include soc-vendor.dtsi /* 包含 SoC 公用的节点定义 */ / { model My Company MyBoard v1.0; compatible mycompany,myboard, vendor,soc-model; /* 1. 内存描述 */ memory80000000 { device_type memory; reg 0x80000000 0x20000000; /* 512MB DDR3 */ }; /* 2. 选择启动用的串口 */ chosen { stdout-path serial0:115200n8; bootargs consolettyS0,115200 earlyprintk; }; /* 3. LED (用 GPIO 控制) */ leds { compatible gpio-leds; status_led: led-0 { label status; gpios gpio3 12 GPIO_ACTIVE_HIGH; }; }; }; /* 4. 使能 SoC 上要用到的外设 (在 .dtsi 中默认为 disabled) */ uart1 { pinctrl-names default; pinctrl-0 pinctrl_uart1; status okay; /* ← 关键: 从 disabled 改为 okay */ }; i2c1 { pinctrl-names default; pinctrl-0 pinctrl_i2c1; status okay; /* 挂在 I2C 总线上的设备 */ eeprom50 { compatible atmel,24c02; reg 0x50; }; }; usdhc2 { /* eMMC / SD 卡 */ pinctrl-names default; pinctrl-0 pinctrl_usdhc2; bus-width 8; no-1-8-v; non-removable; status okay; }; fec1 { /* 以太网 */ pinctrl-names default; pinctrl-0 pinctrl_enet; phy-mode rgmii; phy-reset-gpios gpio1 25 GPIO_ACTIVE_LOW; status okay; };4.3 在 arch/ 下注册新板子# ARM 32bitvimarch/arm/boot/dts/Makefile# 添加一行:dtb-$(CONFIG_SOC_VENDOR)myboard.dtb# ARM 64bitvimarch/arm64/boot/dts/vendor/Makefile dtb-$(CONFIG_ARCH_VENDOR)myboard.dtb4.4 移植关键子系统这是最需要底层功底的部分按优先级排序优先级 1 — 串口 (UART) └── 有了串口, 才能看 boot log, 才能调试后续工作 └── 配置 pinctrl (引脚复用) 时钟 UART 驱动 优先级 2 — 中断控制器 (GIC / NVIC) └── 几乎所有驱动都依赖中断 └── 通常在 SoC 的 .dtsi 中已定义, 确认 compatible 匹配即可 优先级 3 — 时钟控制器 (CCF: Common Clock Framework) └── 所有外设驱动都依赖时钟 └── 移植 clk 驱动, 通常由 SoC 厂商提供 优先级 4 — 定时器 (ARM Generic Timer / SoC 私有定时器) └── 调度器的心跳, 一定要有 └── ARMv7/v8 通常用 arch timer, 配置 device tree 即可 优先级 5 — pinctrl (引脚复用) └── 没有 pinctrl, 所有外设的 GPIO 功能都是错的 └── 通常由 SoC 厂商提供驱动, 只需在 .dts 中添加 pinmux 配置 优先级 6 — 存储控制器 (eMMC / NAND / SPI Flash) └── rootfs 在上面, 必须通4.5 交叉编译工具链配置# 下载工具链 (以 ARM 为例)# Linaro: https://releases.linaro.org/components/toolchain/binaries/wgethttps://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xztarxf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz# 设置环境变量exportPATH/path/to/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATHexportARCHarmexportCROSS_COMPILEarm-linux-gnueabihf-# 验证arm-linux-gnueabihf-gcc--version4.6 生成可启动镜像# 编译make-j$(nproc)# ARM 32bit:# 产物在 arch/arm/boot/# zImage (可自解压内核)# dts/*.dtb (设备树)# ARM 64bit:# 产物在 arch/arm64/boot/# Image (未压缩内核)# Image.gz (压缩内核)# dts/vendor/*.dtb# 最终烧录到板子上 (以 SD 卡为例):# fat32 分区: zImage myboard.dtb# ext4 分区: rootfs (含 /lib/modules/)4.7 整个移植过程的架构图┌──────────────────────────────────────────────────────┐ │ 移植工作流 │ ├──────────────────────────────────────────────────────┤ │ │ │ 拿到板子 → 查阅 Datasheet → 编写 .dts │ │ ↓ ↓ │ │ 确认交叉工具链 注册到 arch/.../dts/Makefile│ │ ↓ ↓ │ │ make defconfig → make menuconfig (裁剪) │ │ ↓ │ │ make zImage make dtbs │ │ ↓ │ │ 烧录到板子 → 串口看 boot log │ │ ↓ │ │ ┌─────────────────────────────────┐ │ │ │ earlyprintk 有输出? │ │ │ │ ├── YES → 继续 │ │ │ │ └── NO → 检查 UART pinctrl/时钟│ │ │ └─────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────┐ │ │ │ 挂载 rootfs 成功? │ │ │ │ ├── YES → 继续 │ │ │ │ └── NO → 检查存储驱动/文件系统 │ │ │ └─────────────────────────────────┘ │ │ ↓ │ │ 逐个调通外设驱动 → 完成! │ │ │ └──────────────────────────────────────────────────────┘5. 验证方法5.1 串口输出验证最重要的第一步# 用 minicom / picocom / putty 连接板子串口# 波特率: 115200, 8N1, 无硬件流控# 上电后应该看到的最早输出:## Booting kernel from Legacy Image at 80800000 ...## Image Name: Linux-6.6.0## Data Size: 2156480 Bytes 2.1 MB## Starting kernel ...#### [ 0.000000] Booting Linux on physical CPU 0x0## [ 0.000000] Linux version 6.6.0 (userhost) (gcc version 12.3.0)## [ 0.000000] CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7)## [ 0.000000] OF: fdt: Machine model: My Company MyBoard v1.0#### ← 看到 Machine model 与你的 .dts 中 model 属性一致 设备树加载成功!5.2 内核启动日志分析# 进入系统后dmesg|less# 关键检查项:dmesg|grep-ierror\|fail\|warn# 查找异常dmesg|grepMachine model# 确认设备树正确dmesg|grepMemory policy# 确认内存大小正确dmesg|greproot# 确认 rootfs 挂载cat/proc/meminfo# 可用内存cat/proc/mtd# 已识别的 MTD 分区5.3 设备树验证# 检查内核解析后的设备树ls/proc/device-tree/cat/proc/device-tree/compatible# 应输出 mycompany,myboard...cat/proc/device-tree/memory80000000/reg|xxd# 十六进制看内存地址和大小# 把 dtb 反编译回 dts, 检查是否正确dtc-Idtb-Odts-o/tmp/decompiled.dts /proc/device-tree5.4 驱动加载验证# 已加载模块lsmod# 已识别设备cat/proc/devices# 查看设备树中哪些节点成功绑定了驱动ls-l/sys/bus/platform/devices/ls-l/sys/class/net/# 网卡ls-l/sys/class/tty/# 串口5.5 文件系统挂载验证df-h# 看分区是否都挂载上了mount# 看挂载选项cat/proc/filesystems# 内核支持的文件系统列表5.6 功能测试# 网络ping-c38.8.8.8# GPIO (如果导出到 sysfs)echo12/sys/class/gpio/exportechoout/sys/class/gpio/gpio12/directionecho1/sys/class/gpio/gpio12/value5.7 镜像大小对比# 各阶段产物大小对比ls-lhvmlinux# 未裁剪的 ELFls-lhvmlinux-o# 裁剪后的 ELFls-lharch/arm/boot/zImage# 最终烧录的镜像size vmlinux# 分析各段 (text/data/bss) 的占用# 更精细的分析: 用 bloat-o-meter (内核源码自带)scripts/bloat-o-meter vmlinux-old vmlinux-new# 输出示例:# add/remove: 0/145 grow/shrink: 2/15 up/down: 48/-125630 (-125582)# ← 裁剪掉了 122KB6. 最佳实践与常见陷阱6.1 裁剪过度的后果症状: 内核编译成功, 但启动到某一步就 kernel panic 常见原因: ├── 关闭了 CONFIG_PRINTK → 没有任何内核日志 !! ├── 关闭了 CONFIG_BLOCK → 无法访问块设备 (eMMC/SD 卡) ├── 关闭了 CONFIG_TMPFS → systemd 无法创建 tmpfs (启动失败) ├── 关闭了 CONFIG_INOTIFY_USER → systemd 无法监控文件变化 ├── 关闭了 CONFIG_SYSFS → /sys 目录为空 (大量用户态工具失效) └── 关闭了 CONFIG_PROC_FS → /proc 目录为空 黄金规则: 不确定的选项保持 y, 验证通过后再裁6.2 Kconfig 依赖关系陷阱# drivers/net/ethernet/Kconfig config NET_VENDOR_STMICRO bool STMicroelectronics devices default y depends on HAS_IOMEM # ← 如果目标架构没有 IOMEM, 这个驱动不会出现! config STMMAC_ETH tristate STMicroelectronics 10/100/1000/EQOS Ethernet depends on HAS_IOMEM HAS_DMA select PHYLIB # ← 选中 STMMAC 会自动选中 PHYLIB select CRC32 # ← 自动选中 CRC32陷阱 1:select可以绕过depends on—— 如果 AselectB, 但 B 的依赖条件不满足, Kconfig 会警告但可能被忽略。陷阱 2:depends on缺失的依赖会导致编译错误 —— 比如某个驱动depends on NET, 网络中有一个 API, 关闭CONFIG_NET后驱动编译失败。调试方法:# 查看某个配置为什么被意外选中的完整依赖链makemenuconfig# 在选项上按 ? → 显示 Depends on 和 Selected by# 搜索配置依赖grep-rselect SOC_VENDORarch/arm/ drivers/|head-206.3 设备树与驱动匹配失败的调试常见症状: 设备树中有节点, 但驱动没有 probe 诊断: 1. 检查 compatible 字符串是否与驱动的 of_match_table 完全一致 2. 检查 status okay (不是 ok 或 disabled) 3. 检查 pinctrl 是否正确 (引脚冲突或未配置) 4. 检查时钟是否正确使能 调试命令: cat /sys/kernel/debug/devices_deferred ← 列出延迟 probe 的设备6.4 增量裁剪方法论不要一次裁掉 50 个选项然后编译 → 烧录 → 启动失败 → 不知道哪个裁错了 正确做法: 第1次: 裁 5 项 → 编译 → 启动 → 验证 第2次: 裁 5 项 → 编译 → 启动 → 验证 ... 每次只改少量选项, 出了问题能快速定位。 进阶: 用 git 管理 .config 的变更 git add .config git commit -m enable USB, disable SATA git diff HEAD~1 .config ← 精确看到改了哪些选项6.5 总结要点┌──────────────────────────────────────────────────────┐ │ Linux 内核裁剪与移植 核心要点 │ ├──────────────────────────────────────────────────────┤ │ │ │ 裁剪的三层原理: │ │ Kconfig (定义选项) ──► .config (选择) ──► Makefile │ │ │ │ 移植的两大支柱: │ │ 设备树 (.dts) —— 描述硬件是什么 │ │ 驱动 (.c) —— 定义硬件怎么用 │ │ │ │ 验证的三级递进: │ │ earlyprintk 有输出 → rootfs 能挂载 → 外设全正常 │ │ │ │ 黄金规则: │ │ 1. 不确定的选项不要裁 │ │ 2. 每次只改少量 │ │ 3. 用 git 追踪 .config 变更 │ │ 4. 先跑通再优化 │ │ │ └──────────────────────────────────────────────────────┘参考文献Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/Device Tree Specification: https://www.devicetree.org/specifications/Bootlin Embedded Linux Training: https://bootlin.com/training/embedded-linux/《Linux 设备驱动开发详解》(宋宝华 著)