管线需求 ≈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)。
进程出现→管线内存全部就位 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 全存活。
所有计数器变化均可由机制解释:场景一零压缩零逐出(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 的压缩页不回吐(单向阀)。无泄漏迹象。
同一设备同一协议独立复测:管线需求不变量再次成立(Δwired 两轮均在 0.99–1.02GB);本轮新增 ①进程级 8 目标全程 footprint 曲线(250ms)②相机窗口逐出/压缩的分离账本 ③freeze 计数器取证(5 App 阶段冻结 2 进程 +154MB,相机窗口零新增)。结论与前轮一致,量化口径更细。
| 场景一:后台干净 | 场景二:五大 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% |
为消除旧方案"每秒 spawn vm_stat 自污染 ~180 页/样本"的问题,本轮用纯内核调用的常驻采样器 camwatch(arm64 CLI,ldid 签名 task_for_pid-allow 等权限):
host_statistics64(HOST_VM_INFO64) 全量 20 个计数器(wired/free/active/inactive/speculative/purgeable/purges/文件页/匿名页/压缩器/compressions/decompressions/swapins/swapouts/pageins/pageouts/faults/cow/zero-fill/reactivations)+sysctl kern.memorystatus_level(jetsam 水位%);sysctl KERN_PROC_ALL 匹配 8 个目标进程(Camera / mediaserverd / backboardd / 五 App)→ task_for_pid+TASK_VM_INFO 取 phys_footprint / internal / compressed——进程账与整机账同 tick 对齐;date 仅 1s 分辨率的坑);测量窗口内零额外进程 spawn(编排决策全部来自 camwatch 输出流的实时解析);图 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 补回基线。
| 计数器(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 | +88MB | free 充足时预读大胆落 bins(对照场景二被即摘) |
Pages purgeable / purges | +18MB / +8MB | 无需丢弃任何可清除页 |
file-backed pages | +262MB | 框架/资源文件页缓存增长(启动的副作用,非管线本体) |
anonymous pages | +489MB | mediaserverd 内部堆(15.7→108MB)+Camera 壳 18MB+系统进程伴随增长 |
compressions / decompressions | 0 / +42MB | 零压缩——这是"free 充裕则压缩器不劳动"的直接证据;decompressions 为系统进程触碰旧压缩页的背景噪声 |
pageins / pageouts / swapins / swapouts | +242MB / 0 / 0 / 0 | 只读入不换出;启动读入即相机框架代码与资源 |
reactivations | +0MB | ≈0:free 充裕时无需从 inactive 拉回 |
图 2-1 场景二全程(185s,250ms/点)。五 App 依序启动把 free 从 2,192MB 压到 97MB;压缩器(右轴色)同步吃进后台 App 驻留页;相机启动(112.3s)与 kill(150.5s)的 wired 起落。
| 计数器 | 变化(pre→相机前基线) | 解读 |
|---|---|---|
Pages free | -2,096MB | 2.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 / +957MB | App 代码资源缓存+用户态堆驻留 |
图 2-2 相机窗口微观(110–122s)。wired +996MB 与压缩量陡涨同步;free 探底 44MB 不枯竭(供给跟上了分配)。
| 供给来源(回收侧) | 量 | 成本/速率 |
|---|---|---|
| 文件页逐出(file-backed) | -891MB | clean 页零 I/O 直接回 free;≈380MB/s 持续 |
| 匿名压缩(compressions) | +474MB gross | 峰值 682MB/s(单 tick);净得 348MB(压缩器占用 +126MB) |
| purgeable 丢弃 | -37MB | ≈0 成本即摘(网易云 volatile 12MB 等) |
| speculative 摘除 | 瞬时 −23MB | spawn tick 内即摘;随后相机预读又回填 +61MB(净 +61MB) |
| inactive / active 队列 | -620MB / -538MB | 队列腾挪:冷文件页出队+框架热页经 reactivation(+780MB)直接复用缓存 |
| 未动用的手段 | jetsam 0 次 · freeze 0 次新增 · swap 0 | 页级腾挪足够,无需进程级献祭 |
图 2-3 五 App 在相机窗口的形态迁移:蓝(驻留匿名)→紫(已压缩)。bilibili internal 278→152MB(其中 ~33MB 被直接丢弃:reusable/purgeable 路径),网易云 216→187MB;淘宝在基线前已被压干(in≈0)。
| App | fp·基线 | internal·基线 | compressed·基线 | fp·预览后 | internal·后 | compressed·后 | Δfp |
|---|---|---|---|---|---|---|---|
| 抖音 | 566 | 89 | 434 | 566 | 48 | 476 | +0 |
| 淘宝 | 315 | 0 | 300 | 315 | 0 | 300 | +0 |
| 京东 | 138 | 104 | 34 | 138 | 89 | 49 | +0 |
| 网易云 | 298 | 216 | 93 | 298 | 187 | 121 | +0 |
| bilibili | 279 | 278 | 0 | 246 | 152 | 93 | -33 |
五 App footprint 合计 1,597→1,564MB:压缩只改变形态(驻留→压缩)不改变记账;净 fp 变化 −33MB 全部来自 bilibili 的可丢弃页。相机杀掉后五 App 全部存活(survivors.txt)。
wired bulk 回落 -1,015MB(峰值 3,473MB/s),mediaserverd 642→50MB,free 回 +1,239MB;压缩器占用 −-29MB(死会话压缩页直接摘除,不经解压),但存活 App 的压缩页不回吐(compressions 只增不减的单向阀)。拆除期 pageins +102MB——系统进程回拉缓存。
| 归属 | 场景一 | 场景二 | 内容 |
|---|---|---|---|
| 内核无主 wired | ≈375MB | ≈471MB | ISP 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.2MB | UI 壳:预览取景器只是管线的"遥控器" |
| backboardd 显示侧 | +22.9MB | +15.2MB | 帧提交/显示合成路径 |
| 合计(管线需求) | ≈1.05GB | ≈1.03GB | Δwired +987/+996MB 为上界口径(iokit⊂wired 有重叠,见注) |
| 阶段 | 场景一 | 场景二 | 说明 |
|---|---|---|---|
| uiopen 下发 → 进程出现 | ≤0.3–0.9s | ≤0.3–0.9s | 受 SSH 链路+时钟对齐(±0.5s)限制只能给界;SpringBoard 派生本身亚秒级 |
| 进程出现 → 壳加载完成 | ~0.8s | ~0.6s | Camera fp 爬到 ~25MB(dyld+框架) |
| 进程出现 → 管线内存全部就位 | 2.04s(wired 达平台 99%) | 1.77s | 含 mediaserverd 建链+DMA wire+IOSurface 映射 |
| 总计(下发→预览稳定) | ≈2.1–2.9s | ≈2.1–2.7s | "预览稳定"以内存平台期判定(方法学声明见 §0) |
| 操作 | 实测速率 | 含义 |
|---|---|---|
| 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.77s | free 48MB 与 free 2.4GB 的就位时间同量级——回收供给对启动速度无可感拖累(本轮 S2 甚至略快,属轮次方差) |
不是预案,是水位驱动的流水线。相机启动把 free 砸到警戒线附近(探底 44MB),回收机器按成本阶梯依次供页:
| 顺位 | 手段 | 本轮实测 | 成本 |
|---|---|---|---|
| 1 | speculative 摘除 | spawn tick 内 −23MB | ≈0(预读赌输的页,零 I/O 丢弃) |
| 2 | purgeable 丢弃 | −-37MB(purges +12MB) | ≈0(volatile 页直接扔) |
| 3 | 文件 clean 页逐出 | -891MB / 2.3s | 零 I/O(脏页才需回写,本轮 pageouts +860 页仅背景噪声) |
| 4 | 匿名压缩 | gross +474MB(峰值 682MB/s) | CPU 换内存(净得 348MB) |
| 5 | freeze(冻结落盘) | 相机窗口 0 新增(五 App 阶段已冻 2 进程 154MB) | I/O,按需后台执行 |
| 6 | jetsam(杀进程) | 0 次 | 进程献祭——页级腾挪够用时根本轮不到 |
| 统计值 | 场景一(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/decompressions | 0 / +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_level | 67%(全程稳定) | 40%→23%(相机期)→46%(杀后) | ✓ 水位百分比与 free/可用页走向吻合;23% 仍未进 warn 带(kevent 零事件可佐证) |
| 操作 | 实测效率 | 场景 |
|---|---|---|
| wire 分配(峰值 burst) | 3,592MB/s(+738MB/205ms) | S1 冷启动 |
| wire 分配(压力下峰值) | 1,897MB/s | S2(回收供页节奏) |
| 管线整体装配 | ~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 / ~600ms | kill 后 |
| 拆除:驱动 teardown 尾 | ~8s 补完最后 ~100MB | kill 后 |
| 拆除:freeze/jetsam/swap | 0 参与 | 全程 |
| 文件 | 内容 |
|---|---|
data/s1_timeline.txt / s2_timeline.txt | camwatch 全程时间线(423 / 735 个采样点,含元数据头) |
data/s1_markers.json / s2_markers.json | 编排标记(uiopen 下发/返回、pid 首见、kill、时钟漂移) |
data/s1_pre|post_vmstat.txt, s2_pre|baseline|post_vmstat.txt | vm_stat+freeze 计数器快照(阶段锚点) |
data/s2_baseline_appscan.txt | 五 App region 级画像(相机启动前) |
data/s1_killed_procs.txt, s2_survivors.txt, s2_crashlog_recent.txt | 前置清理、存活与 jetsam 取证 |
ios-probe/cli/camwatch.c | 采样器源码(55 列 TSV 协议见文件头) |
run_s1.py / run_s2.py / analyze.py / build_charts.py | 编排与分析全链路脚本 |