Read OSS

多进程架构与 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::MessageReceiverIPC::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(发送消息)。这构建出一套统一的生命周期模型:初始化、收发消息、终止。

addMessageReceiverremoveMessageReceiver 方法允许进程动态注册不同消息类型的处理器。消息通过 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
    ...
}

有几点值得关注:

  1. 头部注解(DispatchedFrom=UIDispatchedTo=Networking)明确标注了消息的流向。
  2. -> () 语法定义回复消息(默认为异步)。
  3. AllowedWhenWaitingForSyncReply 标记的消息,即使在进程阻塞等待同步回复时也能被处理。
  4. 平台相关的消息使用 #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

这个过程中有几个关键机制:

  1. 状态镜像 —— WebPageProxy 在本地维护导航状态的副本(当前 URL、标题、加载进度),使 UI 进程无需 IPC 往返即可查询这些信息。

  2. 默认异步 —— 大多数消息都是异步的。UI 进程发出 LoadURL 后立即继续执行,不会阻塞等待页面加载完成。

  3. 双向通信 —— WebPageProxyWebPage 发送指令,WebPageWebPageProxy 回传事件。两端都实现了 MessageReceiver

  4. WebCore 隔离 —— WebCore 的 Page 对象只在 WebContent 进程中被访问。UI 进程永远不会直接调用 WebCore 的 API。

这种代理模式在 WebKit2 中随处可见:NetworkProcessProxyNetworkProcessGPUProcessProxyGPUProcess,以及更多专用的代理对。这套模式如此一致,以至于只要理解了其中一对,就能举一反三地理解其他所有代理对。

提示: 想查找某个组件的所有 IPC 消息,可以搜索 .messages.in 文件:find Source/WebKit -name "*.messages.in"。这类文件有数十个,它们构成了完整的 IPC 接口索引。

下一步

至此,我们已经覆盖了从内存管理、DOM 渲染到多进程 IPC 的各个架构层次。在第 5 篇中,我们将深入 JavaScriptCore——即 JavaScript 引擎——追踪源代码如何经历从解释执行到优化 JIT 的四个编译层级,B3 编译器后端如何生成机器码,以及 Riptide 垃圾回收器如何管理对象生命周期。本文介绍的 IPC 模式,在我们讨论 JSC 垃圾回收器如何与 WebCore 的引用计数对象协调时,还会再次出现。