iOS 相机内存三问

组成与时间曲线(双场景)· wired 何时申请 · 整机如何供给
iPhone 12 Pro · A14 · 6GBiOS 14.8 (xnu-7195.141.2)16KB 物理页越狱真机 root 实测

Q1相机启动的内存占用组成是什么?时间曲线什么样?用例如何设计?

1.1 内存占用组成(总量 1.36GB 的四归属账本)

EXP271/270 进程普查差分(相机冷启动稳定后全量 appscan → 杀相机 → 再普查 → pid 对齐差分):

归属内存类型机理
mediaserverd+547.3MB(48%)iokit_mapped 为主(~497MB)+ internal 少量相机管线真正的主人——AVFoundation 服务端在此,IOSurface 回映射记它的账
内核 wired(无主)+547.6MB(48%)wiredISP 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 和内核里。

1.2 用例设计(双场景对照)

场景 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 点(同参数)

1.3 时间曲线——场景 A(后台干净)

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 全部由空闲池直接支付。

1.4 时间曲线——场景 B(6 App 后台满载)

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)。

1.5 双场景对照总表

指标场景 A场景 B
启动前 free85,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 压缩页不回吐)

Q2wired 内存什么时候申请——用到才申请还是启动预申请?

2.1 直接答案:用到(预览开启)才申请,启动时一分不花

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 配置会话,两层是独立事件

2.2 澄清表面矛盾:"申请即映射" vs "预览才涨"

源码说 wire 是急切同步的(vm_map_wire_nested:7003 逐段 vm_fault_wire:6899,函数返回时整段 100% 物理就位),实测却说冷启动 Δwired=0——矛盾吗?

不矛盾,两个层面:"申请即映射"是 wire 操作的语义(调用发生时同步完成);"预览才涨"是 wire 请求的时机(冷启动根本没人调 wire)。类比:刷卡即时扣款,但只有进店结账才刷卡——前者推不出"钱在持续流出"。

2.3 为什么驱动不预分配(三个理由)

理由展开
池尺寸依赖会话配置分辨率/格式/HDR 帧深度在 launch 时还不知道,无法预算 932MB
闲置预分配是纯浪费6GB 设备白占 15% 不可回收内存(wired = 结构性豁免:不压缩/不换出/不丢弃)
生命周期分层进程启动与媒体会话建立是独立事件——杀 Camera 不等于关管线(mediaserverd 持续存在)

2.4 申请速度实测——"用到才申请"完全来得及

操作吞吐µs/16K页
匿名 zero-fill(基准线)5,025 MB/s3.11
mlock 冷 wire(fault-in+wire)5,189 MB/s3.01
mlock 热 wire(页已在)9,959 MB/s1.57
munlock 解 wire20,009 MB/s0.78

wire 与匿名同速——1GB wired 的内核机械时间只需 ~200ms;管线活体实测瞬时斜率 ≥1.6 GB/s。"wired 分配慢"是误解——慢的是管线配置(DART 映射/驱动协商),不是 wire 本身。惰性申请没有性能代价。

2.5 释放侧(对称问题)

杀 Camera 后:798MB 在 <1s 内 bulk 释放(与 munlock 20GB/s 微基准吻合)+ 尾巴 ~5s(驱动 teardown 异步)。内核 wired 的释放依赖驱动 teardown 而非同步 munlock——豁免的代价是释放不能同步完成

Q3相机启动时,整机系统如何供给内存?

3.1 供给流水线(按顺位逐级加深)

相机请求 1GB
   │
   ▼
① free 池直接支付 ──── 场景A 付清全部 1.36GB,下面各级全程未醒
   │  不够(场景B 只剩 101MB)
   ▼
② 压缩器即时收纳 ──── 场景B 2 秒压缩后台 App 匿名页 +725MB
   │  还不够
   ▼
③ 文件页回收 ──────── speculative 逐出 +67MB(场景B 实测)
   │  还不够
   ▼
④ freezer 冻结 ────── 整进程落盘(§5.1 三幕剧:2GB 压力时四 App 冻结)
   │  还不够
   ▼
⑤ jetsam 击杀 ─────── 本轮全程未触发(5.4GB 总冲击零死亡)

3.2 场景 B 的供给三路分解(实测闭环)

供给路时间窗机理
free 池直接支付~75MB(6,478→1,652,几乎耗尽)瞬时缓冲垫第一顺位
压缩器即时收纳725MB(+46,382 页)t=10→12 两秒后台 App 匿名页同步压缩腾出物理页
speculative/文件页回收~67MB(4,540→243)瞬时投机文件页逐出
freezer/jetsam0(未触发)相机 1GB 停在页级腾挪,不需要进程级献祭

核心洞察:供给顺序是流水线而不是预案——不是预先规划"相机要来,提前冻结后台",而是相机分配逼近水位时同步唤醒回收逐级加深。触发条件是水位(free 逼近底线 1,500 页),不是事件。

3.3 回收与分配并行不悖

场景 B 的 wired 到位速度(~3s)与场景 A 持平——压缩器腾页(2s/725MB)与管线 wire 分配(≥1.6GB/s)同时进行。这是"急切分配 + 页队列同步收纳"的协同设计:管线的 kmem_alloc_contig 走自己的 wire 路径,回收器在另一个核上压缩后台页,两者通过 free 池水位汇合。

3.4 压缩器是"单向阀"(杀相机之后)

实测:压缩页不主动回吐——杀 Camera 后 free 回升到 86k 页就够用了,压缩器里的 51k 页(后台 App 的 725MB)留在压缩态,等它们被再访问时才解压。系统的稳态观是"够用就好"而非"物归原主"。含义:相机用完后,后台 App 的恢复要付解压代价(~12µs/页端到端)——这是"开过相机后后台 App 变慢一下"的微观根源之一。

3.5 与三幕剧(§5.1)的衔接

§5.1 的 5.4GB 冲击实验是"相机 1GB + 人工追加 2GB×2"——第三级才逼出 freezer(四 App 冻结清仓)。本节双场景补齐了前两级(free 池/压缩器)的精确账目:相机自身只用到第二级;第三级以后是极端压力的领地。完整供给阶梯见 3.1 的流水线图。

方法学:测量工具与坑

工具用途
appscanper-pid footprint/rss/internal/compressed(TASK_VM_INFO)
alloceff各内存类型分配/释放效率微基准(匿名/mlock wire/purgeable)
tagmapper-tag 驻留/私有/wired 三列统计
faultscan / mallocz / malltrace缺页统计 / malloc 解剖 / malloc_logger 流量
kernmap / footbreakkernel_map 分解(待 KRW)/ 四大类 ledger(待 iOS 15+)

本文档以三问为纲组织《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(三问结构 + 双场景曲线)