多进程架构与 IPC 系统
前置知识
- ›第 1 篇:架构概览(多进程模型介绍)
- ›了解进程间通信的基本概念(消息传递、序列化)
- ›C++ 虚函数分发机制与模板元编程基础
多进程架构与 IPC 系统
WebKit2 的多进程架构,是该项目自最初从 KHTML fork 以来最重要的架构革新。通过将 Web 内容运行在独立的隔离进程中,WebKit 确保了:被攻击的网页无法访问用户的文件系统,崩溃的标签页不会拖垮整个浏览器,GPU 驱动的漏洞也不会升级为安全隐患。
但多进程架构的价值,最终取决于其 IPC 系统的质量。本文将深入剖析 WebKit 自研的消息传递框架——从 .messages.in 定义文件,到代码生成,再到运行时分发——并通过追踪一次跨进程的页面加载过程,展示这一切是如何协同工作的。
四种进程类型
在第 1 篇中我们已介绍了 WebKit2 使用的四种进程类型,现在来看它们对应的实现类:
flowchart TD
subgraph UI["UI Process"]
APP["Host App (Safari)"]
WPP["WebPageProxy<br/>4,000+ LOC"]
NPP["NetworkProcessProxy"]
GPP["GPUProcessProxy"]
end
subgraph WC["WebContent Process"]
WP["WebPage<br/>wraps WebCore::Page"]
WCORE["WebCore engine"]
JSC_R["JSC runtime"]
end
subgraph NP["Network Process"]
NETP["NetworkProcess"]
NETC["NetworkConnectionToWebProcess"]
end
subgraph GP["GPU Process"]
GPUP["GPUProcess"]
GPUC["GPUConnectionToWebProcess"]
end
WPP <-->|"IPC::Connection"| WP
NPP <-->|"IPC::Connection"| NETP
GPP <-->|"IPC::Connection"| GPUP
WP <-->|"IPC::Connection"| NETC
WP <-->|"IPC::Connection"| GPUC
UI Process 就是应用程序本身。它持有 WebPageProxy——一个镜像网页状态、并通过 IPC 转发用户操作的代理对象。WebPageProxy 实现了 IPC::MessageReceiver 接口,用于处理来自 WebContent 进程的响应。
WebContent Process 运行 WebPage,它封装了 WebCore::Page(渲染引擎),同时实现了 IPC::MessageReceiver 和 IPC::MessageSender。通常情况下,每个标签页都有自己独立的 WebContent 进程。
Network Process 集中处理所有 HTTP 网络请求。将所有网络请求统一路由到单一进程,使 WebKit 能够在不向 WebContent 进程直接开放网络访问权限的前提下,统一执行 Cookie 策略、管理缓存和处理存储。
GPU Process 负责 GPU 加速相关的操作。历史上,GPU 驱动代码一直是安全漏洞的高发地。将其隔离在独立进程中,可以有效防止其影响 WebContent 沙盒和 UI 进程。
AuxiliaryProcess:统一的子进程生命周期
所有子进程(WebContent、Network、GPU)都继承自 AuxiliaryProcess:
classDiagram
class IPC_Connection_Client {
<<interface>>
+didReceiveMessage()
+didClose()
}
class IPC_MessageSender {
<<interface>>
+send()
+sendSync()
#messageSenderConnection() Connection*
}
class AuxiliaryProcess {
+initialize()
+disableTermination()
+enableTermination()
+addMessageReceiver()
+removeMessageReceiver()
}
class WebProcess {
-WebPage pages
}
class NetworkProcess {
-NetworkSession sessions
}
class GPUProcess {
-GPUConnectionToWebProcess connections
}
IPC_Connection_Client <|-- AuxiliaryProcess
IPC_MessageSender <|-- AuxiliaryProcess
AuxiliaryProcess <|-- WebProcess
AuxiliaryProcess <|-- NetworkProcess
AuxiliaryProcess <|-- GPUProcess
这里的关键设计决策在于:AuxiliaryProcess 同时继承了 IPC::Connection::Client(接收消息)和 IPC::MessageSender(发送消息)。这构建出一套统一的生命周期模型:初始化、收发消息、终止。
addMessageReceiver 和 removeMessageReceiver 方法允许进程动态注册不同消息类型的处理器。消息通过 ReceiverName(一个标识目标子系统的枚举值)进行路由,并可选地通过目标 ID(标识具体实例,例如某个特定的网页)进一步定向。
IPC 框架:Connection、Sender、Receiver
核心 IPC 基础设施位于 Source/WebKit/Platform/IPC/ 目录下,由三个核心类组成:
IPC::Connection 是一个线程安全、引用计数的 IPC 通道,底层基于平台原语实现(macOS 上使用 Mach ports,Linux 上使用 Unix domain sockets)。它负责消息的序列化、反序列化和分发。
IPC::MessageSender 是提供 send() 和 sendSync() 方法的接口。发送方无需了解底层连接的细节——只需调用 send(SomeMessage { args... }),框架会自动完成序列化和投递。
IPC::MessageReceiver 是接收端的对应接口,定义了 didReceiveMessage()、didReceiveMessageWithReplyHandler() 和 didReceiveSyncMessage() 等虚方法。每个接收器解析传入的 Decoder,并将消息分发到对应的处理函数。
提示: 调试 IPC 问题时,
Connection.h是最好的切入点。它定义了消息分发线程、同步消息的超时行为,以及连接中断时的错误处理逻辑。
消息定义与代码生成
WebKit 并不手写 IPC 分发代码。它通过自定义 IDL 在 .messages.in 文件中定义消息,再由 Python 脚本生成对应的 C++ 分发代码。
来看 NetworkProcess.messages.in:
[
DispatchedFrom=UI,
DispatchedTo=Networking,
ExceptionForEnabledBy
]
messages -> NetworkProcess : AuxiliaryProcess WantsAsyncDispatchMessage {
InitializeNetworkProcess(struct WebKit::NetworkProcessCreationParameters ...) -> ()
CreateNetworkConnectionToWebProcess(...) -> (...) AllowedWhenWaitingForSyncReply
...
}
有几点值得关注:
- 头部注解(
DispatchedFrom=UI、DispatchedTo=Networking)明确标注了消息的流向。 -> ()语法定义回复消息(默认为异步)。AllowedWhenWaitingForSyncReply标记的消息,即使在进程阻塞等待同步回复时也能被处理。- 平台相关的消息使用
#if USE(SOUP)/#if USE(CURL)进行条件编译保护。
flowchart LR
MSG["NetworkProcess.messages.in"] --> PARSER["webkit/parser.py"]
PARSER --> MODEL["webkit/model.py<br/>(AST of messages)"]
MODEL --> GEN["generate-message-receiver.py"]
GEN --> HEADER["NetworkProcessMessages.h<br/>(message enums, structs)"]
GEN --> RECV["NetworkProcessMessageReceiver.cpp<br/>(dispatch switch)"]
generate-message-receiver.py 脚本使用 webkit.parser 解析 .messages.in 文件,再通过 webkit.messages 生成 C++ 代码。生成的接收器包含一个大型 switch 语句,负责反序列化每种消息类型并调用相应的处理方法。
代码生成的方式至关重要:它保证了类型安全的序列化(编译器会检查消息参数类型是否匹配),消除了样板代码,并且只需编辑一个 .messages.in 文件就能轻松添加新消息。
代理模式:WebPageProxy ↔ WebPage
WebKit2 架构最典型的示例,是 WebPageProxy(UI 进程)与 WebPage(WebContent 进程)之间的镜像关系。让我们追踪一次用户发起的页面加载过程:
sequenceDiagram
participant User
participant WPP as WebPageProxy<br/>(UI Process)
participant IPC as IPC::Connection
participant WP as WebPage<br/>(WebContent Process)
participant WC as WebCore::Page
User->>WPP: loadURL("https://example.com")
WPP->>WPP: Update navigation state
WPP->>IPC: send(LoadURL { url, ... })
IPC->>WP: didReceiveMessage(LoadURL)
WP->>WC: mainFrame().loader().load(request)
WC-->>WP: didStartProvisionalNavigation
WP->>IPC: send(DidStartProvisionalLoadForFrame { ... })
IPC->>WPP: didReceiveMessage(DidStartProvisionalLoad)
WPP->>WPP: Update UI (show loading indicator)
Note over WC: Network process fetches HTML...
WC-->>WP: didCommitNavigation
WP->>IPC: send(DidCommitLoadForFrame { ... })
IPC->>WPP: didReceiveMessage(DidCommitLoad)
WPP->>WPP: Update URL bar, title
这个过程中有几个关键机制:
-
状态镜像 ——
WebPageProxy在本地维护导航状态的副本(当前 URL、标题、加载进度),使 UI 进程无需 IPC 往返即可查询这些信息。 -
默认异步 —— 大多数消息都是异步的。UI 进程发出
LoadURL后立即继续执行,不会阻塞等待页面加载完成。 -
双向通信 ——
WebPageProxy向WebPage发送指令,WebPage向WebPageProxy回传事件。两端都实现了MessageReceiver。 -
WebCore 隔离 —— WebCore 的
Page对象只在 WebContent 进程中被访问。UI 进程永远不会直接调用 WebCore 的 API。
这种代理模式在 WebKit2 中随处可见:NetworkProcessProxy ↔ NetworkProcess、GPUProcessProxy ↔ GPUProcess,以及更多专用的代理对。这套模式如此一致,以至于只要理解了其中一对,就能举一反三地理解其他所有代理对。
提示: 想查找某个组件的所有 IPC 消息,可以搜索
.messages.in文件:find Source/WebKit -name "*.messages.in"。这类文件有数十个,它们构成了完整的 IPC 接口索引。
下一步
至此,我们已经覆盖了从内存管理、DOM 渲染到多进程 IPC 的各个架构层次。在第 5 篇中,我们将深入 JavaScriptCore——即 JavaScript 引擎——追踪源代码如何经历从解释执行到优化 JIT 的四个编译层级,B3 编译器后端如何生成机器码,以及 Riptide 垃圾回收器如何管理对象生命周期。本文介绍的 IPC 模式,在我们讨论 JSC 垃圾回收器如何与 WebCore 的引用计数对象协调时,还会再次出现。