SerenityOS 高级构建指南:磁盘镜像定制、SuperBuild、CMake 选项与 Clang 工具链实战
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
本篇指南以仓库 Documentation/AdvancedBuildInstructions.md 为骨架,面向已经完成基础构建(见 Documentation/BuildInstructions.md)的开发者,系统讲解 SerenityOS 构建系统的高级玩法:如何通过sync-local.sh定制磁盘镜像、如何用SERENITY_ARCH切换目标架构、如何绕过Meta/serenity.sh直接调用 Ninja 目标、如何精确控制 CMake 构建选项与 CMake 缓存、SuperBuild 的宿主/目标分离机制、组件裁剪,以及基于 Clang 的整套工具链构建。读完本文,你将能够根据开发场景自由定制、调试与裁剪 SerenityOS 的构建产物。
定制磁盘镜像:sync-local.sh让改动在重建后持久生效
默认情况下,基础构建指南生成的磁盘镜像内容是固定的;每次重建镜像,手动写入根文件系统的改动都会被覆盖。AdvancedBuildInstructions.md给出的方案是:在项目根目录创建一个名为sync-local.sh的 shell 脚本,构建镜像时构建系统会执行它,从而把定制内容"烧"进镜像文件系统。
示例脚本(来自原文档,路径为项目根目录):
#!/bin/sh set -e cat << 'EOF' > mnt/etc/Keyboard.ini [Mapping] Keymaps=de EOF # Add a file in anon's home dir cp /somewhere/on/your/system/file.txt mnt/home/anon要点说明:
- 工作目录:脚本执行时,当前目录位于被挂载的镜像根文件系统(脚本内使用
mnt/etc/...、mnt/home/...这样的相对路径),因此可以直接写入/etc、/home/anon等系统路径; set -e:任何一条命令失败即中止脚本,避免在镜像处于不一致状态时继续写入;- 默认键盘布局:示例把系统默认键盘布局改成德语(
Keymaps=de)。SerenityOS 的完整键位映射列表位于仓库 Base/res/keymaps/,包含en-us.json、de.json、de-ch.json、fr.json、dvorak.json、colemak.json、jp.json等数十种布局文件; - 与
keymap程序的差异:运行中的系统里,keymap程序同样会修改/etc/Keyboard.ini,但那种改动只对当前会话生效、重启后丢失;而sync-local.sh中的改动写入了镜像本身,会在每次镜像重建时重新应用,因此能够跨重建持久化。
这一机制的实际挂载流程由Meta/serenity.sh的image子命令触发(构建、安装镜像后调用build_image,详见 Meta/serenity.sh),它是定制开发环境(预装文件、调整系统配置、注入测试数据)最直接的手段。
选择目标架构:SERENITY_ARCH
构建脚本默认按宿主机的 CPU 架构构建,但 SerenityOS 是跨架构操作系统,可以显式指定目标架构:
SERENITY_ARCH=aarch64 Meta/serenity.sh runSERENITY_ARCH支持的取值(与 Meta/CMake/Superbuild/CMakeLists.txt 中的SERENITY_ARCH缓存变量一致):
| 取值 | 说明 |
|---|---|
x86_64 | 默认目标,覆盖 QEMU、VirtualBox、VMware 以及绝大多数 PC 硬件 |
aarch64 | ARM 64 位架构,常用于 Raspberry Pi 等 ARM 硬件 |
riscv64 | RISC-V 64 位架构,配合 RISC-V 模拟器或真实硬件使用 |
从 Meta/serenity.sh 的源码看,目标解析逻辑(is_valid_target)会把TARGET参数转换为对应的-DSERENITY_ARCH=CMake 参数;同时TARGET支持aarch64、x86_64、riscv64、lagom四种取值,默认取SERENITY_ARCH环境变量,若未设置则取宿主机架构(${SERENITY_ARCH:-${HOST_ARCH}})。
Ninja 构建目标:绕过脚本直接调用底层目标
Meta/serenity.sh只是对 CMake/Ninja 构建目标的一层抽象,部分目标无法通过脚本直接访问,需要先cd Build/<architecture>再执行ninja <target>。完整清单如下:
| 目标 | 作用 |
|---|---|
ninja limine-image | 构建带 Limine 引导的 x86-64 磁盘镜像(limine_disk_image) |
ninja grub-image | 构建带 GRUB 的 x86-64 磁盘镜像(grub_disk_image),面向传统 BIOS |
ninja grub-uefi-image | 构建带 GRUB 的 x86-64 UEFI 磁盘镜像(grub_uefi_disk_image) |
ninja extlinux-image | 构建带 extlinux 的 x86-64 磁盘镜像(extlinux_disk_image) |
ninja raspberry-pi-image | 构建面向树莓派的 AArch64 磁盘镜像(raspberry_pi_disk_image) |
ninja check-style | 运行与 CI 相同的代码风格检查(针对改动文件) |
ninja install-ports | 把整个 Ports 目录树复制进已安装的 rootfs,供在 SerenityOS 内构建 Ports 使用 |
ninja lint-shell-scripts | 用 shellcheck 检查源码树中的 shell 脚本风格 |
ninja all_generated | 构建全部生成代码,适合在不需要完整系统构建的情况下,配合compile_commands.json运行分析工具 |
ninja configure-components | 启动组件配置工具,详见下文"组件配置"一节 |
另外,Meta/serenity.sh的run子命令在默认(非 limine)路径下实际调用的是qemu-image目标(见build_image函数),只有显式设置SERENITY_RUN=limine时才改用limine-image。
CMake 构建选项:为特定开发场景打开开关
构建系统把大量可选功能做成 CMake 选项,供不同开发场景(内存调试、模糊测试、覆盖率、内核调试等)按需开启。以下是原文档列出的完整选项及说明:
ENABLE_ADDRESS_SANITIZER/ENABLE_KERNEL_ADDRESS_SANITIZER:分别为 Lagom 测试用例与内核开启内存破坏(缓冲区溢出、内存泄漏等)的运行时检查;ENABLE_KERNEL_UNDEFINED_SANITIZER:为内核开启未定义行为运行时检查;ENABLE_KERNEL_UNDEFINED_SANITIZER_ALWAYS_DEADLY:让上述内核 UBSan 检查在编译器视角下"永远致命"(一旦触发即终止);ENABLE_KERNEL_COVERAGE_COLLECTION:启用 KCOV API 与内核覆盖率插桩,仅用于覆盖率引导的内核模糊测试;ENABLE_USERSPACE_COVERAGE_COLLECTION:为用户态启用覆盖率插桩,当前仅支持 Clang 构建;ENABLE_MEMORY_SANITIZER:在 Lagom 测试用例中检测未初始化内存访问;ENABLE_UNDEFINED_SANITIZER:在 Lagom 与 SerenityOS 用户态开启 UBSan(如空指针解引用、有符号整数溢出);UNDEFINED_BEHAVIOR_IS_FATAL:让所有 UBSan 错误不可恢复,可降低ENABLE_UNDEFINED_SANITIZER的性能开销;ENABLE_COMPILER_EXPLORER_BUILD:仅对 Lagom 生效,跳过非库实体的构建;ENABLE_FUZZERS:为系统各组件构建模糊测试器;ENABLE_FUZZERS_LIBFUZZER:构建基于 Clang libFuzzer 的模糊测试器;ENABLE_FUZZERS_OSSFUZZ:构建与 OSS-Fuzz 兼容的模糊测试器;模糊测试相关背景见 Meta/Lagom/ReadMe.md;ENABLE_EXTRA_KERNEL_DEBUG_SYMBOLS:以内核-Og -ggdb3编译(默认为-O2),方便调试内核代码;ENABLE_ALL_THE_DEBUG_MACROS:用于 CI 检查调试代码能否编译,日常不建议开启(会刷屏且拖慢系统);ENABLE_ALL_DEBUG_FACILITIES:同时启用ENABLE_ALL_THE_DEBUG_MACROS与ENABLE_EXTRA_KERNEL_DEBUG_SYMBOLS,同样仅供 CI;ENABLE_COMPILETIME_FORMAT_CHECK:编译期校验std::format风格格式化字符串的合法性,默认开启;ENABLE_PCI_IDS_DOWNLOAD:构建时下载 PCI 设备 ID 数据库(pci.ids),若本地不存在,默认开启;BUILD_LAGOM:构建 Lagom,把 SerenityOS 的各类库与程序带到宿主机上运行;ENABLE_KERNEL_LTO:以内核链接时优化(LTO)构建;ENABLE_MOLD_LINKER:用户态使用 mold 链接器(可通过Toolchain/BuildMold.sh构建);ENABLE_JAKT:把 jakt 编译器构建为 Lagom 宿主工具,并启用 jakt 语言编写的应用与库;JAKT_SOURCE_DIR:jakt 开发者本地检出路径,用于快速迭代测试,例如cmake -S Meta/Lagom -B Build/lagom -DENABLE_JAKT=ON -DJAKT_SOURCE_DIR=/home/me/jakt;INCLUDE_WASM_SPEC_TESTS:下载并纳入 WebAssembly 规范测试套件,需安装prettier与 1.0.35 以上版本的wabt;INCLUDE_FLAC_SPEC_TESTS:下载并纳入 xiph.org FLAC 测试套件;SERENITY_TOOLCHAIN:选择 GNU 工具链或实验性 Clang 工具链,详见下文"Clang 工具链"一节;SERENITY_ARCH:指定目标架构,支持x86_64、aarch64、riscv64;BUILD_<component>:构建指定组件,如BUILD_HEARTS(注意必须全大写),可用组件清单见构建目录下的components.ini;关闭组件后务必执行ninja clean与rm -rf Build/x86_64/Root;推荐通过ConfigureComponents工具配置,见下文;BUILD_EVERYTHING:构建全部可选组件,开启后覆盖其他BUILD_<component>标志;SERENITY_CACHE_DIR:设置下载文件的共享缓存目录,一般仅在维护发行包时需要;ENABLE_NETWORK_DOWNLOADS:允许构建过程中联网下载,默认开启;关闭后可离线构建,但SERENITY_CACHE_DIR的目录结构必须符合构建预期;ENABLE_ACCELERATED_GRAPHICS:启用基于原生图形库加速绘制的图形特性。
这些选项的实际落点在仓库的 Meta/CMake/serenity_options.cmake、Meta/CMake/lagom_options.cmake 与 Meta/CMake/common_options.cmake 中定义,SuperBuild 再通过serenity_option宏把同名缓存变量透传给 Lagom 与 Serenity 两个子构建(见 Meta/CMake/Superbuild/CMakeLists.txt)。
按组件开关调试宏
SerenityOS 大量模块内置调试功能,主要表现为向调试控制台输出额外日志,通过<组件名>_DEBUG宏逐个控制,完整清单见 Meta/CMake/all_the_debug_macros.cmake(覆盖PROCESS_DEBUG、ACPI_DEBUG、VFS_DEBUG、TCP_DEBUG、KMALLOC_DEBUG、HEARTS_DEBUG等上百个宏)。日常开发建议只开启需要的宏,而不是用ENABLE_ALL_THE_DEBUG_MACROS一刀切。
CMake 缓存操作:三种方式修改构建配置
CMake 把变量与选项缓存在二进制目录(Build/...)中,开发者可以随时调整set()到持久配置缓存里的变量。有三种主要方式:
cmake path/to/binary/dir -DVAR_NAME=Valueccmake(终端 TUI 界面)cmake-gui(图形界面)
选项既可以在首次cmake创建二进制目录时作为初始缓存传入,也可以在目录创建后通过上述任一方式修改。布尔类选项(如ENABLE_<setting>、<组件名>_DEBUG)用ON/OFF控制:
# Reconfigure an existing binary directory with process debug enabled $ cmake -B Build/x86_64 -DPROCESS_DEBUG=ON修改后重新执行ninja或cmake --build即可让改动生效。
SuperBuild 配置:宿主工具与目标系统的分离构建
Serenity 使用地道的 Serenity C++ 编写宿主工具,为目标构建生成代码与数据。"SuperBuild" 模式把"核心 Serenity 库的宿主构建"与"整个操作系统的目标构建"分离:一方面让项目的 CMakeLists 明确区分宿主/目标构建,另一方面统一了不同编译器工具链与不同架构的处理方式。
推荐的运行方式./Meta/serenity.sh run等价于以下 SuperBuild 命令序列:
$ cmake -GNinja -S Meta/CMake/Superbuild -B Build/superbuild-x86_64 -DSERENITY_ARCH=x86_64 -DSERENITY_TOOLCHAIN=GNU $ cmake --build Build/superbuild-x86_64 $ ninja -C Build/x86_64 setup-and-run从 Meta/CMake/Superbuild/CMakeLists.txt 的源码可以看到,superbuild-<arch>目录的 CMake 配置会创建两个 ExternalProject:
lagom:项目宿主构建(源码目录为Meta/Lagom,二进制目录为Build/lagom,安装前缀为Build/lagom-install),负责构建目标系统编译所需的全部代码生成器与宿主工具,背景见 Meta/Lagom/ReadMe.md;serenity:主构建,使用选定工具链为目标架构编译整个系统,且显式依赖lagom-install步骤(DEPENDS lagom-install),并通过-DCMAKE_PREFIX_PATH指向 Lagom 的安装前缀,让find_package(Lagom REQUIRED)找到宿主工具用于代码生成自定义命令。
SuperBuild 配置还会根据-DSERENITY_ARCH与-DSERENITY_TOOLCHAIN生成所选工具链/架构的 CMake 交叉编译工具链文件,模板分别是 Toolchain/CMake/GNUToolchain.txt.in 与 Toolchain/CMake/ClangToolchain.txt.in(同时还会生成对应的meson-cross-file-<toolchain>.txt)。此外,SuperBuild 把下游构建目录统一放在Build/<arch><toolchain后缀>(非 GNU 工具链会追加小写工具链名作为后缀)。
手工等价流程(原文档给出的展开形式)大致是:
# Generate CMakeToolchain.txt mkdir -p Build/x86_64 cp Toolchain/CMake/GNUToolchain.txt.in Build/x86_64/CMakeToolchain.txt sed -i 's/@SERENITY_ARCH@/x86_64/g' Build/x86_64/CMakeToolchain.txt sed -i 's/@SERENITY_SOURCE_DIR@/'"$PWD"'/g' Build/x86_64/CMakeToolchain.txt sed -i 's/@SERENITY_BUILD_DIR@/'"$PWD"'\/Build\/x86_64/g' Build/x86_64/CMakeToolchain.txt # Configure and install Lagom cmake -GNinja -S Meta/Lagom -B Build/lagom -DCMAKE_INSTALL_PREFIX=${PWD}/Build/lagom-install ninja -C Build/lagom install # Configure and install Serenity, pointing it to Lagom's install prefix cmake -GNinja -B Build/x86_64 -DCMAKE_PREFIX_PATH=${PWD}/Build/lagom-install -DSERENITY_ARCH=x86_64 -DCMAKE_TOOLCHAIN_FILE=${PWD}/Build/x86_64/CMakeToolchain.txt ninja -C Build/x86_64 install两者差异在于:SuperBuild 在 Lagom 的 install 阶段与 Serenity 的 configure/build 阶段之间建立了依赖关系,因此只要把后续ninja/cmake --build指向superbuild-<arch>目录,宿主机与目标构建共享的头文件或 cpp 文件一旦变更,就会自动触发重建,并把新生成的宿主工具与库同步到 lagom-install。
SuperBuild 的主要限制是:非选项类 CMake 缓存变量(如组件配置、调试标志)必须等构建开始后才能设置——因为 Serenity 与 Lagom 的CMakeCache.txt要等 SuperBuild 构建推进到对应阶段才会生成。构建后的调试标志调整示例:
# Initial build, generate binary directories for both child builds $ cmake -GNinja -S Meta/CMake/Superbuild -B Build/superbuild-x86_64 -DSERENITY_ARCH=x86_64 -DSERENITY_TOOLCHAIN=GNU $ cmake --build Build/superbuild-x86_64 # Turn on process debug and don't build the browser for the Serenity build $ cmake -B Build/x86_64 -DPROCESS_DEBUG=ON -DBUILD_BROWSER=OFF $ ninja -C Build/x86_64 install # Build host tests in Lagom build $ cmake -S Meta/Lagom -B Build/lagom -DBUILD_LAGOM=ON $ ninja -C Build/lagom install组件配置:ConfigureComponents交互式裁剪系统
如果想精细选择构建与安装哪些系统组件,可以使用辅助程序ConfigureComponents。它依赖whiptail(多数发行版在newt或libnewt软件包中提供)。在Build/x86_64目录下执行:
$ ninja configure-components工具会先询问要使用的构建类型,再允许手动增删组件;确认后,它会基于选择运行相应的 CMake 命令,并执行ninja clean与rm -rf Root清理旧构建产物。可用的组件清单在构建目录的components.ini中,命令行下也可以通过-DBUILD_<组件名>=ON/OFF(全大写)直接控制,组件选项的定义见 Meta/CMake/serenity_components.cmake。
运行测试
宿主测试(Lagom 在宿主机上运行)与目标测试(在 SerenityOS 内运行)的差异、以及 CI 测试失败的排查方法,详见 Documentation/RunningTests.md。补充一点脚本层细节:Meta/serenity.sh test lagom [TEST_NAME_PATTERN]会把测试名透传给 ctest 的-R正则过滤器,并配合--output-on-failure --test-dir <build_dir>执行;而Meta/serenity.sh test <target>则以graphics_subsystem_mode=off system_mode=self-test内核参数在 QEMU 中跑自测镜像(见 Meta/serenity.sh 的run_tests与test分支)。
在 VirtualBox 与 VMware 中运行
除 QEMU 外,SerenityOS 也支持在 VirtualBox 与 VMware 中运行。安装步骤分别见 Documentation/VirtualBox.md 与 Documentation/VMware.md。
裸机安装
更"硬核"的玩家可以准备合适的硬件,把 SerenityOS 安装到物理 PC 上,步骤见 Documentation/BareMetalInstallation.md。
Windows/WSL2 下的文件系统性能
如果你使用 Windows 原生 QEMU 二进制,QEMU 无法直接访问 WSL2 安装的 ext4 根分区,只能经由 9P 网络文件共享访问,WSL2 发行版根目录对应网络路径\\wsl$\{distro-name}。
更快的替代方案:把Build/_disk_image与Build/Kernel/Kernel复制到 Windows 原生分区(如/mnt/c)后再执行ninja run,此时SERENITY_DISK_IMAGE就是一个普通 Windows 路径(如'D:\serenity\_disk_image'),可绕开 9P 带来的性能损耗。
Clang 工具链:摆脱 GCC 专属行为
SerenityOS 也支持用 Clang 替代 GCC 构建,目的是避免依赖编译器特有行为,并借助内置静态分析器捕获更多缺陷。两种工具链编译出的代码在大多数场景下行为一致,限制是Ports 目前还无法用 Clang 构建。
构建 Clang 工具链:
Toolchain/BuildClang.sh脚本会构建一套既能编译宿主机应用、也能编译 SerenityOS 的 Clang 工具链。
警告:脚本运行期间电脑可能严重变慢甚至短暂卡死,通常发生在 CPU 核心数多于可用内存(GB)时。解决办法是设置
MAKEJOBS环境变量为小于 CPU 核心数的值,限制并行编译任务数。
构建完成后即可用它编译 SerenityOS:要么设置SERENITY_TOOLCHAIN=Clang构建选项,要么直接给Meta/serenity.sh传 TOOLCHAIN 参数:
Meta/serenity.sh run x86_64 ClangSerenity-aware clang 工具
构建 Clang 工具链的同时也会构建 libTooling 系列工具——clang-format、clang-tidy 以及(可选的)clangd,它们都把 SerenityOS 视为合法目标,安装到Toolchain/Local/clang/bin。把编辑器插件指向这套自建工具,并配合 Clang 构建生成的compile_commands.json,可以获得比宿主发行版自带工具更丰富的报错信息。
若要同时构建 clangd,先设置环境变量再运行脚本:
CLANG_ENABLE_CLANGD=ON Toolchain/BuildClang.sh更新 clang-format
部分发行版自带的 clang-format 版本过旧,按优先级提供三种获取新版本的方式:
- apt 系发行版:使用 LLVM 官方 apt 软件源安装最新版 clang-format;
- 编译 SerenityOS 定制的 LLVM:按上文用
Toolchain/BuildClang.sh从源码编译,使用Toolchain/Local/clang/bin/clang-format作为编辑器与终端工具,meta-lint-ci 的 pre-commit 钩子会自动选用 Toolchain 里的 clang-format 二进制; - 按 LLVM 官方文档从源码编译 LLVM:自行编译整套 LLVM 源码以获得新版本。
小结
SerenityOS 的构建体系在 CMake 之上做了多层封装:Meta/serenity.sh负责日常编排(build/install/image/run/gdb/test 等子命令,详见脚本顶部的帮助文本 Meta/serenity.sh),SuperBuild 负责宿主工具与目标系统的联动,Ninja 目标与 CMake 缓存则提供细粒度控制。掌握本文介绍的sync-local.sh、SERENITY_ARCH、CMake 选项、ConfigureComponents与 Clang 工具链,即可根据自身开发需求定制镜像、裁剪组件、切换架构并搭建更强大的静态分析环境。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考