Ladybird 浏览器多进程架构解析:进程隔离、沙箱机制与跨进程调用链
本文基于 Ladybird 官方文档 Documentation/ProcessArchitecture.md,系统讲解该浏览器的多进程架构设计:每个标签页独占一个 WebContent 渲染进程,网络请求与图片解码分别剥离到 RequestServer 与 ImageDecoder 独立进程中,并深入源码层剖析进程拉起方式、沙箱实现(pledge()/unveil())以及从 OutOfProcessWebView 到 Web::Document 的跨进程对象模型。读完本文,你可以理解 Ladybird 如何以进程边界换取面对恶意网页内容时的稳定性与安全性,并能定位到仓库中对应的服务入口、IPC 协议与沙箱代码。
一、整体进程模型
Ladybird 采用多进程架构(multi-process architecture)来提升面对任意(甚至可能是恶意的)网页内容时的稳定性与安全性。其核心分工可以概括为:
- Browser(浏览器主应用):每个实例可以打开一个或多个标签页(tab);
- WebContent:每个标签页对应一个为其专属拉起的 WebContent 服务进程,负责渲染该页内容;
- RequestServer:网络请求(HTTP/HTTPS 等)从 WebContent 中剥离出来,交给独立的 RequestServer 进程;
- ImageDecoder:图片解码(PNG、JPEG、BMP、ICO、PBM 等)也从 WebContent 剥离,交给独立的 ImageDecoder 进程。
这种“渲染、网络、解码三分离”的结构意味着:一张格式恶意的图片崩溃只会牺牲一个短命的 ImageDecoder 进程;一个页面里的网络逻辑异常也影响不到 UI 主进程。所有进程都会使用 pledge() 和 unveil() 机制进行激进沙箱化,并且除 Browser 外,其余进程都以与桌面主用户隔离的无权限用户身份运行。
需要说明的是,官方文档开头明确标注:
NOTE: This document is partly aspirational, in that the state of the code does not yet fully reflect what's described here. Implementation is underway.
即文档描述的某些设计(例如系统级 LookupServer 处理全部出站 DNS)是目标形态,当前代码尚在演进中。本文在介绍文档蓝图的同时,会结合当前仓库源码指出实际实现状态。
二、三大进程的职责边界
2.1 WebContent 进程:HTML/CSS 引擎与 JS 运行时
WebContent 进程承载整个浏览器的“大脑”:
- 托管主 HTML/CSS 引擎 LibWeb;
- 运行 JavaScript 引擎 LibJS;
- 接收来自 Browser 进程输入事件,并将网页内容绘制到共享位图(shared bitmaps)中;
- 与外界通信只能通过 RequestServer 进行——从源码结构看,WebContent 自身不直接持有网络出口。
在 WebContent 服务入口 中可以验证这一设计。该进程启动时的关键步骤包括:
- 初始化事件循环并安装 LibWeb 的事件循环插件(
Web::Platform::EventLoopPlugin::install); - 通过
Web::Bindings::initialize_main_thread_vm(Web::HTML::AgentType::SimilarOriginWindow)初始化 JavaScript 主线程虚拟机——这正是 LibJS 在 WebContent 进程内运行的落点; - 在沙箱启用时调用
RendererSandbox::apply_sandbox(config_path, cache_path)(见 main.cpp 第 239-240 行),把文件系统等权限收窄到配置目录与缓存目录; - 注册两个跨进程回调,分别是连接资源加载器(即 RequestServer)与连接图片解码器。
其中第 4 步是理解“WebContent 只能经 RequestServer 与外界通信”的关键。在 Services/WebContent/main.cpp 第 252-259 行:
webcontent_client->on_request_server_connection = &heap {
if (auto result = connect_to_resource_loader(heap, handle); result.is_error())
dbgln("Failed to connect to resource loader: {}", result.error());
};
webcontent_client->on_image_decoder_connection = [](auto const& handle) {
if (auto result = connect_to_image_decoder(handle); result.is_error())
dbgln("Failed to connect to image decoder: {}", result.error());
};
connect_to_resource_loader() 收到连接句柄后,用其创建 IPC::Transport,构造一个 Requests::RequestClient,然后交给 Web::ResourceLoader(main.cpp 第 272-285 行);connect_to_image_decoder() 则构造 ImageDecoderClient::Client 并安装为 Web::Platform::ImageCodecPlugin 的实现(main.cpp 第 287-300 行)。也就是说,LibWeb 内部发起的一切资源请求与图片解码,最终都被转发到另外两个进程去执行——文档中“剥离网络与解码”的设计在这里得到了直接的代码印证。
WebContent 进程还暴露了相当完整的命令行开关,均通过 Core::ArgsParser 解析(main.cpp 第 155-190 行),其中与安全/隔离直接相关的包括:
| 参数 | 作用 |
|---|---|
--site-isolation <mode> |
站点隔离模式,可取 disable、top-level(默认)或 iframe,解析逻辑位于 LibWebView 的 SiteIsolation 模块 |
--certificate <path> |
指定信任的证书文件('C' 短选项) |
--mach-server-name <name> |
指定 Mach 服务名(macOS 下用于查找浏览器进程) |
--wait-for-debugger |
启动后等待调试器附加 |
--disable-sandbox |
禁用进程沙箱(默认启用沙箱) |
--enable-http-memory-cache |
开启 HTTP 内存缓存 |
--headless |
以无头模式运行 |
--test-mode / --expose-internals-object / --expose-experimental-interfaces |
测试与实验性接口开关 |
2.2 RequestServer 进程:唯一的网络出口
文档描述:RequestServer 进程可以使用网络协议(如 HTTP 或 HTTPS)向外请求文件;每个 WebContent 进程都拥有自己专属的一个 RequestServer,代表它进行上传下载。对于 DNS 解析,文档蓝图是 RequestServer 向系统级的全局 LookupServer 服务求助,由后者统一处理所有出站 DNS 请求。
从当前仓库源码看,RequestServer 已具备完整的独立服务能力,其入口 Services/RequestServer/main.cpp 的主要流程:
- 解析命令行参数,支持:
--certificate <path>('C'):信任的证书文件,当前仅支持单个根证书(源码中留有 FIXME:尚不支持多张自定义根证书);--http-disk-cache-mode <mode>:HTTP 磁盘缓存模式,可取enabled(Normal)、testing(Testing)或disabled,由 LibHTTP 的 DiskCache 模块 实现;--resource-map <path>:加载 URL 到本地文件的 JSON 映射,由RequestServer::ResourceSubstitutionMap::load_from_file载入,便于测试时把远端资源替换为本地文件;--cache-path <path>:profile 缓存目录;--wait-for-debugger/--disable-sandbox/--mach-server-name。
- 初始化 libcurl(
RequestServer::initialize_libcurl()),说明其网络实现基于 curl 栈(对应 Services/RequestServer/CURL.h 等文件); - 若未禁用沙箱,调用
RequestServer::apply_sandbox(certificates, cache_path)(main.cpp 第 106-107 行),沙箱实现位于 Services/RequestServer/Sandbox.h 及其 Linux/macOS 平台变体; - 通过
IPC::take_over_accepted_client_from_system_server<RequestServer::ConnectionFromClient>(...)接管系统服务器转交的连接,进入事件循环。
值得注意的是 DNS 部分:当前仓库中 RequestServer 自带 Resolver 模块,负责解析;文档中“系统全局 LookupServer 统一处理出站 DNS”的描述属于 SerenityOS 式系统设计的延续,属于文档标注的“aspirational”部分,当前实现可以推断为由 RequestServer 自行完成解析,这一点在仓库中没有全局 LookupServer 服务目录作为佐证。
2.3 ImageDecoder 进程:每张图片一个全新进程
ImageDecoder 进程负责把 PNG、JPEG、BMP、ICO、PBM 等编码格式解码为位图。它的隔离强度是三者中最高的:
- 每张图像都在一个全新的 ImageDecoder 进程中解码;
- 这些进程被强沙箱化,除了接收编码后的位图数据并在解码成功时把位图返回给 WebContent 外,几乎不能做任何其他事。
配合 WebContent 侧的 on_image_decoder_connection 回调(见 2.1 节)可以看出调用链:LibWeb 需要解码一张图 → 经由 ImageCodecPlugin(实现为 WebView::ImageCodecPlugin)→ 通过 ImageDecoderClient::Client 的 IPC 连接 → 到达 Services/ImageDecoder 中的解码服务。解码完成(或失败崩溃)后该短命进程即被回收,恶意图片攻击的爆炸半径被压缩到单个进程内。
三、进程是如何被拉起的:portal 套接字与 SystemServer
文档对进程拉起方式给出了明确描述:
要获取一个全新的 WebContent 进程,任何具备合适文件系统权限者都可以通过连接
/tmp/session/%sid/portal/webcontent套接字来拉起一个(%sid为当前登录会话 id)。该套接字由 SystemServer 管理,并且会为每一个连接拉起一个 WebContent 新实例。RequestServer 与 ImageDecoder 遵循同样的基本模式,区别在于这些服务是由 WebContent 按需拉起的,而不是由 Browser 拉起。
对应到当前代码,这个“系统服务器转交连接”的机制由 LibIPC 统一实现。三个服务进程的入口都调用了同一组设施:
- WebContent:
IPC::take_over_accepted_client_from_system_server<WebContent::ConnectionFromClient>(mach_server_name, enable_test_mode)(Services/WebContent/main.cpp 第 248 行); - RequestServer:
IPC::take_over_accepted_client_from_system_server<RequestServer::ConnectionFromClient>(...)(Services/RequestServer/main.cpp 第 114-121 行)。
即客户端(Browser 或 WebContent)连接到系统服务器暴露的 portal 套接字,系统服务器接受连接后把 IPC 通道“移交”给新拉起的进程。这一模式同时解释了“每个标签页一个 WebContent”与“每个 WebContent 一个 RequestServer”的进程谱系。
在客户端一侧,浏览器应用内的 OutOfProcessWebView(前向声明见 Libraries/LibWebView/Forward.h 第 34 行)负责编排这些辅助进程的创建。Libraries/LibWebView/ProcessManager.cpp 中的 ProcessType 枚举把 WebContent、RequestServer、ImageDecoder 等服务类型统一管理,Application.cpp 则按 ProcessType::WebContent 等类型分发启动参数与调试选项——从源码结构看,Application 层是“一个标签页 → 一条进程树”这一关系的调度中枢。
平台差异也值得注意:非 macOS 平台走上述系统服务器套接字路径;macOS 上 WebContent 通过 Mach 端口从 bootstrap server 查找浏览器端口再建立传输(Services/WebContent/main.cpp 第 242-246 行),而 Windows 上 IPC 通道还带有 InitTransport 消息用于交换对端 PID(main.cpp 第 276-279 行)。
四、沙箱:pledge/unveil 与无权限用户
文档给出的安全基线有两条:
- 所有进程都使用
pledge()与unveil()机制被激进地沙箱化——这两个原语分别承诺(pledge)收窄进程可用的系统调用集合、揭示(unveil)进程可访问的路径白名单; - 除 Browser 外,所有进程都运行在与桌面主用户隔离的无权限用户身份下,进一步抬高跨进程提权/滥用的门槛。
当前仓库把这一机制模块化为独立组件:
- Services/RendererSandbox.h 声明
RendererSandbox::apply_sandbox(Optional<StringView> config_path, Optional<StringView> cache_path),并配有 RendererSandboxLinux.cpp、RendererSandboxMacOS.cpp 与 RendererSandboxUnimplemented.cpp 三套平台实现,WebContent 进程在启动早期(见 2.1 节)即调用它,把文件系统可见性限制到配置与缓存路径; - RequestServer 则有自己的沙箱模块 Services/RequestServer/Sandbox.h(含
SandboxLinux.cpp/SandboxMacOS.cpp/SandboxUnimplemented.cpp),在initialize_libcurl()之后、接管 IPC 连接之前调用,确保 curl 网络栈运行在收窄后的权限域内; - 两条路径都提供
--disable-sandbox开关以便调试,但默认沙箱是开启的。
由于各服务以不同平台策略执行沙箱(Unimplemented 变体用于尚无原生沙箱支持的系统),阅读源码时应注意“平台能力决定实际隔离强度”这一前提。
五、类级结构:从 OutOfProcessWebView 到 Web::Document
文档的“Class overview”一节给出了进程内部的对象层级,配图如下:
按文档描述,各层职责如下:
OutOfProcessWebView(GUI 应用进程内):一个被放置在窗口某处的控件,负责拉起所有辅助进程等杂务;WebContentClient:OutOfProcessWebView内部持有,实现 WebContent IPC 协议的客户端侧;WebContent::ConnectionFromClient(WebContent 进程内):WebContentClient的对端,其内部持有WebContent::PageHost,而PageHost托管着 LibWeb 引擎的主对象Web::Page;Web::Page→Web::Frame:一个 Page 拥有一个主 Frame,并可能有与<frame>/<iframe>对应的子 Frame;Web::Document:每个 Frame 持有一个 Document,即 DOM 树的根节点。
这些关键类在当前仓库中均可直接定位:
WebContent::ConnectionFromClient与WebContent::PageHost分别位于 Services/WebContent/ConnectionFromClient.h 与 Services/WebContent/PageHost.h;- WebContent 服务侧的 IPC 协议由 WebContentServer.ipc、WebContentClient.ipc 等
.ipc文件描述,与WebContentClient的客户端协议互为镜像; Web::Page、Web::Frame、Web::Document则位于 LibWeb(Libraries/LibWeb)中。文档明确声明:详细描述 LibWeb 对象模型超出该文档范围,本文同样止步于进程边界处的对象链。
六、与文档蓝图的差异及源码佐证小结
| 文档蓝图 | 当前仓库状态 | 依据 |
|---|---|---|
| 每标签页一个 WebContent,网络/解码外置 | 已实现,且 WebContent 通过 on_request_server_connection / on_image_decoder_connection 回调接入外部服务 |
Services/WebContent/main.cpp 第 252-259 行 |
| portal 套接字 + SystemServer 按连接拉起进程 | 以 LibIPC 的 take_over_accepted_client_from_system_server 实现 |
Services/RequestServer/main.cpp 第 114-121 行 |
| 所有进程 pledge/unveil 沙箱 + 无权限用户 | 模块化为 RendererSandbox / RequestServer Sandbox,按平台实现,默认开启 | Services/RendererSandbox.h、Services/RequestServer/Sandbox.h |
| 系统全局 LookupServer 统一处理 DNS | 当前 RequestServer 内置 Resolver,全局 LookupServer 未见对应服务目录,属于 aspirational 部分 | Services/RequestServer/Resolver.h |
OutOfProcessWebView → WebContentClient → PageHost → Page/Frame/Document 对象链 |
各类均存在,IPC 协议以 .ipc 文件显式定义 |
Services/WebContent/ConnectionFromClient.h、Services/WebContent/PageHost.h |
七、延伸阅读
- 原始架构文档:Documentation/ProcessArchitecture.md;
- WebContent 服务入口与参数全集:Services/WebContent/main.cpp;
- RequestServer 服务入口(curl 栈、磁盘缓存、资源替换映射):Services/RequestServer/main.cpp;
- 渲染器沙箱抽象:Services/RendererSandbox.h 与平台实现 Services/RendererSandboxLinux.cpp、Services/RendererSandboxMacOS.cpp;
- 浏览器侧进程编排:Libraries/LibWebView/ProcessManager.h、Libraries/LibWebView/Application.cpp;
- 站点隔离模式定义:Libraries/LibWebView/SiteIsolation.h;
- 构建与编译细节可参考 Documentation/BuildInstructionsLadybird.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

