iPhone 12 Pro · 6G DDR 五维拆解与正交恒等式

实测设备:iPhone 12 Pro · A14 · iOS 14.8.1 (18H17, xnu-7195.140.44) · 越狱(Taurine) · 解锁态 · 页大小 16KB
采样:2026-10-31 · memcensus v3 原子快照(单次 host_statistics64)+ 293/295 进程全量普查(零拒绝)
口径:每项均给出源(sysctl / vm_statistics64 / task_vm_info / ioreg),可复现

一、核心结论先看这个

DDR(6 GiB) = R_chip + W + F + (E + N − S) + C + T + R_pool   ← 页状态分桶,天然正交,页页闭合

能正交相加,但必须换分母:用「页状态」做桶 → 各项互斥、严格可加(下面每项都实测闭合到页); 用「进程 footprint」做桶 → 必炸:朴素五维求和 = 5198.7 MiB ≠ 6144 MiB,差 945.3 MiB,五个交叉项已逐一量化(见第四节)。 修正版五维(文件缓存计入可用 + 去两处重复)已做到严格加总 = 6,144.0 —— 见第七节。

⚠ 最容易踩的第一个坑:hw.memsize = 5735.95 MiB,不是 6 GiB。 物理芯片 6 GiB 要从 ioreg dram-size 读(0x180000000 = 6,442,450,944 B)。第一层 408.05 MiB 固件预切,OS 根本看不见。

二、三层 DDR 恒等式全部实测闭合

408
752
E+N−S 2776
C 1406
F 632
171
R_chip 固件预切 W wired(内核及驱动主体) E+N−S 文件缓存+用户态匿名(扣 speculative) C 压缩器槽位 F 可用(⊇speculative) R_pool 页池内预留
层 / 状态页数MiB含义与数据源
物理 DDR393,2166,144.0ioreg dram-size = 0x180000000
├ R_chip(固件预切)26,115408.05dram-size − hw.memsize(SEP/AOP 等,OS 不可见)
└ hw.memsize367,1015,735.95sysctl hw.memsize(= host_basic.max_mem)
  ├ W wire48,111751.7vm_stat「Pages wired down」=内核及驱动主体
  ├ F free(立即可用)40,425631.6含 speculative 17,536 页(274.0 MiB)
  ├ active92,4701,444.8E=ext 138,073(2,157.4)+ N=int 57,137(892.8)= active+inactive+speculative(恒等式③)
  ├ inactive85,2041,331.3
  ├ C 压缩器占用89,9681,405.8槽位;内存放 181,044 stored 页(2,828.8 MiB)数据
  ├ T throttled00.0vm_stat「Pages throttled」
  └ R_pool(页池内预留)10,923170.7secluded 9,600 页=150 MiB(ioreg kern.secluded_mem_mb,ANE 隔离区)+ 残差 1,323 页 20.7 MiB
恒等式验证(5 个快照全部精确成立): ① Σ页状态 356,178 + R_pool 10,923 = 367,101 = hw.memsize/16384 ✓ ② R_chip 26,115 + 367,101 = 393,216 = 6 GiB 整页 ✓ ③ E + N = 138,073+57,137 = 195,210 = active+inactive+speculative ✓ (连续 5 快照) ④ W + F + (E+N−S) + C + T + Rp = 48,111+40,425+177,674+89,968+0+10,923 = 367,101 页 = hw.memsize ✓ ⑤ Σ任务 volatile 账本 1,216 页 ≈ vm_stat「Pages purgeable」1,270 ✓

S(speculative 17,536 页)在公式中必须显式扣一次:它物理上在 free 里、归属上又在 E+N 里——双重身份,只许出现一次。

三、五维归属实测293/295 进程全量普查

生态 774
系统服务 1936
内核≈1278
预留 579
可用 632
缺口 945
维度实测进程数口径与构成
生态应用773.6 MiB16Σ phys_footprint(路径 /var/containers/):bili 277.8 · News 256.1 · WeChat 91.2 · imeituan 64.6…
系统服务1,936.3 MiB277Σ phys_footprint(其余全部):SpringBoard 93.6 · backboardd 89.9 · launchd 82.5 · WebKit×4…
内核及驱动≈1,278 MiB—wire 751.7(实测)+ 内核可分页 12.9(实测 N 未归属)+ 压缩器未归属槽位 513.8(按 stored 占比推算)
预留578.7 MiB—R_chip 408.05 + R_pool 170.7(secluded 150 + 残差 20.7)
可用631.6 MiB—free 立即可用(⊇ speculative 274.0);另有文件缓存 2,157.4 属「潜在可回收」未计入
朴素五维求和5,198.7≠ 6,144 → 差 945.3 MiB,见下一节
归属陷阱①:WebKit 渲染进程(为 bili/News 渲染网页)住在 /System/Library/Frameworks/WebKit.framework/, 被分进「系统服务」桶——生态应用的真实成本被低估。陷阱②:volatile purgeable 驻留页记在 owner 的 volatile 账本(实测 Σ 1,216 页 ≈ vm_stat 1,270 ✓),不在 internal 字段。

四、交叉项量化五维不能朴素相加的四个原因

#交叉项量化实测后果
①压缩记账换算差footprint 按数据 stored 体积记 1,743.7 MiB,物理只占槽位 1,405.8 MiB五维 footprint 口径高估 ~338 MiB
②共享页双记账(external / resident 字段)Σ任务 external = 290,724 页 ≈ 2.1× 物理 138,073 页;Σ resident = 5,457 MiB > 6 GiB污染 external/resident 字段——所幸 external 不进 footprint;fp 真正的交叠只有 iokit 残差 29.4 MiB(与内核 wired 桶重叠)
③文件缓存盲区2,157.4 MiB 不在任何 footprint 里,但物理在 DDR 里五维覆盖不了这部分
④speculative 双重身份274.0 MiB 既在 free 又在 E+N公式里必须 −S 只算一次
⑤NV 账本陷阱ledger_purgeable_nonvolatile 13.9 MiB 若加在 internal 之上恰好多出 13.9NV 已含在 int/compressed 里——实测 Σint+Σvolatile+内核可分页 = N 精确闭合,无需 NV 项

差额精确对账(run4 实测):缺口 945.3 = 文件缓存未计入 +1,883.4 − 压缩换算差 902.9 − iokit/tag 交叠 29.4(余 5.7 为槽位推算的舍入)。加文件缓存是必要条件,但不充分——见第六节演示。

五、优化正交公式推荐口径

DDR(6 GiB) = R_chip ← ioreg dram-size − hw.memsize + W wire ← vm_stat wired (内核及驱动主体) + F free ← vm_stat free (可用,⊇ speculative) + (E + N − S) ← E=ext, N=int, S=speculative(E+N=active+inactive+S 恒等式③) + C compressor ← 「Pages occupied by compressor」 + T throttled ← 常为 0 + R_pool ← hw.memsize/16384 − Σ页状态(secluded + 残差) 五维映射(归属视图,叠加在 N、C 桶内部做分子拆分): 生态应用 = Σ fp(APP) · 系统服务 = Σ fp(SYS) 内核及驱动 = W + N_未归属 + C_未归属份额(按 stored 占比推) 预留 = R_chip + R_pool · 可用 = F ⚠ 归属视图参与求和必须显式带 Δ交叉项(压缩换算、iokit 交叠、文件缓存、−S), 否则差 945.3 MiB;正交求和请只用页状态公式。

六、把文件缓存加进「可用」就行了吗?对账演示:会超 938 MiB

朴素五维 5,198.7 + 文件缓存 E−S 1,883.4 ← 五维确实漏了它,该加 ───────────────────────── = 7,082.1 MiB vs 6,144 → 超出 938.1(不是补齐!)

超出的 938.1 恰好就是两处重复记账:压缩换算差 902.9 + iokit/tag 交叠 29.4(余 5.7 舍入)。 所以正确操作是「加文件缓存 + 去两处重复」同时做——做完即得修正版五维(第七节),严格加总 = 6,144.0。

「可用」的两种口径数值说明
立即可用(free)631.6 MiB分配器直接拿,零成本(内含 speculative 274.0)
宽口径可用(free + 文件缓存 E−S)2,515.0 MiB合理口径,但回收有代价:文件页清掉后重用需重新 IO;active 里的文件页要先老化
压缩换算差是什么(bili 实例): fp 记的 compressed = 264.4 MiB(数据"原始体积",stored 口径) 物理占用 ≈ 264.4 ÷ 2.0 = ~131 MiB 槽位(整机压缩率 181,044 stored ÷ 89,968 槽 = 2.0×) → 全局 Σ任务 compressed = 1,794.9 MiB「数据」,但压缩机物理只占 1,405.8 MiB → fp 桶(stored 口径)与内核槽位桶同表相加 = 同一批 DDR 算两遍(多算 ~902.9) 为什么这样设计:footprint 是配额/杀伤视角("解压回来是多少"),对 jetsam 正确,对物理账错误。
共享双记账是什么(含更正): external 字段 = 缺页记账:谁触碰过共享文件页(dyld 共享缓存/系统资源)谁账上记一笔 → Σ任务 external = 290,724 页 = 2.1× 物理的 138,073(物理一页,账本 N 笔) ⚠ 更正:external 不进 phys_footprint,此 2.1× 不污染五桶 fp 求和; fp 真正的交叠只有 iokit 残差 29.4 MiB(fp − int − cmp − nv − tagfp 实测)。 警示反例:谁用 resident_size 建桶,Σres = 5,457 MiB > 6 GiB,直接穿帮。

七、修正版五维(物理去重)严格加总 = 6,144.0 MiB ✓

维度(物理去重)页数MiB构成(实测 run4)
生态应用26,997421.8internal 4,763 页 + volatile 79 页 + 压缩槽 22,140 页(fp 视角 773.6)
系统服务86,4021,350.0internal 50,319 + volatile 1,137 + 压缩槽 34,946(fp 视角 1,936.3)
内核及驱动81,8181,278.4wire 48,111 + 可分页 824 + 压缩槽 32,883(减法定义,保闭合)
预留37,038578.7R_chip 26,115 + R_pool 10,923
可用(立即)40,425631.6free(⊇ speculative 17,536)
文件缓存(可回收,可并入可用)120,5371,883.4E − S
合计393,2166,144.0= 6 GiB 整,页页闭合 ✓

去重四规则若坚持五维直接相加

#规则原因
1主干求和只用页状态内核给每页恰一个状态,天然去重,什么都不用扣
2压缩求和用槽位口径;内核侧槽位用减法定义(comp_occ − 任务侧)stored 是数据体积(2.0×),物理只占槽;减法定义让闭合精确
3iokit/tag 全归内核桶(唯一归属);NV/volatile 账本不加在 internal 之上fp 残差与 wire 重叠;NV 已含在 int/compressed(实测教训 13.9 MiB)
4文件缓存物理单计 E−S;永不用 Σ任务 external / resident_sizespec 已在 free;external/resident 是缺页记账(2.1×)

验证器(每次普查后跑三条):① Σint+Σvolatile+内核可分页 = N(实测 57,137 精确)② 任务槽+内核槽 = comp_occ ③ 修正五维和 = 393,216 页。任务侧压缩槽按 stored 占比分摊是推算,但总和按构造精确。

八、原始数据run4 原子快照 + 普查合计

host_statistics64 原子快照
free / speculative40,425 / 17,536 页
active / inactive92,470 / 85,204 页
wired / throttled48,111 / 0 页
purgeable(volatile)1,270 页
ext / int138,073 / 57,137 页
comp_occ / comp_stored89,968 / 181,044 页
进程普查(KERN_PROC_ALL)
APP(16)/ SYS(277)773.6 / 1,936.3 MiB fp
Σtask internal / 全局 int55,097 / 57,137 页
未归属 internal(≈内核可分页)824 页(12.9 MiB)
Σtask compressed(stored)+tag114,874 页
未归属 stored → 槽位推算32,883 页(513.8 MiB)
Σtask volatile 账本1,216 页
TOP 进程(footprint)MiB桶TOP 进程(footprint)MiB桶
bili-universal277.8生态WeChat91.2生态
News256.1生态backboardd89.9系统
SpringBoard93.6系统launchd82.5系统

九、复现方法与工具坑全部可在越狱设备上重放

# ① 三层数字
sysctl hw.memsize hw.pagesize
vm_stat                                  # 各页状态
ioreg -p IODeviceTree -l | grep -E "dram-size|secluded"   # 6GiB 物理与 secluded

# ② 全进程普查(探针 ios-probe/cli/memcensus.c,编译后设备侧 ldid 签名)
xcrun -sdk iphoneos clang -arch arm64 -o memcensus memcensus.c
#   部署后: /var/mobile/ios_memslice/memcensus
#   输出 M/V/E/K/S 行 + 每进程 P 行(fp/internal/external/compressed/账本)

# ③ 恒等式核验(本页所有 ✓)
#   states+R_pool==hw.memsize/16384 ; R_chip+memsize==dram-size
#   ext+int==active+inactive+speculative
工具坑(实测踩过):① proc_listallpids() 在 iOS 只返回 73/295 子集——必须用 sysctl KERN_PROC_ALL,否则系统服务桶低估 5 倍;② task_for_pid(0) 恒拒 kr=0x5(四项 entitlements 也不行),内核桶只能减法+推算;③ 共享文件页被每任务重复记 external 账本,物理求和勿用 Σtask ext。