4. Linux内核基础与编译

本章是进入系统级调试前的关键基石。你将理解Linux内核的源码结构、配置哲学,并以vexpress开发板为目标,完整走通从源码到可启动镜像的编译流程。掌握这些内容后,后续的QEMU加载与GDB联合调试才能有的放矢。

学习目标: 理解内核镜像的组成(vmlinux vs zImage)、掌握设备树(DTB)的作用、能够独立为vexpress平台编译并生成启动所需的关键文件。

4.1 Linux内核简介

Linux内核是一个宏内核(Monolithic Kernel),但兼具模块化设计。它负责管理硬件资源、提供系统调用接口、调度进程与内存。在嵌入式领域,内核通常与根文件系统分离,由Bootloader(如U-Boot)加载到内存后启动。

内核源码目录结构遵循清晰的层次划分,下表列出了与编译和调试最密切的目录:

目录 作用 调试关注点
arch/ 体系结构相关代码(arm, arm64, x86等) 启动入口、MMU、中断向量表
drivers/ 设备驱动(串口、网卡、MMC等) 驱动初始化顺序、probe流程
init/ 内核初始化(main.c) start_kernel执行流
kernel/ 核心功能(调度、irq、printk) 进程0、1的创建
mm/ 内存管理(页分配、slab) bootmem到伙伴系统的切换
scripts/ 编译与配置工具(dtc, kconfig) 设备树编译规则
关键概念: 内核不是“一个文件”,而是一个由vmlinux(ELF格式,含符号表)、zImage(压缩后的自解压镜像)、dtb(设备树二进制)组成的启动三元组。调试时主要使用vmlinux,运行时使用zImage+dtb。

4.2 源码获取

推荐从kernel.org获取主线源码,或使用LTS版本以获得长期支持。以Linux 5.10 LTS为例:

# 方式一:直接下载tar包
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.210.tar.xz
tar -xf linux-5.10.210.tar.xz
cd linux-5.10.210

# 方式二:使用git克隆(推荐,便于切换分支和打补丁)
git clone --depth=1 -b v5.10 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
注意: 如果使用git浅克隆(--depth=1),后续无法查看完整提交历史,但编译不受影响。建议保留完整克隆用于调试分析。

4.3 内核配置:defconfig与menuconfig

内核配置是编译前最关键的一步。配置系统基于Kconfig语言,提供两种主要方式:

  • defconfig(默认配置): 每个架构/平台提供一份最小可用配置,位于 arch/<arch>/configs/ 下。例如vexpress的默认配置为 vexpress_defconfig。
  • menuconfig(交互式配置): 基于ncurses的图形界面,用于微调内核功能、开启调试选项。

配置流程如下:

# 第一步:加载vexpress默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig

# 第二步:交互式微调(可选,但强烈建议开启调试符号)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

在menuconfig中,务必确认以下调试相关选项:

配置项 路径 说明
CONFIG_DEBUG_INFO Kernel hacking → Compile-time checks 生成DWARF调试符号(vmlinux必备)
CONFIG_GDB_SCRIPTS Kernel hacking → GDB scripts 提供lx-symbols等辅助调试命令
CONFIG_EARLY_PRINTK Kernel hacking → Early printk 早期串口输出,便于跟踪启动崩溃
CONFIG_DEBUG_LL Kernel hacking → Low-level debug 低级别调试,需配合具体UART地址
调试技巧: 在menuconfig中按 / 键可搜索配置项。例如输入 DEBUG_INFO 可直接定位,避免手动翻找。

4.4 编译流程(以vexpress架构为例)

vexpress是ARM Versatile Express系列开发板的QEMU模拟目标,使用Cortex-A9 MPCore处理器。编译命令如下:

# 设置环境变量(也可直接写在make参数中)
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 编译内核镜像与设备树
make -j$(nproc) zImage
make -j$(nproc) dtbs

# 编译内核模块(可选,后续可放入根文件系统)
make -j$(nproc) modules

编译完成后,关键文件位于以下路径:

文件 路径 格式 用途
vmlinux 源码根目录 ELF(未压缩,含符号) GDB调试目标文件
zImage arch/arm/boot/ 自解压二进制 QEMU加载的内核镜像
vexpress-v2p-ca9.dtb arch/arm/boot/dts/ 扁平设备树二进制 描述vexpress板级硬件信息
编译加速: 首次编译建议先执行 make -j1 观察完整输出,确认无误后再使用 -j$(nproc) 并行编译。如果修改了设备树源文件(.dts),只需重新执行 make dtbs,无需重编整个内核。

4.5 生成的关键文件详解

理解三个关键文件的生成链路与内部结构,是后续调试的基础。下图展示了从源码到最终镜像的编译流程:

Linux内核编译流程(vexpress) C源码 + Kconfig 设备树源(.dts) 链接脚本(.lds) 编译 + 链接 DTC编译 vmlinux (ELF) vexpress-v2p-ca9.dtb objcopy + gzip zImage (自解压) 调试时使用: vmlinux → GDB加载符号 zImage + dtb → QEMU加载启动

4.5.1 vmlinux

vmlinux是编译链接后生成的原始ELF文件,包含完整的符号表、调试信息(如果开启了CONFIG_DEBUG_INFO)。它是GDB调试的“地图”,但体积较大(通常几十MB),不适合直接用于启动。

# 查看vmlinux的段信息
arm-linux-gnueabihf-objdump -h vmlinux | head -20

# 查看内核符号表(可用于地址回溯)
arm-linux-gnueabihf-nm vmlinux | grep start_kernel

4.5.2 zImage

zImage由vmlinux经过objcopy去除ELF头部、再经过gzip压缩,并在头部附加自解压代码(decompress_kernel)生成。QEMU加载zImage后,CPU首先执行解压代码,将内核解压到指定地址,然后跳转到解压后的入口。

# 查看zImage的入口地址(前64字节包含解压头)
hexdump -C arch/arm/boot/zImage | head -4

4.5.3 设备树(DTB)

设备树是描述板级硬件信息的结构化数据,替代了旧版内核中的大量板级文件(board-*.c)。vexpress的设备树源文件位于 arch/arm/boot/dts/vexpress-v2p-ca9.dts,编译后生成dtb。

# 反编译dtb查看硬件拓扑
arm-linux-gnueabihf-objdump -d arch/arm/boot/dts/vexpress-v2p-ca9.dtb > dtb_dis.S

# 或使用dtc工具直接查看
dtc -I dtb -O dts arch/arm/boot/dts/vexpress-v2p-ca9.dtb | head -50
调试要点: 如果内核启动时卡在“Uncompressing Linux... done, booting the kernel.”之后无输出,通常是因为设备树中串口地址(chosen/stdout-path)与QEMU模拟的串口不一致。此时应检查dtb中的uart节点地址是否匹配QEMU的 -serial 参数。

4.6 验证编译产物

编译完成后,建议执行以下快速验证:

# 检查关键文件是否存在
ls -lh vmlinux
ls -lh arch/arm/boot/zImage
ls -lh arch/arm/boot/dts/vexpress-v2p-ca9.dtb

# 检查vmlinux是否包含调试符号
arm-linux-gnueabihf-readelf -S vmlinux | grep debug

# 检查zImage是否为有效ARM镜像(前4字节应为魔数)
hexdump -C arch/arm/boot/zImage | head -1

如果 readelf 输出中包含 .debug_info、.debug_line 等段,说明调试符号已正确生成,后续GDB可以正常解析源码行号与变量。

常见问题: 如果编译报错“arch/arm/boot/dts/vexpress-v2p-ca9.dtb: No such file or directory”,请确认执行了 make dtbs 而非仅 make zImage。设备树编译与内核镜像编译是独立的make目标。