ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RISC-V Linux 内核启动镜像头(Boot Image Header)完全解析:64 字节格式、字段语义与引导器实现

RISC-V Linux 内核启动镜像头(Boot Image Header)完全解析:64 字节格式、字段语义与引导器实现 RISC-V Linux 内核启动镜像头Boot Image Header完全解析64 字节格式、字段语义与引导器实现【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读本文以 Linux 内核仓库中 Documentation/arch/riscv/boot-image-header.rst 为核心系统讲解 RISC-V Linux 解压内核镜像decompressed kernel image开头的 64 字节引导头boot image header包括每个字段的布局与字节序、text_offset/image_size/flags/version的取值规则、以及该头如何同时服务于传统引导器与 EFI stub。读完本文你将掌握引导器bootloader、kexec、UEFI 固件解析 RISC-V Linux Image 所需的一切字段细节并能对照 arch/riscv/include/asm/image.h 与 arch/riscv/kernel/head.S 在源码层面验证每一项规范。一、为什么要有一个 64 字节的引导头RISC-V 规范要求内核镜像必须携带一个固定格式、固定偏移的引导头原因是引导链上的所有参与者——固件、U-Boot、OpenSBI、kexec、UEFI——都需要在不解析内核内部符号、不依赖链接器脚本的情况下仅凭镜像开头的少量字节即可完成三件事判断该镜像是否是一个合法的 RISC-V Linux 内核通过 magic 校验确定应当把镜像加载到内存的哪个偏移处text_offset确定需要为镜像预留多少内存image_size。该头格式与 PE/COFF 兼容并大量借鉴 ARM64 内核的引导头设计。文档明确指出未来 ARM64 与 RISC-V 的引导头有望合并为一种通用格式避免镜像头格式的泛滥The intention is for this header format to be shared between multiple architectures见 arch/riscv/include/asm/image.h 的注释。二、64 字节头的完整布局文档给出了解压后的 Linux 内核镜像开头的 64 字节结构字段按偏移排列如下偏移Offset字段类型/大小含义0x00code0u32可执行代码第一条指令0x04code1u32可执行代码0x08text_offsetu64小端镜像加载偏移0x10image_sizeu64小端内核有效镜像大小0x18flagsu64小端内核标志位0x20versionu32本头的版本号0x24res1u32 0保留0x28res2u64 0保留0x30magicu64 0x5643534952魔数小端即 ASCII RISCV0x38magic2u32 0x05435352魔数 2小端即 RSC\x050x3cres3u32保留用于 PE COFF 偏移上述布局在 C 语言侧由 arch/riscv/include/asm/image.h 中的struct riscv_image_header一一对应字段顺序、大小与文档完全一致struct riscv_image_header { u32 code0; u32 code1; u64 text_offset; u64 image_size; u64 flags; u32 version; u32 res1; u64 res2; u64 magic; u32 magic2; u32 res3; };在汇编侧arch/riscv/kernel/head.S 中的_start入口通过.dword/.word/.ascii指令按完全相同顺序填充这些字段并明确警告Do not modify it without modifying the structure and all bootloaders that expects this header format!!不要在不修改结构体以及所有依赖该头格式的引导器的前提下改动它。2.1code0/code1可执行代码区code0与code1并非单纯的数据而是真正的可执行指令。在非 EFI 配置下_start的第一条指令是j _start_kernel随后是 4 字节保留字整体形成 8 字节对齐的可执行区域当启用CONFIG_EFI时code0会被替换为一条解码后恰好是 ASCIIMZ的压缩指令c.li s4, -13这正是 UEFI 识别 PE/COFF 镜像的标志。三、text_offset镜像应当加载到哪里text_offset表示内核镜像相对内存起始位置的加载偏移小端存储。内核本身在 arch/riscv/kernel/head.S 中按配置写入实际值CONFIG_RISCV_M_MODEM 模式运行下为0 MB/* Image load offset (0MB) from start of RAM for M-mode */RV64__riscv_xlen 64下为2 MB0x200000RV32 下为4 MB0x400000。引导器必须将镜像加载到 RAM 起始地址加上text_offset的位置。需要特别指出的是kexec 加载器把该值同时当作镜像的内存对齐要求来使用在 arch/riscv/kernel/kexec_image.c 中kbuf.buf_align le64_to_cpu(h-text_offset)即以text_offset作为kexec_add_buffer()的缓冲对齐粒度确保新内核被放到满足偏移约定的地址。四、image_size引导器的强制校验项image_size表示内核的有效镜像大小Effective Image size由 arch/riscv/kernel/head.S 以_end - _start计算得出——即从入口符号到镜像结束符号之间的字节数。文档强调image_size对引导器是必填项缺失将导致启动失败Image size is mandatory for boot loader to load kernel image. Booting will fail otherwise.。这一约定在 kexec 代码中得到了严格执行image_load()首先检查h-image_size若为 0 直接返回-EINVALarch/riscv/kernel/kexec_image.c随后以le64_to_cpu(h-image_size)作为memsz申请目标内存同文件 L72。五、flags字节序标志flags字段当前只定义了一位Bit 0内核字节序。1表示大端BE0表示小端LE。在 arch/riscv/include/asm/image.h 中定义#define RISCV_IMAGE_FLAG_BE_SHIFT 0 #define RISCV_IMAGE_FLAG_BE_MASK 0x1 #define RISCV_IMAGE_FLAG_LE 0 #define RISCV_IMAGE_FLAG_BE 1 #define __HEAD_FLAGS (__HEAD_FLAG(BE))注意一个实现细节目前当配置为CONFIG_CPU_BIG_ENDIAN时编译会直接报错#error conversion of header fields to LE not yet implemented——也就是说当前仓库中头字段到小端的转换尚未实现因此实际编译出的内核头字段一律以小端写入__HEAD_FLAG_BE恒为RISCV_IMAGE_FLAG_LE。kexec 加载时同样会校验字节序匹配image_load()读取h-flags得到be_image与内核自身是否CONFIG_CPU_BIG_ENDIAN比较不一致则拒绝加载arch/riscv/kernel/kexec_image.c。六、version版本兼容机制version是一个 u32 字段高低 16 位分别表示主、次版本位域含义Bits 0:15次版本号MinorBits 16:31主版本号Major这种主版本在高位、次版本在低位的编码方式是为了保证新旧版本头部之间的兼容性preserves compatibility across newer and older version of the header。当前版本定义为0.2对应源码宏arch/riscv/include/asm/image.h#define RISCV_HEADER_VERSION_MAJOR 0 #define RISCV_HEADER_VERSION_MINOR 2 #define RISCV_HEADER_VERSION (RISCV_HEADER_VERSION_MAJOR 16 | \ RISCV_HEADER_VERSION_MINOR)该值在 arch/riscv/kernel/head.S 通过.word RISCV_HEADER_VERSION写入头部。版本 0.2 带来的行为变化kexec 的image_probe()arch/riscv/kernel/kexec_image.c正是依据版本决定校验策略if (h-version RISCV_HEADER_VERSION memcmp(h-magic2, RISCV_IMAGE_MAGIC2, sizeof(h-magic2))) return -EINVAL;即当头部版本 ≥ 0.2 时改用magic2字段进行魔数校验版本低于 0.2 的旧镜像则继续依赖magic字段。源码注释明确引用本文档作为该行为的依据According to Documentation/arch/riscv/boot-image-header.rst, use magic2 field to check when version 0.2.七、两个魔数magic与magic2头部包含两个魔数这是历史上 ARM64 头格式借鉴过程中的一个事故留下的双轨设计magic0x30 偏移u64小端存储后为 ASCIIRISCV。文档指出该字段自版本 0.2 起已废弃deprecated未来版本可能移除。它本应与 ARM64 头的magic字段对上但遗憾的是并没有对上。magic20x38 偏移u32小端存储后为 ASCIIRSC\x05即0x05435352。它取代magic与 ARM64 头对齐是版本 0.2 及以后的实际校验依据。对应源码宏arch/riscv/include/asm/image.h#define RISCV_IMAGE_MAGIC RISCV\0\0\0 #define RISCV_IMAGE_MAGIC2 RSC\x05写入位置见 arch/riscv/kernel/head.Smagic用.ascii写入 8 字节随后.balign 4对齐后再用.ascii写入 4 字节的magic2保证magic2恰好落在偏移 0x38 处。读者在手动解析镜像头时应优先校验magic2并注意区分两个字段的字节序与小端数值。八、res3与 EFI stub头如何兼作 PE/COFFres30x3c 偏移u32当前保留其用途是存放PE COFF 头偏移。RISC-V 的 EFI stub 复用了这个 64 字节头UEFI 规范要求内核镜像开头必须是 PE/COFF 头才能作为 EFI 应用被加载于是code0被替换为MZ魔数——在 arch/riscv/kernel/head.S 中CONFIG_EFI开启时第一条指令是c.li s4,-13该指令编码解码后恰为 ASCIIMZ源码注释原文This instruction decodes to MZ ASCII required by UEFI.res3偏移 0x3c指向 PE/COFF 头其余部分的起始位置pe_head_start - _startarch/riscv/kernel/head.S完整的 PE 头内容由 arch/riscv/kernel/efi-header.S 中的__EFI_PE_HEADER宏生成其中按CONFIG_64BIT分别使用IMAGE_FILE_MACHINE_RISCV64/IMAGE_FILE_MACHINE_RISCV32机器类型、PE32/PE32 可选头格式并声明.text/.data两个节区。也就是说同一份镜像在非 EFI 引导器眼里是跳转指令 引导头在 UEFI 固件眼里则是一个合法的 PE/COFF 应用镜像。这是文档所述该头格式与 PE/COFF 兼容的具体落地。九、实战如何读取并校验一个 RISC-V Image 头结合以上字段以下给出引导器或工具代码读取头的推荐流程字段取值与校验逻辑均有文档与源码依据读入前 64 字节按struct riscv_image_headerarch/riscv/include/asm/image.h的布局解析校验版本读取version小端主版本 version 16次版本 version 0xffff当前内核为 0.2校验魔数若version 0x000200000.2比较magic2与0x05435352否则比较magic与0x5643534952kexec 的image_probe()即此逻辑arch/riscv/kernel/kexec_image.c确认镜像大小image_size必须非零否则拒绝引导文档强制要求计算加载地址load_addr RAM_START text_offset并将镜像按text_offset对齐放置kexec 将其用作buf_align按flagsBit 0 判断字节序1为大端、0为小端与运行环境字节序不符则拒绝加载可选支持 EFI检测偏移 0 处是否为大端序可读的0x5a4d即MZ若是则按res3指向的偏移继续解析完整 PE/COFF 头arch/riscv/kernel/efi-header.S。十、相关文档与源码索引官方规范文档Documentation/arch/riscv/boot-image-header.rstRISC-V 启动流程总览Documentation/arch/riscv/boot.rst头结构体定义与全部宏arch/riscv/include/asm/image.h头在入口汇编中的实际布局与text_offset取值arch/riscv/kernel/head.SEFI PE/COFF 头生成宏arch/riscv/kernel/efi-header.Skexec 对引导头的完整消费逻辑probe/loadarch/riscv/kernel/kexec_image.c总结RISC-V Linux 的 64 字节引导头是整个引导链的公共契约text_offset决定加载位置image_size决定内存占用flags声明字节序version提供向前兼容的演进通道双魔数magic/magic2完成镜像合法性校验而res3与MZ指令则让同一份镜像在 UEFI 环境下无缝变身 PE/COFF 应用。从本文引用的源码可以看到无论是内核自身head.S 写入、kexec 加载器kexec_image.c 消费还是 EFI stubefi-header.S 扩展都严格遵循 Documentation/arch/riscv/boot-image-header.rst 定义的这一格式——理解它是正确实现 RISC-V 引导器与启动调试的基础。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表