news 2026/9/12 11:24:11

SerenityOS 移植 Quake III Arena:ioquake3 八个补丁的逐项深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SerenityOS 移植 Quake III Arena:ioquake3 八个补丁的逐项深度解析

SerenityOS 移植 Quake III Arena:ioquake3 八个补丁的逐项深度解析

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

导读:本文以 SerenityOS 仓库中 Ports/quake3/patches/ReadMe.md 为骨架,逐项拆解将 ioquake3(Quake III Arena 的开源引擎)移植到 SerenityOS 所需的八个补丁。你将了解到每个补丁解决了什么编译/链接/运行问题、修改了哪些文件、底层原因是什么,以及这套补丁在 SerenityOS 移植体系中的生成与使用机制。读完本文,你既能按图索骥地理解这些补丁的每一行改动,也能举一反三地把类似的大型 C 语言游戏引擎移植到 SerenityOS 上。

背景:为什么需要这八个补丁

SerenityOS 是一个从零开始、使用自定义内核与用户态库实现类 Unix 操作系统的开源项目。它的 C 库(LibC)、POSIX 语义、动态链接器(ld.so)、mmapanon_create等内核接口都有独特的实现方式。因此,将 ioquake3 这样体量庞大、年代久远的 C 引擎直接交叉编译,必然会在平台探测、头文件、链接库、可执行文件命名、可执行内存映射等方面遇到一系列不兼容问题。

在 SerenityOS 中,外部软件通过Ports/目录下的移植脚本体系进行构建。每个 port 目录包含一个package.sh描述文件、一个可选的patches/补丁目录以及对应的ReadMe.md补丁说明。quak3 的移植位于 Ports/quake3/package.sh,它:

  • https://github.com/ioquake/ioq3拉取固定提交6d74896557d8c193a9f19bc6845a47e9d0f77db2,版本号标记为1.34,并对归档做 SHA-256 校验;
  • 依赖SDL2depends=("SDL2"));
  • 安装到/usr/local/games/quake3/,启动器命令为/usr/local/games/quake3/ioquake3,并注册到&Games分类;
  • 安装完成后通过post_install生成autoexec.cfg(强制cl_renderer "opengl1"r_fullscreen "0"cg_drawfps "1"),并为可执行文件在/etc/fstab.d/quake3中写入wxallowed挂载选项以允许匿名可执行内存。

这套补丁正是围绕上述目标,由移植者(Jesse Buhagiar)在 2022 年 3 月 25–26 日之间连续提交,按00010008编号顺序应用。下面逐项解析。

补丁应用机制:从 package.sh 到 patch 的完整链路

在深入补丁内容之前,先理解 SerenityOS 移植脚本是如何应用这些补丁的,这决定了补丁的格式与顺序要求。

Ports/.port_include.sh 中定义了补丁应用逻辑:

  • patch_internal()(约第 402–429 行)遍历${PORT_META_DIR}/patches/*.patch,对每个补丁先尝试git am --keep-cr --keep-non-patch(若源码仓库是 git 克隆),失败则回退到patch -p"$patchlevel"patchlevel=1,即默认剥掉一级路径前缀);
  • 应用成功后打patched标签标记;
  • do_patch()(第 560 行)在构建流程中调用pre_patchpatch_internal,属于fetch → patch → configure → build → install链路上的一环。

而本文主角patches/ReadMe.md本身也不是手写的:do_generate_patch_readme()(第 649–722 行)会用git mailinfo从每个.patch文件中提取Subject:与提交说明正文,自动生成## 补丁文件名+ 描述 的 Markdown 结构(第 710–715 行)。也就是说,ReadMe.md 的内容严格来源于补丁自身的 commit message,这保证了文档与补丁改动的一一对应关系。Ports 构建流程还支持generate_patch_readme子命令,以及rebuild时通过git am --3way检测上游变更并自动重新生成补丁(第 781–826 行)。

0001-Meta-Refactor-Makefile-to-support-Serenity.patch

主题:重构 ioquake3 的 Makefile 以支持 Serenity 平台。

这是整个移植的奠基性补丁,改动集中在Makefile(16 处插入、28 处删除),核心内容如下。

硬编码平台与架构

原 Makefile 通过uname动态探测COMPILE_PLATFORMCOMPILE_ARCH(含 i686→x86、arm→arm 的 sed 归一化,以及 arm64/aarch64 特判)。但交叉编译到 SerenityOS 时,宿主机的uname结果是构建机自身,无法代表目标平台。补丁将这两项直接改为:

COMPILE_PLATFORM=serenity COMPILE_ARCH=${SERENITY_ARCH}

其中${SERENITY_ARCH}由 SerenityOS 移植环境注入(构建时通过环境变量提供,典型取值如x86_64i686等)。

关闭不适用于 SerenityOS 的构建选项

补丁在ifndef默认值处批量将以下选项从默认启用改为显式关闭:

选项原默认值补丁后默认值含义
BUILD_BASEGAME1构建 baseq3 游戏逻辑(保持开启)
BUILD_MISSIONPACK0不构建资料片 missionpack
BUILD_RENDERER_OPENGL20不构建 OpenGL 2 渲染器
USE_OPENAL10关闭 OpenAL 音频
USE_OPENAL_DLOPEN10关闭 OpenAL 动态加载
USE_CURL10关闭 cURL 网络下载
USE_CURL_DLOPEN10关闭 cURL 动态加载
USE_CODEC_VORBIS10关闭 Vorbis 音频解码
USE_CODEC_OPUS10关闭 Opus 音频解码
USE_MUMBLE10关闭 Mumble 语音
USE_VOIP10关闭 VoIP 语音

这样做的原因很直接:SerenityOS 的 ports 体系采用“按需依赖”策略,package.sh只声明了SDL2依赖,OpenAL、cURL、Vorbis、Opus、Mumble 等并未移植或未启用,若保持默认开启会导致链接失败或运行期缺失动态库。渲染器也只保留与 SerenityOS 软渲染/GL 环境匹配的opengl1

新增 Serenity 平台构建分支

补丁将原本的 OpenBSD 平台分支改写为 Serenity 分支:

ifeq ($(PLATFORM),serenity) BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \ -pipe -DUSE_ICON -DMAP_ANONYMOUS=MAP_ANON CLIENT_CFLAGS += $(SDL_CFLAGS)

注意其中的-DMAP_ANONYMOUS=MAP_ANON:ioquake3 源码中大量使用MAP_ANONYMOUS,而 SerenityOS 头文件使用MAP_ANON命名,这一宏映射避免了大量源码改动。同时补丁还删除了 darwin 分支里“若 CC 是 cc/gcc 则清空”的交叉编译逻辑,避免覆盖 SerenityOS 工具链注入的CC

0002-Engine-Add-Serenity-so-q_platform.h.patch

主题:在q_platform.h中新增 Serenity 平台定义块。

code/qcommon/q_platform.h是 ioquake3 的平台抽象头文件,所有源码都依赖它来确定字节序、路径分隔符、架构字符串、动态库扩展名等。补丁在文件末尾(Q3VM 段之前)追加了 29 行:

#if defined(__serenity__) #include <sys/types.h> #define Q3_LITTLE_ENDIAN #define OS_STRING "serenity" #define ID_INLINE inline #define PATH_SEP '/' #ifdef __i386__ #define ARCH_STRING "x86" #elif defined __amd64__ #undef idx64 #define idx64 1 #define ARCH_STRING "x86_64" #endif #define DLL_EXT ".so" #endif

要点解析:

  • 判定宏__serenity__:SerenityOS 的编译环境(Meta/CMake与工具链)会全局定义该宏,作为所有平台适配代码的开关;
  • Q3_LITTLE_ENDIAN:SerenityOS 目前支持的目标架构(x86、x86_64 等)均为小端,显式声明避免运行时字节序探测开销与误判;
  • ARCH_STRINGidx64:供启动横幅与版本信息使用;x86_64 下#undef idx64再定义,确保与引擎内其他判定一致;
  • DLL_EXT ".so":SerenityOS 的动态库扩展名与 Linux 一致,渲染器插件renderer_opengl1_*.so依赖此定义被正确加载。

0003-Engine-Add-sys-select.h-include-for-Serenity.patch

主题:为 SerenityOS 补充<sys/select.h>头文件包含。

这是最能体现“同一套 POSIX 接口在不同系统上分布不同”的补丁。补丁说明指出:Quake III 的网络代码大量使用select()系统调用,而 Linux 恰好能在某些被间接包含的头文件中得到select()声明,SerenityOS 则严格要求显式#include <sys/select.h>,否则会产生隐式声明错误(implicit declaration error)。

补丁在三处文件各加了 4 行条件包含:

  • code/qcommon/net_ip.c:网络核心(UDP socket 轮询)——在#include <sys/filio.h>之后、typedef int SOCKET之前插入;
  • code/sys/con_tty.c:终端控制台输入轮询(tty 模式)——在<sys/time.h>之后插入;
  • code/sys/sys_unix.c:Unix 平台系统层——在<sys/wait.h>之后插入。

同一补丁还顺带将net_ip.c中 IPv6 组播相关的NET_JoinMulticast6/NET_LeaveMulticast6#ifndef __serenity__整体包起来——因为 SerenityOS 网络栈(至少在该版本)不支持 IPv6 组播,且函数体内引用了 SerenityOS 头文件中不存在的结构。

0004-Meta-Add-ldl-library-for-Serenity-target.patch

主题:为 Serenity 平台目标追加-ldl链接库。

这是对 0001 新增的 Serenity Makefile 分支的一处补丁级修正,改动仅 1 行:

THREAD_LIBS=-lpthread - LIBS=-lm + LIBS=-lm -ldl

dldlopen/dlsym/dlclose)在 ioquake3 中用于运行时动态加载渲染器插件(USE_RENDERER_DLOPEN)与游戏模块(.so)。Linux 的 glibc 从 2.34 起把dl符号并入 libc,无需显式-ldl;而 SerenityOS 的 LibDL 是独立库,必须显式链接,否则渲染器插件加载相关的符号会出现“undefined reference”链接错误。补丁同时保留-lpthread(线程库)与-lm(数学库)。

0005-Engine-Move-ifdef-to-more-sensible-location.patch

主题:把#ifdef移动到更合理的位置。

这是对 0003 的代码整洁性修正(提交说明只有一句 "No linker errors in this dojo!",暗示此前的写法虽能编译通过但结构不佳)。0003 曾把整个NET_JoinMulticast6/NET_LeaveMulticast6函数体用#ifndef __serenity__包住,导致 Serenity 分支下函数体为空但函数签名仍然保留,且#endif落在函数体末尾、右花括号之后。

0005 将其重构为“函数签名与花括号始终存在、仅函数体内部条件编译”的标准写法:

void NET_JoinMulticast6(void) { #ifndef __serenity__ int err; /* ... 原实现 ... */ #endif } void NET_LeaveMulticast6() { #ifndef __serenity__ /* ... 原实现 ... */ #endif }

这样在任意平台下函数原型都保持一致,#endif位置正确,避免了空函数体/花括号错位可能引发的告警与链接一致性问题。

0006-Meta-Add-ARCH-to-TOOLS_CFLAGS.patch

主题:把架构字符串注入 TOOLS_CFLAGS。

ioquake3 构建体系中有两类编译目标:主程序(client/server/game)与构建期工具(TOOLS,如q3asmq3lcc等用于编译 QVM 字节码的交叉工具)。补丁在 Serenity 分支的BASE_CFLAGS后追加:

TOOLS_CFLAGS += -DARCH_STRING=\"$(COMPILE_ARCH)\"

这样工具程序在编译期就能通过宏拿到目标架构字符串(例如-DARCH_STRING="x86_64"),用于在生成 QVM 或打印 banner 时报告正确的架构。结合 0002 中q_platform.hARCH_STRING的定义,可以推断:主程序运行时由q_platform.h提供ARCH_STRING,而构建期工具因为编译路径不同(可能不经过完整平台头文件),需要由 Makefile 显式注入该宏。

0007-Meta-Remove-extension-from-main-game-exe.patch

主题:去掉主游戏可执行文件的扩展名。

ioquake3 默认的CLIENTBIN会带.$(ARCH)之类后缀(FULLBINEXT),例如ioquake3.x86_64。这不符合 SerenityOS 可执行文件的习惯命名,也会让package.sh中写死的启动器路径/usr/local/games/quake3/ioquake3对不上。

补丁对Makefile做了 5 处替换($(CLIENTBIN)$(FULLBINEXT)$(CLIENTBIN)),覆盖:

  • TARGETS目标列表(含USE_RENDERER_DLOPEN与否两条路径);
  • 主可执行文件的链接规则($(B)/$(CLIENTBIN): $(Q3OBJ) $(LIBSDLMAIN));
  • 安装规则($(INSTALL) ... $(COPYBINDIR)/$(CLIENTBIN))。

同时renderer_opengl1_$(SHLIBNAME)等渲染器插件的.so命名保持不变,说明只对主二进制去扩展名,动态库插件命名不受影响。

0008-Engine-Use-Serenity-style-PROT_EXEC-mmap.patch

主题:改用 SerenityOS 风格的可执行内存映射。

这是技术含量最高的一个补丁,触及 Quake III VM(虚拟机)JIT 的核心。Quake III 的Q3VM会把字节码即时编译(JIT)为 x86 机器码,存放在一段需要PROT_EXEC权限的内存中。原实现(code/qcommon/vm_x86.cVM_Compile)的做法是:

vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); /* 写入机器码后 */ mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC);

即先以可写映射分配、写入后再mprotect提升为可执行。SerenityOS 出于安全考虑,对mmap(PROT_EXEC)mprotect到可执行的行为有更严格的管控(可执行内存需要经过显式的匿名内存对象授权,即wxallowed挂载选项,参见package.shpost_install)。补丁为 SerenityOS 分支重写了这段逻辑(27 处插入、2 处删除):

#ifdef __serenity__ // Round up by a page for `anon_create` (so we don't blow up in the Kernel) int compiledOfsPageAligned = compiledOfs + (4096u - (compiledOfs % 4096u)); // Create the fd... int fd = anon_create(compiledOfsPageAligned, O_CLOEXEC); if (fd == -1) Com_Error(ERR_FATAL, "VM_CompileX86: anon_create failed (a very bad thing!)"); vm->codeBase = mmap_with_name(NULL, compiledOfs, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0, "Quake3 VM Page"); if(vm->codeBase == MAP_FAILED) Com_Error(ERR_FATAL, "VM_CompileX86: can't mmap memory"); close(fd); #else /* 原 mmap + mprotect 路径保留 */ #endif

关键点:

  • anon_create()是 SerenityOS 特有的系统调用,创建一个匿名的、可授权给mmap的内存对象(fd),并显式声明对页面的执行权限;O_CLOEXEC防止 fd 泄漏给execve的子进程;
  • 长度先按 4096 字节页向上取整(compiledOfs + (4096u - (compiledOfs % 4096u))),注释直言“以免在内核里爆炸”(避免内核因非页对齐大小拒绝);
  • mmap_with_name(...)同样是 SerenityOS 扩展接口,除了映射还附带调试友好的名字"Quake3 VM Page",方便在/proc或内核调试器中识别这块 JIT 内存;
  • 补丁顶部为vm_x86.c增加了#include <fcntl.h><unistd.h>(注释标明是为pledge())与<serenity.h>anon_create/mmap_with_name的声明所在);
  • 由于 Serenity 分支在映射时就直接授予了PROT_EXEC,后续的mprotect步骤被#ifndef __serenity__跳过。

补丁顺序与依赖关系小结

八个补丁按编号顺序应用,逻辑上存在清晰的依赖链条:

编号层次解决问题关键文件
0001构建系统平台/架构识别、裁剪特性、新增 Serenity 分支Makefile
0002平台抽象__serenity__平台定义块code/qcommon/q_platform.h
0003头文件select()隐式声明、IPv6 组播裁剪net_ip.ccon_tty.csys_unix.c
0004链接追加-ldlMakefile
0005代码质量修正 0003 的#ifdef位置net_ip.c
0006构建系统工具程序注入ARCH_STRINGMakefile
0007构建系统主可执行文件去扩展名Makefile
0008运行期内存JIT 可执行内存改用anon_create+mmap_with_namecode/qcommon/vm_x86.c

依赖关系:0002 提供__serenity__语义基础,0003/0008 的条件编译都依赖它;0004、0006、0007 都是对 0001 所建 Serenity 分支的增量修正;0005 修复 0003 引入的代码风格问题。应用顺序不可随意调换。

移植完成后的运行与验证

补丁全部应用并构建成功后,Quake III Arena 通过 Ports/quake3/package.sh 的启动器元数据(launcher_name="Quake III Arena"launcher_category="&Games"launcher_command=/usr/local/games/quake3/ioquake3)出现在 SerenityOS 的游戏菜单中。

需要注意的运行时前提:

  • 游戏数据post_install明确提示用户需要自行从正版 Quake 3 安装中拷贝baseq3数据到/usr/local/games/quake3/,本移植只负责引擎本身;
  • wxallowed 挂载post_install/etc/fstab.d/quake3写入bind,nodev,nosuid,wxallowed,这是 0008 中 JIT 可执行内存能够成功映射的配套前提——若缺失,anon_create/mmap_with_name申请PROT_EXEC会被内核拒绝;
  • 渲染与显示:生成的autoexec.cfg强制cl_renderer "opengl1"(SerenityOS 上可用的渲染后端)、r_fullscreen "0"cg_drawfps "1",对应 0001 中仅保留 OpenGL 1 渲染器的构建决策;
  • 网络:0003/0005 裁掉了 IPv6 组播路径,select()驱动的传统 UDP 客户端/服务器网络功能保留可用。

从本移植可借鉴的 SerenityOS 移植方法论

结合Ports/.port_include.sh的机制与 quake3 补丁,可以总结出移植大型 C 项目到 SerenityOS 的通用套路:

  1. 平台探测要硬编码而非运行时探测(0001):交叉编译场景下uname不可信,用__serenity__宏 + 环境变量(如SERENITY_ARCH)替代;
  2. 头文件依赖要显式(0003):不要指望“恰好被间接包含”,<sys/select.h><sys/types.h>等要按 POSIX 规范显式包含;
  3. 按需裁剪特性(0001 的选项表):未移植的依赖库(OpenAL/cURL/Vorbis 等)一律关闭,减少链接与运行期风险;
  4. 独立库要显式链接(0004):SerenityOS 的 LibDL 等不并入 libc,-ldl-lpthread等缺一不可;
  5. 遵守内存安全模型(0008):可执行内存必须走anon_create+mmap_with_name(或等价授权路径),并配套wxallowed挂载,且注意页对齐;
  6. 命名与路径符合系统习惯(0007):可执行文件不带架构后缀,安装路径与启动器元数据保持一致;
  7. 善用自动生成机制:补丁说明 ReadMe.md 由generate_patch_readme子命令从 commit message 自动生成,补丁可用git am(源码仓库存在时)或patch -p1(普通源码包)两种方式应用,无需手工维护文档与补丁的对应关系。

参考文件索引

  • 补丁清单与说明:Ports/quake3/patches/ReadMe.md
  • 补丁源码(8 个):Ports/quake3/patches/
  • Port 描述与安装逻辑:Ports/quake3/package.sh
  • 补丁应用与 ReadMe 生成机制:Ports/.port_include.sh(patch_internaldo_patchdo_generate_patch_readme
  • 涉及的上游引擎文件(补丁目标):code/qcommon/q_platform.hcode/qcommon/net_ip.ccode/qcommon/vm_x86.ccode/sys/con_tty.ccode/sys/sys_unix.cMakefile

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

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

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

Flutter在OpenHarmony实现动态柱状图的实践指南

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

作者头像 李华
网站建设 2026/9/12 11:23:02

MATLAB NURBS工具箱深度解析:从数据结构到曲面建模实践

简介&#xff1a;MATLAB NURBS工具箱是一套面向MATLAB环境的专业扩展库&#xff0c;专注于非均匀有理B样条&#xff08;NURBS&#xff09;曲线与曲面的创建、编辑和可视化&#xff0c;适用于计算机图形学、CAD/CAM/CAE以及科研数据分析等场景&#xff0c;能帮助工程师、科研人员…

作者头像 李华
网站建设 2026/9/12 11:22:48

自动化测试技术体系与实战进阶指南

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

作者头像 李华
网站建设 2026/9/12 11:22:46

MySQL CASE WHEN语句详解与应用实践

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

作者头像 李华
网站建设 2026/9/12 11:22:17

Transformer架构与LLM核心技术解析

1. Transformer架构深度解析1.1 从Seq2Seq到Self-Attention的进化之路2017年那篇《Attention Is All You Need》论文彻底改变了NLP领域的游戏规则。传统RNN架构存在两个致命缺陷&#xff1a;一是必须顺序处理序列数据导致计算无法并行&#xff0c;二是长距离依赖难以捕捉。Tran…

作者头像 李华
网站建设 2026/9/12 11:21:25

CCF GESP C++4级认证备考指南与真题解析

1. CCF GESP认证体系概述中国计算机学会&#xff08;CCF&#xff09;推出的GESP编程能力等级认证&#xff0c;是目前国内最具权威性的青少年编程能力测评体系之一。作为C语言学习路径上的重要里程碑&#xff0c;4级认证标志着学习者已经掌握了面向对象编程的核心概念和中级算法…

作者头像 李华