iOS WebKit鸿蒙 ArkWeb 渲染进程深度对比

以 XNU(xnu-11215.1.10,iOS 18)内核源码为证据基座,逐条锚定 iOS 端内存治理与 JIT 安全机制;对照鸿蒙 ArkWeb 官方架构文档,剖析两大移动 Web 引擎在「渲染进程」上的设计与取舍。
XNU · xnu-11215.1.10(iOS 18) WebKit2 多进程架构 ArkWeb · Chromium M114→M144 行号可复核

① 一句话概览

iOS 的 WebKit 是 Apple 自研的多进程 Web 引擎(WebKit2 架构):应用进程只持有 WKWebView 这个"遥控器",真正解析 HTML、执行 JS、排版绘制的是独立的 WebContent 进程,网络走 Networking 进程,整套进程由 XNU 内核的 per-task 内存账本(ledger)、jetsam 优先级带、freezer 冻结机制精细治理——内核源码里甚至有对 com.apple.WebKit.WebContent按名字特判

鸿蒙的 ArkWeb 渲染进程则基于 Chromium + CEF 构建(官方明示),是"五进程模型"中的一环:Web 渲染进程负责 HTML 解析/排版/绘制 + JS/WASM 执行,GPU 光栅化与合成送显被拆到独立的 Web GPU 进程并与系统 RenderService 对接;移动端默认多个 Web 实例共享一个渲染进程以省内存,2in1 设备才默认独立进程。鸿蒙侧官方文档明确写着「Web 内核对内存大小的申请无限制约束」,内存治理策略与 iOS 完全不同。

本质区别一句话:两者都把"网页代码"隔离进无特权的独立渲染进程(防沙箱逃逸),但 iOS 走的是自研引擎 + 内核级强内存配额 + 按站点拆进程的路线;鸿蒙走的是 Chromium 血统 + 共享渲染进程省内存 + 独立 GPU 进程对接自研合成器的路线。

iOSApp(UI进程) → WebContent进程 → Networking进程
合成:RenderServer
VS
HarmonyOSApp进程 → Web渲染进程 → Web GPU进程
孵化:Foundation + Web孵化进程
合成:RenderService

② WebKit 是什么?它有哪些功能

WebKit 是 Apple 主导的开源 Web 浏览器引擎(C++ 为主,部分 C/汇编在 JavaScriptCore,Cocoa 集成为 Objective-C)。它是 iOS/macOS 上的系统框架,Safari、邮件、备忘录、图书、新闻、App Store 都用它,第三方 App 内嵌网页也必须通过 WKWebView

2.1 组件构成(每个组件对应 Source/ 下一个目录)

组件功能
bmallocWebKit 自己的 malloc:bump pointer allocator + IsoHeap(每种类型隔离到独立页面),核心价值是防御 use-after-free 引发的类型混淆攻击。
WTFWeb Template Framework:模板/容器库(Vector、HashMap、HashSet)与智能指针(Ref/RefPtr/WeakPtr),全代码库的基础设施。
JavaScriptCore (JSC)JS 引擎,四层执行体系:解释器 → Baseline JIT → DFG JIT → FTL JIT(B3 后端),逐层用编译时间换执行速度;JSC 在 iOS 上的 JIT 依赖 XNU 的 mman.h L127 MAP_JIT 机制。
WebCoreWebKit 最大的组件:HTML/XML/CSS 解析器、DOM 树 / Render Tree、HTML/SVG/MathML 元素实现、独有的 CSS JIT(现存唯一的 CSS 即时编译器)。
WebCore/PAL平台抽象层,让 WebCore 在 macOS/iOS/Windows/Linux 保持平台无关。
WebKitLegacy (WebKit1)单进程时代的旧架构,实现 WebView/UIWebView(iOS 8 起废弃)。
WebKit (WebKit2)多进程架构,实现 WKWebView:UI 进程、WebContent 进程、Networking 进程、WebInspector/WebDriver。

2.2 WebKit 的核心功能清单

引擎能力

  • HTML5 / CSS3 / SVG / MathML 全量解析与渲染
  • JavaScript 执行(JSC 四层 JIT + WASM)
  • DOM 事件、Browsing Context、历史栈管理
  • 媒体:MSE(媒体源扩展)、压缩流模块
  • WebGPU / WebGL(ANGLE 抽象,Metal 后端)
  • 存储:Cookie、LocalStorage、IndexedDB、Cache Storage

平台与安全能力

  • 进程隔离:网页代码只能碰 WebContent 沙箱
  • 站点隔离:跨站 iframe 拆进独立进程
  • 智能防跟踪(ITP)、隐私报告
  • WKWebView 嵌入:导航代理、UI 代理、自定义 URL scheme、消息脚本
  • Web Inspector 远程调试、WebDriver 自动化
  • 内存压力响应(配合 XNU knote 通知链,见 §6.7)

③ iOS WebKit 的进程模型

3.1 WebKit2 四类进程

进程职责iOS 特点
UI 进程
(即宿主 App)
持有 WKWebView、处理用户输入、页面生命周期、权限弹窗、原生 UI(菜单/弹窗)。 与 App 同进程;WebKit 以系统 framework 形态嵌入。
WebContent 进程
com.apple.WebKit.WebContent
跑网站的代码:HTML 解析、DOM/RenderTree、JS 执行(JSC JIT)、排版、绘制、Media 处理。Safari 每个标签页通常各一个。 沙箱最严:无直接网络 socket(出网必须经 Networking 进程)、无任意文件写、IPC 白名单;受 MACF 策略管控(见 §6.6)。
Networking 进程 所有网络请求、TLS、HTTP 缓存、Cookie;存储管理(默认/隐私会话隔离)。 同一会话的所有 WebContent 共享一个 Networking 进程的网络会话。
WebInspector / WebDriver 开发者工具与自动化。 Inspect WebContent 进程。

3.2 站点隔离(Site Isolation)

WebKit 文档定义 Site = scheme + eTLD+1(可注册域)。开启站点隔离后,跨站 iframe 不再和主框架同进程:

  • RemoteFrame / RemotePageProxy:帧树中代表"另一个进程里的文档"的占位节点,JS 仍可遍历帧树但摸不到跨站 DOM。
  • BrowsingContextGroup:追踪所有经 window.open opener 关系连接的页面及其全部进程;导航跨域时整棵帧树森林要在新进程重建。
  • FrameProcess:记录"哪些帧在用哪些进程",帧全部移除后进程终止或进 WebProcessCache 复用。
  • ProvisionalFrameProxy / ProvisionalPageProxy:导航期间的临时进程驻留,收到网络响应才正式换树,失败可回滚。

为什么要拆进程?渲染引擎解析不可信网页,是整个系统最大的攻击面。任何 Blink/WebCore/JSC 的内存破坏漏洞,最坏也只能拿到 WebContent 的无特权上下文——这正是 iOS WebKit 与鸿蒙 ArkWeb 共同的设计出发点。

3.3 渲染结果的显示链路

iOS 上 WebContent 进程把绘制结果编码成 RemoteLayer 树(IOSurface 后端的 backing store),提交给系统的 RenderServer(即 backboardd 的合成职责)做最终合成送显。WebContent 自身不直接与 GPU 驱动做全量光栅化提交——这层经由平台图形栈与 ANGLE(Metal) 完成。

④ 鸿蒙 ArkWeb 的进程模型

4.1 官方五进程模型

进程唯一性职责(华为官方文档原文语义)
应用进程应用唯一 主进程,含 UI 主线程与 Web 相关线程:网络线程、Video 线程、Audio 线程、IO 线程;负责 Web 组件对外接口与回调、需与其他系统服务交互的功能(网络、媒体服务)。
Foundation 进程系统唯一 接收应用进程的孵化请求,管理应用进程与 Web 渲染进程的绑定关系
Web 孵化进程系统唯一 接收 Foundation 请求,孵化 Web 渲染进程与 Web GPU 进程;孵化后做安全沙箱降权、预加载动态库提速。
Web 渲染进程应用可指定多实例共享或独立 运行 Web 渲染进程引擎(HTML 解析、排版、绘制、渲染)+ ArkWeb 执行引擎(JavaScript、WebAssembly)。
Web GPU 进程应用唯一 光栅化、合成送显,与 GPU、RenderService 交互,提升应用进程稳定性与安全性。
与 iOS 最大的结构差异之一:ArkWeb 的网络线程在应用进程里,而不是像 WebKit 那样拆出独立的 Networking 进程。但 ArkWeb 把 GPU 光栅化/合成 单独拆成了 Web GPU 进程(应用唯一),这一层在 iOS 的 WebKit 里并不以独立进程形态存在(iOS GPU 工作分布在 WebContent 内的图形栈与系统 RenderServer 合成)。

4.2 渲染进程模式的运行时控制

  • setRenderProcessMode(RenderProcessMode.SINGLE=0 / MULTIPLE=1):控制渲染子进程单/多进程。移动设备默认单(共享)进程渲染,2in1 设备默认多进程渲染;传入非法枚举值系统自动回落到多进程模式。
  • sharedRenderProcessToken:多渲染进程模式下,相同 token 的 Web 组件优先复用与该 token 绑定的渲染进程;绑定关系在渲染进程初始化阶段形成,所有关联组件移除后解绑。
  • terminateRenderProcess():应用可主动杀渲染进程;会影响同进程的全部其他实例。
  • onRenderExited(renderExitReason):监听渲染进程退出原因(OOM、crash、正常退出);多组件共用进程时每个受影响组件都会触发回调。
  • onRenderProcessNotResponding / onRenderProcessResponding:渲染进程无响应监听(无法处理输入或未按时导航时触发,持续无响应可反复触发)。

4.3 渲染布局两种模式(决定与系统合成器的关系)

模式机制限制与适用
异步渲染
(默认)
Web 组件作为图形 surface 节点,独立送显(自己管合成节拍)。 高度 ≤ 7680 物理像素,超出白屏;不支持动态切换。适合纯 Web 为主体的页面,性能功耗更优。
同步渲染 Web 组件作为图形 canvas 节点,跟随系统组件一起送显 不支持 DSS(显示子系统)合成;高度最大 500,000 px。适合 Web 与 ArkUI 组件同滚动的超长混合页面。

另有同层渲染(same-layer rendering)支撑小程序类宿主场景,让 Web 内容与原生组件处于同一渲染层。

4.4 内核血统:Chromium + CEF

OpenHarmony web_webview 仓库 README 原文:"nweb is the native engine of the OpenHarmony webview component and is built based on Chromium and the Chromium Embedded Framework (CEF)"。华为官方版本对应表:

HarmonyOS 版本ArkWeb 内核(Chromium)
4.0 及之前M99
4.1 – 5.1M114
6.0M132(默认)/ M114(可选)
6.1M132
7.0M144(默认)/ M132(可选)

因此 ArkWeb 的"Web 渲染进程"在血统上是 Chromium RenderProcess:Blink 排版引擎 + V8 JS 引擎(ARM64 上同时配 Les Wasm 解释器),经 CEF/nweb 适配层接入鸿蒙图形栈(RenderService)与系统服务。

⑤ 渲染进程逐项对比

维度iOSWebKit WebContent鸿蒙ArkWeb Web 渲染进程
引擎血统 Apple 自研 WebKit:WebCore + JavaScriptCore(四层 JIT) Chromium/Blink + V8(经 CEF 适配),版本 M99→M144
进程数策略 每标签页通常一个 WebContent;站点隔离下跨站 iframe 再拆进程 移动端默认多实例共享一个渲染进程;2in1 默认独立;token 定向复用
进程内职责 HTML 解析、DOM/RenderTree、JS(JSC JIT)、排版绘制、媒体 HTML 解析、排版、绘制、渲染 + JS/WASM 执行引擎(职责声明几乎对等)
网络 独立 Networking 进程;WebContent 无 socket 权限 网络线程在应用进程(与 Video/Audio/IO 线程并列)
GPU/合成 图形栈经 ANGLE(Metal);结果以 RemoteLayer/IOSurface 交 RenderServer 合成 独立 Web GPU 进程做光栅化与合成送显,对接 RenderService;异步/同步两种送显模式
进程创建方 UI 进程请求系统(launchd 系)拉起 WebContent 应用 → Foundation 进程 → Web 孵化进程(孵化 + 沙箱降权 + 预加载动态库)
内存治理 XNU per-task phys_footprint ledger + jetsam band + freezer 冻结;内核对 WebContent 有专门统计与冻/解冻特判(§7) 官方文档:「Web 内核对内存大小的申请无限制约束」;治理交由上层(OOM 时 onRenderExited 回调通知应用)
崩溃/无响应感知 进程退出由 WebKit 内部 + 系统服务处理,页面级 reload 显式 API:onRenderExited(OOM/crash/正常)、onRenderProcessNotResponding/Responding
JS JIT 与 W^X JSC JIT 用 MAP_JIT;XNU 强制 JIT 区 RWX 全权限、W^X 互斥检查(§6.5) V8 JIT;鸿蒙内核为 Linux 血统,沿用标准 W^X/双映射策略
可丢弃内存 MADV_FREE_REUSABLE/REUSE(XNU 定义)支撑 WebKit 内存缓存丢弃 Chromium 的 discardable memory(ashmem/memfd 系)
宿主组件 WKWebView(iOS 8+,唯一现代入口;UIWebView 已废弃) ArkUI Web 组件 + WebviewController(@kit.ArkWeb)

⑥ XNU 源码证据:iOS 如何"管住" WebContent

以下全部锚点基于 xnu-11215.1.10(iOS 18),文件归档于本报告仓库 xnu-render-analysis/xnu-11215.1.10/,行号可直接复核。跨版本数据来自 iOS 12/13/14/16/17/18 六版本同口径对比。

6.1 phys_footprint:每个进程一本"内存账"

task.c L1178-1196 的注释块给出了权威定义——WebContent 进程的内存上限按这个口径计量:

/* phys_footprint: This is the sum of:
 *   + (internal - alternate_accounting)
 *   + (internal_compressed - alternate_accounting_compressed)
 *   + iokit_mapped
 *   + purgeable_nonvolatile + purgeable_nonvolatile_compressed
 *   + page_table                                    */
 1241 task_ledgers.internal = ledger_entry_add(t, "internal", ...);
 1243 task_ledgers.iokit_mapped = ledger_entry_add_with_flags(t, "iokit_mapped", ...);
 1249 task_ledgers.page_table = ledger_entry_add_with_flags(t, "page_table", ...);
 1251 task_ledgers.phys_footprint = ledger_entry_add(t, "phys_footprint", "physmem", "bytes");
 1253 task_ledgers.internal_compressed = ledger_entry_add(t, ...);
 1290 task_ledgers.frozen_to_swap = ledger_entry_add(t, "frozen_to_swap", ...);  // CONFIG_FREEZE

要点:匿名脏页 + 压缩器里的份额 + IOKit 映射 + 非易失 purgeable + 页表都算进足迹——WebContent 里庞大的解码图像、IOSurface、JIT 页无一漏网。iOS 18 还加了 media/graphics/neural 分类记账task.c L1259-1295),为分类限费铺路。

超限回调直接挂在账本上:task.c L1460

 1460 ledger_set_callback(t, task_ledgers.phys_footprint,
      task_footprint_exceeded, NULL, NULL);

6.2 超限处理:挂起 → EXC_RESOURCE → 决定生死

 7081 PROC_CROSSED_HIGH_WATERMARK__SEND_EXC_RESOURCE_AND_SUSPEND(...)
      // 先挂起进程(SUSPEND),再发 EXC_RESOURCE 例外;
      // hwm_user_cores 时还会先落一份 coredump(L7104-7112)
 7207 task_footprint_exceeded(int warning, ...)
 7240     // CONFIG_DEFERRED_RECLAIM:先同步回收再复核余额,
      // 余额已回落则直接 return(给进程自救机会)
 7284 task_process_crossed_limit_no_diag(...)
 7297     PROC_CROSSED_HIGH_WATERMARK__SEND_EXC_RESOURCE_AND_SUSPEND(...);  // 真超限
 7319     PROC_CROSSED_HIGH_WATERMARK__SEND_EXC_RESOURCE_AND_SUSPEND(...);  // 诊断阈值

memlimit 分 active/inactive 两档(App 前后台切换时换限额),fatal 与否由 memorystatus 决定。WebContent 进程超限的最终结局通常是 jetsam 的 per-process-limit 原因被杀。

6.3 Jetsam:按"优先级带"从高往低杀

Jetsam 原因码全表 kern_memorystatus.c L103-117

jettisoned | highwater | vnode-limit | vm-pageshortage | proc-thrashing
fc-thrashing | per-process-limit | disk-space-shortage | idle-exit
zone-map-exhaustion | vm-compressor-thrashing | vm-compressor-space-shortage
low-swap | sustained-memory-pressure | vm-pageout-starvation

优先级带 kern_memorystatus.c L126-143:FOREGROUND / AUDIO_AND_ACCESSORY / CONDUCTOR / DRIVER_APPLE / HOME / EXECUTIVE / IMPORTANT / CRITICAL(数值定义在 sys/proc.h 的 JETSAM_PRIORITY_*)。kill 主循环 kern_memorystatus.c L3884-3990(memorystatus_thread_internal) 的次序:先清超 highwater 的进程 → 再 swap 全部 app → 然后逐 band 从最不重要往重要杀,每杀一个都回头重查 highwater。

关键限额参数:

  • L293 MEMORYSTATUS_CRITICAL_BASE_PERCENTAGE_SMALL = 5%(小内存机 critical 档基线;中/大内存机比例不同,L287-307 注释表)
  • L381 memorystatus_ios13extended_footprint_limit_mb = 1800(扩展上限)
  • L384-387 entitled 设备可调 max_task_footprintL610 sysctl kern.max_task_pmem
  • L613 legacy_footprint_bonus_mb = 50(老 App 补偿额度)

6.4 Freezer:把后台 WebContent 冻起来

// kern_memorystatus_freeze.c (xnu-11215.1.10)
  104 unsigned int memorystatus_frozen_count_webcontent = 0;
  224 static void memorystatus_demote_frozen_processes(bool urgent_mode);
  312 calculate_thaw_percentage(uint64_t frozen_count, uint64_t thaw_count);
  366 get_thaw_percentage_webcontent();   // WebContent 专属解冻率

Freezer 把后台进程的用户态匿名页整进程收进压缩器/swap(iOS 18 桌面级 iPad 与 Mac 上 swap 路径完备:vm_compressor_backing_file.c L47/L88/L108/L146/L182,含 FIOPINSWAP ioctl、VSWAP 标记与 ENCRYPTED_SWAP)。冻结名额有限(memorystatus_frozen_processes_max),解冻率决定下一轮还愿不愿意冻它——这正是 §7 里 WebContent 特判的意义。

冻/解冻会计账进 ledger 的 frozen_to_swap 条目 task.c L1290 / L5099-5121

6.5 MAP_JIT 与 W^X:WebContent 里 JSC 的 JIT 空间怎么给

// bsd/sys/mman.h
 127 #define MAP_JIT   0x0800  // Allocate a region that will be used for JIT purposes
 212 #define MADV_FREE_REUSABLE  7   // pages can be reused (by anyone)
 213 #define MADV_FREE_REUSE     8   // caller wants to reuse those pages
// osfmk/vm/vm_map.c — JIT 区域的三重约束
 626/2882 printf("CODE SIGNING: ... curprot cannot be write+execute...");  // W^X 互斥
 2895 if (entry_for_jit && cur_protection != VM_PROT_ALL) {
      // 非 macOS 平台:MAP_JIT 区必须是全 RWX,否则拒绝
 2930     printf("CODE SIGNING: ... JIT requires RWX: failing.");
      return KERN_PROTECTION_FAILURE;
      }
 2945 if (map->map_disallow_new_exec == TRUE) { ... }  // 可执行锁死:封掉新增可执行映射

解读:iOS 上 W 与 X 永久互斥(L626/L2882);带 com.apple.security.cs.allow-jit 身份的进程才能用 MAP_JIT,且该区域权限必须是完整的 RWX(L2895-2930,"JIT requires RWX");进程还能自愿锁死后续可执行映射(L2945)。WebContent 里 JSC 的 Baseline/DFG/FTL JIT 代码页就活在这个 MAP_JIT 区里,受 pg_tlb/code signing 监控。对照:鸿蒙的 V8 JIT 走 Linux 血统内核的标准 W^X 双映射,无 MAP_JIT 概念。

MADV_FREE_REUSABLE/REUSE 则是 WebKit 内存缓存"可丢弃"语义的内核支撑——内存吃紧时这些页可被任何人复用,不计入进程压力。

6.6 MACF:WebContent 沙箱的内核挂钩

XNU 的强制访问控制框架(TrustedBSD MAC)在内核埋了几百个策略点,sandbox/ContainerManager 等策略模块(闭源)挂上来:

// security/mac_process.c (xnu-11215.1.10)
 209 mac_cred_label_associate_fork(kauth_cred_t cred, proc_t proc);
 323 mac_proc_check_debug(...);   // 谁能调试谁
// 加上 mac_cred_label_associate_kernel/user、mac_proc_check_visible 等全套钩子

WebContent 进程"无网络 socket、无任意文件写、IPC 白名单"的沙箱画像,就是用户态沙箱 profile 编译成 MACF 规则后在全部内核策略点上过滤的结果。

6.7 内存压力通知:WebContent 的"自救"通道

// bsd/kern/kern_memorystatus_notify.c (xnu-11215.1.10)
  150 vm_pressure_level_t memorystatus_vm_pressure_level = kVMPressureNormal;
  129 static struct knote *vm_pressure_select_optimal_candidate_to_notify(...);
  228 if (level == kVMPressureWarning || kVMPressureUrgent) { notify... }
  232 else if (level == kVMPressureCritical) { ... }
  443 uint64_t memorystatus_kill_on_sustained_pressure_window_s = 60*10;  // 10 分钟窗口
  453 static void sustained_pressure_handler(void*, void*);      // iOS16+ 引入

内核按 Normal→Warning→Urgent→Critical 分级,经 knote 通知到"最值得通知的进程"(前台优先)。WebContent 收到 pressure 事件后清 JS GC、丢内存缓存、释放解码位图——这是它超限前的自救窗口。持续高压 10 分钟仍不缓解,iOS 16+ 会直接触发 sustained-memory-pressure 击杀(六版本实测:iOS 12/13/14 无此逻辑,16 起出现)。

⑦ 实锤:XNU 内核按名字特判 WebContent 进程

iOS 16(xnu-8792.61.2)起,freezer 代码里出现了对渲染进程的按进程名硬编码统计——内核为 WebContent 单独建了一本"冻结台账":

// kern_memorystatus_freeze.c (xnu-11215.1.10)
// 冻结时:
  628 if (strcmp(p->p_name, "com.apple.WebKit.WebContent") == 0) {
  629     memorystatus_frozen_count_webcontent++;
  630     os_atomic_inc(&memorystatus_freezer_stats.mfs_processes_frozen_webcontent, ...);
       }
// 同样逻辑在 memorystatus_freeze_process() 内再出现一次:L1903-1905

// kern_memorystatus.c (xnu-11215.1.10)
// 进程移除时核销:
 2715 if (strcmp(p->p_name, "com.apple.WebKit.WebContent") == 0) {
 2716     assert(memorystatus_frozen_count_webcontent > 0);
 2717     memorystatus_frozen_count_webcontent--;
       }
// memorystatus_on_resume() 里解冻记账:
 3217 if (strcmp(p->p_name, "com.apple.WebKit.WebContent") == 0) {
 3218     os_atomic_inc(&memorystatus_freezer_stats.mfs_processes_thawed_webcontent, ...);
       }

并用专属 sysctl 暴露 WebContent 解冻率:kern_memorystatus_freeze.c L366-380(get_thaw_percentage_webcontent / kern.memorystatus_freezer_thaw_percentage_webcontent)

为什么值得单列?这是"内核反过来给某个用户态引擎开小灶"的直接证据:iOS 设备上后台标签页的 WebContent 进程既多又大,Freezer 需要单独评估"冻 WebContent 划不划算"(解冻率太高说明反复冻/解冻浪费带宽),进而影响冻结名额分配策略。鸿蒙侧没有任何等价的内核特判——ArkWeb 的内存治理完全在框架层(且官方明示内核不设申请上限),这是两平台最深刻的设计哲学差异之一。

⑧ 演化时间线(六版本实测口径)

iOSxnu tag与 Web 渲染进程相关的变化
12xnu-4903.270.47freezer 代码还内联在 kern_memorystatus.c(freeze 文件不存在);对 WebContent 无任何特判(仅 L2942 注释提到 WebContent 用 inactive limit)
13xnu-6153.141.1freeze 拆分成独立文件
14.8xnu-7195.141.2仍然无 WebContent 特判(L2626 注释同 iOS12)
16xnu-8792.61.2转折点:① freezer 出现 com.apple.WebKit.WebContent 硬编码统计(冻结台账);② 新增 sustained-memory-pressure 击杀(10 分钟持续高压);③ swap 相关引用大增(48→55→101,对应桌面级 swap 能力)
17xnu-10063.141.1延续 iOS16 机制(WebContent 特判 20 处、sustained kill 3 处)
18xnu-11215.1.10延续并微调;ledger 新增 media/graphics/neural 分类记账与 frozen_to_swap 条目;CONFIG_DEFERRED_RECLAIM 给超限进程同步回收自救的机会

鸿蒙侧并行演化:HarmonyOS 4.0(Chromium M99)→ 4.1-5.1(M114)→ 6.0(M132 默认)→ 7.0(M144 默认)。同一时期把渲染进程模式 API 化(setRenderProcessMode / sharedRenderProcessToken / onRenderProcessNotResponding 均为 API 12 引入),并新增 M114→M132 差异适配指南。

⑨ 总结:同一道题的两种解法

Apple 的解法:隔离优先,内核兜底

  • 隔离粒度最细:每标签页一个 WebContent,站点隔离再按 eTLD+1 拆分,网络也独立成进程
  • 内核有牙齿:phys_footprint 账本 + EXC_RESOURCE 挂起 + jetsam per-process-limit,超限即死
  • 内核懂 Web:freezer 为 WebContent 单独记账,评估"冻结它划不划算"
  • 代价:进程多、内存基线高,靠 freezer/压缩器/swap 循环把成本赚回来

鸿蒙的解法:内存优先,框架自治

  • 默认共享渲染进程:移动端多 Web 实例挤一个进程,内存省到极致(离线预创建一个 Web 组件约 200MB)
  • 进程分层孵化:Foundation + Web 孵化进程统一拉起、降权、预加载
  • GPU 独立成进程:Web GPU 进程专职光栅化/合成,对接 RenderService
  • 内核不设限:Web 申请内存无内核级上限约束,OOM 由框架层回调(onRenderExited)交给应用处理
  • 代价:共享进程下一处崩溃全体连坐(terminateRenderProcess 影响所有实例),隔离性弱于 iOS

给开发者的实用结论:在 iOS 上做混合开发,WKWebView 的崩溃与内存问题要盯 WebContent 进程的 phys_footprint(可在 Xcode 的 Memory Report 里看分进程曲线),系统会在超限时杀进程救全局;在鸿蒙上则要主动用 setRenderProcessMode(MULTIPLE) 换隔离性、用 onRenderExited/onRenderProcessNotResponding 兜住共享进程的连坐风险——两个平台对"渲染进程"的治理哲学完全不同,移植方案不能照抄。

⑩ 参考来源与复核方式

来源用途
apple-oss-distributions/xnu @ xnu-11215.1.10(GitHub)osfmk/kern/task.c、osfmk/vm/vm_map.c、bsd/kern/kern_memorystatus(_freeze/_notify).c、bsd/vm/vm_compressor_backing_file.c、security/mac_process.c、bsd/sys/mman.h 全文抓取归档
xnu 六版本存档(iOS 12/13/14/16/17/18)跨版本对比:WebContent 特判出现于 iOS16;sustained-pressure kill 出现于 iOS16;swap 引用 48→50→55→101→102→102
WebKit 官方文档(docs.webkit.ac.cn 中文镜像:WebKit 简介 / Site Isolation)组件构成、JSC 四层 JIT、WebKit2 多进程、站点隔离机制(RemoteFrame/BrowsingContextGroup/FrameProcess/WebProcessCache)
Apple Developer Documentation — WKWebView / WKContentViewWKWebView 定位、UIWebView 废弃关系、导航与 UI 代理
华为开发者文档 — ArkWeb进程 / Web组件渲染模式 / ArkWeb简介 / 使用离线Web组件 / ArkWeb术语ArkWeb 五进程模型、渲染进程模式 API、渲染布局两模式、Chromium 版本对应表、离线组件内存数据
OpenHarmony web_webview 仓库 README"nweb 基于 Chromium + CEF 构建"的官方表述

本页所有 XNU 行号引用格式为 文件 L行号,对应归档文件 xnu-render-analysis/xnu-11215.1.10/(task.c = osfmk/kern/task.c 10396 行、vm_map.c = osfmk/vm/vm_map.c 24141 行 等),可逐条 grep 复核。文档类论断以各官方文档原文为准。

⑪ 追问:不断进入新详情页,旧的不可见页面由谁管理?

场景:淘宝里连续点进多个商品详情页,旧页面早已不可见——iOS 会管理它们吗?答案是会,但不是一个框架,而是四层各管一段;而且 iOS 的"管理"是"停止渲染 + 冻结执行 + 按需清缓存 + 超限杀进程",从不主动替 App 销毁页面对象

11.1 四层分工总表

框架/组件对不可见页面做什么
① 视图层UIKit(UINavigationController)+ Core Animation/RenderServer 导航栈持有所有 VC(不销毁);不可见 view 被移出 window,RenderServer 不再合成(不耗 GPU);但 iOS 6 起不再自动卸载 view,内存警告只发通知,释放与否由 App 决定
② 引擎层WebKit(WebContent 进程) H5 页面的挂起与回收:BackForwardCache(容量仅 0–2 页)、activity state 冻结渲染、MemoryPressureHandler 清缓存、缓存类进程遇压直接退出(本节源码锚点)
③ 进程层RunningBoard / launchd App 退后台时挂起全部 WebContent 进程(task_suspend)
④ 内核层XNU pressure 通知(⑦的 notify 链)、ledger 限额、jetsam、freezer——最终裁判

11.2 原生详情页(UIKit 侧)

如果详情页是原生 VC:UINavigationController 把整个栈都留在内存里,UIKit 只负责可见性——push 完成后旧 view 移出 window(view.window == nil),RenderServer 的合成树里没有它,GPU 零开销;CPU 侧也不再给它布局/绘制回调。但内存不会自动回收:iOS 6 起 viewDidUnload 废弃,系统不再卸载视图;内存紧张时 UIKit 只投递 didReceiveMemoryWarning,旧页面释放多少完全取决于 App 自己。

11.3 H5 详情页(WebKit 侧,源码锚点)

情形 A:同一个 WKWebView 里连续导航(历史栈)

旧页面离开时若可缓存,整个 Page 被挂起后放进 BackForwardCache(旧称 PageCache)——JS 停、定时器停、渲染停,但 DOM 树和 JS 堆留在 WebContent 进程内存里,回退命中即瞬时恢复(触发 pageshow persisted)。

// WebCore/history/BackForwardCache.cpp(WebKit main,本仓 webkit-src/ 归档)
 590 std::unique_ptr<CachedPage> BackForwardCache::suspendPage(Page& page);
 726 void BackForwardCache::prune(PruningReason) {
          while (pageCount() > maxSize()) { ... 逐出最旧 ... } }
// 逐出原因只有三种:MemoryPressure / ProcessSuspended / ReachedMaxSize(L462-470)

容量小得惊人(按页面数计,WebKit/Shared/CacheModel.cpp 的 calculateMemoryCacheSizes):

CacheModelRAM ≥512MBRAM ≥256MB更小
DocumentViewer(纯展示型)0 页00
DocumentBrowser2 页10
PrimaryWebBrowser(Safari)2 页10

也就是说:连点 5 个商品页,只有最近 1–2 页能留在缓存里,更早的已被销毁,回退时只能重新加载。此外缓存还有过期时间(backForwardCacheExpirationInterval)。

情形 B:App 为每个/每几个详情页开独立 WKWebView(大厂常用)

不可见 webview 的 view 不在 window 里 → WebKit 的 activity state 丢失 IsVisible → 渲染更新冻结、requestAnimationFrame 停止、DOM 定时器节流。WebKit 对"整进程全是不可见页面"还有专门策略——把普通警告当致命级处理

// WebKit/WebProcess/WebProcess.cpp L509-511(原文注释)
// If this process contains only non-visible content (e.g. only contains
// background tabs), then treat the memory warning as if it was a critical
// warning to maximize the amount of memory released for foreground apps.
if (m_pagesInWindows.isEmpty() && critical == Critical::No)
    critical = Critical::Yes;

而预热的、被缓存的(CachedWebContent/PrewarmedWebContent/Suspended)进程遇到内存压力直接退出自己,不尝试省内存(WebProcess.cpp L519-529)。

11.4 内存压力下的连锁反应(WebKit + XNU 协作)

// WTF/wtf/cocoa/MemoryPressureHandlerCocoa.mm L84-117(WebContent 进程内)
  87 memoryPressureEventSource() = dispatch_source_create(
           DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0,
           NORMAL | WARN | CRITICAL | PROC_LIMIT_WARN | PROC_LIMIT_CRITICAL, queue);
// XNU 内核压力/进程限额事件 → dispatch → respondToMemoryPressure()

// WebCore/page/MemoryRelease.cpp L122-128(关键释放路径)
 126 BackForwardCache::singleton().pruneToSizeNow(0, pruningReason);
//  ↳ 内存压力 或 进程即将挂起 时:缓存清零,旧详情页全部销毁
 130 MemoryCache::singleton().pruneLiveResourcesToSize(0, /*销毁全部解码图像*/);
// 另有 CSSValuePool drain、布局缓存清理、JSC GC、bmalloc scavenger 归还物理页

若释放后仍超 per-process phys_footprint 限额:MemoryPressureHandler 的 memoryKillCallback 上报 UI 进程(WebProcess.cpp L545-552),进程被杀,App 收到 webViewWebContentProcessDidTerminate: 回调后重载页面——这正是用户偶见"网页白屏自动刷新"的完整因果链。App 退后台后则由 RunningBoard 挂起、XNU freezer 冻结(见 §6.4/⑦)。

11.5 结论

  • 可见性与渲染归 UIKit/CA:不可见 = 不合成、不布局、不耗 GPU,但对象仍在。
  • H5 旧页的挂起与回收归 WebKit:BackForwardCache(0–2 页)+ activity state 冻结 + MemoryPressureHandler 按压清空。
  • 进程级生死归 XNU/RunningBoard:限额、挂起、冻结、击杀,一路兜底。
  • iOS 不做"页面栈裁剪":无论原生 VC 还是 WKWebView 实例,系统从不主动销毁;只保留最近 3 个详情页、WebView 复用池这类策略,是淘宝/微信这类 App 在应用层自己实现的。

⑫ 追问:iOS purgeable 与鸿蒙 Chromium discardable 类似吗?

一句话回答:设计目标是同一道题(把"可再生的缓存数据"标记成系统可回收,避免 OOM 时才被动释放),但实现深度完全不同——XNU 的 purgeable 是内核一等公民(状态机 + token 成熟时钟 + ledger 足迹豁免),Chromium 在鸿蒙(Linux 内核)上的 discardable 是用户态管理器 + madvise 家族,内核自始至终不知道哪些页"可丢弃"。最有说服力的证据藏在 Chromium 自己的源码里:它的苹果平台后端直接调用 MADV_FREE_REUSABLE——那正是 XNU purgeable 的用户态入口。

12.1 两者是什么

XNU purgeable(iOS)

Mach VM 一等原语:以 VM_FLAGS_PURGABLE 建对象(或 mmap 后 madvise(MADV_FREE_REUSABLE)),对象带内核维护的状态机 NONVOLATILE / VOLATILE / EMPTY / DISCARDING

  • volatile = "系统需要时可以扔,扔了给我零页"
  • 回收决策、时机、受害者选择全在内核
  • WebKit 的用法:RemoteLayerBackingStore 把不可见层的 IOSurface 标 volatile;解码位图缓存由 ImageIO/CG 放进 purgeable zone;JSC 也 import vm_purgable_control
  • 值得注意:WebCore 自己的 PurgeableBuffer API 已从 WebKit main 移除(实测 404)——现代 WebKit 不再自己造 purgeable 内存,而是用系统框架层的实现

Chromium discardable(鸿蒙 ArkWeb)

base::DiscardableMemory 跨平台抽象,只有两个状态:locked / unlocked(API 头文件原话)。鸿蒙是 Linux 内核,走 DiscardableSharedMemory 后端:跨进程共享内存段 + 段头 CAS 锁位。

  • Lock() = "别扔";Unlock() = "可以扔"
  • 预算、LRU、打洞时机由浏览器侧用户态管理器决定
  • ArkWeb(Chromium M114+)用它管图片解码缓存、Skia 纹理、tile 光栅缓存等
  • 段按 last_known_usage 时间戳 LRU 排序,超预算即被 Purge

12.2 决定性源码:Chromium 的平台分叉

Chromium base/memory/discardable_shared_memory.cc(本仓归档 purgeable-report/src/chromium_dsm.cc)的 Purge() 按平台选择 madvise 参数——同一个抽象,两种哲学

// Linux / ChromeOS / Android(含鸿蒙这一 Linux 血统分支):
// "provide MADV_REMOVE which is preferred as it has a behavior
//  that can be verified in tests"
#define MADV_PURGE_ARGUMENT MADV_REMOVE        // 立即打洞释放

// Apple 平台:
// "MADV_FREE_REUSABLE is similar to MADV_FREE, but also marks the pages
//  with the reusable bit, which allows both Activity Monitor and
//  memory-infra to correctly track the pages"
#define MADV_PURGE_ARGUMENT MADV_FREE_REUSABLE  // = XNU purgeable 入口!

Lock() 的苹果分支注释更直白(chromium_dsm.cc L277-296):

// On macOS, there is no mechanism to lock pages. However, we do need to call
// madvise(MADV_FREE_REUSE) in order to correctly update accounting for
// memory footprint via task_info().
// ... the corresponding call to MADV_FREE_REUSABLE is in Purge(), since
// that's where the memory is actually released, rather than Unlock(),
// which is a no-op on macOS.

解读:在苹果平台上 Chromium 的"discardable"整个儿降级为 XNU purgeable 的包装——Unlock 是空操作,丢弃决策交还内核状态机;在 Linux 系(鸿蒙)上则是用户态管理器主动 MADV_REMOVE 立即释放,内核只看到一次普通的打洞操作。"discardable" 是个跨平台抽象:落到苹果内核就是 purgeable,落到 Linux 内核就是 madvise。

12.3 机制逐项对比

维度iOSXNU purgeable鸿蒙Chromium discardable
抽象层 内核 VM 一等原语(vm_purgable_control / VM_FLAGS_PURGABLE;用户态经 MADV_FREE_REUSABLE 桥接) 用户态 C++ 抽象(base::DiscardableMemory)+ 平台后端(鸿蒙 = 共享内存段 + madvise)
状态模型 内核状态机:NONVOLATILE / VOLATILE / EMPTY / DISCARDING,带 8 个 volatile 组 + FIFO/LIFO/obsolete 语义 两态 locked/unlocked:段头 64 位 CAS(1 位锁 + 时间戳),跨进程可见
谁决定丢弃 内核:vm_pageout_scan 每轮先问 ripe token("Before anything...",vm_pageout.c L2999-3019 / iOS18 调用点 L3171);压力等级强制清组(warning=2/urgent=5/critical=8);jetsam 杀进程前先清场可免杀 用户态管理器:按 last_known_usage 时间戳 LRU + 内存预算超限即 Purge;分配失败时回调 on_no_memory 释放空闲段后重试,再失败才 TerminateBecauseOutOfMemory
丢弃时机哲学 :volatile 页一直驻留(真机实测:ImageIO 解码缓存 VOLATILE 状态下 draw#3 仅 2.3ms,数据还在物理页),直到系统真正需要页才零成本收割 (Linux 分支):MADV_REMOVE 当场打洞,页立刻还给内核;苹果分支才是懒的(MADV_FREE_REUSABLE)
公平性/成熟度 token"邮票成熟系统":volatilize 时刻发行 token,面值 = 当时 inactive 队列流量;翻完一遍才 ripe——最久未碰牌的最先丢;受害者选择还结合 jetsam 优先级(vm_purgeable.c iOS18 L726) 时间戳 LRU:越久未 Lock 的段越先被 Purge;无内核侧"成熟度"概念
内存记账 标 VOLATILE 瞬间移出 phys_footprint(ledger 记 purgeable_volatile 科目,task.c 注释:footprint 只含 purgeable_nonvolatile)——jetsam 不算它;MADV_FREE_REUSE 取回时再记回 内核无对应足迹概念;段的用量由浏览器侧管理器自行记账(tracing MemoryAllocatorDump);鸿蒙内核本身对 Web 内存申请无限制
丢弃后重建 下次访问零页 + 状态查询(VM_PURGABLE_EMPTY);libmalloc 的 malloc_make_nonpurgeable 回读旧状态即知数据已丢 下次 Lock() 返回 PURGED/FAILED(zero-fill),对象应销毁重建
典型使用者 WebKit RemoteLayerBackingStore(4 个触发点:退后台快照后 / 进程挂起前 / layer 不可达 / 前台闲置 200ms–1s);ImageIO 解码缓存(VM_MEMORY_IMAGEIO / malloc purgeable zone,tag=3);CoreGraphics、QuartzCore/SkyLight、Metal、CoreML、JSC(全系统普查见 CENSUS) 图片解码缓存、Skia 位图缓存、cc tile 光栅缓存、GPU discardable 纹理(gpu::DiscardableManager),跨渲染进程统一走浏览器侧 allocator

12.4 XNU 侧的完整机制链(行号锚点)

// vm_purgeable.c(iOS 18 = xnu-11215.1.10,本仓 purgeable-report/src/vm_purgeable_ios18.c)
  62 struct token { token_cnt_t count; ... };      // "成熟度"邮票
  77 int available_for_purge = 0;                 // ripe token 计数:可零成本回收的对象数
 158 vm_purgeable_token_add(queue);              // volatilize 时发行 token,面值=当时 inactive 页流量
 877 vm_purgeable_object_purge_one(force_purge_below_group, ...);
      // 只碰"成熟"对象;force 时清 group < 阈值(压力等级→2/5/8)
1483 vm_purgeable_accounting();                   // VOLATILE 瞬间 ledger 换边:移出 phys_footprint

// 回收入口(vm_pageout.c iOS 14.8 = xnu-7195.141.2)
2999 /* Before anything, we check if we have any ripe volatile objects around. */
3019 retval = vps_purge_object();          // 页回收扫描的第一顺位,先于文件页/压缩
4822 force_purge: warning=2 / urgent=5 / critical=8   // 压力等级旁路成熟度

这条机制链的含义:purgeable 丢弃是 iOS 内存回收的"第一顺位"——零成本(不用压缩、不用写盘、不用杀人),排在文件页回收和压缩器前面。而这一切对被标 volatile 的进程是"免费"的:它的 phys_footprint 同瞬间下降,jetsam 高水位考核也随之宽松。

12.5 真机实验佐证(前次会话数据,[EXP])

ImageIO 解码 4000×3000 JPEG(解码缓冲 45.8 MiB),mach_vm_region_recurse + vm_purgable_control(GET_STATE) 探测:

draw#1 (解码)    : 100.0 ms
  0x114230000  46880 KiB  state=EMPTY     tag=70 (VM_MEMORY_IMAGEIO)  // 解码 scratch:用完即 EMPTY
  0xbda800000  46912 KiB  state=EMPTY     tag=3  (MALLOC purgeable)   // 解码缓存刚建
draw#2 (缓存命中): 51.5 ms
  0xbda800000  46912 KiB  state=VOLATILE  tag=3                       // ★ 缓存常驻 VOLATILE
draw#3 (释放重建): 2.3 ms                                                            // volatile ≠ 已回收!页还在,命中零成本

这正是 purgeable 哲学的实证:缓存主动标 VOLATILE 让出记账,但物理页仍在;内存充裕时命中几乎免费(2.3ms),内存紧张时内核第一批收割它们。对照 Chromium 在 Linux 上的 MADV_REMOVE 是"立即还给内核",两种时机模型截然相反——但目标函数一致:别等 OOM 才动手

12.6 结论

  • 是同类设计:都把"可再生数据"标记成可回收,都避免 OOM 被动释放,都靠 zero-fill + 状态查询实现重建。
  • 但层次不同:purgeable 是内核原语(状态机/成熟时钟/足迹豁免/第一顺位回收);鸿蒙上的 discardable 是用户态框架(共享内存段 + 时间戳 LRU + madvise 执行),内核不知情。
  • 记账差异最本质:iOS 的 volatile purgeable 不计入 phys_footprint,直接影响 jetsam 生死判卷;鸿蒙无此口径(官方明示 Web 内存申请无内核限制),治理全在框架层。
  • 合流彩蛋:Chromium 在苹果平台的 discardable 后端就是 XNU purgeable(MADV_FREE_REUSABLE/REUSE),Unlock 为空操作、决策交还内核——两条技术在苹果平台合流,在鸿蒙平台分叉。
  • WebKit 自身的演变:WebCore 的 PurgeableBuffer 已移除;如今 iOS WebKit 的 purgeable 全部经由系统框架(ImageIO/CG/IOSurface/JSC),WebContent 自己只在 RemoteLayerBackingStore 上调 IOSurfaceSetPurgeable。