news 2026/9/23 22:51:24

Hermes 中的 boost.context:基于 fiber 的协作式多任务上下文切换原理与集成实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hermes 中的 boost.context:基于 fiber 的协作式多任务上下文切换原理与集成实践
  • 语言运行时
  • 编译器
  • 移动开发

【免费下载链接】hermes

A JavaScript engine optimized for running React Native.

项目地址:https://gitcode.com/gh_mirrors/hermes/hermes
点击查看免费下载

导读

boost.context 是一个为 C++ 提供「单线程内协作式多任务」能力的基础库,它通过抽象当前线程的执行状态(栈、栈指针、寄存器、CPU 标志与指令指针)来构建协程、用户态线程(fiber)等高级抽象。本文以 Hermes 仓库内置的 boost.context README 为核心,结合其汇编级源码与 Hermes 的 StackExecutor 集成实现,讲解 execution_context 抽象、fiber 挂起/恢复机制、上下文切换的性能优势,以及 Hermes 如何用 fiber API 在自己的执行栈上运行 JS 回调。

一、什么是 boost.context:单线程上的协作式多任务

boost.context 是 Boost 库族中的基础库(foundational library),其定位非常明确:在一个线程上提供一种协作式多任务(cooperative multitasking)的机制。它自身不实现协程、不实现调度器,而是向更上层提供「保存与恢复执行上下文」的原子能力,上层可以据此自由构建:

  • 协程(coroutines)
  • 协作式线程(cooperative threads,即用户态线程/userland threads)
  • 类似 C# 中yield关键字在 C++ 中的等价物。

这一点从库的源码组织上也能印证:仓库中的libs/context/src目录只包含fiber.cppcontinuation.cppdummy.cppuntested.cpp及平台相关的posix/stack_traits.cppwindows/stack_traits.cpp,本身并不包含任何调度器或协程语义,全部是底层执行上下文的搬运逻辑(见 libs/context/src 目录)。

1.1 execution_context:执行路径上的一帧快照

README 中给出了该库的核心数据模型:

通过抽象当前线程中的当前执行状态——包括栈(含局部变量)和栈指针、所有寄存器与 CPU 标志、指令指针——一个execution_context实例就代表了应用程序执行路径上的一个特定点。

换句话说,一次上下文切换就是对上述全部状态的一次整体保存与恢复。由于保存的是完整的寄存器集与栈内容,从被保存点恢复后,程序可以"无缝"继续执行,仿佛从未离开过。boost::context::continuationboost::context::fiber都是这一抽象在 API 层的具体呈现。

1.2 fiber:挂起与转移执行控制

README 对 fiber 的定义是:

A fiber 提供了挂起当前执行路径并把执行控制转移出去的手段,从而允许另一个 fiber 在当前线程上运行。这种带状态的转移机制,使一个 fiber 能够从嵌套函数内部挂起,并在之后从挂起点恢复执行。

关键特性可拆解为三点:

  1. 任意深度挂起:挂起点不必是顶层函数,fiber 可以从任意嵌套调用栈深处挂起;
  2. 精确恢复:恢复时从挂起点继续,局部变量、栈帧完好无损;
  3. 线程可迁移:虽然一个 fiber 表示的执行路径某一时刻只在一个线程上运行,但它可以在任意时刻被迁移到另一个线程(源码实现层面由thread_local的 activation record 机制保证)。

1.3 为什么需要它:一次上下文切换的成本对比

README 给出了一个非常直观的性能依据:

切换类型是否涉及系统调用x86 上的成本量级
线程间切换是(需进入 OS 内核)超过 1000 个 CPU 周期
fiber 间控制转移否(单线程内完成)少于 100 个 CPU 周期

之所以有近一个数量级的差距,是因为线程切换要经过内核调度,而 fiber 切换是纯用户态操作:只需要保存/恢复寄存器与栈指针,然后跳转即可,完全不触碰内核。

版本要求:boost.context 需要C++11(README 末尾明确注明 "boost.context requires C++11!")。本仓库内置的 boost 1.86.0 即满足该要求。

二、核心 API:fiber 的创建、挂起与恢复

虽然 README 是概念性的,但从源码可以确认它的两大 API 族:continuationfiber。而 Hermes 仓库在 libs/context/CMakeLists.txt 中明确做了裁剪:

else() # Hermes only uses the fiber API, not the continuation API. # Skip continuation.cpp since continuation_ucontext.hpp is not included. set(IMPL_SOURCES src/fiber.cpp ) endif()

也就是说,Hermes 只编译src/fiber.cpp,跳过continuation.cpp。因此在本文语境下,我们重点讨论fiber API

2.1 fiber 的典型生命周期

fiber API 的核心对象是boost::context::fiber,其基本用法模式(与 Hermes 的 StackExecutor.cpp 中的用法一致)为:

#include <boost/context/fiber.hpp> #include <boost/context/protected_fixedsize_stack.hpp> namespace ctx = boost::context; // 创建一个 fiber:分配独立栈,入口是一个接收 ctx::fiber&& 的可调用对象 ctx::fiber f(std::allocator_arg, ctx::protected_fixedsize_stack(128 * 1024), [](ctx::fiber &&next) { // ... 用户代码 ... // 把控制权交还调用者 next = std::move(next).resume(); return std::move(next); }); // 主路径启动/恢复 fiber(resume 返回控制权并传入新的 fiber 对象) f = std::move(f).resume();

几个关键设计点:

  • resume()的双向语义:主路径调用resume()进入 fiber,fiber 内部再次调用resume()则把控制权交还主路径,且resume()的返回值就是"对方"传入的 fiber 对象,因此必须用std::move保存,否则当前 fiber 会因持有悬空引用而出错——这正是源码注释 "The std::move() here is required by older compilers" 强调的细节;
  • 所有权随控制权转移:fiber 对象本身是移动语义对象,控制权在谁手里,谁就持有对应的fiber实例;
  • 终止语义:当 fiber 的可调用对象返回而不是再次resume()时,fiber 执行结束。

2.2 栈分配器与 stack_traits

fiber 需要自己的栈。boost.context 提供多种栈分配器,Hermes 使用的是protected_fixedsize_stack(带保护页的固定大小栈),它通过在栈底放置不可读写的保护页来检测栈溢出。

配套的boost::context::stack_traits提供栈尺寸查询,POSIX 实现见 src/posix/stack_traits.cpp,其行为可以总结为:

方法POSIX 实现值说明
default_size()128 × 1024 字节(128 KiB)未指定栈大小时使用的默认值
minimum_size()MINSIGSTKSZ(未定义时为 131072 字节)最小允许栈尺寸
maximum_size()getrlimit(RLIMIT_STACK)rlim_max系统栈上限
page_size()sysconf(_SC_PAGESIZE)系统页大小
is_unbounded()RLIM_INFINITY == limit栈上限是否无界

Hermes 的 StackExecutor.cpp 正是这样使用它们的:当未显式传入栈大小时,回退到ctx::stack_traits::default_size()(即 128 KiB)。

三、源码级原理:汇编切换、activation record 与 thread_local

3.1 三层汇编函数:make / jump / ontop

fcontext 实现(默认实现,对应 CMake 中的BOOST_CONTEXT_IMPLEMENTATION=fcontext)的核心是三个汇编函数,每个都针对「架构 × ABI × 二进制格式 × 汇编器」提供专属版本,见 src/asm 目录:

  • make_*.S:创建新的执行上下文——在新栈上布置初始栈帧,填入入口函数地址,返回一个可被 jump 的 context 指针;
  • jump_*.S:保存当前上下文(寄存器、栈指针等),切换到目标上下文,即完成一次"交出/拿回"控制权;
  • ontop_*.S:在目标上下文"之上"执行一个额外函数后再继续,用于实现上层语义(如向正在运行的协程注入结果)。

jump_x86_64_sysv_elf_gas.Smake_arm64_aapcs_elf_gas.S等文件为代表,命名规范是{make|jump|ontop}_{架构}_{ABI}_{二进制格式}_{汇编器}.{S|asm}

CMake 侧通过四个缓存变量组合出要编译的汇编文件(见 CMakeLists.txt):

set(_asm_suffix ${BOOST_CONTEXT_ARCHITECTURE}_${BOOST_CONTEXT_ABI}_${BOOST_CONTEXT_BINARY_FORMAT}_${BOOST_CONTEXT_ASSEMBLER}${BOOST_CONTEXT_ASM_SUFFIX}) set(ASM_SOURCES src/asm/make_${_asm_suffix} src/asm/jump_${_asm_suffix} src/asm/ontop_${_asm_suffix} )

由 CMake 探测自动推导的四个变量分别是:

变量取值示例含义
BOOST_CONTEXT_ARCHITECTUREx86_64arm64riscv64s390x目标架构
BOOST_CONTEXT_ABIsysvaapcsmso32/n64二进制调用约定
BOOST_CONTEXT_BINARY_FORMATelfmach-opexcoff目标文件格式
BOOST_CONTEXT_ASSEMBLERgasmasmarmasm汇编器类型

其中 ABI 的推导逻辑(CMakeLists.txt)是:ARM 系用aapcs,Windows 用ms,MIPS 按 32/64 位选o32/n64,其余默认sysv。此外 Apple 平台比较特殊,直接使用三个平台无关文件make_apple.Sjump_apple.Sontop_apple.S,由预处理宏在编译期决定目标 CPU(注释中提到这允许clang -arch x86_64 -arch arm64一次编译出双架构产物)。

3.2 activation record:thread_local 的"当前上下文"登记

fiber的 C++ 侧封装维护了一个每线程的 activation record。见 src/fiber.cpp:

namespace boost { namespace context { namespace detail { // zero-initialization thread_local fiber_activation_record * fib_current_rec; thread_local static std::size_t counter; // schwarz counter fiber_activation_record_initializer::fiber_activation_record_initializer() noexcept { if ( 0 == counter++) { fib_current_rec = new fiber_activation_record(); } } ... fiber_activation_record *& fiber_activation_record::current() noexcept { // initialized the first time control passes; per thread thread_local static fiber_activation_record_initializer initializer; return fib_current_rec; } }}}

这段代码透露了几个关键机制:

  1. thread_local存储:每个线程各自持有fib_current_rec,这正是 README 所说 "fiber 可在任意时刻迁移到另一线程" 的实现基础——切换线程后,新线程通过current()惰性初始化自己的 record;
  2. Schwarz Counter 惯用法:用静态计数器保证fib_current_rec只被构造/销毁一次,且按依赖顺序正确析构(析构时断言当前仍处于主上下文,见BOOST_ASSERT(current_rec->is_main_context()));
  3. 惰性初始化current()首次被调用时才通过局部thread_local static触发初始化器,避免全局构造开销。

continuation.cppactivation_record::current()的写法与此完全对称,只是 record 类型不同(见 continuation.cpp)。

另外值得注意的是:fiber.cpp通过BOOST_USE_UCONTEXT/BOOST_USE_WINFIB宏可以切换到基于 POSIXucontext或 Windows Fiber API 的替代实现(对应 CMake 的BOOST_CONTEXT_IMPLEMENTATION=ucontext|winfib),但默认的fcontext实现完全不依赖操作系统 API,这正是其高性能的来源。

四、Hermes 中的实际集成:StackExecutor 与保护栈

4.1 集成方式

Hermes 在 lib/Support/StackExecutor.cpp 中把 boost.context 的 fiber 封装为StackExecutor:为一段 JS 原生回调分配独立的 128 KiB 保护栈,并在该栈上执行用户函数。启用条件是编译宏HERMES_USE_BOOST_CONTEXT(见文件第 10 行#if defined(HERMES_USE_BOOST_CONTEXT) && HERMES_USE_BOOST_CONTEXT)。

核心实现逻辑(StackExecutor.cpp):

class StackExecutor { void *arg_ = nullptr; void (*fn_)(void *) = nullptr; // 每次 fiber 恢复时执行的函数;为 nullptr 则 fiber 终止 ctx::fiber fiber_; public: explicit StackExecutor(size_t stackSize = 0) : fiber_(std::allocator_arg, ctx::protected_fixedsize_stack( stackSize ? stackSize : ctx::stack_traits::default_size()), this { while (fn_) { // 循环直到 callee 变为 nullptr (*fn_)(arg_); // 在独立栈上执行 next = std::move(next).resume(); // 交还控制权 } return std::move(next); // 终止 fiber }) {} void exec(void *arg, void (*fn)(void *arg)) { assert(fn_ == nullptr && "StackExecutor recursive call??"); arg_ = arg; fn_ = fn; fiber_ = std::move(fiber_).resume(); // 进入/恢复 fiber arg_ = nullptr; fn_ = nullptr; } ~StackExecutor() { // 通过 resume 让 fiber 观察到 fn_ == nullptr 而退出 fiber_ = std::move(fiber_).resume(); } };

这段代码与 README 描述的 fiber 语义一一对应:

  • "fiber 能从嵌套函数挂起"(*fn_)(arg_)内部可以任意调用更深层函数,只要最终回到这里调用resume(),控制权就能完整交还;
  • "带状态的转移机制"exec()每次调用把fn_/arg_换成新值后再resume(),fiber 循环会以新参数重新执行,而栈上的一切局部状态保持连续;
  • "从挂起点恢复":多次exec()之间,fiber 的栈帧原封不动,仅入口函数指针被替换。

同时它还演示了protected_fixedsize_stack的价值:Hermes 用它在一个独立、带保护页的栈上运行第三方/JS 侧回调,一旦栈溢出会立即触发保护页错误,而不是静默破坏内存。

4.2 平台回退

有意思的是,StackExecutor.cpp 提供了三套实现,从源码结构上可以推断其设计意图:

  1. 启用HERMES_USE_BOOST_CONTEXT:使用上述 fiber 实现,单线程内完成切换,性能最优;
  2. __wasm__且无线程支持时:退化为 no-op(直接内联调用,带编译警告),因为 wasm 单线程环境没有独立的执行栈概念;
  3. 其余情况:退化为基于SerialExecutor+std::packaged_task的线程池实现,用 OS 线程串行执行回调。

这种三态设计说明:boost.context 的 fiber 是 Hermes 在"需要独立执行栈但不想付出线程切换成本"场景下的首选实现,其他平台则按能力降级。

4.3 单元测试

boost.context 自带的测试位于 libs/context/test,包括:

  • test_fiber.cpp:fiber API 的挂起/恢复/终止行为测试;
  • test_callcc.cpp:continuation API 的测试;
  • test_fcontext.cpp:底层 fcontext 切换原语的测试;
  • test_apply.cpp/test_invoke.cpp:上下文调用约定的测试。

这些测试与上文所述 API 一一对应,是理解"每个 API 语义最终落在哪个汇编原语上"的很好入口。

五、总结

boost.context 为 C++ 生态提供了一个教科书级的执行上下文抽象:

  • 概念层execution_context保存"栈 + 栈指针 + 寄存器 + CPU 标志 + 指令指针"这一整帧状态,fiber 借此实现单线程内、任意嵌套深度的挂起与恢复;
  • 性能层:fiber 切换是纯用户态操作,不经过内核,x86 上开销少于 100 个 CPU 周期,而线程切换因系统调用通常超过 1000 个周期;
  • 实现层:默认fcontext实现由 make/jump/ontop 三组手写汇编完成寄存器与栈的搬运,C++ 侧以thread_localactivation record + Schwarz Counter 管理每线程的当前上下文,并通过stack_traits与多种栈分配器(如protected_fixedsize_stack)管理栈生命周期;
  • 工程层:Hermes 将其裁剪为只使用 fiber API(见 CMakeLists.txt),在 StackExecutor.cpp 中用它实现"在独立保护栈上执行回调",并配套了 wasm / 线程池两套平台回退方案。

对于希望在 C++ 中实现协程、用户态线程或yield语义的开发者,boost.context 的这套"基础能力 + 可裁剪集成"设计,以及在 Hermes 中的真实落地,都是极具参考价值的范本。

  • 语言运行时
  • 编译器
  • 移动开发

【免费下载链接】hermes

A JavaScript engine optimized for running React Native.

项目地址:https://gitcode.com/gh_mirrors/hermes/hermes
点击查看免费下载

相关推荐

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

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

Flask绩效管理系统源码解析:40文件分层架构与二次开发实战

简介&#xff1a;这是一套基于Python与Flask框架开发的绩效管理系统设计源码&#xff0c;面向希望学习企业级Web开发实践的学生、开发者&#xff0c;以及需要快速搭建绩效管理平台的中小团队。系统围绕员工绩效考核、项目测试与月度报表等业务场景&#xff0c;将数据访问、业务…

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

ITD分解原理与实战:故障信号诊断如何识别轴承齿轮冲击特征

简介&#xff1a;ITD分解&#xff08;瞬时时间差分&#xff09;是机械故障诊断中常用的信号处理技术&#xff0c;能从复杂的振动或状态信号中分离出携带故障特征的不同分量&#xff0c;广泛用于旋转机械、轴承等设备的早期异常识别。这一MATLAB代码包面向故障诊断与信号处理学习…

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

抖音网页版登录入口在哪?右上角头像扫码登录全解析

1. 从“找不到入口”说起&#xff1a;这个问题的本质是什么“抖音网页版登录入口在哪”——这个问题看起来简单到不值一提&#xff0c;但如果你在搜索引擎里翻几页&#xff0c;会发现问的人远比想象中多。有人是在公司电脑上想摸鱼刷会儿视频&#xff0c;有人是想用大屏看直播更…

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

大型集团基础设施架构蓝图设计:BPIT运营模式与四级分层模型落地指南

简介&#xff1a;这份PPT是埃森哲大型集团管控信息化战略规划项目系列中的蓝图设计方案&#xff0c;聚焦基础设施架构与BPIT运营模式&#xff0c;面向企业信息化规划人员、架构师及集团IT管理者&#xff0c;用于解决多业务系统难集成、难共享、重复建设等管控难题。资源包共1个…

作者头像 李华
网站建设 2026/9/23 22:44:32

无人机巡维保障系统后端:状态机与轨迹数据处理实践

简介&#xff1a;基于Java语言的uav-patrol-backend巡维保障系统后端源码&#xff0c;面向无人机巡维业务的后端开发人员&#xff0c;提供了一套模块化、可扩展的服务端实现&#xff0c;可支撑无人机巡维任务管理、设备状态监控与数据上报等核心服务。项目包含51个文件、共93KB…

作者头像 李华