iOS 相机启动内存实测报告

双场景复测(2026-09-02):后台干净 vs 五大 App 后台 · 整机+进程双账本 · 申请分配效率 · 回收供给效率 · 全计数器合理性
追问深挖(2026-09-02/03):压缩速度归因 · 锁页×回收生产者-消费者模型 · 文件页回收策略 iOS/Linux 源码对比
iPhone 12 Pro · A14 · 6GBiOS 14.8(xnu-7195.141.2)16KB 物理页越狱真机 root 实测2026-09-02/03
⚡ 答案速览(TL;DR)——三个最终问题的直接回答
Q1:相机申请多少内存?系统供给多少?哪些内存?

管线需求 ≈1.0GB,与内存环境无关(两场景 Δwired +987MB vs +996MB,差<1%)。归属四份:①内核无主 wired(ISP DMA 环形缓冲/驱动固件)≈375MB;②mediaserverd iokit_mapped(IOSurface 管线缓冲)+612MB;③Camera 壳进程仅 26.1MB;④backboardd 显示侧 +22.9MB。整机抽取:场景一从 free 池抽 -1,639MB(管线+框架缓存副作用);场景二 free 只动 +42MB,全部由回收供给(文件页逐出 -891MB+压缩净得 348MB+purgeable/spec 即摘 ~60MB)。

Q2:启动多快?申请效率多快?free 不足时如何供给?

进程出现→管线内存全部就位 2.04s(场景一)/ 1.77s(场景二),加 SB 派生亚秒级,冷启动到预览稳定 ≈2.1–2.9s。分配机械极快:wire 峰值 3,592MB/s(单 205ms tick +738MB)——慢在管线编排不在分配。free=48MB 时照样 1.77s 就位:回收机器水位驱动,供给顺序 spec/purgeable 即摘(零成本)→ 文件 clean 页逐出 -891MB(≈380MB/s、零 I/O)→ 匿名压缩 +474MB gross(峰值 682MB/s)。jetsam/freeze 全程零触发,五 App 全存活

Q3:各统计值变化与合理性

所有计数器变化均可由机制解释:场景一零压缩零逐出(compressions/purges/swapouts 全程恒定),free −-1,639MB=管线+框架文件缓存(active +521MB、文件页 +262MB);场景二呈镜像结构——free 探底 44MB 不枯竭、压缩器陡涨(stored +1,474MB 全程)、speculative/purgeable 被即摘、reactivations +780MB(框架从缓存直接拉回)。杀相机后 wired bulk 回落(3,505MB/s)+驱动 teardown 尾 ~8s;存活 App 的压缩页不回吐(单向阀)。无泄漏迹象。

本轮复测与 2026-08-29 报告的关系

同一设备同一协议独立复测:管线需求不变量再次成立(Δwired 两轮均在 0.99–1.02GB);本轮新增 ①进程级 8 目标全程 footprint 曲线(250ms)②相机窗口逐出/压缩的分离账本 ③freeze 计数器取证(5 App 阶段冻结 2 进程 +154MB,相机窗口零新增)。结论与前轮一致,量化口径更细。

追问一:压缩回收为什么这么快?算法还是硬件?

都不是主角——架构才是。补充实验实测:零页 MZV 波爆发 1310MB/s、真实数据特征波 975–994MB/s×1s,且全部工作落在 E 簇(能效核,系统态 0.8→1.1 核),P 簇全程空闲——2 个优先级 91 的专用压缩线程绑 E 簇(vm_pageout.c:4332)+ 每核 ~780MB/s 的 WKdm 手写汇编 + 需求不在回收路径上串行。Linux 复刻吞吐无障碍(lz4≥WKdm),差距在执行位置与默认策略(详见 §7)。

追问二:free 不足 100MB,怎么还能批量锁 1GB 页?

不是免回收,是回收在别的线程上并行跑。锁页线程是纯消费者:拿不到页就 VM_PAGE_WAIT 睡觉(vm_page_wait 无一行回收代码);三批生产者线程(scan+压缩机×2,优先级 91)并行补货。实证:相机第一个 250ms 窗口 wire 爆发 1897MB/s,同窗口供给(逐出 1324+压缩 682)= 2006MB/s 同步开动,free 探底 44MB 后在 wire 爬坡最猛阶段不降反升回到 126MB(详见 §8)。

追问三:文件页回收策略,iOS 与 Linux 有区别吗?

释放原语一样便宜,策略完全不同。XNU 用固定成本瀑布把回收最大比例导向最便宜路径(cleaned→投机型→文件页,匿名页连拿 2 个即限流,文件缓存 14% 地板,"压缩器积压时继续偷文件页"——vm_pageout.c:2145 注释自白);Linux 用 swappiness×refault 成本比例模型追求全局最优,且直接回收在申请者上下文执行。Linux 领先项:workingset refault 距离(逐错保护更精确)(详见 §9)。

§0实验设计与方法学

场景一:后台干净场景二:五大 App 后台
前置状态kill -9 全部后台应用(36 进程+补杀),vm_stat 确认 Pages free = 139,208 页 = 2,175MB > 2GB;采样起点 free 2,475MB依序启动 抖音→淘宝→京东→网易云音乐→bilibili(各 20s 间隔,uiopen 拉起),五 App 全部在案后采样基线
相机动作uiopen --bundleid com.apple.camera,ps 尾锚验证进程存活,预览静置 ≥30s(7.2s–43.4s 窗口),随后 kill -9 观察拆除同左(112.3s 启动,150.5s kill;预览静置 33s)
基线水位free 2,475MB(充裕)· level 67%free 97MB(贴地)· level 40% → 相机期 23%

采样器:camwatch(本轮新制探针)EXP

为消除旧方案"每秒 spawn vm_stat 自污染 ~180 页/样本"的问题,本轮用纯内核调用的常驻采样器 camwatch(arm64 CLI,ldid 签名 task_for_pid-allow 等权限):

精度与边界的诚实声明:① uiopen 下发→进程出现的时间受 SSH 链路+Mac/设备时钟对齐(±0.5s)限制,只能给出 ≤0.3–0.9s 的界;进程出现→就位的时间戳全部来自设备自身时钟(精确到采样间隔 200/250ms)。② "进入预览"以内存平台期(wired+mediaserverd footprint 稳定)判定——SSH 上下文无 UI 自动化(zxtouch/WDA 未装),这是等效替代。③ Camera/mediaserverd/backboardd 为平台二进制,vm_region 递归读不到(appscan regions=0),故其构成用 footprint − internal − compressed ≈ iokit_mapped 差分法EST;五 App 的 region 级画像可直读。④ free ⊇ speculative(host.c:815)SRC;jetsam 可用内存公式含 speculative(kern_memorystatus.c:868)SRC

§1场景一:后台干净(free=2,475MB)冷启动相机

场景一全程时间线

图 1-1 场景一全程(85s,200ms/点)。wired 从 795MB 抬到 1,782MB(Δ+987MB);free 从 2,475MB 降到 836MB(Δ-1,639MB);mediaserverd footprint 34→731MB。杀相机后 free 全额回位。

场景一启动微观

图 1-2 启动微观(4.5–9s)。三段式:①壳进程加载(Camera fp →26MB)②mediaserverd 建链(iokit 映射涌入)③集中 wire:单 205ms tick 内 wired +738MB(3,592MB/s)。

场景一拆除

图 1-3 杀相机后的回收(43–52s)。kill 后 ≤200ms 进程消失,wired bulk −906MB/202ms;mediaserverd 678→46MB;随后 ~8s 驱动 teardown 慢尾把 wired 补回基线。

整机计数器账本(基线 → 预览平台期)EXP

计数器(vm_stat)变化机制解读
Pages wired down+987MB相机管线主体:ISP DMA 缓冲+IOSurface 锁页+驱动固件结构(Q1 详拆)
Pages free(含 speculative)-1,639MB充裕环境直接从 free 池抽取;回收机器纹丝不动
Pages active+521MB相机/mediaserverd 框架文件页+管线用户态结构进入活跃队列
Pages inactive+142MB启动过程置换出的冷页入 inactive
Pages speculative+88MBfree 充足时预读大胆落 bins(对照场景二被即摘)
Pages purgeable / purges+18MB / +8MB无需丢弃任何可清除页
file-backed pages+262MB框架/资源文件页缓存增长(启动的副作用,非管线本体)
anonymous pages+489MBmediaserverd 内部堆(15.7→108MB)+Camera 壳 18MB+系统进程伴随增长
compressions / decompressions0 / +42MB零压缩——这是"free 充裕则压缩器不劳动"的直接证据;decompressions 为系统进程触碰旧压缩页的背景噪声
pageins / pageouts / swapins / swapouts+242MB / 0 / 0 / 0只读入不换出;启动读入即相机框架代码与资源
reactivations+0MB≈0:free 充裕时无需从 inactive 拉回
场景一要点:供给方式=直接抽取 free 池。相机管线 1GB+框架缓存副作用 ~0.6GB 全部由 -1,639MB 的 free 落差买单,压缩器、逐出、purgeable 丢弃、swap、jetsam 全部零参与。启动窗口内 compressions/purges/swapouts 三个累计计数器一字不动——回收机器"机制常驻、劳动按需"的最干净对照。

§2场景二:五大 App 后台(free=97MB)冷启动相机

场景二全程时间线

图 2-1 场景二全程(185s,250ms/点)。五 App 依序启动把 free 从 2,192MB 压到 97MB;压缩器(右轴色)同步吃进后台 App 驻留页;相机启动(112.3s)与 kill(150.5s)的 wired 起落。

2a. 五 App 就位阶段(0→100s)EXP

计数器变化(pre→相机前基线)解读
Pages free-2,096MB2.2GB 被 5 个重 App 吃光(抖音 567 / 淘宝 320 / 京东 146 / 网易云 350 / bilibili 358 峰值 footprint,合计 1,741MB)
compressions(累计)+1,474MB后台化 App 的驻留匿名页被渐进压缩(淘宝 in≈0.4MB 几乎全压缩;抖音 compressed 434MB)
Pages occupied by compressor+394MB压缩器占用净增;全程序压缩比 ≈2.9:1(stored 内容量/占用)
swapouts+157MB≈freeze_pageouts 增量 9,869 页(154MB)——freezer 冻结 2 个后台进程落盘(freeze_count 0→2),非传统换出
pageins / reactivations+2,906MB / +1,457MB五 App 冷启动读入+相互顶替时的缓存回拉
file-backed / anonymous+323MB / +957MBApp 代码资源缓存+用户态堆驻留

2b. 相机启动窗口(112.3→114.1s):free=48MB 供给 1GB 的全账本EXP

场景二相机窗口

图 2-2 相机窗口微观(110–122s)。wired +996MB 与压缩量陡涨同步;free 探底 44MB 不枯竭(供给跟上了分配)。

供给来源(回收侧)成本/速率
文件页逐出(file-backed)-891MBclean 页零 I/O 直接回 free;≈380MB/s 持续
匿名压缩(compressions)+474MB gross峰值 682MB/s(单 tick);净得 348MB(压缩器占用 +126MB)
purgeable 丢弃-37MB≈0 成本即摘(网易云 volatile 12MB 等)
speculative 摘除瞬时 −23MBspawn tick 内即摘;随后相机预读又回填 +61MB(净 +61MB)
inactive / active 队列-620MB / -538MB队列腾挪:冷文件页出队+框架热页经 reactivation(+780MB)直接复用缓存
未动用的手段jetsam 0 次 · freeze 0 次新增 · swap 0页级腾挪足够,无需进程级献祭
五App内存形态迁移

图 2-3 五 App 在相机窗口的形态迁移:蓝(驻留匿名)→紫(已压缩)。bilibili internal 278→152MB(其中 ~33MB 被直接丢弃:reusable/purgeable 路径),网易云 216→187MB;淘宝在基线前已被压干(in≈0)。

2c. 五 App 进程账本(footprint / KB→MB)EXP

Appfp·基线internal·基线compressed·基线fp·预览后internal·后compressed·后Δfp
抖音5668943456648476+0
淘宝31503003150300+0
京东138104341388949+0
网易云29821693298187121+0
bilibili279278024615293-33

五 App footprint 合计 1,597→1,564MB:压缩只改变形态(驻留→压缩)不改变记账;净 fp 变化 −33MB 全部来自 bilibili 的可丢弃页。相机杀掉后五 App 全部存活(survivors.txt)。

2d. 杀相机拆除(150.5s)

wired bulk 回落 -1,015MB(峰值 3,473MB/s),mediaserverd 642→50MB,free 回 +1,239MB;压缩器占用 −-29MB(死会话压缩页直接摘除,不经解压),但存活 App 的压缩页不回吐(compressions 只增不减的单向阀)。拆除期 pageins +102MB——系统进程回拉缓存。

Q1相机启动会申请多少内存?系统需要供给多少?分别是哪些内存?

账本一:进程视角(谁在申请)EXP

归属场景一场景二内容
内核无主 wired≈375MB≈471MBISP DMA 环形缓冲、驱动固件结构、新映射页表——只进全局 wired 计数器,任何进程 footprint 都看不见
mediaserverd iokit_mapped+612MB(1.9→614)+525MB相机管线真正的主人:AVFoundation 服务端的 IOSurface/DMA 缓冲(三方共享:App 绘制/GPU 渲染/ISP 写入)
mediaserverd internal+92MB(15.7→108)+管线编排的用户态堆
Camera 壳进程26.1MB(internal 17.9MB)24.2MBUI 壳:预览取景器只是管线的"遥控器"
backboardd 显示侧+22.9MB+15.2MB帧提交/显示合成路径
合计(管线需求)≈1.05GB≈1.03GBΔwired +987/+996MB 为上界口径(iokit⊂wired 有重叠,见注)
管线需求不变量:两场景 Δwired 差 <1%(+987 vs +996MB)。相机管线的需求量由会话配置(分辨率/格式/缓冲深度)决定,与内存环境无关——free 只剩 48MB 照样拿到全款。iokit⊂wired 的重叠拆分依赖"mediaserverd 的 IOKit 映射全部 wired"这一合理假设EST,总量口径(Δwired)不受影响。

账本二:整机视角(系统实际供给)EXP

回答"哪些内存"的三层结论:①形态——wired(不可压缩/换出/丢弃,DMA 刚性)为绝对主体(>90%),少量匿名堆,几乎无文件页;②归属——内核无主账+mediaserverd 为主、Camera 壳极小("进程账极小、内核账极大"的极端案例);③获取路径——IOKit 预装配(IOSurfaceCreate 返回时全部就位),不走 CPU 缺页懒分配。

Q2相机启动耗时多长?涉及内存的申请效率有多快?free 不足时如何回收供给?

启动耗时链条EXP

阶段场景一场景二说明
uiopen 下发 → 进程出现≤0.3–0.9s≤0.3–0.9s受 SSH 链路+时钟对齐(±0.5s)限制只能给界;SpringBoard 派生本身亚秒级
进程出现 → 壳加载完成~0.8s~0.6sCamera fp 爬到 ~25MB(dyld+框架)
进程出现 → 管线内存全部就位2.04s(wired 达平台 99%)1.77s含 mediaserverd 建链+DMA wire+IOSurface 映射
总计(下发→预览稳定)≈2.1–2.9s≈2.1–2.7s"预览稳定"以内存平台期判定(方法学声明见 §0)

申请分配效率EXP

操作实测速率含义
wire 分配(峰值)3,592MB/s(S1)/ 1,897MB/s(S2)分配原语本身极快——"wired 分配慢"是误解;S2 峰值略低是回收供页的节奏,不是分配慢
管线整体就位1GB / 2.04s ≈ 500MB/s 平均瓶颈在管线编排(DART 映射/驱动会话协商),不在页分配
mediaserverd iokit 涌入~450MB/s(fp 曲线斜率)IOSurface 批量映射
环境对比S1 2.04s vs S2 1.77sfree 48MB 与 free 2.4GB 的就位时间同量级——回收供给对启动速度无可感拖累(本轮 S2 甚至略快,属轮次方差)

free 不足时的回收供给机制(场景二实测)EXP

不是预案,是水位驱动的流水线。相机启动把 free 砸到警戒线附近(探底 44MB),回收机器按成本阶梯依次供页:

顺位手段本轮实测成本
1speculative 摘除spawn tick 内 −23MB≈0(预读赌输的页,零 I/O 丢弃)
2purgeable 丢弃−-37MB(purges +12MB)≈0(volatile 页直接扔)
3文件 clean 页逐出-891MB / 2.3s零 I/O(脏页才需回写,本轮 pageouts +860 页仅背景噪声)
4匿名压缩gross +474MB(峰值 682MB/s)CPU 换内存(净得 348MB)
5freeze(冻结落盘)相机窗口 0 新增(五 App 阶段已冻 2 进程 154MB)I/O,按需后台执行
6jetsam(杀进程)0 次进程献祭——页级腾挪够用时根本轮不到
关键证据链:①free 探底 44MB 但从未枯竭(供给速率 ≥ 分配速率);②water level 40%→23% 期间 compressions 陡涨 +474MB 与 wired 上涨+996MB 同窗口发生;③五 App footprint 全程可见且无一死亡;④swap/jetsam 计数器零增量。"先开多个大 App 再开相机易触发回收"的机理=此流水线。

Q3各内存统计值变化情况与合理性分析

统计值场景一(free 充裕)场景二(free 贴地)合理性判定
Pages free-1,639MB(含 spec +88)+42MB(探底 44MB 后回升)✓ 两场景语义一致:充裕时是"水源",紧张时是"水位计"——只反映池子余量,不反映供给能力
Pages speculative+88MB(大胆预读)瞬时 −23MB 后回填 +61MB✓ 与 free ⊇ speculative 的口径一致(host.c:815);"带着收益的 free",压力时第一个被摘
Pages wired down+987MB+996MB✓ 相机场景的主科目;两场景差<1% 证明管线需求不变量;wired 是"时变池"而非静态基线
Pages purgeable/purges+18MB / +8MB−-37MB / +12MB✓ 充裕时增长(缓存可丢则丢的自愿登记),紧张时即摘
file-backed+262MB(框架入缓存)-891MB(最大供给源)✓ 同一科目两种角色:无压力时是缓存堆积,有压力时是第一提款机
anonymous+489MB-206MB(相机增量被后台压缩抵消)✓ 净额=新增 − 被压缩移出,需与 compressions 联读
compressions/decompressions0 / +42MB+474MB / +36MB✓ 累计型单调计数器;场景一零压缩是"够用就好"的直接证据
Pages in compressor-9MB+126MB(杀相机后 −-29MB)✓ 水位类;死会话压缩页直接摘除,存活 App 压缩页不回吐(单向阀)
reactivations+0MB+780MB✓ 场景二大量回拉=框架代码页从 inactive 缓存直接复用(省 pagein);对应 pageins 仅 +194MB
pageins/pageouts/swap+242MB / 0 / 0+194MB / +860 页 / 0✓ 逐出 clean 文件页零 I/O;swapouts 增量全部来自 freeze 落盘(非传统换页)
kern.memorystatus_level67%(全程稳定)40%→23%(相机期)→46%(杀后)✓ 水位百分比与 free/可用页走向吻合;23% 仍未进 warn 带(kevent 零事件可佐证)
两条解读纪律(避免误读统计值):①队列账 vs 内容账正交——active/inactive/speculative/free 按"在哪个队列"记账,file-backed/anonymous/compressor 按"是什么内容"记账,两套有重叠,不能混加;②水位类 vs 累计类分开读——free/compressor pages 是瞬时水位(进程退出会回吐),compressions/purges/pageins 是终身累计(只增不减)。场景一的"compressions 恒定"与场景二的"compressions 陡涨"对比正是水位驱动状态的开关证据。

§5效率汇总:申请分配 × 回收

操作实测效率场景
wire 分配(峰值 burst)3,592MB/s(+738MB/205ms)S1 冷启动
wire 分配(压力下峰值)1,897MB/sS2(回收供页节奏)
管线整体装配~1GB/2.04s ≈ 500MB/s两场景同量级
spec/purgeable 即摘≈0 成本(单 tick 内)S2 spawn 瞬间
文件 clean 页逐出≥380MB/s 持续(-891MB/2.3s)S2 相机窗口
匿名压缩(burst)682MB/s(净收益按 2.9:1 打折)S2 相机窗口
freeze 落盘154MB(后台慢速执行)五 App 阶段
拆除:wired bulk 回收3,505MB/s(−906MB/202ms)kill 后第一 tick
拆除:mediaserverd 卸载678→46MB / ~600mskill 后
拆除:驱动 teardown 尾~8s 补完最后 ~100MBkill 后
拆除:freeze/jetsam/swap0 参与全程
效率结论:回收成本阶梯 spec/purgeable 即摘(≈0)< 文件 clean 逐出(零 I/O,380MB/s+)< 匿名压缩(CPU 付费,burst 682MB/s)< freeze(I/O)< jetsam(进程献祭)。iOS 相机场景的压力供给在第二、三级就完成了全部工作——这是"重 App 后台堆叠时开相机依然流畅"的内存侧解释。

§6方法学细节 · 数据完整性 · 原始数据

实验执行记录

  • 2026-09-02 21:12:38–21:15:44(本机时间),设备解锁态全程验证(实验前后 appstate 均为 NO)
  • 场景一前置:杀 36 个后台应用进程(+补杀 1 残留),vm_stat 确认 Pages free=139,208 页=2,175MB>2GB 后方启动相机
  • 场景二:5 App 依序 uiopen,各 20s 间隔,存活验证全部通过(pid 由 camwatch 流实时确认)
  • 相机两次均以 kill -9 结束(模拟最强拆除路径),拆除后 survivors 检查:五 App 全存活、Camera 已死
  • CrashReporter 取证:实验窗口内零 JetsamEvent(最近一条为实验前 4.5 小时)

已知局限(如实标注)

  • uiopen→进程出现的时间界受 SSH 链路与 Mac/设备时钟对齐(±0.5s)限制
  • "预览就位"以内存平台期判定,非 UI 事件(SSH 上下文无 UI 自动化)
  • 平台二进制(Camera/mediaserverd/backboardd)region 不可读 → iokit 用差分法估算
  • kevent MEMORYSTATUS 未触发(未进 warn 带),压力过程以 level 列为准
  • 单轮实验(每场景一次);与前轮(2026-08-29)同协议互证,关键不变量两轮一致

原始数据与工具(本站数字的可追溯来源)

文件内容
data/s1_timeline.txt / s2_timeline.txtcamwatch 全程时间线(423 / 735 个采样点,含元数据头)
data/s1_markers.json / s2_markers.json编排标记(uiopen 下发/返回、pid 首见、kill、时钟漂移)
data/s1_pre|post_vmstat.txt, s2_pre|baseline|post_vmstat.txtvm_stat+freeze 计数器快照(阶段锚点)
data/s2_baseline_appscan.txt五 App region 级画像(相机启动前)
data/s1_killed_procs.txt, s2_survivors.txt, s2_crashlog_recent.txt前置清理、存活与 jetsam 取证
data/comp_cpu_camwatch.txt / comp_cpu_cpuper.txt追问篇实验①:anonpress 零页波(MZV 路径)+每核 tick 差分(§7)
data/dirty_cpu_camwatch.txt / dirty_cpu_cpuper.txt追问篇实验②:dirtyapp 真实数据特征波+每核 tick 差分(§7 核心数据)
data/cam2_cpu_camwatch.txt / cam2_cpu_cpuper.txt追问篇实验③:free 充裕相机重演对照(全程零压缩,§7 对照组)
ios-probe/cli/camwatch.c采样器源码(55 列 TSV 协议见文件头)
ios-probe/cli/cpuper.c / dirtyapp.c追问篇新探针:每核 sys/usr/idle 差分 · 真实 App 数据特征压力源
run_s1.py / run_s2.py / run_compcpu.py / run_camcpu.py / run_dirtycpu.py / analyze.py / build_charts.py / build_charts2.py编排与分析全链路脚本

§7追问一:压缩回收为什么这么快——算法 × 硬件 × 架构归因

场景二实测相机窗口压缩爆发峰值 682MB/s(§2b)。这一节回答:这个速度是回收算法写得好还是CPU 硬件能力强?方法是三组补充实验(新探针 cpuper 每核差分+dirtyapp 真实数据压力源)+ XNU/Linux 源码对照。结论先行:都不是主角——决定性的是系统架构。压缩爆发只需要 2 个专用内核线程 × 每核 ~780MB/s 的算力(跑在能效核上),而相机的 1GB 申请根本不在这条回收路径上排队。

7a. 三组补充实验:量化爆发到底用了多少硬件EXP

压缩波实验速率对比

图 7-1 压缩波实验:单窗口压缩速率(500ms 粒度,对齐压力起点)。零页波(MZV 快速路径)峰值 1310MB/s;真实数据特征波 975–994MB/s 持续 1s——两波同数量级,说明管线(扫描摘页+unmap+队列+段存储)支持 ≥1.3GB/s,真实数据时绑定约束才落到 WKdm 算术本身。

实验压力源与数据特征压缩爆发关键读数
comp:零页波anonpress 2200MB 全零匿名页1310MB/s(658MB/0.5s 单窗口)走 MZV 单值快速路径(近零算术):全程压缩比 9.2:1(+728MB 输入 → 占用仅 +79MB);free 底 30MB
dirty:真实数据特征波dirtyapp 2400MB(75% 字典型/15% 零/10% 随机,模拟实测 App 页谱)975–994MB/s × 1s(三窗口 234→975→994)全程 +1455MB 输入 → 占用 +1119MB(合成数据难压,比 1.30:1);free 底 37MB;free 贴地且无新需求时压缩器立即停工(纯需求驱动)
cam2:充裕对照free=1599MB 重演相机启动0(全程零压缩)对照组:水位够用则压缩机器完全不劳动——与场景一同构
推论:①每核有效速率与锚点独立实测吻合(WKdm 16K 内核同源汇编压缩真实 App 脏页 774–793 MiB/s,压缩率 0.40–0.67)——两条独立证据链指向同一量级;②架构天花板 ≈ 2 线程 × ~780MB/s ≈ 1.5GB/s,所有实测爆发(682–1310MB/s)都在天花板之下,差值来自当时 E 簇上的背景负载,不是能力不足。

7b. 哪些核在干活:全部落在 E 簇,P 簇空闲EXP

每核系统态占用对比

图 7-2 压缩爆发期间每簇系统态占用。E 簇(c0–c3)系统态从背景基线 ~0.8 核抬到 ~1.1 核;P 簇(c4–c5)全程 ≤0.08 核 ≈ 空闲。1s 采样窗与 0.5s 爆发窗对齐存在 ±1s 模糊,净增值只取量级。

这印证了 XNU 源码里的刻意设计:压缩线程 thread_bind_cluster_type(self, 'E', true) SRC(vm_pageout.c:4332)——软绑定 E 簇,注释明说"E 簇不可用时才允许上 P 核"。含义:回收绝不去抢前台的 P 核。若当时相机在前台跑 ISP 管线,压缩爆发一颗性能核都不会占用。

7c. XNU 源码证据链SRC

源码事实(xnu-7195.141.2)为什么快
线程模型vm_pageout_internal_start(vm_pageout.c:5065):iOS 压缩线程数 1,__AMP__+ebound 默认提到 2 个kernel_thread_start_priority(..., BASEPRI_VM=91)(sched.h:149;用户前台 QoS 最高 47)优先级 91 = 抢占一切用户态,有活立刻跑;2 线程并行度随积压自然伸缩(第二个线程仅当积压≥一批才唤醒)
绑核thread_bind_cluster_type(self,'E',true)(:4332)+ TH_OPT_VMPRIV(:4311)E 簇专用+内核资源记账豁免(防自我压缩死锁);不与前台抢 P 核
流水线pgo_maxlaundry=1024 页;每批取 128 页后立刻释放队列锁再压缩(:3936–3986);压缩完 32 页批量归还 free 池(MAX_FREE_BATCH:3909);扫描器 vm_pageout_cluster(:717–744)挂 pgo_pending+thread_wakeup扫描器与压缩机经批队列解耦,锁竞争摊薄;纯事件驱动(空闲时 assert_wait)
算法本体arm64 默认 CMODE_HYB 混合编码(vm_compressor_algorithms.c:441–447):先 WKdm,失败或产物≥12288B 改试 LZ4 择优(:296–405)难压页有兜底;16K 页一个 codec 决策开销极小
WKdm 实现手写汇编 WKdmCompress_16k.s:主分类循环标量整数指令(字典查找天然串行);tag 打包段 NEON 向量化(ld1.2s 一次 16 个 tag+位操作);MZV 单值页字典零初始化顺手检出→4 字节编码;早退检查点 CHKPT_BYTES=416/SHRUNK=426(处理 416B 后产物超 426B 即放弃)90 年代字典码+现代工程打磨:单核 ~780MB/s;不在难压页上浪费周期;零页近零成本
触发水位驱动:free<free_min 唤醒扫描器(vm_resident.c:3351);free_target 公式封顶 31MB(iOS 上限 2000 页)持续提前量:相机启动前 100s 内背景压缩已完成 1.5GB,窗口期只剩边缘回收

7d. 归因结论:各占几成

因素贡献说明
WKdm 算法设计★★☆90 年代压缩缓存研究一系的字典码,本身并不先进,比 LZ4 还简单 GEN
Apple 实现质量★★★手写 asm+NEON 打包+MZV+早退+LZ4 择优——这层让单核跑到 ~780MB/s
A14 硬件★★☆E 核即有 ~780MB/s(16KB 工作集全程 L1 驻留);该量级任何现代手机 SoC 都具备 GEN
系统架构★★★★★专用线程(优先级 91/绑 E 簇)+需求不在回收路径串行(§8)+提前量+零成本文件页逐出垫底(§9)

7e. Linux 能否达到同样效果?

维度iOS 14.8Linux 5.10/6.1
单核压缩算力~780MB/s(E 核实测)EXPlz4 同量级或更快 GEN
压缩执行位置专用内核线程×2,优先级 91,绑 E 簇直接回收在申请者上下文同步执行(vmscan.c:3233 try_to_free_pages);zram 在 bio 提交者上下文压缩(zram.c:1627→1342→1366)——kswapd 写 swap 时 kswapd 压,App 直接回收时 App 自己的线程压
后台回收线程2 个高优先级专用线程kswapd 每 NUMA 节点 1 个(vmscan.c:4043 kthread_run),普通优先级、不绑簇
匿名页可回收性压缩机内建,无条件必须配置 swap/zram/zswap(vmscan.c:2250:!may_swap || nr_swap_pages<=0 则匿名页完全不回收)
同值页检测MZV:字典初始化顺手检出,4B 编码page_same_filled:线性扫描全页(zram.c:210–226)
结论:①吞吐上 Linux 完全可达——lz4 单核 ≥ WKdm,Android(zram+lz4+PSI+lmkd)每天在 6GB 设备上做同样的事;②机制上可以组装(zram+调大 watermark gap 让 kswapd 提前干活+MGLRU+PSI 主动回收+cpuset 绑核),Android 就是这么组装的;③两个结构性残留差异(源码可证):mainline 把重回收留给申请者上下文(停顿进 App 关键路径),以及默认不配 swap 时匿名页完全不可回收——"先堆五个大 App 再开相机"的局,默认 Linux 连压缩这一级供给都拿不出来。诚实边界:未在同硬件做 Linux A/B 实测 GEN

§8追问二:free 不足 100MB 怎么还能锁 1GB 页——锁页×回收的生产者-消费者模型

§2b 说"IOKit bulk-wire 预装配",追问:free 都不足 100MB,如何批量锁页?不还是要先回收吗?要回收——但关键不是"要不要回收",而是回收工作在谁的线程里跑。不是"先回收、再锁页"的串行,而是"锁页照常进行、回收在别的核上并行补货"的生产者-消费者模型。

8a. 锁页路径是纯消费者——它从不自己回收SRC

IOKit wire 路径(vm_object_iopl_request,vm_pageout.c:6036–6067)拿页只有三步:vm_page_grab_options() 从 free 池拿一页 → 拿不到则 VM_PAGE_WAIT() 睡觉 → 被叫醒后重试同一 offset。展开后的 vm_page_wait(vm_resident.c:3813)逻辑:特权线程 free>0 从不等;水位够直接走;否则登记等待者+唤醒回收 daemon+assert_wait 睡觉——没有一行代码去做扫描/逐出/压缩

生产者消费者架构示意

图 8-1 XNU 锁页×回收的生产者-消费者结构(源码行号锚点)。消费者遇缺货的唯一动作是叫醒生产者并睡觉;三批生产者(scan+压缩机×2)在 E 簇并行补货。

防死锁设计:free 池底部的保留池只留给回收机器自己(vm_resident.c:3175:vm_page_free_count < vm_page_free_reserved && !(TH_OPT_VMPRIV) 则拒绝分配)——保证生产者永远能拿到页继续干活(压缩本身也要临时占页)。

8b. 实证:供需同窗口开动,free 在锁页最猛阶段不降反升EXP

场景二相机窗口生产者消费者实证

图 8-2 场景二相机窗口放大(+0→+2.3s)。第一个 250ms 窗口:wire 爆发 1897MB/s 的同一窗口,供给(文件页逐出 1324+压缩 682)= 2006MB/s 同步开动;free 探底 44MB 后在 wire 持续爬坡阶段回升到 126MB——供给侧追平并反超消耗。

读法:锁页线程全程没有真正饿到(未触发长时间 VM_PAGE_WAIT 睡眠——若睡了,wire 曲线会停顿,实测曲线是平滑的爆发→稳态)。free 底 44MB 不是"差点枯竭",而是水位机器正常工作的证据。

8c. 为什么供给能跟上:三层缓冲

缓冲层机制本轮实证
①提前量(最大)五 App 启动的 100s 里,每次 free 下探都唤醒 scan daemon 干到 free_target 才停——最慢的活(压缩后台 App)在相机开火前就干完了相机启动前压缩机已背景吸收 1.5GB;freeze 又落盘 154MB;相机窗口只剩边缘压缩 474MB
②最大供给不走压缩文件页逐出是 clean 页零 I/O 队列操作(§9 详析)883MB/2.3s ≈ 380MB/s,由 scan 线程直接完成
③爆发速率富余压缩机爆发能力 ~1GB/s(§7),2 线程天花板 1.5GB/s窗口期实际只需 682MB/s 峰值——余量 2–3 倍

失败模式是"慢"不是"崩"(本轮均未发生,水位 23% 未进 warn 带):锁页线程 vm_page_wait 睡觉 → 代价是相机启动变慢(不是失败)→ 优先级 91 的生产者全速补货 → 仍不够 → jetsam 分级杀后台兜底(零触发)。

与 Linux 的结构性对照:Linux 的 alloc_pages 慢路径 → try_to_free_pages(vmscan.c:3233)在申请者自己的上下文里同步扫 LRU、unmap、(配 zram 时)压缩——同一个线程既当消费者又当生产者,这就是 direct reclaim 停顿的根源。XNU 从设计上不让申请者做回收苦役。

§9追问三:文件页回收策略——iOS vs Linux 源码级对比

§8 指出最大一笔供给(883MB)来自文件页逐出且零 I/O。先修正一个因果:零 I/O 文件页回收的价值不在"快"本身,而在它是唯一能跟上锁页消耗速度的供给侧路径——这也是 XNU 把它放在策略最高优先级的原因。释放一个 clean 文件页,两边都是"摘链表+unmap+归还 free"的纯内存操作,原语层无差异;差异全在策略层

9a. XNU:按成本分队列+固定瀑布SRC

XNU受害者选择瀑布

图 9-1 XNU 受害者选择瀑布(vps_choose_victim_page,vm_pageout.c:2331)与三重文件页偏置。右侧为 vm_pageout.c:2145 注释原文——压缩器积压时扫描器不等它,继续偷文件页:两条供给线永不串行。

设计点XNU 实现(源码锚点)含义
队列分类学按成本物理分队列:cleaned(刚洗净)/ speculative 年龄队列(预读从未触碰,LRU 之外,目标上限 5% 内存)/ inactive external(文件页)/ anonymous(匿名页)——对照 Linux 的 4 条 LRU 双挂牌回收投机型页=弹队列节点:无 pmap 引用检查、无 unmap、无 TLB flush
固定成本瀑布cleaned → speculative → background → 文件页 ↔ 匿名页,裁决全偏文件页:ANONS_GRABBED_LIMIT=2(:1462,连拿 2 个匿名页必须回头);filecache_min 地板 ≈可用内存 14%(:2030–2066,divisor=70);jetsam 反向覆盖(:2436–2461,文件缓存远超临界值时改拿文件页)确定性策略:内存告急时行为完全可预测(文件页先扛);匿名页是配给制
死文件快车道引用位命中但 vnode_pager_get_isinuse==FALSE(文件已无人打开)→ 只 re-deactivate 不给二次机会(:3412–3420);对象级整体逐出 100 页/轮(:1966+)换掉的 dylib/读完的文档缓存页不会被一次引用位误判救活;死对象缓存批量消失
clean/dirty 分流pmap_disconnect_options 原子取 refmod;clean 非 precious → 立即挂 local_freeq 本地批,delayed_unlock 摊销锁开销,vm_page_free_list 批量归还(:3531–3652, :3246)全程零 I/O、零压缩、零新分配——本质是内存操作速度
脏页清洗节流external iothread 默认 THROTTLE_LEVEL_PAGEOUT_THROTTLED(后台 I/O 优先级,:4255–4283),仅压力期解除(:2273/:2699)平时清洗脏页不抢前台磁盘带宽
free 目标刻意极小free_target=15+free/100,iOS 封顶 2000 页=31MB(:211–240)系统设计上不维持大 free 池——弹性缓冲就是文件缓存+投机型页;实测 S2 基线 free=48MB 恰好贴 target 上方运行

9b. Linux:正统 LRU+比例成本模型SRC

设计点Linux 实现(5.10/6.1 源码锚点)与 XNU 的差异
结构4 条 LRU(active/inactive × anon/file);预读页直接折叠进 inactive file LRU无独立投机型队列——回收预读页也走完整 LRU 摘除路径
二次机会page_check_references(vmscan.c:986–1047):referenced_ptes → SetPageReferenced+KEEP;二次 → ACTIVATE;VM_EXEC 文件页首次使用即激活(XNU 无此规则);referenced_page 且非 swapbacked → RECLAIM_CLEAN语义几乎同构;Linux 多 VM_EXEC、少死文件快车道(靠 inode/dentry 老化间接达到,粒度更粗)
anon/file 配比get_scan_count(:2237+):swappiness × refault 成本反比加权(anon_cost/file_cost),每边至少 1/3 压力;file_is_tiny→全力拿匿名;cache_trim_mode→全力拿文件比例模型 vs 固定瀑布:Linux 追求全局代价最优(哪边 refault 少多压哪边),XNU 追求确定性(永远先拿最便宜的)
逐错保护workingset refault 距离(:903/:929,5.10 已有):被逐页留 shadow entry,回读时算 refault 距离,距离小立即激活;6.1 MGLRU(lru_gen_look_around,MIN_LRU_BATCH=64)世代老化加速扫描Linux 领先项:XNU 只有引用位粗判,无 refault 距离等价物
执行位置kswapd 每节点 1 线程+直接回收在申请者上下文(:3233);脏页交 per-bdi flusher+cgroup writeback与 §7/§8 结论汇合:不是 Linux 释放文件页慢,而是释放文件页的活可能落在 App 自己的调用栈里

9c. 结论:原语无差异,策略三差异

差异内容对相机类场景的影响
①选择哲学XNU 确定性瀑布(便宜路径优先+多重偏置钉死边界)vs Linux 比例成本模型(统计驱动全局最优)告急时 XNU 行为可预测;Linux 取决于当时 refault 统计
②快车道覆盖XNU 两条 Linux 没有的:投机型页 LRU 外免引用检查、死文件免二次机会+对象级逐出;Linux 一条 XNU 没有的:workingset refault 距离XNU 的便宜路径覆盖比例更大;Linux 逐错保护更精确(各有胜负)
③执行位置与 I/O 节流XNU 回收永远在专用高优先级线程+脏页清洗默认后台 I/O 优先级;Linux 直接回收把整套选择逻辑跑在申请者线程里最重要的一条:同样的相机场景,默认 Linux 更容易把回收停顿放进 App 关键路径
一句话:iOS 相机场景的"压缩回收快"= 单核 ~780MB/s 的够用算力(实现打磨×硬件各半)× 2 个高优先级 E 簇专用线程 × 需求不在回收路径上串行 × 提前量与零成本文件页逐出垫底。四层里只有前两层是"算法/硬件"问题——而这两层任何现代 SoC+Linux 都能复制;真正的护城河是后两层的架构决策。Linux 要达成同样效果需要组装调优(Android 已证明可行),差异是"默认就一体设计" vs "组装件哲学"。
实测环境:iPhone 12 Pro(iPhone13,3)· A14 · 6GB · iOS 14.8(Darwin 20.6.0, xnu-7195.140.44~1)· Taurine 越狱 · 16KB 物理页。
证据分级:[EXP] 本轮设备实测 · [EST] 结构估算 · [SRC] 源码行号锚点 · [GEN] 生态经验判断。姊妹报告:iOS 内存管理深度剖析(主书) · 前轮相机报告(2026-08-29,同协议独立复测互证)。生成:camwatch/cpuper 探针+paramiko 编排,全程零人工介入设备;§7–§9 追问篇基于 xnu-7195.141.2 与 linux-5.10/6.1 源码档案逐行核对。