简介:《OpenHarmony-X86-引导程序》是一份面向X86架构硬件运行OpenHarmony系统的启动引导资源,适用于嵌入式开发者、系统集成工程师,以及希望将鸿蒙开源系统移植到PC或服务器场景的实践者,主要解决X86设备冷启动时的引导加载、内核装载与硬件抽象层衔接等问题。压缩包仅4.43MB,共含311个文件,以265个mod引导模块为主体,辅以efi可执行文件、cfg配置、lst模块清单和png示意图,既展示了从UEFI到GRUB再到内核的完整引导链路,也便于按需裁剪模块。目前已有1697人学习/下载,在X86适配场景中具备一定参考热度。资源内保留了EFI启动文件、GRUB配置、模块依赖列表与加载顺序定义,可以直接作为定制启动流程的基线;对于需要分析X86引导过程、排查启动异常或编译精简模块的开发者,这是具备实操价值的样例。理解这份引导程序还需要了解引导加载程序原理与内核加载机制,因此它也适合作为系统底层学习的小型案例。
1. OpenHarmony 上 x86,难的不是编译而是引导
大多数人默认 OpenHarmony 是 ARM 的专属系统,拿到 x86 的板子第一反应是“官方不支持吧”。实际上只要引导链路走得通,x86 平台跑 OpenHarmony 内核反而比某些 ARM 开发板省事——你有标准 UEFI 固件、有现成的 grub2 工具链、有随处可见的串口调试线。这个资源包里装的不是系统镜像,而是一整套引导程序框架:从 UEFI 启动到 grub2 菜单、再到内核 cmdline 传参和 ramdisk 加载,每一步都有对应的脚本和配置文件。适合两类人:一是要在 x86 工控机或 NUC 上评估 OpenHarmony 的开发者,二是想彻底搞懂 Linux 系内核引导机制、把启动流程每一帧都看清楚的学习者。这里不讨论怎么编译系统,只讲怎么让它正常站起来。
2. 引导链路拆解:x86 平台为什么绕不开这三段
2.1 从 UEFI 到 grub2:firmware 怎么把控制权交出来
x86 平台的 OpenHarmony 引导和 ARM 上最大的区别在第一跳。ARM 开发板通常用 U-Boot,x86 板子则几乎全是 UEFI 固件。UEFI 启动时会扫描 ESP 分区(EFI System Partition)里的 .efi 文件,找到 \EFI\BOOT\BOOTX64.EFI 就加载执行。这个 BOOTX64.EFI 就是我们放进去的 grub2 引导器。
很多人第一次接触会问:OpenHarmony 内核不能直接被 UEFI 加载吗?理论上如果内核编成了 EFI stub 格式,UEFI 可以直接引导 vmlinuz。但实际项目中没人这么干,原因很直接:你需要在启动阶段动态决定内核参数、切换不同的 ramdisk、甚至同一块硬盘上保留多个系统版本回退。grub2 给了你一个交互式命令行和脚本环境,这是 EFI stub 给不了的。
这套资源的引导配置文件不是随手写的模板,它包含了对分区 UUID 的自动探测逻辑。传统 grub.cfg 里直接写 root=/dev/sda3 这种硬编码,换一块硬盘或者换一个 USB 口就可能崩。资源里的做法是:
search --no-floppy --fs-uuid --set=root 你的根分区UUID这段命令的含义是让 grub 遍历所有可见设备,找到 UUID 匹配的分区,把它设为 root 变量。后面所有加载内核和 ramdisk 的路径都基于这个变量展开,相对路径写起来也干净。--set=root 指定结果变量名,--fs-uuid 表示按文件系统 UUID 匹配,--no-floppy 跳过软驱设备加快扫描速度。
2.2 内核传参:cmdline 才是 OpenHarmony 上 x86 的关键玄学
OpenHarmony 内核跑在 x86 上,系统能否起来,多半取决于 cmdline 是否给了足够的线索。ARM 平台很多设备驱动靠设备树描述,x86 平台没有设备树的概念,一切靠 ACPI 和内核参数。
资源包里给出的 cmdline 模板有几个参数值得单独拿出来说:
root=/dev/mmcblk0p3 rw rootwait console=ttyS0,115200n8 console=tty0 quiet- root= 指定根文件系统位置。这个资源的默认配置用的是 /dev/mmcblk0p3,对应很多 x86 开发板引导时把系统装在 eMMC 或 SD 卡上的场景
- rw 表示根分区挂载为读写,系统起来后要写 /var、/tmp 这类目录,只读会直接导致启动失败
- rootwait 让内核无限期等待根设备出现,对 USB 硬盘或慢速 SD 卡是必要的,否则内核可能在设备枚举完成前就尝试挂载
- console=ttyS0,115200n8 把内核日志输出到串口,调试必备
- console=tty0 同时保留显示输出,两者共存时内核会把消息发给所有 console 设备
- quiet 减少启动时的内核打印,如果调试阶段建议去掉,否则看不到设备枚举过程
实际调试中遇到无声无息的黑屏,九成和 console 参数有关。常见做法是先去 quiet 和 consoleblank=0,让内核全部日志铺满屏幕。曾经在一台工控机上怎么都起不来,去掉 quiet 才发现是 ACPI 表解析阶段卡死了。
2.3 引导器选型:为什么是 grub2 而不是 systemd-boot
资源包选 grub2,一个很实际的理由是生态完整。x86 平台上的 OpenHarmony 移植还不是主流操作,文档少、可参考的案例少,那么引导器就应该选最不挑环境、报错最直接的那个。grub2 的命令行交互界面对排查启动问题几乎是不可替代的:启动时按 c 进入 grub shell,可以手动执行 ls、search、cat 这些命令确认设备和文件系统状态。systemd-boot 配置简单,但一旦起不来基本只能干瞪眼。
处理多系统共存的时候 grub2 的 os-prober 和 chainloader 也比 systemd-boot 灵活。比如一块硬盘上同时有 OpenHarmony 根分区和另一个 Linux 发行版,grub2 可以做启动菜单选择。资源包里的 40_custom 脚本就展示了怎么用 chainloader 跳转。
menuentry 'Other Linux' { search --no-floppy --fs-uuid --set=root 目标分区UUID chainloader +1 }这里 chainloader +1 表示加载当前 root 分区起始扇区的引导代码,适合 MBR 分区表或者待跳转分区自带引导器的情况。如果是 EFI 系统,应该改成 chainloader /EFI/BOOT/BOOTX64.EFI 直接加载另一个系统的 EFI 引导文件。
3. 手把手配置引导菜单:直接可以抄作业的 grub.cfg
3.1 基础菜单结构:定义默认项和超时
资源包里提供的 grub.cfg 不是精简到没法看的玩具,而是带完整菜单框架、可以直接放到真实设备上跑的结构。最外层是超时和默认项定义。
set default=0 set timeout=5 menuentry 'OpenHarmony x86' { insmod part_gpt insmod ext2 search --no-floppy --fs-uuid --set=root 4f7a-9c3b-2e11-8a6d linux /boot/kernel-ohos root=/dev/mmcblk0p3 rw rootwait console=ttyS0,115200n8 console=tty0 initrd /boot/ramdisk-ohos.img }set default=0 表示默认启动第一个菜单项,set timeout=5 表示停留 5 秒后自动进入默认项。insmod part_gpt 加载 GPT 分区表支持模块,insmod ext2 加载 ext 文件系统驱动,这两个模块不加载的话后面 search 和 linux 命令都无法工作,因为 grub 既读不出分区也读不出文件。
linux 和 initrd 两行是核心。linux 指令加载内核镜像到内存,后面跟的是传给内核的 cmdline。initrd 指令把 ramdisk 镜像加载到内存,内核起来后会把它作为临时根文件系统。注意这里 root=/dev/mmcblk0p3 和前面 search 的 UUID 指向的分区应该是同一个,只不过 search 是 grub 阶段用来定位启动文件,root= 是内核阶段用来挂根文件系统。两者职责不同,经常有人把这两处搞混。
3.2 多版本回退菜单:给你的系统留一颗后悔药
做过系统移植的人都有这种经历:新内核起不来,旧内核又被覆盖了,只能重新刷机。资源包里的配置专门做了多版本菜单,每个版本对应一个菜单项,避免这种尴尬。
menuentry 'OpenHarmony x86 5.10.1' { linux /boot/kernel-5.10.1 root=/dev/mmcblk0p3 rw rootwait initrd /boot/ramdisk-5.10.1.img } menuentry 'OpenHarmony x86 5.10.0 (backup)' { linux /boot/kernel-5.10.0 root=/dev/mmcblk0p3 rw rootwait initrd /boot/ramdisk-5.10.0.img }这里把内核版本号直接写进文件名,好处是菜单一眼可以看出当前引导的是哪个版本。开发阶段建议至少保留两个版本,一个是已知能启动的兜底版本,一个是正在调试的新版本。grub.cfg 里 set default= 可以临时改成备份项的编号,按住 shift 键也可以在启动时强制显示菜单手动选择,鼠标键盘操作比重新烧录快太多了。
3.3 ramdisk 与根文件系统的配合方式
OpenHarmony 的 x86 引导中,ramdisk 的作用在某些场景下会被放大。如果根文件系统是只读的 squashfs 镜像,那么 ramdisk 不仅要完成内核模块的加载,还要承担把 overlayfs 挂起来的任务——也就是把可写层覆盖到只读层上面。
mount -t squashfs /dev/mmcblk0p3 /mnt/root-ro mount -t overlay overlay -o lowerdir=/mnt/root-ro,upperdir=/mnt/root-rw,workdir=/mnt/root-work /mnt/root这段脚本通常放在 ramdisk 里的 init 脚本中。第一步把只读的 squashfs 挂到临时目录,第二步用 overlayfs 把可写目录叠加上去,最终根目录变成可写的。lowerdir 是只读底层,upperdir 是改动写入层,workdir 是 overlayfs 的工作目录,三者缺一不可。如果 ramdisk 里没有这套逻辑,只读根文件系统的系统会出现启动后无法创建临时文件、服务起不来的现象。
资源包里附带的 ramdisk 构建脚本用的就是这种方案,把 init 脚本做成模块化的,加载设备驱动、挂载根 fs、切换到真实根目录三步分离,每一步都有 set -x 开启详细日志,方便调试时在串口上逐行看执行到哪一步挂掉的。
4. 制作可启动镜像:把引导程序固化到介质上
4.1 分区表布局:ESP 分区不能省
拿到这套引导程序资源,第一步不是改代码,而是把镜像写到一块空白 U 盘或者硬盘上。x86 设备从 UEFI 启动,介质上必须有一个 FAT 格式的 ESP 分区,否则固件根本找不到启动文件。
sudo parted /dev/sdX --script mklabel gpt sudo parted /dev/sdX --script mkpart ESP fat32 1MiB 513MiB sudo parted /dev/sdX --script set 1 esp on sudo parted /dev/sdX --script mkpart root ext4 513MiB 100%三行 parted 命令完成分区布局。mklabel gpt 把磁盘标签设为 GPT,这是 UEFI 启动的前提,Legacy MBR 虽然也能引导但不是这套资源的目标场景。mkpart ESP fat32 1MiB 513MiB 创建 512MiB 的 ESP 分区,set 1 esp on 给它打上 ESP 标记。第二个分区给根文件系统,ext4 格式。1MiB 起始偏移是给 grub2 的 BIOS 引导区留空间,虽然纯 UEFI 模式用不到,但保留这个偏移不会错。
分区做完后分别格式化:
sudo mkfs.vfat -F 32 /dev/sdX1 sudo mkfs.ext4 /dev/sdX24.2 拷贝引导文件到 ESP 分区
引导程序资源包的核心文件是 grubx64.efi、grub.cfg、kernel 和 ramdisk 镜像。ESP 分区的目录结构有讲究,UEFI 固件默认找 \EFI\BOOT\BOOTX64.EFI,所以 grubx64.efi 必须放成这个名字。
sudo mount /dev/sdX1 /mnt sudo mkdir -p /mnt/EFI/BOOT sudo cp grubx64.efi /mnt/EFI/BOOT/BOOTX64.EFI sudo cp grub.cfg /mnt/EFI/BOOT/grub.cfggrub.cfg 放在 BOOTX64.EFI 同目录下是 grub2 的默认搜索路径之一,它会先找 /EFI/BOOT/grub.cfg,找不到再尝试 /boot/grub/grub.cfg。对于做引导移植的场景,把两者都放一份是稳妥做法。grub.cfg 里 linux 和 initrd 指向的 /boot/kernel-ohos 和 /boot/ramdisk-ohos.img 文件在哪个分区?答案在 4.1 里的 grub.cfg 配置——search 命令会定位到根文件系统所在分区,然后从该分区的 /boot 目录读取这些文件。所以要么把整个根分区内容拷贝进去,要么至少把 /boot 目录结构搭好。
这里有个资源包已经处理好的细节:root=/dev/mmcblk0p3 是块设备路径,但实际运行时内核设备名可能因为存储控制器驱动加载顺序不同而变化。更稳的办法是 root=UUID=一串十六进制值,内核会按 UUID 去扫设备。资源包里的模板两种写法都给了,注释里明确建议生产环境用 UUID。提取 UUID 用 blkid 命令:
sudo blkid /dev/sdX24.3 验证引导介质完整性
写完了别急着拔 U 盘,先用 grub 自带的校验工具确认配置语法没问题。资源包里带了 grub-script-check 脚本,和 grub2 工具链一起装机时有。
grub-script-check grub.cfg这个工具只检查语法是否正确,不检查文件路径是否存在,所以还需要手动确认内核和 ramdisk 文件路径。
sudo mount /dev/sdX2 /mnt/root ls -lh /mnt/root/boot/kernel-ohos ls -lh /mnt/root/boot/ramdisk-ohos.img特别注意文件的权限位,内核和 ramdisk 文件不需要可执行权限,但必须对 grub 可读。曾经遇到过文件权限 600、只有 root 可读的情况,grub 在加载时表现为主机直接重启,不报任何错误——这个问题非常隐蔽,检查权限是第一步。再确认分区的 UUID 和 grub.cfg 里写的一致,不一致的话 search 命令会静默失败。
blkid /dev/sdX1比较输出里的 UUID 和 grub.cfg 里 search 行的值就可以了。
5. 引导排查避坑:起不来别急着重刷,先看这几个位置
5.1 现象:grub 菜单能显示,选启动项后黑屏
这是最常见的故障态。菜单出来了、倒计时走了、回车之后屏幕一黑就没有下文。原因列表里第一个要看的就是 cmdline 的 console 参数。前面提到过这个追查套路——黑屏后先敲一下键盘看屏幕有没有反应,如果 NumLock 灯都无响应,大概率在内核早期初始化就挂了。此时唯一的信息来源是串口。
解决方法是把 console=tty0 去掉,只保留 console=ttyS0,115200n8,然后接上 USB 转串口线看完整日志。串口输出比屏幕输出更全,内核早期日志不会丢。看到最后一行打印停在某个驱动初始化、或者 ACPI 报错,再对症处理。资源包的调试文档里专门有一章列出常见卡点对应的日志特征。
5.2 现象:串口输出显示 VFS: Unable to mount root fs
内核起来了,设备驱动加载了,但挂载不了根文件系统。原因很直接:root= 参数指向的设备不存在。在 x86 平台上,块设备命名可能随驱动加载顺序变化——同样的 /dev/mmcblk0p3,换一次 BIOS 设置或者换一个启动介质插口就可能变成 /dev/mmcblk1p3。解决方式是改用 UUID。
root=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx这种写法由内核自己扫描所有块设备的 UUID,不依赖设备节点命名顺序。另外一个注意点是 initrd 里要有对应块设备的驱动,比如 NVMe 硬盘需要 nvme 驱动,如果 ramdisk 里没打进去,内核永远找不到 root 设备。确认方法是在串口日志里搜 nvme 或 mmcblk 字样,看设备是否被枚举出来。
5.3 现象:initrd 解压失败,报 bad gzip magic
ramdisk 镜像损坏或者格式不对。资源包里构建 ramdisk 时常见的压缩格式是 gzip,grub 的 initrd 指令能自动识别压缩格式,但它不管压缩流是否完整。最常出现这种错误是在 Windows 环境下解压再重新打包 ramdisk,文件格式被改变。解决办法是在 Linux 环境重新用构建脚本生成 ramdisk:
gzip -t ramdisk-ohos.img这条命令测试 gzip 压缩流的完整性,返回正常才说明文件没坏。如果构建脚本输出的本来就是未压缩的 cpio 格式,grub 一样能加载,但文件会大很多。
5.4 现象:grub 菜单都出不来,直接进固件设置
ESP 分区没有正确制作,或者 BOOTX64.EFI 文件不在标准路径。UEFI 固件找不到启动项时通常不会报错,直接回退到固件设置界面。检查方法有两个:一是看 ESP 分区是否标记了 esp 标志,二是确认 BOOTX64.EFI 文件名完全正确。x86 UEFI 固件对启动文件名的大小写不敏感,但路径分隔符必须用反斜杠,拷贝时别把整个 EFI 目录放在盘符根路径下。
有时候是固件里启动了 Secure Boot 导致 grubx64.efi 签名校验失败。资源包里的 grubx64.efi 没有签名,遇到这种情况要么在固件里关掉 Secure Boot,要么给 grub 配一个 Machine Owner Key 做签名。对调试阶段而言,关掉 Secure Boot 是最直接的路径。
5.5 现象:内核启动后重启循环
这种症状最迷惑人。如果串口日志最后几行是 kernel panic,那就是根文件系统 init 进程执行失败;如果日志直接断开没有任何 panic 信息,多半是 ACPI 触发了系统复位。x86 平台上跑非主流系统,ACPI 表里某些 _DSM 方法不兼容可能导致固件直接断电重启。解决方案是给 cmdline 加 acpi=off 试一次——如果加了之后能正常启动,说明确实是 ACPI 兼容性问题,再逐步缩小范围定位具体是哪个表的问题。但注意 acpi=off 会禁用大部分电源管理和设备枚举功能,只适合做故障定位,不适合长期运行。
6. 验证引导成果:三段日志确认系统真正跑起来了
引导程序配好,不能只看屏幕亮了就宣布成功。OpenHarmony 内核起来后,你要在三个时间点各瞄一眼日志,确认整个链路是健康的。
第一段在 grub 阶段。启动时按 c 进入 grub shell,执行 ls 命令看到所有分区,再执行 ls (hd0,1)/ 能看到 EFI 目录里的文件。这说明 grub 的文件系统驱动、分区识别、设备扫描都正常。这一步能拦下 80% 的“文件没拷全”类问题。
第二段在内核早期。串口或屏幕输出里出现 Freeing unused kernel memory 和 Run /init as init process 字样,说明内核解压完成、ramdisk 挂载成功、init 进程开始执行。
第三段是系统真正起来。串口上有 getty 登录提示符,或者网络接口能拿到 DHCP 地址。
从那以后,我每次做完引导配置都会强制走一遍这三段日志检查,不跳过任何一步。之前偷懒只看屏幕亮了就收工,结果有一次 root 分区虽然挂上了但 overlay 层没起来,系统看似正常,一写文件就报只读错误,排查花了一下午。从那以后连 mount 状态都用 cat /proc/mounts | grep overlay 确认过才放心。
这套流程走完,OpenHarmony 在 x86 上的引导链路就是完全可控的状态了——从 UEFI 到 grub 到内核再到 rootfs,每一跳都有日志可查、有命令可验证。就算以后再遇到起不来的新内核,也只需要按着 5.5 里的排查顺序走下去,大概率十分钟内能定位到问题。希望帮到你。
本文还有配套的精品资源,点击获取