EXP271/270 进程普查差分(相机冷启动稳定后全量 appscan → 杀相机 → 再普查 → pid 对齐差分):
| 归属 | 量 | 内存类型 | 机理 |
|---|---|---|---|
| mediaserverd | +547.3MB(48%) | iokit_mapped 为主(~497MB)+ internal 少量 | 相机管线真正的主人——AVFoundation 服务端在此,IOSurface 回映射记它的账 |
| 内核 wired(无主) | +547.6MB(48%) | wired | ISP DMA 缓冲/驱动固件——不进任何进程 footprint,只有全局 wired 计数器可见 |
| Camera 进程 | +25.4MB(2%) | 匿名 internal | 纯 App 壳(UI/按钮/权限对话框) |
| backboardd | +21MB(2%) | iokit_mapped | 显示合成侧的 surface 映射 |
组成的本质:相机内存 = 2% 的 App 壳(匿名)+ 48% 的管线主人(iokit_mapped)+ 48% 的驱动 DMA(wired)+ 2% 显示合成。Camera App 只是遥控器,真正的内存在 mediaserverd 和内核里。
| 场景 A:后台干净 | 场景 B:后台满载 | |
|---|---|---|
| 前置条件 | 杀掉所有可杀进程,free 稳定 1.3GB+ | 依次启动 6 App(抖音/邮件/地图/Notes/Music/Books)等进后台,free 压到 101MB |
| 操作序列 | uiopen 相机 → 预览 26s → killall Camera → 观察 50s | 同左(同参数对照) |
| 采样 | 0.4s×115 点:vm_stat(wired/free/压缩器/speculative)+ 双进程 appscan(Camera/mediaserverd) | 0.4s×110 点(同参数) |
t(s) wired free cam_fp msd_fp ← 关键事件 0 42,051 85,928 - - 9 42,051 85,920 - - ← 相机拉起 10 92,749 37,682 22.8 247.5 ← 瞬时段 +50,697页=792MB ≤0.5s(≥1.6GB/s) 11 106,994 1,647 26.4 584.5 ← free 探底 26MB(距底线147页) 13 107,604 1,651 24.8 615.3 ← 全部到位 16~41 105,4xx 4,5xx 23.7 615.x ← 稳态(压缩器/回收器全程无动作) 41.5 105,399 4,6xx 23.6 613.9 ← killall Camera 42 54,332 72,119 - - ← bulk 释放 798MB <1s 43~47 50,205→44,421 80,xxx ← 尾巴 -93MB/5s(驱动teardown) 48+ 42,118 90,397 ← 基线恢复
曲线形状:单极突降——free 一次跳水 1.36GB,wired 三步台阶(42k→93k→107k),压缩器/回收器计数器全程躺平。1GB 全部由空闲池直接支付。
t(s) wired free cmp_stored spec ← 关键事件 0 43,072 6,478 5,339 4,296 ← 基线:free 仅 101MB 10 43,159 6,178 5,312 4,540 ← 相机拉起 10 46,423 3,074 5,312 4,543 11 97,302 2,439 31,431 243 ← 压缩器暴力启动!spec 被清 12 104,601 1,652 46,762 930 ← 两秒收纳 725MB 13 108,320 2,525 51,694 6,787 ← wired 全部到位(~3s,不比A慢) 14~41 108,7→106,6 2.4k~3.0k 51.5k 7.2k ← 稳态 42.5 106,6xx 2,8xx 51,4xx 7,4xx ← killall 43 51,790 78,080 51,130 7,925 ← bulk 释放 46~65 46,8→44,5 83.5k→86.1k 51,1k 持平 ← 压缩页不回吐
曲线形状:双极联动——free 探底的同时压缩器陡涨 725MB,两条曲线呈镜像。同一件事(相机要 1GB)在两种内存环境走完全不同的供给剧本(详见 Q3)。
| 指标 | 场景 A | 场景 B |
|---|---|---|
| 启动前 free | 85,928 页(1.34GB) | 6,478 页(101MB) |
| wired 上涨 | +63,553 | +65,163(持平) |
| 压缩器即时收纳 | ≈0 | +46,382 页 = 725MB |
| speculative 供给 | 无变化 | 4,540→243(~67MB) |
| wired 到位耗时 | ~3.5s | ~3s(不受影响) |
| 后台 App 命运 | n/a | 全部活着(freeze=0) |
| 杀后 free 回升 | 90,397(全回) | 86,056(51k 压缩页不回吐) |
EXP分阶段差分实验:
| 阶段 | Δwired | 说明 |
|---|---|---|
| 冷启动 + 立即 STOP(不进预览) | +0 | 启动本身零 wired——进程拉起/代码装载/UI 构建都不碰 |
| 预览 25s | +0 | 缓冲池还没铺开 |
| 预览 45s | +59,697 页(932MB) | 管线全速运转时一次到位 |
结论:wired 属于"预览"不属于"启动"。触发链:预览开启 → AVFoundation 建 capture session → AppleH13CamIn 驱动配置 → kmem_alloc_contig → vm_map_wire_kernel——App 进程拉起 ≠ mediaserverd 配置会话,两层是独立事件。
源码说 wire 是急切同步的(vm_map_wire_nested:7003 逐段 vm_fault_wire:6899,函数返回时整段 100% 物理就位),实测却说冷启动 Δwired=0——矛盾吗?
不矛盾,两个层面:"申请即映射"是 wire 操作的语义(调用发生时同步完成);"预览才涨"是 wire 请求的时机(冷启动根本没人调 wire)。类比:刷卡即时扣款,但只有进店结账才刷卡——前者推不出"钱在持续流出"。
| 理由 | 展开 |
|---|---|
| 池尺寸依赖会话配置 | 分辨率/格式/HDR 帧深度在 launch 时还不知道,无法预算 932MB |
| 闲置预分配是纯浪费 | 6GB 设备白占 15% 不可回收内存(wired = 结构性豁免:不压缩/不换出/不丢弃) |
| 生命周期分层 | 进程启动与媒体会话建立是独立事件——杀 Camera 不等于关管线(mediaserverd 持续存在) |
| 操作 | 吞吐 | µs/16K页 |
|---|---|---|
| 匿名 zero-fill(基准线) | 5,025 MB/s | 3.11 |
| mlock 冷 wire(fault-in+wire) | 5,189 MB/s | 3.01 |
| mlock 热 wire(页已在) | 9,959 MB/s | 1.57 |
| munlock 解 wire | 20,009 MB/s | 0.78 |
wire 与匿名同速——1GB wired 的内核机械时间只需 ~200ms;管线活体实测瞬时斜率 ≥1.6 GB/s。"wired 分配慢"是误解——慢的是管线配置(DART 映射/驱动协商),不是 wire 本身。惰性申请没有性能代价。
杀 Camera 后:798MB 在 <1s 内 bulk 释放(与 munlock 20GB/s 微基准吻合)+ 尾巴 ~5s(驱动 teardown 异步)。内核 wired 的释放依赖驱动 teardown 而非同步 munlock——豁免的代价是释放不能同步完成。
相机请求 1GB │ ▼ ① free 池直接支付 ──── 场景A 付清全部 1.36GB,下面各级全程未醒 │ 不够(场景B 只剩 101MB) ▼ ② 压缩器即时收纳 ──── 场景B 2 秒压缩后台 App 匿名页 +725MB │ 还不够 ▼ ③ 文件页回收 ──────── speculative 逐出 +67MB(场景B 实测) │ 还不够 ▼ ④ freezer 冻结 ────── 整进程落盘(§5.1 三幕剧:2GB 压力时四 App 冻结) │ 还不够 ▼ ⑤ jetsam 击杀 ─────── 本轮全程未触发(5.4GB 总冲击零死亡)
| 供给路 | 量 | 时间窗 | 机理 |
|---|---|---|---|
| free 池直接支付 | ~75MB(6,478→1,652,几乎耗尽) | 瞬时 | 缓冲垫第一顺位 |
| 压缩器即时收纳 | 725MB(+46,382 页) | t=10→12 两秒 | 后台 App 匿名页同步压缩腾出物理页 |
| speculative/文件页回收 | ~67MB(4,540→243) | 瞬时 | 投机文件页逐出 |
| freezer/jetsam | 0(未触发) | — | 相机 1GB 停在页级腾挪,不需要进程级献祭 |
核心洞察:供给顺序是流水线而不是预案——不是预先规划"相机要来,提前冻结后台",而是相机分配逼近水位时同步唤醒回收逐级加深。触发条件是水位(free 逼近底线 1,500 页),不是事件。
场景 B 的 wired 到位速度(~3s)与场景 A 持平——压缩器腾页(2s/725MB)与管线 wire 分配(≥1.6GB/s)同时进行。这是"急切分配 + 页队列同步收纳"的协同设计:管线的 kmem_alloc_contig 走自己的 wire 路径,回收器在另一个核上压缩后台页,两者通过 free 池水位汇合。
实测:压缩页不主动回吐——杀 Camera 后 free 回升到 86k 页就够用了,压缩器里的 51k 页(后台 App 的 725MB)留在压缩态,等它们被再访问时才解压。系统的稳态观是"够用就好"而非"物归原主"。含义:相机用完后,后台 App 的恢复要付解压代价(~12µs/页端到端)——这是"开过相机后后台 App 变慢一下"的微观根源之一。
§5.1 的 5.4GB 冲击实验是"相机 1GB + 人工追加 2GB×2"——第三级才逼出 freezer(四 App 冻结清仓)。本节双场景补齐了前两级(free 池/压缩器)的精确账目:相机自身只用到第二级;第三级以后是极端压力的领地。完整供给阶梯见 3.1 的流水线图。
| 工具 | 用途 |
|---|---|
appscan | per-pid footprint/rss/internal/compressed(TASK_VM_INFO) |
alloceff | 各内存类型分配/释放效率微基准(匿名/mlock wire/purgeable) |
tagmap | per-tag 驻留/私有/wired 三列统计 |
faultscan / mallocz / malltrace | 缺页统计 / malloc 解剖 / malloc_logger 流量 |
kernmap / footbreak | kernel_map 分解(待 KRW)/ 四大类 ledger(待 iOS 15+) |
sed 's/^ *//;s/ .*//'本文档以三问为纲组织《iOS 内存管理深度解析》相机相关全部结论(§5.1/§5.5b–§5.5n)。 源码锚点基于 xnu-7195.141.2(iOS 14.8);实测数据来自 iPhone 12 Pro 越狱真机。 主书:ios-memory-book.pages.dev · 2026-08-28 · v3.0(三问结构 + 双场景曲线)