
SerenityOS 移植 Guile 3.0.8交叉编译类型尺寸修复与栈回收补丁深度解析【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity本篇技术指南围绕 SerenityOS 移植 GNU GuileScheme 实现过程中维护在 Ports/guile/patches/ReadMe.md 的两份关键补丁展开逐一讲解其背景、改动内容与底层原理一份解决交叉编译时获取目标系统类型尺寸的问题另一份适配 SerenityOS 与 Linux 在madvise(2)语义上的差异。读完本文你将理解 SerenityOS 移植第三方软件时典型的补丁编写思路并掌握 guile 端口的完整构建流程与补丁应用机制。一、Guile 端口概览package.sh 中的移植骨架在深入补丁之前先了解 guile 端口的整体配置。SerenityOS 的每个第三方软件端口都通过一个 Bash 脚本package.sh描述guile 端口定义在 Ports/guile/package.sh#!/usr/bin/env -S bash ../.port_include.sh portguile version3.0.8 files( mirror://gnu/guile/guile-${version}.tar.gz#f25ae0c26e911af1b5005292d4f56621879f74d6958b30741cf67d8b6feb2016 ) depends(gmp libunistring libffi bdwgc libiconv) useconfiguretrue use_fresh_config_subtrue config_sub_paths(build-aux/config.sub) configopts(--disable-lto --disable-jit) pre_configure() { run autoreconf }这段脚本透露出关键移植信息版本与来源当前移植的是 Guile 3.0.8从 GNU 镜像站下载URL 以mirror://gnu/...形式声明尾部#后为用于校验的 SHA256 哈希f25ae0c2...。根据 Ports/README.md 的说明mirror://TYPE/PATH会按顺序尝试一组已配置的镜像基地址。依赖链guile 依赖 gmp、libunistring、libffi、bdwgcBoehm-Demers-Weiser GC与 libiconv 五个端口执行installdepends步骤时会自动安装这些依赖。构建配置useconfiguretrue表示使用 autoconf 体系--disable-lto与--disable-jit关闭了 LTO 与 JIT 支持这是移植到非主流平台时常见的保守选择use_fresh_config_subtrue会在 patch 阶段用新版本的config.sub替换build-aux/config.sub使 autoconf 识别*-serenity宿主三元组pre_configure中先执行autoreconf重新生成 configure 脚本因为补丁修改了configure.ac。二、补丁 0001交叉编译时获取目标系统的类型尺寸2.1 问题背景补丁0001-build-When-cross-compiling-get-type-sizes-of-the-tar.patch解决的是 Guile 构建系统在交叉编译cross-compiling场景下的一个回归缺陷其上游问题记录见 https://issues.guix.gnu.org/54198。Guile 的构建脚本在生成配置文件scmconfig.h时需要确定目标平台上各种 C 类型的字节大小。原实现直接使用sizeof (TYPE)表达式。在交叉编译时configure 阶段运行的是**宿主机build machine上的可执行程序而生成的配置头文件要描述的是目标机target machine**上的 ABI。若直接对宿主机上的类型执行sizeof得到的尺寸可能完全不同于目标系统从而生成错误的配置。2.2 修复方案补丁的修复思路是用SIZEOF_*宏取代sizeof (TYPE)的直接调用。正如补丁提交说明中强调的SIZEOF_TYPE宏由 autoconf 的AC_CHECK_SIZEOF机制在 configure 阶段根据目标系统探测结果生成天然支持交叉编译——它记录的是目标机的类型尺寸而不是运行 configure 的宿主机。补丁具体涉及两个文件libguile/gen-scmconfig.c该程序main函数负责生成scmconfig.h。改动将所有sizeof引用替换为对应的SIZEOF_*宏引用例如用SIZEOF_INTMAX_T等宏输出目标系统的类型尺寸。configure.ac新增对intmax_t的AC_CHECK_SIZEOF调用确保SIZEOF_INTMAX_T宏会被 autoconf 定义。AC_CHECK_SIZEOF是 autoconf 内置宏它在 configure 时编译并运行或交叉编译时通过链接探测一段小测试程序来测定类型大小并把结果写为SIZEOF_*预处理宏。2.3 回归来源补丁说明还追溯了该缺陷的引入历史回归由提交5e5afde06fd9dd0992294d6c7dc9f9966c0caa37引入但直到提交717e787da6ae75bbaa53139c0ef3791cd758a9d8之后才真正显现。这是典型的改动本身无害、但在后续某个提交触发后才暴露的连锁回归SerenityOS 移植时以单独补丁形式记录并修复方便上游Guix/GNU Guile同步跟进。由于当前仓库只保留了补丁提交信息patches 目录 中未包含 0001 的完整 diff 文件上述改动细节以补丁提交说明为准。三、补丁 0002移除 return_unused_stack_to_os 的函数体3.1 问题本质madvise 语义的平台差异补丁0002-Remove-contents-of-return_unused_stack_to_os.patch的提交说明一句话点明了矛盾guile attempts to madvise(2) away parts of the stack, but serenity only supports madvise(2) on entire mmaped regions.即Guile 试图用madvise(2)把 VM 栈中已经不再使用的部分页面归还给操作系统但 SerenityOS 的madvise只支持作用于整个 mmap 映射区域。Guile 的虚拟机会为 Scheme 程序动态管理一块栈内存scm_vm_prepare_stack分配栈底记录在vp-stack_bottom。原实现中return_unused_stack_to_os函数位于libguile/vm.c执行以下操作取vp-stack_bottom栈底与vp-sp当前栈指针之间的字节范围将两端地址按页大小向下取整对齐lo ~(page_size - 1U)若lo hi对这段范围反复调用madvise(lo, hi - lo, MADV_DONTNEED)遇到EAGAIN重试若madvise返回错误且errno ! ENOSYS则打印madvise failed上游注释明确说明对 GNU/Hurd 这类未实现madvise的系统不告警因为用户无能为力。这段代码的完整 diff 保留在 0002-Remove-contents-of-return_unused_stack_to_os.patch 中libguile/vm.c共删除 24 行#if HAVE_SYS_MMAN_H ... #endif整段被移除函数体被清空但函数声明保留避免改动更多调用点。3.2 SerenityOS 端 madvise 的实现约束源码级证据SerenityOS 内核的sys$madvise实现在 Kernel/Syscalls/mmap.cpp其行为恰好印证了补丁说明ErrorOrFlatPtr Process::sys$madvise(Userspacevoid* address, size_t size, int advice) { VERIFY_NO_PROCESS_BIG_LOCK(this); TRY(require_promise(Pledge::stdio)); auto range_to_madvise TRY(Memory::expand_range_to_page_boundaries(address.ptr(), size)); if (!range_to_madvise.size()) return EINVAL; if (!is_user_range(range_to_madvise)) return EFAULT; return address_space().with( - ErrorOrFlatPtr { auto* region space-find_region_from_range(range_to_madvise); if (!region) return EINVAL; if (!region-is_mmap()) return EPERM; if (region-is_immutable()) return EPERM; if (advice MADV_SET_VOLATILE || advice MADV_SET_NONVOLATILE) { // ... 仅处理匿名、可 purge 的 VMObject 的 volatile 语义 return was_purged ? 1 : 0; } return EINVAL; }); }对照内核代码可以得出几点关键结论区域级而非范围级语义内核通过find_region_from_range在地址空间中查找覆盖给定范围的整个 region只支持对整个 mmap 区域操作而不是对区域内任意子范围。Guile 的栈并非以单个 mmap 区域独立存在其栈顶到栈底的部分页面请求自然无法命中一个完整 region。仅支持 volatile 语义对MADV_SET_VOLATILE/MADV_SET_NONVOLATILE之外的所有 advice包括MADV_DONTNEED、MADV_WILLNEED、MADV_SEQUENTIAL、MADV_RANDOM、MADV_NORMAL一律返回EINVAL且 volatile 语义还要求 region 的 vmobject 是匿名且可 purge 的。可见 SerenityOS 的madvise只承担内存可回收的职责与 Linux 上MADV_DONTNEED的丢弃页面内容语义并不对应。非 mmap 区域返回EPERMis_mmap()检查意味着不是由mmap创建的区域直接被拒绝。内核侧的 advice 常量定义于 Kernel/API/POSIX/sys/mman.hMADV_NORMAL0x0、MADV_SET_VOLATILE0x1、MADV_SET_NONVOLATILE0x2、MADV_DONTNEED0x3、MADV_WILLNEED0x4、MADV_SEQUENTIAL0x5、MADV_RANDOM0x6其中并未定义MADV_FREE——Guile 若在支持MADV_FREE的系统上还有另一条路径在 SerenityOS 上同样不可用。因此Guile 原本把栈中未使用页面归还内核的优化在 SerenityOS 上既无对应系统调用语义支撑又会因返回EINVAL/EPERM触发告警路径。移植补丁选择直接清空函数体而非报错退出属于移除不可用优化、保留接口兼容的温和处理VM 栈仍然正常分配与增长只是不再主动向内核归还闲置页面功能不受影响仅损失少量内存回收的收益。四、补丁体系是如何工作的从 ReadMe.md 到应用机制4.1 ReadMe.md 的角色patches/ReadMe.md是 SerenityOS 端口补丁目录的标准清单文档为每个.patch文件提供一段说明。它既服务于维护者快速回顾每个补丁修了什么、为什么修也服务于使用者在构建前评估补丁对行为的影响。这份 ReadMe 并非手工维护而是可以由端口系统自动生成在 Ports/.port_include.sh 的do_generate_patch_readme函数中脚本会遍历patches/*.patch从每个 git 补丁中提取Subject:提交标题与正文提交信息组装成## \文件名小节并写入 ReadMe.md若补丁不是合法 git 补丁或缺少提交信息会被跳过并告警。这也解释了为何 ReadMe 中每个小节都以补丁文件名为标题、以提交说明为正文——它正是 git 提交信息的结构化呈现。dev 模式退出时同样会触发该生成逻辑引导维护者同步更新 ReadMe。4.2 补丁如何被应用根据 Ports/README.md 对patch步骤的说明默认动作会应用端口patches/*.patch目录下的全部补丁patchlevel变量默认 1即patch -p1控制路径剥离层级。底层实现同样在 Ports/.port_include.sh优先尝试git am --keep-cr --keep-non-patch以保留 git 提交元数据失败则回退到传统patch -p$patchlevel成功后打上patchedtag确保同一补丁只应用一次。补丁应用顺序即文件名排序0001-先于0002-。这保证了先完成交叉编译相关的构建系统修复补丁 0001再处理运行时行为适配补丁 0002顺序与依赖关系吻合。五、构建与验证5.1 前置条件根据 Ports/README.md移植构建的前提是已经构建好 SerenityOS 并处于 Serenity 构建环境中。补丁的验证必须落在 SerenityOS 的交叉编译工具链上下文内这正是补丁 0001 之所以关键的场景。5.2 安装 guile 端口进入端口目录并直接运行package.sh不带参数等价于依次执行installdepends、fetch、patch、configure、build、installcd Ports/guile ./package.sh在patch阶段两份补丁会依次应用到解包后的guile-3.0.8源码树先应用 0001 使交叉编译的 configure 正确探测目标系统类型尺寸配合use_fresh_config_subtrue与config_sub_paths指定的新config.sub再应用 0002 移除对 SerenityOS 不支持的madvise调用的依赖。5.3 验证补丁生效配置阶段观察 configure 输出中SIZEOF_INTMAX_T等宏的探测值确认其反映的是x86_64-serenity或其他*-serenity目标而非宿主机尺寸生成的scmconfig.h中SIZEOF_*应与目标 ABI 一致。运行阶段在 SerenityOS 上启动guileREPL执行基本的 Scheme 求值验证 VM 正常运转栈深度较大的递归或尾调用程序不应出现异常说明return_unused_stack_to_os被清空后 VM 栈分配不受影响。5.4 维护提示若升级 Guile 版本修改package.sh的version与files哈希两份补丁可能因上游代码变化而失效需要借助./package.sh dev进入开发模式按 Ports/.port_include.sh 的机制重新生成补丁并更新 ReadMe.md。六、小结两份补丁代表了 SerenityOS 移植第三方软件的两类典型工作构建系统适配补丁 0001——修复交叉编译中的 ABI 探测缺陷属于构建正确性问题若不修复生成的配置头文件会在运行时引发难以排查的崩溃运行时语义适配补丁 0002——移除对内核未提供能力局部区域madvise的依赖属于平台差异问题内核实现Kernel/Syscalls/mmap.cpp决定了这类移植改动不可避免。两者共同构成 guile 端口得以在 SerenityOS 上编译、运行的最小必要改动集而 ReadMe.md 与 package.sh 的组合则为维护者提供了完整的可追溯、可复现的移植记录。【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考