news 2026/9/28 2:30:42

LinuxKit raw-efi 镜像构建深度解析:基于 systemd-boot 与 UKI 的 ESP 生成全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LinuxKit raw-efi 镜像构建深度解析:基于 systemd-boot 与 UKI 的 ESP 生成全流程
  • 操作系统
  • 云原生
  • 容器运行时

【免费下载链接】linuxkit

A toolkit for building secure, portable and lean operating systems for containers

项目地址:https://gitcode.com/gh_mirrors/li/linuxkit
点击查看免费下载

导读

tools/mkimage-raw-efi/是 LinuxKit 项目中负责生成raw EFI 磁盘镜像(raw-efi输出格式)的核心工具:它接收一个包含内核、initrd 与命令行参数的 tarball,在容器内完成 EFI System Partition(ESP)的初始化、systemd-boot 引导器与 Linux Unified Kernel Image(UKI)的组装,最终在标准输出上吐出一份可直接写入磁盘的 GPT 格式原始镜像。读完本文,你将掌握该工具从 stdin 协议、UKI 构建、FAT32 最小化计算到 GPT 分区组装的完整工作原理,并能通过linuxkit build -format raw-efi在本地复现这一镜像构建流程。

一、工具定位:LinuxKit 的 EFI 启动盘输出格式

LinuxKit 的linuxkit build命令支持多种输出格式,其中raw-efi专门面向UEFI 固件启动场景。在 output.go 中可以看到它的注册逻辑:

"raw-efi": func(base string, image io.Reader, size int, arch string) error { kernel, initrd, cmdline, _, err := tarToInitrd(image) if err != nil { return fmt.Errorf("error converting to initrd: %v", err) } err = outputImg(outputImages["raw-efi"], base+"-efi.img", kernel, initrd, cmdline, arch) if err != nil { return fmt.Errorf("error writing raw-efi output: %v", err) } return nil },

构建产物命名为base-efi.img,其镜像包由 images.yaml 中声明的linuxkit/mkimage-raw-efi:c00e49ece2f03e47f3a8e047a2eeea7bd4228a34提供——这正是本目录下 Dockerfile 构建出的工具镜像。

与 BIOS 时代的raw-bios(使用 syslinux,见 make-bios)不同,raw-efi走的是UEFI 原生启动路径:ESP 中只放 systemd-boot 引导器与一个 UKI 内核镜像,不依赖 MBR 引导扇区。

二、ESP 分区布局(官方 README 核心内容)

README.md 明确指出:脚本通过mkfs.vfat初始化 EFI System Partition,并向其中填充 systemd-boot 与 LinuxKit UKI 所需的目录和文件。最终 ESP 的目录结构如下:

ESP ├── EFI │ ├── BOOT # 根据架构放置以下引导器二进制之一 │ │ ├── BOOTX64.EFI # amd64 │ │ ├── BOOTAA64.EFI # arm64 │ │ └── BOOTRISCV64.EFI # riscv64 │ └── Linux │ └── linuxkit.efi # LinuxKit Unified Kernel Image (UKI) └── loader └── loader.conf # systemd-boot 配置文件

布局中两个要点值得注意:

  1. 引导器文件名按 UEFI 规范命名:EFI/BOOT/BOOTX64.EFI(amd64)、BOOTAA64.EFI(arm64)、BOOTRISCV64.EFI(riscv64)。这些是 UEFI 固件约定的"可移除介质默认引导路径"文件名,固件无需读取配置即可按架构自动发现。
  2. UKI 自动发现机制:放在EFI/Linux目录下的 UKI 不需要在loader/entries中显式添加启动条目——systemd-boot 会自动扫描该目录并生成启动菜单项。这正是 loader 目录下只有loader.conf一个配置文件的原因。

三、工具链与运行环境

Dockerfile 揭示了该工具的构建与运行环境,它采用多阶段构建:

  • 第一阶段从linuxkit/systemd-boot镜像提取编译好的 systemd-boot EFI 二进制;
  • 第二阶段基于linuxkit/alpine准备运行时,通过apk add安装:
    • dosfstools:提供mkfs.vfat,用于创建 FAT32 文件系统;
    • mtools:提供mmd/mcopy,用于向 FAT 镜像写入目录与文件;
    • sgdisk/sfdisk:GPT 分区表工具;
    • libarchive-tools:提供bsdtar,用于解压 stdin 输入的 tarball;
    • py3-pefile:ukify 构建 UKI 时的 PE 文件解析依赖;
    • binutils、busybox、xfsprogs等基础工具;
  • 最终阶段从 scratch 组装:COPY --from=mirror /out/ /带入 Alpine 根文件系统,COPY --from=systemd-boot . .带入引导器,COPY . .带入make-efi脚本本身,并以ENTRYPOINT [ "/make-efi" ]作为容器入口。

四、标准输入/输出协议与输入内容约定

make-efi脚本(tools/mkimage-raw-efi/make-efi)以 Unix 管道的方式工作,其协议在脚本注释中写明:

# input is a tarball on stdin with kernel and cmdline in /boot # output is an iso on stdout
  • 输入:标准输入上的压缩 tarball,其中必须包含/boot下的内核与命令行参数文件;
  • 输出:标准输出上的 raw 磁盘镜像(脚本末尾的cat $IMGFILE完成这一职责)。

解压与中间产物提取的关键代码:

[ -t 0 ] || bsdtar xzf - INITRD="$(find . -name '*.img')" KERNEL="./kernel" CMDLINE_FILE="$(find . -name cmdline)" CMDLINE="$(cat $CMDLINE_FILE )" UKI_FILE="linuxkit.efi"
  • [ -t 0 ] || bsdtar xzf -:仅当标准输入不是 tty 时才从 stdin 解压。注释特别说明 BSD 版 tar 能自动识别压缩格式,而 GNU tar 不能,这也是选择libarchive-tools的原因;若 stdin 是 tty,则依赖挂载的卷提供文件(调试场景)。
  • 通过find定位*.img(initrd)与cmdline文件,内核固定命名为kernel,命令行内容直接读出拼入启动参数。

五、UKI(Unified Kernel Image)的构建

脚本为 root 分区预生成一个随机 PARTUUID,并调用 systemd 的ukify工具将内核、initrd 与命令行打包为单个 EFI 可执行文件:

# PARTUUID for root PARTUUID=$(cat /proc/sys/kernel/random/uuid) # this is displayed as boot loader entry name OS_RELEASE="NAME=\"LinuxKit\"" cat >> loader.conf <<EOF timeout 0 EOF ukify build --linux=$KERNEL \ --initrd=$INITRD \ --cmdline="$CMDLINE text" \ --os-release=$OS_RELEASE \ --output=$UKI_FILE
  • ukify build将--linux(内核)、--initrd(initrd 镜像)、--cmdline(内核命令行)合并进一个linuxkit.efi文件。命令行为原有的$CMDLINE追加了text参数,强制内核使用文本模式控制台,便于在串口等无图形环境下排障。
  • --os-release=NAME="LinuxKit"写入 UKI 的 os-release 段,systemd-boot 会用该名称显示启动菜单项。
  • loader.conf仅写入timeout 0,即启动菜单不等待、直接引导默认项,符合 LinuxKit 追求"快速无交互启动"的定位。

值得一提的是,PARTUUID变量随后被用作 ESP 分区的--partition-guid,为分区表提供一个确定的、可被根文件系统root=PARTUUID=引用的标识(见下节 GPT 组装)。

六、FAT32 文件系统参数与最小化尺寸计算

这是脚本中最精细的部分:为了让 ESP 尽量小,它对 FAT32 的各个结构做了逐项手工核算,而不是直接交给mkfs.vfat按默认值分配。

6.1 固定参数

FAT_SIZE=32 SECTOR_SIZE=512 SECTORS_PER_CLUSTER=8 # these always include the Boot Sector and FS Information Sector (sector 0 and 1) among others for FAT32 RESERVED_SECTORS=32
参数值含义
FAT_SIZE32FAT32 类型(每条目 4 字节)
SECTOR_SIZE512扇区大小(字节)
SECTORS_PER_CLUSTER8每簇 8 扇区,即 4KB 簇
RESERVED_SECTORS32保留扇区,包含引导扇区(sector 0)与 FS 信息扇区(sector 1)等

6.2 数据量与 FAT 开销递推

UKI_FILE_SIZE=$(stat -c %s "$UKI_FILE") EFI_FILE_SIZE=$(stat -c %s "$BOOTFILE_DST") SYSTEMD_BOOT_FILE_SIZE=$(stat -c %s loader.conf) # this is the minimum size of our EFI System Partition ESP_DATA_SIZE=$(( $UKI_FILE_SIZE + $EFI_FILE_SIZE + $SYSTEMD_BOOT_FILE_SIZE )) ESP_DATA_SECTORS=$(( $ESP_DATA_SIZE / $SECTOR_SIZE )) # File Allocation Table Sectors = (clusters + 2) * bytes per cluster / sector size FILE_ALLOCATION_TABLE_SECTORS=$(( ( $ESP_DATA_SECTORS / $SECTORS_PER_CLUSTER + 2 ) * ( $FAT_SIZE / 8 ) / $SECTOR_SIZE )) # there are two file allocation tables hence 2 * $FILE_ALLOCATION_TABLE_SECTORS FAT_OVERHEAD_SECTORS=$(( $RESERVED_SECTORS + ( 2 * $FILE_ALLOCATION_TABLE_SECTORS ) )) FAT_OVERHEAD_SIZE=$(( $FAT_OVERHEAD_SECTORS * $SECTOR_SIZE ))

计算链条清晰可循:

  1. 数据量:ESP 至少容纳三个文件——UKI、systemd-boot 二进制、loader.conf,三者字节数之和即ESP_DATA_SIZE;
  2. 簇数:ESP_DATA_SECTORS / SECTORS_PER_CLUSTER得到占用的簇数,FAT32 的 FAT 表条目数 = 簇数 + 2(两个保留条目:0 与 1 号簇标记);
  3. 单张 FAT 表扇区数:(簇数 + 2) × 4 字节(FAT32 每条目)/ 512;
  4. 总开销:保留扇区(32)+ 两张 FAT 表(FAT32 默认镜像两份 FAT),换算成字节得FAT_OVERHEAD_SIZE。

6.3 对齐到 2048 扇区

# (x+1024)/1024*1024 rounds up to multiple of 1024KB, or 2048 sectors # some firmwares get confused if the partitions are not aligned on 2048 blocks # we will round up to the nearest multiple of 2048 blocks # since each block is 512 bytes, we want the size to be a multiple of # 2048 blocks * 512 bytes = 1048576 bytes = 1024KB ESP_FILE_SIZE_KB=$(( ( ( ($ESP_DATA_SIZE + $FAT_OVERHEAD_SIZE + 1024 - 1) / 1024 ) + 1024 - 1) / 1024 * 1024 )) ESP_FILE_SIZE_SECTORS=$(( $ESP_FILE_SIZE_KB * 1024 / $SECTOR_SIZE ))

脚本注释点明了原因:部分固件在分区未按 2048 块对齐时会出现识别混乱。因此 ESP 文件总尺寸被向上取整到 1024KB(2048 块 × 512 字节)的整数倍。

七、ESP 镜像写入:mkfs.vfat + mtools

得到精确尺寸后,脚本用mkfs.vfat创建最小化 FAT32 镜像,再通过 mtools 系列命令填充目录结构:

mkfs.vfat -v -F $FAT_SIZE -S $SECTOR_SIZE -s $SECTORS_PER_CLUSTER -R $RESERVED_SECTORS -C $ESP_FILE $(( $ESP_FILE_SIZE_KB )) > /dev/null echo "mtools_skip_check=1" >> /etc/mtools.conf && \ mmd -i $ESP_FILE ::/EFI mmd -i $ESP_FILE ::/EFI/BOOT mmd -i $ESP_FILE ::/EFI/Linux mmd -i $ESP_FILE ::/loader mcopy -i $ESP_FILE $BOOTFILE_DST ::/EFI/BOOT/ mcopy -i $ESP_FILE $UKI_FILE ::/EFI/Linux/ mcopy -i $ESP_FILE loader.conf ::/loader
  • mkfs.vfat的-F 32(FAT32)、-S 512(扇区)、-s 8(每簇扇区)、-R 32(保留扇区)与脚本手算参数一一对应,-C表示直接创建指定大小(KB)的文件镜像;
  • mmd依次建立EFI/BOOT、EFI/Linux、loader目录,与 README 中的布局完全一致;
  • mcopy将三个文件按规划路径拷入:引导器进EFI/BOOT/,UKI 进EFI/Linux/,配置进/loader;
  • mtools_skip_check=1跳过 mtools 的磁盘一致性预检,因为该 FAT 镜像由脚本精确构造。

八、GPT 分区表与最终磁盘镜像组装

ESP 文件就绪后,脚本开始组装最终磁盘镜像:

ONEMB=$(( 1024 * 1024 )) SIZE_IN_BYTES=$(( $(stat -c %s "$ESP_FILE") + 4*$ONEMB )) BLKSIZE=512 MB_BLOCKS=$(( $SIZE_IN_BYTES / $ONEMB )) dd if=/dev/zero of=$IMGFILE bs=1M count=$MB_BLOCKS ESP_SECTOR_START=2048 ESP_SECTOR_END=$(( $ESP_SECTOR_START + $ESP_FILE_SIZE_SECTORS - 1 )) sgdisk --clear \ --new 1:$ESP_SECTOR_START:$ESP_SECTOR_END --typecode=1:ef00 --change-name=1:'EFI System' --partition-guid=1:$PARTUUID \ --attributes 1:set:2 \ $IMGFILE dd if=$ESP_FILE of=$IMGFILE bs=$BLKSIZE count=$ESP_FILE_SIZE_SECTORS conv=notrunc seek=$ESP_SECTOR_START
  • 总尺寸:ESP 文件大小 + 4MB 余量(脚本注释说明分别预留 BIOS boot、MBR 与 GPT 的 1MB 空间);先dd从 /dev/zero 生成空白底图,保证分区表之外的区域为零填充;
  • 对齐起点:ESP 从扇区 2048 开始(1MB 边界,兼容传统 BIOS 引导区与 GPT 头部),结束扇区与 ESP 文件精确吻合;
  • 分区属性:sgdisk创建单个分区——类型码ef00(EFI System Partition)、名称EFI System、--partition-guid=1:$PARTUUID写入前文生成的随机 GUID;--attributes 1:set:2设置第 2 个属性位,即legacy BIOS bootable标志,使该 ESP 在 BIOS 兼容模式下同样可被识别为可引导分区;
  • 写入:最后用dd conv=notrunc seek=2048将 ESP 镜像按扇区数精确拷入磁盘镜像的对应偏移。

九、架构选择与 systemd-boot 二进制映射

make-efi通过TARGETARCH环境变量(缺省回退到uname -m)选择对应架构的引导器:

ARCH=${TARGETARCH:-`uname -m`} case $ARCH in x86_64) BOOTFILE_SRC=/usr/lib/systemd/boot/efi/systemd-bootx64.efi BOOTFILE_DST=BOOTX64.EFI ;; aarch64) BOOTFILE_SRC=/usr/lib/systemd/boot/efi/systemd-bootaa64.efi BOOTFILE_DST=BOOTAA64.EFI ;; riscv64) BOOTFILE_SRC=/usr/lib/systemd/boot/efi/systemd-bootriscv64.efi BOOTFILE_DST=BOOTRISCV64.EFI ;; esac

映射关系与 README 中的布局注释一一对应:x86_64 → BOOTX64.EFI、aarch64 → BOOTAA64.EFI、riscv64 → BOOTRISCV64.EFI,源文件来自容器内 systemd-boot 包的/usr/lib/systemd/boot/efi/目录(由 Dockerfile 第一阶段从linuxkit/systemd-boot镜像带入)。这解释了为何该工具可以跨 amd64 / arm64 / riscv64 三个架构产出各自的 EFI 启动盘。

十、在 LinuxKit 构建流程中使用 raw-efi 格式

将 raw-efi 接入标准构建流程的方式是使用linuxkit build的-format参数。在 build.go 中,buildFormats是一个可重复指定的formatList(支持逗号分隔或多次传参),合法取值来自mobybuild.OutputTypes()。典型用法:

linuxkit build -format raw-efi linuxkit.yml

构建后会在当前目录生成<名字>-efi.img(命名规则见 output.go 的base+"-efi.img")。该原始镜像可直接dd到磁盘或 U 盘,在支持 UEFI 的机器上启动;也可以交给 QEMU 等虚拟机验证。

需要留意的是兼容性前提:官方文档 platform-qemu.md 明确说明qcow-efi与raw-efi格式"可能可用,但当前未测试"(The formats qcow-efi and raw-efi may also work, but are currently not tested)。因此在生产使用前,建议先在目标平台或 QEMU 上做一次实际启动验证。

十一、调试技巧

脚本为排障保留了钩子:

set -e # for debugging [ -n "$DEBUG" ] && set -x

设置DEBUG环境变量即可开启 shell 的-x跟踪,打印每一步执行的命令与变量展开结果。此外,脚本把除最终镜像外的所有日志重定向到 stderr:

# we want everything except the final result to stderr ( exec 1>&2; ... ) cat $IMGFILE

这意味着 stdout 上只出现纯粹的磁盘镜像字节流,任何警告、进度信息都走 stderr——既保证了管道下游(如linuxkit build或dd)收到的数据纯净,也便于在容器日志中观察构建过程。

结语

tools/mkimage-raw-efi/虽小,却完整演示了"现代 UEFI 启动盘的构造艺术":从 stdin 协议接收内核与 initrd,用 ukify 合成 UKI,手工推演 FAT32 的最小化布局,再以 sgdisk 组装 GPT 分区,最终通过 stdout 交付一份对齐、紧凑、可直接启动的 raw 磁盘镜像。理解它的每个计算步骤,不仅能帮你排查 LinuxKit EFI 启动问题,也能为自研嵌入式或云原生引导镜像提供一套可复用的参考实现。

  • 操作系统
  • 云原生
  • 容器运行时

【免费下载链接】linuxkit

A toolkit for building secure, portable and lean operating systems for containers

项目地址:https://gitcode.com/gh_mirrors/li/linuxkit
点击查看免费下载

相关推荐

上一篇:攻克iOS内存难题:SDWebImage智能缓存清理与成本计算全攻略
下一篇:如何使用PHPoAuthLib快速实现第三方登录?从入门到精通的完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 2:29:55

Python空气质量数据挖掘与机器学习可视化分析系统实战

简介&#xff1a;本资源面向环境科学、数据挖掘与机器学习方向的学习者与研究者&#xff0c;提供一套基于Python的空气质量数据可视化分析系统源码及配套数据&#xff0c;可用于城市群划分、污染传输网络构建与传播过程探索等课题实践。项目采用BS架构&#xff0c;前端整合HTML…

作者头像 李华
网站建设 2026/9/28 2:29:34

WinPcap实现UDP精确发包:绕过协议栈的源码与调试指南

简介&#xff1a;一份基于WinPcap实现的UDP发包程序源码包&#xff0c;面向网络编程初学者、协议分析爱好者及课程设计开发者。程序演示了如何借助WinPcap在驱动层完成UDP数据报的构造与发送&#xff0c;适合用作理解UDP无连接特性和底层封包机制的入门范例&#xff0c;也可延伸…

作者头像 李华
网站建设 2026/9/28 2:27:38

暗黑破坏神2 MOD修改工具装备编辑防具物品

装备编辑-防具物品 对应的是 armor.txt 这张基础防具数据表。在暗黑破坏神2 的 TXT 体系里,防具并不只是一个提供防御值的静态条目,它同时参与角色需求判定、掉落生成、商店售卖、孔位规则、名称显示以及描述文本联动。像 minac、maxac 会直接影响基础防御范围,reqstr、reqd…

作者头像 李华
网站建设 2026/9/28 2:26:12

Python皮肤电信号情绪识别实操:从预处理到模型训练

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华