SerenityOS 的 LLVM/Clang 工具链补丁全解析:从系统搜索路径到 RISC-V 特性检测的 7 个移植要点
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
本指南以 Toolchain/Patches/llvm/ReadMe.md 为核心,逐一拆解 SerenityOS 为将 LLVM/Clang 移植到自家内核与用户态环境而维护的 7 个补丁,涵盖默认搜索路径、平台适配、共享库构建、profile 插桩、libc++/libc++abi 支持与 RISC-V CPU 特性检测。读完本文,你将理解 SerenityOS 如何借助补丁体系让 Clang 既能在宿主机上交叉编译 SerenityOS 内核与应用,又能在 SerenityOS 系统内部原生构建 Ports,并能对照源码定位每一处改动背后的真实原因。
背景:为什么 SerenityOS 需要一套 LLVM 补丁
SerenityOS 是一个从零开始构建的类 Unix 操作系统,其工具链同时承担两类任务:在宿主机上交叉编译 SerenityOS 自身,以及在系统内(通过 Ports 体系)为 SerenityOS 构建第三方软件。由于 SerenityOS 的 LibC、动态链接器与 POSIX 子集与主流 Linux/BSD 存在差异,LLVM/Clang 上游代码中许多"默认按 Linux 处理"的平台判断需要被显式地告知"这里是 SerenityOS"。
这些差异被集中整理为 7 个补丁文件,存放于 Toolchain/Patches/llvm/,构建脚本 Toolchain/BuildClang.sh 会在编译前按序应用它们:
for patch in "$DIR"/Patches/llvm/*.patch; do patch -p1 < "$patch" > /dev/null done从脚本可以看到,默认使用patch -p1逐个应用;若以--dev模式构建,则改用git am --keep-non-patch以保留提交元数据(Toolchain/BuildClang.sh)。脚本还会对已解压的 llvm-project 目录记录.patch.applied的 MD5 指纹,只要补丁集有变化就会重新解压、重新打补丁,避免增量状态下补丁状态错乱。
补丁 0001:为 Clang 增加 /usr/local 默认搜索路径
对应文件:0001-clang-Add-usr-local-to-the-default-search-path.patch
该补丁修改clang/lib/Driver/ToolChains/Serenity.cpp,向链接器命令行追加-L=/usr/local/lib,并在 Clang 系统头文件搜索路径中加入${SysRoot}/usr/local/include。其动机在补丁说明中写得很明确:这些路径是构建 Ports 时用 Clang 找到已安装依赖所必需的。
在 SerenityOS 的 Ports 体系中,第三方软件被安装到/usr/local(包括/usr/local/include与/usr/local/lib),与系统自带的/usr分区隔离。如果 Clang 默认只搜索/usr/include与/usr/lib,那么 Ports 编译时便无法找到先前通过 Ports 安装的库头文件与链接库。补丁通过两处精确改动解决:
- 链接阶段:
CmdArgs.push_back("-L=/usr/local/lib");追加在--pop-state之后,确保链接器在搜索系统库之前能看到/usr/local/lib; - 编译阶段:
addSystemInclude(DriverArgs, CC1Args, concat(D.SysRoot, "/usr/local/include"));被插入到addSystemInclude(..., concat(D.SysRoot, "/usr/include"))之前,且遵守-nostdlibinc的跳过语义。
从源码结构看,这两处改动都位于 Clang Driver 中专用于 SerenityOS 的Serenity.cpp工具链实现中,说明 SerenityOS 在 Clang 中已有独立的三元组(triple)支持(如x86_64-serenity),补丁只需补齐该工具链类的默认路径。
补丁 0002:为在 SerenityOS 上构建 LLVM 添加平台适配
对应文件:0002-llvm-Add-support-for-building-LLVM-on-SerenityOS.patch
这是让 LLVM 自身能在 SerenityOS 环境里编译并运行的核心补丁,共修改 6 个文件,覆盖四种平台差异:
1. wait4 缺失的桩实现
SerenityOS 不支持查询子进程的资源使用信息,因此没有wait4。补丁在 [llvm/lib/Support/Unix/Program.inc] 中仿照 AIX 的做法为 SerenityOS 声明并实现了一个llvm::sys::wait4,内部直接转调::waitpid(pid, status, options),忽略rusage参数:
#ifdef __serenity__ pid_t (llvm::sys::wait4)(pid_t pid, int *status, int options, struct rusage*) { return ::waitpid(pid, status, options); } #endif这样 LLVM 的sys::Wait逻辑无需改动即可在 SerenityOS 上编译。
2. Orc 禁用 POSIX 共享内存
SerenityOS 尚未支持 POSIX shm,因此补丁在两处(MemoryMapper.cpp与ExecutorSharedMemoryMapperService.cpp)把预处理器条件从LLVM_ON_UNIX && !defined(__ANDROID__)扩展为同时排除__serenity__,使共享内存映射路径在 SerenityOS 上被编译掉。
3. 增大默认线程栈到 4MiB
SerenityOS 给每个线程默认分配 1MiB 栈,这对 LLVM 的部分应用(如大型编译任务)偏小。补丁在HandleLLVMOptions.cmake中为SERENITYOS分支增加链接器参数:
elseif(SERENITYOS) # SerenityOS sets a very low default stack size value, so increase it to 4MB manually. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,stack-size=4194304") endif()即通过-Wl,-z,stack-size=4194304将可执行文件默认栈大小提升到 4MB。
4. 字节序与文件系统挂载判断
- 在 [llvm/include/llvm/ADT/bit.h] 的平台列表中追加
defined(__serenity__),使bit.h使用<endian.h>获取字节序信息; - 在 [llvm/lib/Support/Unix/Path.inc] 中,将
__serenity__加入使用f_flag(而非f_flags)的平台宏列表,并在is_local_impl中让 SerenityOS 直接返回false——因为 SerenityOS 尚不支持远程文件系统挂载,无需查询挂载类型。
值得注意的是,本补丁(2022 年提交)与LLVMConfig.cmake中set(RUNTIMES_${target}_CMAKE_SYSTEM_NAME SerenityOS)配合,说明 SerenityOS 已通过 CMake 的SERENITYOS变量识别自身平台(Toolchain/CMake/LLVMConfig.cmake)。
补丁 0003:构建共享 libLLVM 与 libClang
对应文件:0003-tools-Support-building-shared-libLLVM-and-libClang-f.patch
SerenityOS 需要libLLVM.so与libclang.so这类共享库形态(LLVMConfig.cmake中设置了LLVM_BUILD_LLVM_DYLIB ON与LLVM_LINK_LLVM_DYLIB ON,见 Toolchain/CMake/LLVMConfig.cmake)。该补丁从两个层面保证共享库能正确生成:
--whole-archive全量归档:在 [llvm/tools/llvm-shlib/CMakeLists.txt] 中,将--whole-archive组合归档参与共享库构建的LIB_NAMES时,跳过SERENITYOS(原有排除列表为 Solaris ld、MINGW、CYGWIN),确保共享库成员来自全部所需静态库;- 禁用符号版本脚本:在 [HandleLLVMOptions.cmake] 中将
SERENITYOS加入LLVM_HAVE_LINK_VERSION_SCRIPT 0的平台列表(与 APPLE、CYGWIN、AIX 并列)。原因是 SerenityOS 的加载器不支持符号版本化,生成携带 ELF 版本节(.gnu.version*)的库只会浪费空间。
补丁 0004:启用 profile 插桩(InstrProfiling)
对应文件:0004-compiler-rt-Enable-profile-instrumentation-for-Seren.patch
该补丁让 SerenityOS 获得与 Linux 等平台相近的-fprofile-instr-generate插桩能力,改动分三层:
- CMake 开关:在 [compiler-rt/cmake/config-ix.cmake] 的平台白名单中加入
SerenityOS,使COMPILER_RT_HAS_PROFILE为真,从而构建libclang_rt.profile运行库; - 运行库平台文件:在 [compiler-rt/lib/profile/InstrProfilingPlatformLinux.c] 的预处理器条件中加入
defined(__serenity__),复用 Linux 版插桩实现;同时在 [InstrProfilingPlatformOther.c] 中反向排除__serenity__,避免两套实现冲突; - 驱动侧链接:在 [clang/lib/Driver/ToolChains/Serenity.cpp] 的链接任务中,当
ShouldLinkCompilerRuntime为真时调用TC.addProfileRTLibs(Args, CmdArgs),把libclang_rt.profile.a自动加入链接。
补丁还向 Clang 的 driver 测试 [clang/test/Driver/instrprof-ld.c] 添加了针对x86_64-pc-serenity的 FileCheck 用例,验证静态链接与-shared两种场景下都会链接到libclang_rt.profile.a——这是移植代码同时配套回归测试的典型做法。该能力与LLVMConfig.cmake中RUNTIMES_${target}_COMPILER_RT_BUILD_PROFILE ON的配置相互印证(Toolchain/CMake/LLVMConfig.cmake)。
补丁 0005:libc++ 对 SerenityOS 的适配
对应文件:0005-libcxx-Add-support-for-SerenityOS.patch
libc++ 是 SerenityOS 选择的 C++ 标准库实现(LLVM_ENABLE_RUNTIMES中包含libcxx,见 Toolchain/CMake/LLVMConfig.cmake)。该补丁向 libc++ 声明 SerenityOS 的 LibC 具备哪些能力,核心是新增 273 行的 [libcxx/include/__locale_dir/support/serenity.h],并做四处联动:
1. 全新的 locale 支持层
serenity.h是 libc++ locale 基础 API 的 SerenityOS 实现,定义了__locale_guard(通过uselocale保存/恢复线程 locale)、__newlocale/__freelocale/__setlocale、字符转换(__toupper/__tolower)、宽字符分类(__iswctype等)、多字节转换(__mbrtowc/__wcsnrtombs等)与格式化(__snprintf/__asprintf)等一系列内联封装。这些函数大多通过__locale_guard临时切换 locale 后调用对应的 C 函数。
这套 API 之所以可行,是因为 SerenityOS 的 LibC 已实现相应的 locale 基础设施:newlocale/uselocale/freelocale定义于 Userland/Libraries/LibC/locale.cpp,nl_langinfo_l定义于 Userland/Libraries/LibC/langinfo.cpp,locale_t类型见 Userland/Libraries/LibC/locale.h。补丁同时把serenity.h注册进 [libcxx/include/CMakeLists.txt] 的安装文件列表,并在 [locale_base_api.h] 的平台分支中通过#elif defined(__serenity__)引入。
2. 使用 pthread 实现多线程
在 [libcxx/include/__config] 中,__serenity__被加入启用_LIBCPP_HAS_THREAD_API_PTHREAD的平台列表,声明 SerenityOS 的多线程能力由 pthread 库提供。这与LLVMConfig.cmake中显式关闭LIBCXX_HAS_PTHREAD_LIB/LIBCXXABI_HAS_PTHREAD_LIB的"硬编码自动检测结果"策略并不矛盾——后者针对的是交叉编译时对尚未构建完成的 sysroot 的探测保护(Toolchain/CMake/LLVMConfig.cmake)。
3. 使用 libc++ 内建字符类型表
__serenity__同时被加入_LIBCPP_PROVIDES_DEFAULT_RUNE_TABLE平台列表,即 libc++ 使用自己内建的字符类型表,而非 LibC 提供的表。补丁说明给出原因:要让 locale.cpp 的其余部分正确使用 LibC 的字符类型表需要大量额外移植工作,内建表是更务实的取舍。
4. 禁用 catopen
[libcxx/include/__locale_dir/messages.h] 中,__serenity__被加入不定义_LIBCPP_HAS_CATOPEN的排除列表——SerenityOS 的 LibC 不提供消息目录(message catalog)接口。
补丁 0006:RISC-V 的 __init_riscv_feature_bits 实现
对应文件:0006-RISCV-Implement-__init_riscv_feature_bits-for-Sereni.patch
SerenityOS 是同时支持 RISC-V 架构的操作系统(LLVMConfig.cmake的目标列表包含riscv64-serenity,构建目标含riscv64,见 Toolchain/CMake/LLVMConfig.cmake)。该补丁让 compiler-rt 的 RISC-V CPU 模型初始化在 SerenityOS 上可用:
- 在 [compiler-rt/lib/builtins/cpu_model/riscv.c] 中,为
__serenity__提供initRISCVFeature实现:弱符号声明__get_riscv_feature_bits,若存在则调用它填充__riscv_feature_bits与__riscv_cpu_model;否则将长度置 0、vendor/arch/impl ID 置 0; - 通过最高优先级构造函数
__init_riscv_feature_bits_ctor调用__init_riscv_feature_bits(0),并借助FeaturesBitCached保证只初始化一次; - 同时放宽 Clang 对 RISC-V 函数多版本(FMV)的 OS 限制:诊断消息改为 "only supported on Linux and SerenityOS",[clang/lib/CodeGen/CodeGenFunction.cpp] 中的
EmitRISCVMultiVersionResolver接受llvm::Triple::OSType::Serenity。
这里提到的"动态链接器提供的 magic 函数"确实存在于仓库源码中:SerenityOS 的动态链接器在 Userland/Libraries/LibELF/DynamicLinker.cpp 通过define_magic_function("__get_riscv_feature_bits"sv, __get_riscv_feature_bits)注册该符号,其实现位于 Userland/Libraries/LibELF/Arch/riscv64/ExtensionBitmask.cpp,内部通过archctl(ARCHCTL_RISCV64_GET_CPU_INFO, ...)向内核查询 CPU 扩展位掩码与 CPU 型号。整个链条为:编译器插桩 → compiler-rt 构造函数 → 动态链接器 magic 函数 →archctl系统调用 → 内核返回特性信息。
补丁 0007:libc++abi 定义 __cxa_thread_atexit
对应文件:0007-libcxxabi-Define-__cxa_thread_atexit-on-serenity.patch
__cxa_thread_atexit是 Itanium ABI 中用于thread_local变量析构的扩展接口(在 glibc 中实现,尚未成为 ABI 正式组成部分)。SerenityOS 的动态链接器已支持线程局部存储的析构回调机制,因此该补丁在 libc++abi 的两处平台条件中追加defined(__serenity__):
- [libcxxabi/include/cxxabi.h]:声明
extern "C" int __cxa_thread_atexit(...)的宏条件从__linux__ || __Fuchsia__扩展为包含__serenity__; - [libcxxabi/src/cxa_thread_atexit.cpp]:实际定义的编译条件同步扩展,使 SerenityOS 链接到该实现。
这保证了 SerenityOS 上thread_local对象在线程退出时能正确执行析构,是 C++ 运行时完整性的关键一环。从代码注释可以推断,该机制与 DynamicLinker.cpp 中注册的__create_new_tls_region、__free_tls_region、__call_fini_functions等 magic 函数共同构成完整的 TLS 生命周期管理。
补丁之外:完整的工具链构建链路
理解 7 个补丁后,再回看它们如何嵌入整体构建会更有全局感。Toolchain/BuildClang.sh 展示了完整流程:
- 依赖检查:要求 ninja、cmake、GNU patch 与可用的 C/C++ 编译器;若宿主机提供 LLD 则
-fuse-ld=lld加速链接(Toolchain/BuildClang.sh); - 下载与打补丁:按固定 commit(
LLVM_COMMIT)下载 llvm-project 压缩包,校验 MD5 后解压并按序应用本文所述的 7 个补丁(Toolchain/BuildClang.sh); - 链接 LibC 头文件:对
x86_64、aarch64、riscv64三个架构分别调用Meta/CMake/link_libc_headers.cmake生成 sysroot; - CMake 配置与编译:以
Toolchain/CMake/LLVMConfig.cmake为缓存文件配置,其中LLVM_TARGETS_TO_BUILD为X86;AArch64;RISCV,LLVM_ENABLE_PROJECTS为llvm;clang;lld;clang-tools-extra,LLVM_ENABLE_RUNTIMES为compiler-rt;libunwind;libcxxabi;libcxx(Toolchain/CMake/LLVMConfig.cmake); - 安装与符号链接:
ninja install/strip安装到Toolchain/Local/clang/,并为每个架构创建x86_64-serenity-clang、aarch64-serenity-clang、riscv64-serenity-clang等驱动别名,同时生成携带--sysroot的*.cfg配置文件(Toolchain/BuildClang.sh)。
补丁 0001 与 0003 正对应这套流程中"在系统内构建 Ports"与"产出共享库"两个需求,其余补丁则分别对应 LibC 能力差异、profile 工具链与 RISC-V 平台特性——7 个补丁共同构成了 SerenityOS 移植 LLVM/Clang 的最小充分集。
小结
| 补丁 | 修改对象 | 核心要点 |
|---|---|---|
| 0001 | clang Driver(Serenity.cpp) | 增加/usr/local/include、/usr/local/lib默认路径,支撑 Ports 依赖查找 |
| 0002 | LLVM Support/Orc/CMake | wait4 桩、禁用 shm、默认栈 4MiB、字节序与文件系统判断 |
| 0003 | llvm-shlib / CMake | --whole-archive构建共享库,禁用符号版本脚本 |
| 0004 | compiler-rt + clang Driver | 启用 InstrProfiling profile 插桩,含 driver 回归测试 |
| 0005 | libc++ | 新增support/serenity.hlocale 层,pthread、内建 rune 表、禁用 catopen |
| 0006 | compiler-rt + clang CodeGen | RISC-V 特性检测对接动态链接器 magic 函数,放开 FMV 限制 |
| 0007 | libc++abi | 定义__cxa_thread_atexit,完善 thread_local 析构 |
如需深入,可对照阅读补丁源码、构建脚本 Toolchain/BuildClang.sh 与 CMake 配置 Toolchain/CMake/LLVMConfig.cmake,并可在 Userland/Libraries/LibELF/DynamicLinker.cpp 与 Userland/Libraries/LibC/locale.cpp 中验证补丁所依赖的系统侧接口。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考