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,直接穿帮。

七、五维表(fp 正常算 + 压缩节省单列一行)加总 = 6,144.0 ✓

行MiB口径与构成(实测 run4)
生态应用773.6Σ fp(APP),正常算(其压缩数据按 stored 原始体积含在 fp 内)
系统服务1,936.3Σ fp(SYS),正常算
内核及驱动1,798.5wire 751.7 + 内核可分页 12.9 + 内核压缩数据(stored) 1,033.9(= comp_stored − Σ任务压缩,实测减法)——不加 comp_occ
预留578.7R_chip 408.05 + R_pool 170.7
可用(立即)631.6free(⊇ speculative 274.0)
文件缓存(可回收,可并入可用)1,883.4E − S
压缩节省行−1,423.0−(comp_stored − comp_occ):实际从总 fp 优化掉的量,整机一条
fp 卫生小项−35.0−iokit/tag 残差 40.1(与 wire 重叠)+ volatile 19.0(fp 不含)− NV 13.9(fp 内重复)
合计6,144.1≈ 6,144.0 = 6 GiB ✓(0.1 舍入)

闭合校验:基数里的压缩数据(stored) = 任务侧 1,794.9(在 fp 内)+ 内核侧 1,033.9(内核行)= comp_stored 2,828.8 ✓ → 节省行把整机 stored 一次换算成 comp_occ 口径(2,828.8 − 1,405.8 = 1,423.0)。

⚠ 若内核桶不补「内核压缩数据(stored) 1,033.9」这一行:合计 = 5,145.2,差 998.8 MiB——节省行把内核侧的压缩节省(520.1)也减了,但内核的 stored 从未加进任何桶(fp 只装了整机 stored 的 63%),且内核压缩槽位(513.8)无桶收留。该行是实测减法(comp_stored − Σ任务压缩),不是推算。

历史口径(均与本题恒等于 6,144.0,仅桶间分布不同):整机零假设版(生态 75.9 / 系统 804.0 / 内核含压缩器 2,170.4)与归属视图版(421.8 / 1,350.0 / 1,278.4),详见 REPORT §10.39b/c。

去重四规则fp 口径求和的卫生规则

#规则原因
1主干求和只用页状态内核给每页恰一个状态,天然去重,什么都不用扣
2压缩换算用「节省行」整机一条(−(comp_stored−comp_occ));基数里 stored 必须齐全——任务侧在 fp 内,内核侧走内核行stored 是数据体积(2.0×),物理只占槽;基数不齐则减了从未加过的量(差 998.8 实测)
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 精确)② 任务 stored + 内核 stored = comp_stored(基数闭合,本表成立的必要条件)③ 本表合计 = 6,144.0 ± 0.1。

八、场景实验:公式有效性验证5 场景全成分对比,全部闭合 6,144.0(差 0.0)

S0 基线 → S1 拉起 B站/微信/抖音/淘宝/小红书 → S2 相机前台(预览管线全开)→ S3 相机后台化+Safari/Maps/照片/音乐 → S4 全家桶+相机再前台(free 仅剩 4,768 页)。同一公式逐场景计算:

成分(MiB)S0 基线S1 多应用S2 相机S3 相机退S4 极限
生态应用(fp)764.21,728.01,739.71,921.71,929.9
系统服务(fp)1,935.02,024.22,718.32,360.32,874.4
·wire745.8794.41,778.2819.61,785.0
·内核可分页4.2133.881.4245.062.8
·内核压缩 stored1,074.01,078.71,277.31,061.71,367.6
预留586.5566.3560.1564.2555.3
可用(free)626.0214.599.0317.374.5
文件缓存(E−S)1,886.81,255.7720.7921.5698.0
压缩节省行 −(comp_stored−comp_occ)−1,407.6−1,492.4−2,033.9−1,817.8−2,442.4
fp 卫生小项−70.9−158.9−796.8−249.5−761.1
合计(目标 6,144.0)6,144.06,144.06,144.06,144.06,144.0
偏差0.0 ✓0.0 ✓0.0 ✓0.0 ✓0.0 ✓

五个极端状态(free 40,067→4,768 页、wire 47,731→114,242 页、压缩槽 90,070→144,920 页)下合计恒 = 6,144.0,偏差 0.0——公式有效性验证通过。

占用分析 ①:相机的内存签名(S1→S2)

相机链组件S1S2(相机前台)Δ
wire(ISP/GPU/IOSurface 锁页)794.41,778.2+983.8
mediaserverd(采集管线)38.4708.4+670.0
Camera app 自身 fp—23.9+23.9
文件缓存(被征用供给)1,255.7720.7−535.0
volatile purgeable 页2,210 页8 页≈清空

相机前台总成本 ~2.2 GiB,但 Camera app 自身 fp 仅 23.9 MiB——成本住在 wire(锁页缓冲)与 mediaserverd;S3 相机退场时 wire 回落 −958.6、mediaserverd 708.4→48.6,完全可逆。

占用分析 ②:后台生态 App 的压缩收容(S1 前台新拉 → S4 后台,fp / 驻留 / 压缩 MiB)

应用S1 fpS1 驻留S1 压缩S4 fpS4 驻留S4 压缩
抖音 Aweme564.6345.5154.8573.10.5507.7
淘宝 Taobao4iPhone315.0209.597.2318.30.5306.9
小红书 discover170.2170.20.8177.519.8156.5
Maps / Music(S3 新入)———106.8 / 46.516.8 / 40.769.0 / 4.6

铁证:后台化后驻留清零、数据全进压缩器(抖音 345.5→0.5 驻留),而 fp 几乎不变(564.6→573.1)——fp 是配额视角(stored 口径),物理成本全部转移到压缩槽。这正是「压缩节省行」必须存在的实证:节省行 S0→S4 从 −1,407.6 加深到 −2,442.4(节省了 2.4 GiB),压缩器用 2,264 MiB 槽位装下了 4,707 MiB 数据。

九、原始数据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。