| 章 | 主题 | XNU 选择 | Linux 选择 | 判定 |
|---|---|---|---|---|
| 1 | 总体架构 | 三线一体(记账/回收/查杀)+256MB 极小 swap | 多子系统分立+swap 中心化 | 各有胜场 哲学差 |
| 2 | 页队列状态机 | 双轨 inactive+throttled 隐藏队列+speculative | per-node×per-memcg LRU+MGLRU 世代 | 平手 手机简/服务器隔离 |
| 3 | 记账体系 | ledger+phys_footprint(查杀同口径) | memcg(记账与 OOM 决策割裂) | XNU 胜 单币种设计 |
| 4 | 回收五阶段 | 单线程+FCS 流控+jetsam 提前量 | kswapd+直接回收双路+水位阶梯 | 手机 XNU 胜 延迟毛刺少 |
| 5 | 真实场景 | 相机 5.4GB 冲击:2s 回血+冻结保后台 | 同压力走 zram+lmkd 杀后台 | 体验 XNU 胜 内存利用 Linux 胜 |
| 6 | 压缩器 | WKdm/LZ4 混合,VM 内嵌 | zram 块设备+算法可插拔 | 集成vs生态 |
| 7 | NAND 寿命 | 策略整体性(挂起+丢弃+小 swap)98% 吸收 | f2fs/dirty 节流/TRIM 文件系统协同 | Linux 胜 生态可调性 |
| 8 | Purgeable | token 状态机+记账瞬移(一等公民) | MADV_FREE 懒提示(仅匿名) | XNU 完胜 语义代差 |
| 9 | Freezer | 整进程内存搬走(冻结=零占用) | cgroup freezer 仅停 CPU(内存原样) | XNU 能力独有 |
| 10 | Jetsam | 210 band+三级 aging 流水线(内核态) | OOM badness+Android lmkd(用户态) | 手机 XNU 胜 通用 Linux 胜 |
| 11 | 压力通知 | 四级+滞回(面向 App 行动) | PSI 连续量(面向系统调度) | 各有胜场 |
| 12 | 分配器 | zalloc 安全起点→2020s 补性能 | SLUB 性能起点→2020s 补安全 | 已趋同 书中原判 |
| 13 | 限额 | 双限额(前台宽/后台严)内核强制 | Android 框架层 memoryClass+lmkd | 语义 XNU 胜 通用 Linux 胜 |
| 14 | 缺页/COW | 4GB VA 预算+影子对象 COW | 47bit VA+fork+rmap | Linux 完胜 VA 空间 |
| 15 | 观测接口 | vm_stat/task_info/Instruments 一致链 | /proc+tracepoint+eBPF 可编程 | Linux 完胜 观测生态 |
| 16 | vm_stat 字段 | free ⊇ speculative(经典陷阱) | MemAvailable 自解释估算 | Linux 略胜 语义清晰 |
| 17 | 测试落地 | repro-all.sh 一键复现 | stress-ng/drop_caches/psi 压测 | 方法论互通 |
判定说明:胜负均限定场景(手机=单用户交互设备优先保前台体验;服务器/通用=多租户吞吐优先)。"能力独有"= 对方主线无对应机制。
关键性质:ARM64 硬件页表遍历器没有笔——遇到 AF=0 不能自己补 1,只能罢工报警(access flag fault,FSC 0x09/0x0A/0x0B)。"AF 位被谁置 1"从头到尾只有软件一个答案(pmap.c:10802 的 WRITE_PTE_STRONG)。x86 上硬件代笔、内核无感知的难题被 ISA 直接消灭。
| 传统(v5.10 主线) | MGLRU(v6.1+) | |
|---|---|---|
| 姿势 | 反向:rmap 反查——page_referenced (rmap.c:850) → page_referenced_one (:767) → ptep_clear_flush_young_notify (:788),每页每映射一次 | 正向:lru_gen_look_around (vmscan.c:4589)——按 VMA 批量正扫页表,一个 PMD 窗口 256 个 PTE 一批,MIN_LRU_BATCH=64 (mmzone.h:384) |
| 成本 | O(映射数)/页:锁+树+四级页表走查+TLB flush,重共享页每页几μsEST | 每 PTE 常数,缓存友好(页表本身连续) |
| 注意 | — | 驱逐仍走 rmap(try_to_unmap, vmscan.c:1876)——正扫只取代了"判定",解映射的老本行没动 |
pte_young 就是测 PTE_AF,pte_mkold 就是清它(arch/arm64/include/asm/pgtable.h:103、:222)——与 XNU 用同一个位;do_page_fault 正常接住(arch/arm64/mm/fault.c:759-761);handle_pte_fault 的"PTE 已存在"分支全程不调 mark_page_accessed(实测 0 处)——门卫只装电池、不写登记本,证据记回 PTE 而非物理页。referenced_ptes > 1 → 升 active,vmscan.c:1017);③ 已有解——MGLRU 批发正扫已把判定成本降到每 PTE 常数。同一扇门,XNU 当收发室,Linux 当普通门修——专用系统可以赌硬件,通用系统只能赌抽象。
vm_page_balance_inactive 在整个内核里只有一个调用者——回收扫描循环(vm_pageout.c:2950),且每轮迭代预算 1 页;vm_page_free_wanted 上,free > free_min 时根本不醒(vm_resident.c:3351)——free 富余时老化近乎停摆;| XNU | Linux(MGLRU) | |
|---|---|---|
| 分级粒度 | 两级(active/inactive)+ 每轮二跳检查 | 多代多档(世代×tier) |
| 驱动方 | 回收线程兼差(1:1 预算) | kswapd 独立线程 + 直接回收兜底 |
| 空闲时 | 整个机器停摆(不老化) | kswapd 仍可被水位唤醒做后台整理 |
| 流式 IO 防护 | speculative 队列隔离SRC | tier 沉底SRC |
| 档位 | 吞吐 | 带宽 | CPU | 瓶颈 |
|---|---|---|---|---|
| 稳态 | 4,824 页/s | 75 MB/s | ~0.3% | 需求节奏(episode 结构) |
| 爆发 | ~13,000 页/s | 200 MB/s | 4.3%(0.26/6 核) | 流水线节奏(CPU 仅 26% 利用) |
| 饱和 | 50,010 页/s | 781 MB/s | ~1.0 核 | 压缩机 CPU(≈单核 WKdm 774MB/s) |
全路径回收成本 21.3μs/页(16KB),其中 WKdm 压缩 20.2μs(95%)——判定+队列+摘映射合计 ≤1.1μs。爆发档交叉验证:19,200 页 × 20.2μs = 0.388 core·s 理论值 vs 0.387 实测。
metacompressor(vm_compressor_algorithms.c:296):WKdm 打头,结果 ≥2048B 或失败转 LZ4(lz4_threshold=2048);page_same_filled,zram_drv.c:210/:1356——零页/重复页白捡)SRC;| XNU jetsam | Linux OOM | Android PSI+lmkd | |
|---|---|---|---|
| 决策位置 | 内核态SRC | 内核态SRC | 用户态GEN |
| 选择依据 | band 位图 + 前后台状态 | badness(内存占用×oom_score) | PSI 压力 + oom_score_adj + 进程组策略 |
| 触发粒度 | 提前量(压力前预杀后台) | 事后(OOM 才杀) | 连续量(按 stall 比例分档) |
| 哲学 | 厂商全栈:策略内核直调 | 救系统不救体验 | 策略外移:内核只计量,裁决归用户态 |
| 冷页类型 | XNU 处理 | 每页成本 | Linux 对应物 |
|---|---|---|---|
| ① 干净文件页 | 直接扔,将来从 flash 重读 | ~0-1μsEST | 页缓存丢弃(同构) |
| ② purgeable/volatile | 直接没收(nonvolatile 才保护) | ~0EXP | MADV_FREE 懒释放(提示式) |
| ③ 整进程冷态 | freezer 冻结 / jetsam 杀掉归零 | ≈0(摊销) | cgroup freezer(只停 CPU,内存不搬) |
| ④ 活进程脏匿名页 | 压缩器压走 | 20.2μsEXP | zram 压缩换出 |
实测的 21.3μs 全路径成本压的是第④类——唯一没有免压缩通道的类。iOS 冷页策略的真正回答不是"把 20.2μs 变小"(编解码器已是 WKdm/LZ4 混合+硬件加速形态,实测贴着单核 WKdm 上限跑),而是让前三级免费通道吃掉绝大多数流量:与其花 2.5 秒压掉后台 app 的 200MB,不如零成本杀掉它整个归零。
按"解决什么痛点 → 怎么做 → XNU 有无对应物/需不需要"三问过堂。结论:Linux 的优化清单几乎每一项都在补偿通用性,且大部分在 XNU 上要么不存在对应痛点,要么已有更便宜的解。
以下每章三段式:书里怎么讲(含书内锚点)→ Linux 对应物(含源码锚点/分级标签)→ 判词。与 Part I 重叠的章(2/4/6)只讲增量,不重复深潜内容。
iOS 内存管理的骨架是三条线拧成的单股绳:记账线(每物理页归属 task 的 ledger,phys_footprint 就是查杀口径)、回收线(vm_pageout_scan 偷页,不征求进程同意)、查杀线(memorystatus 按 210 band 收割 + freezer 免死通道)SRC。真机实测修正了"iOS 无 swap"的流传说法:6GB 机型上 vm.swapusage 显示 256MB 加密 swap 真实存在,压力期观测 8613 次 swapout(134MB 落盘)——角色是压缩器之后的第二级小号泄压阀(仅占 6GB 的 4%),而非桌面式平等换页层EXP。
Linux 是多子系统联邦:swap 子系统(平等的换页层,可多设备、可加密、可优先级)、cgroup/memcg(记账与限额)、vmscan(回收)、OOM killer(查杀)——各自独立演进、独立配置。swap 在 Linux 是一等公民:桌面/服务器可以 swappiness=60 常态换页,Android 用 zram 造出内存 swap。GEN
| XNU | Linux | |
|---|---|---|
| swap 地位 | 刻意极小(4%),泄压阀而非层EXP | 一等公民,可配置成主力 |
| 子系统耦合 | 三线一体(记账=查杀口径) | 联邦制(记账/回收/查杀各自为政) |
| 默认行为 | 压缩优先、杀进程兜底 | 按配置漂移(发行版决定人格) |
页的 vmp_q_state 枚举是全书钥匙SRC:free 三道水位(min/target/absolute)、active、双轨 inactive(11=internal 匿名/12=external 文件——"2:1 比例"等回收策略的基础)、throttled 隐藏队列(不换页时匿名页暂存于此,对扫描不可见)、speculative(带保护期的投机页)。老化由 vm_page_balance_inactive 以 1:1 预算从属于回收,inactive 目标为可用页的 1/2(vm_pageout.c:227)。
LRU 按 node × memcg × (anon/file) × (active/inactive) 多维展开,外加 unevictable 链(mlocked 页);MGLRU 后变成 per-memcg 世代序列SRC。"throttled 隐藏队列"的 Linux 对应物是 sc->may_swap/swappiness 开关:swap 不可用时直接不扫匿名(而非藏起来)GEN。speculative 的对应物是 page cache 的 workingset 标记与 refault 距离保护SRC。
| XNU | Linux | |
|---|---|---|
| 队列数量级 | 全局一套双轨(简单) | node×memcg×4 起(隔离强) |
| 匿名/文件分流 | 两条 inactive 队列物理分开 | 两条 LRU 物理分开(同构!) |
| swap 不可用时 | 匿名页藏进 throttled 队列 | 调低扫描倾向(may_swap) |
| 投机/保护期 | speculative 队列+保护期 | workingset+refault 距离 |
每个物理页归属一个 task 的 ledger 账户(internal/external/purgeable…),记账时机 = 页插入 pmap 的瞬间;jetsam 判死用的 phys_footprint = RSS + 压缩器占用 + IOKit/交替映射 + purgeable nonvolatile——不含 clean 文件页(可丢,杀了也释放不出)。书里明确建议 Linux 开发者把 ledger 类比 cgroup memory.stat,但记账时机不同SRC。
memcg 在分配/缺页时 charge 到 cgroup:memory.current = anon + page cache + kernel(slab 等)+swap。两个口径差:① page cache 计入 memory.current 但可随时 reclaim——账面虚高;② OOM 决策还要另算 oom_score(进程维度),与 cgroup 账本(组维度)经常打架GEN。
| XNU ledger | Linux memcg | |
|---|---|---|
| 记账粒度 | per-task(进程) | per-cgroup(组,进程须另行读取) |
| 记账时机 | 页进 pmap 瞬间 | 分配/缺页时 |
| 含 page cache? | 不含 clean(口径=可杀释放量) | 含(口径=占用总量) |
| 与查杀关系 | 同一数字(footprint) | 需换算(oom_score 另算) |
vm_pageout_scan 的五阶段:初始化速赢(pmap 层归还多余页)→ 队列平衡与锁管理 → 流量控制状态机(FCS)→ 挑选受害者(顺序是精华)→ 处置。机制常驻、劳动按需:扫描线程平时睡,水位跌破才醒SRC。真机差分实测(10 秒窗口)证实空闲期扫描计数零增长EXP。压力全链路行为见 Part I §3 三档阶梯。
双路回收:kswapd(每 node 一线程,low 水位唤醒回补到 high)+ 直接回收(分配线程在 min 水位下亲自下场回收,SWAP_CLUSTER_MAX=32 页一批,vmscan.c:1939/:3262SRC)+ throttle_direct_reclaim 节流(:3149)。MGLRU 世代推进替代传统 list 扫描SRC。
| XNU | Linux | |
|---|---|---|
| 回收主体 | 单线程(+分配者阻塞等待) | kswapd 后台 + 分配线程前台双路 |
| 过载时分配者 | 阻塞在 vm_page_wait(统一排队)SRC | 自己下场回收(直接回收,延迟自付) |
| 流控 | FCS 状态机(全速/限速/让位) | 水位阶梯+节流+congestion |
| 兜底 | jetsam 提前量(不等回收失败) | OOM(等回收真失败才杀) |
三幕实测EXP:① 冷启动相机分配 1.36GB——回收系统纹丝不动(free 从 133,376 页砸到 46,930,警戒线 31MB 外全是余量,扫描器没醒);② 追加 2GB 高熵不可压——free 一度砸到 2,860 页(距 min 一线之遥),但 2 秒内 765MB 匿名页送压缩、126MB 投机页直接丢,free 弹回 1.2GB——全程压力级别 1、零进程死亡;③ 幕后四个后台 App 被整体冻结(freezer 收编,不杀不占)。
① 同构——free 池直接吸收(Android 冷启动相机同样主要吃 page cache/free);② zram 压缩匿名页 + lmkd 按 PSI 分档杀后台——大概率有后台进程死亡(cached app 首当其冲);③ cgroup freezer 只停 CPU,内存不释放,压力真大时 Android 的答案就是杀GENEST。
数据结构是 c_segment:压缩页按 16KB 粒度打包进段(段自身是压缩池的分配单位),slot 记录 (codec, offset, popcount)。压缩主路径带混合策略:WKdm 打头,≥2048B 或失败转 LZ4(metacompressor,lz4_threshold=2048,vm_compressor_algorithms.c:296SRC);ARM popcount 硬件加速;16KB 页专用变体;per-CPU scratch 免锁。解压侧对称(popcount 快速校验)。实测 20.2μs/页、774MB/s 单核贴顶EXP。
zram 块设备 + zsmalloc(size class 紧凑分配,功能对应 c_segment 的 slot 打包)+ 算法可插拔(lz4/zstd/842)+ 同页去重(page_same_filled,zram_drv.c:210/:1356——全零/重复页一个 element 就存,白捡SRC)+ writeback 三级温度。解压走块设备读路径(更长的调用链EST)。
| XNU 压缩器 | zram 生态 | |
|---|---|---|
| 位置 | VM 内嵌(refault 零跨层) | 块设备层(跨 swap 子系统) |
| 算法 | WKdm/LZ4 混合+自适应状态机 | 可插拔(zstd 压缩比更高可选) |
| 同页去重 | 无(zero-filled 有单独快路径) | 有(same_filled) |
| 第三级温度 | 无(冷到底靠 jetsam) | 有(writeback 到 flash) |
| 加密 | 段级(休眠/整体加密体系内) | 依赖上层(dm-crypt 等) |
三层防线SRC+EXP:第 0 层 NAND 硬件合同(固件磨损均衡+7320 备用块池,ioreg 实测 Toshiba TLC);第 1 层内核写入节流(98% 写入吸收率——压缩器优先让脏页不落盘、限流队列控制换页 IO、swap 刻意极小);第 2 层系统策略兜底(挂起进程零脏页——"不跑的进程不产生脏页"、purgeable 丢弃走重算不走闪存、墓碑 5 具上限)。结论:6GB RAM+TLC 组合获得十年级寿命预期。
① writeback 节流:balance_dirty_pages 按比例(dirty_ratio/dirty_background_ratio)限脏页总量,BDI 感知设备速度GEN;② 文件系统协同:f2fs 专为 flash 设计(日志结构、SSR、分段 GC),ext4 discard/TRIM 定期回收块GEN;③ swappiness 可调低护 flash;④ Android 同样小 swap + zram 减写。
| XNU | Linux | |
|---|---|---|
| 节流位置 | VM 层(限流队列+压缩器优先+挂起) | 块层+文件系统层(writeback+f2fs) |
| 可调性 | 基本不可调(策略焊死) | dirty_ratio/swappiness/挂载选项全开放 |
| 独特武器 | 挂起模型(后台零脏页) | f2fs(文件系统级磨损感知) |
| 实测锚点 | 98% 吸收率EXP | 无本会话实测GEN |
四状态 token 状态机(VOLATILE/NONVOLATILE/EMPTY/FROZEN)+ 成熟(ripe)分级:丢弃顺序按 ripe 程度遍历(vm_purgeable_purge_object,:821-1000SRC)。最妙的是记账切换:进入 VOLATILE 瞬间 footprint 账面瞬移(PurgeableNonvolatile→PurgeableVolatile 账户间划转)——App 的内存压力评分立刻下降,"我可以被丢弃"从口头承诺变成账本事实SRC。实测:64MB nonvolatile 触摸后计数仅 +123(只统计 VOLATILE 态),EMPTY 后 purges +4,102≈4,096 ✓EXP。
MADV_FREE(madvise.c:743 起的路径——仅对匿名 VMA 有效,标记可懒释放:回收时直接丢页不改盘,但不改变任何记账,下一次写会把标记悄悄撤销)SRC;Android 历史上的 ashmem(pin/unpin 语义几乎就是 purgeable 的翻版,后被 memfd 取代弱化)GEN。
| purgeable | MADV_FREE/ashmem | |
|---|---|---|
| 语义强度 | 契约(状态机+token 持有量) | 提示(内核可不理会) |
| 记账联动 | VOLATILE 瞬移(footprint 立降) | 无(账面不变) |
| 丢弃粒度 | 按 token 成熟度有序 | 无序(LRU 顺带) |
| 数据可恢复性 | FROZEN 态可带内容挂起 | 无对应 |
先分清概念:挂起(suspend)是"人不动了",冻结(freeze)是"家也搬走了"——freeze_top_process 只挑已挂起进程(freezer.md:43SRC),把整进程的(脏)内存页送压缩器/swap,进程在内存账上近乎清零;解冻时按需解压回。相机实验第三幕的四个后台 App 就是这条通道(书§5)。
cgroup freezer:__refrigerator 是一个 set_current_state(TASK_FROZEN); schedule(); 循环(kernel/freezer.c:62-90SRC)——只是让任务睡在调度器里,内存一页不动。Android 11+ 用它冻 cached app 的 CPU(省电),但内存压力来临时冻结帮不上忙,还得 lmkd 杀。内核态"整进程换出再挂起"在主线 Linux 无对应物GEN。
| XNU freezer | cgroup freezer | |
|---|---|---|
| 冻结时 CPU | 停 | 停 |
| 冻结时内存 | 搬走(压缩/落盘,账面清零) | 原样驻留 |
| 内存压力贡献 | ≈整进程 footprint×(1-压缩比) 立即释放 | 零 |
| 解冻成本 | 解压回读(延迟秒级可见) | 唤醒即可(零成本) |
| 主力场景 | 内存压力+后台保命 | 省电/快照(S4 休眠另算) |
210 个优先级 band 从 idle=0 到 critical,查杀永远从 band 低端开始SRC。设计精华是三级 aging 流水线:退后台→AGING_BAND1(10)(10 秒防"刚切出就被杀",:412)→若持 importance assertion 转去 stuck(15)(再宽限 30 秒,:413;"相信进程的自我声明一次,但不相信第二次")→idle=0。aging band 整体低于 ELEVATED_INACTIVE(40) 由 assert 强制(数据结构层面的关注点分离);macOS 上三级 aging 全归零(桌面无"后台即死刑"压力)。
两条线:服务器线 OOM killer(badness=内存占用×oom_score,事后型——真 OOM 才动手,救系统不救体验)SRC;Android 线用户态 lmkd(PSI 驱动+oom_score_adj 1000 档,提前型)。Android 侧也有 band 思想(foreground/visible/cached 的 adj 梯度)与 aging 思想(cached 进程按 LRU 逐级加压)GEN。
| jetsam | Linux OOM | Android lmkd | |
|---|---|---|---|
| band 数量 | 210(细粒度) | badness 连续分 | adj 1000 档 |
| 缓冲带 | 10s→30s 两级 aging(语义精确) | 无(一击必杀) | 有(隐式,按 PSI 梯度) |
| 提前量 | 有(水位+band 双触发) | 无 | 有(PSI 分档) |
| 延迟 | 内核态直接收割(快) | 内核态(快但迟) | 用户态轮转(多一跳) |
四级(normal/warn/critical/critical-系统自身)+ 滞回设计:上升与下降阈值是两根不同的线(memorystatus_notify.md:60-73SRC)。设计围绕一个矛盾:内核水位连续波动,但 App 只认得懂离散级别且每级绑定明确动作(级别即催促强度)。kevent → 用户态 → didReceiveMemoryWarning 链路。
PSI(v4.20):把"因内存而停顿"计量成 some/full 两口径×avg10/60/300 三窗口的连续量,/proc/pressure/memory 可读可 poll 可配阈值触发GEN;旧 vmpressure(cgroup 三级 low/medium/critical)是 XNU 四级的同族GEN。
| 四级+滞回 | PSI | |
|---|---|---|
| 信号形态 | 离散 4 级(面向 App 行动) | 连续百分比(面向调度/告警) |
| 抗抖动 | 显式滞回(双阈值) | 窗口平均(天然惯性) |
| 消费方 | App(didReceiveMemoryWarning) | 运维/lmkd/调度器 |
| 测量对象 | 水位派生量 | 停顿时间(更接近用户体验) |
书里已有完整横向对比(此处引用+补足):zalloc 每类型一个 zone(292 个实测),隔离目的=安全缓解(四选项默认全开:SEQUESTER VA 隔离/SUBMAP 数据隔离/KEXT 圈禁/STRICT_IOKIT_FREE,zalloc.c:476-493SRC);SLUB 的 per-CPU 缓存为性能而生。2020 年代两者趋同:zalloc 补了 per-CPU 缓存(zpc),SLUB 补了随机化 freelist。终点一致:类型隔离 × per-CPU 私有 × 防利用加固。
内核侧书上已对齐。用户态:XNU 侧 libmalloc(nano/tensor 区域)+ scudo(安全优先,MTE 协同)——受 4GB VA 预算约束被迫缩设计(33 类×256MB≈8.4GB 主区装不下,书§12.3 实测EXP);Linux 侧 ptmalloc/tcmalloc/jemalloc 百家争鸣,47bit VA 随便铺GEN。
| zalloc+libmalloc/scudo | SLUB+ptmalloc 等 | |
|---|---|---|
| 内核隔离哲学 | 安全起点(UAF 不跨类型) | 性能起点(per-CPU 快路径) |
| 2020s 演化 | 补性能(zpc) | 补安全(freelist 随机化) |
| 用户态生态 | 官方统一(scudo 强制安全) | 可换装(tcmalloc/jemalloc 各有场景) |
| 约束 | 4GB VA 预算挤压设计EXP | 47bit VA 自由 |
双限额机制: JetsamProperties 给每进程两套 footprint 限额——active(前台宽)/inactive(后台严,显著更低),进程状态切换时内核写入 ledger(kern_memorystatus.c:1310-1326SRC)。entitlement 是突破默认限额的唯一正规通道:iOS 13 extended entitlement 可在 2GB 级设备抬到 1800MB(:418,"almost 2GB"不是修辞——给系统自身留不可压缩开销)。iOS 用生命周期管内存(前台/后台两套规则),macOS 用水位管内存——同一内核两种人格。
Android:ActivityManager 的 memoryClass/largeHeapClass 是框架层约定(App 申请大堆走 manifest),内核不知道这事;执行靠 lmkd 按 adj 梯度杀GEN。服务器:cgroup memory.max 是组限额硬约束(内核态执行,但无前台/后台语义)GEN。
| 双限额+entitlement | memoryClass / cgroup | |
|---|---|---|
| 执行层 | 内核态(限额写入 ledger,超限即查杀依据) | 框架层(Android)/内核态(cgroup) |
| 前后台语义 | 有(active/inactive 两套) | 无(adj 梯度近似) |
| 豁免通道 | entitlement(审核留痕) | largeHeap 标志/调整 memory.max |
| 语义精度 | 高(生命周期感知) | 低-中(静态) |
真机实测:普通进程 mmap 保留 4GB(1<<32)成功、8GB 失败——iOS 给进程的 VA 预算远小于桌面/LinuxEXP。这不是物理限制而是内核对 VM map 的主动约束,直接后果:scudo 的 8.4GB 主区设计装不下必须缩;稀疏数据结构按 4GB 设计。COW 用影子对象:fork 共享 VM object,子 map 标只读,写→缺页→逐页复制。实测 COW 粒度精确到 1 页(16KB):子进程写 1 页 RSS +1 页EXP。
47bit(256TB)VA 随便保留(MAP_NORESERVE 走起);COW 用 fork 的 dup_mmap 复制 VMA+PTE 置只读,写→缺页→复制页+rmap 重挂。语义完全等价,但实现路径不同:Linux fork 复制页表(页多则 fork 慢,故有 vfork/posix_spawn 优化);XNU 影子对象延迟复制(fork 快,读路径多一层间接)GEN。
| XNU | Linux | |
|---|---|---|
| VA 预算 | 4GB/进程(刻意约束) | 256TB/进程(自由) |
| COW 语义 | 影子对象,逐页复制(同) | dup_mmap+rmap,逐页复制(同) |
| fork 大空间成本 | 低(影子延迟) | 页表复制随页数涨 |
| 大地址空间程序 | 被迫缩设计 | 随便跑 |
XNU 观测链:sysctl(模拟器/macOS 可读,真机受限)、task_info/task_events_info 进程级 API、mach_zone_info 枚举 zone、Instruments(VM Tracker 的 Dirty/Compressed/Clean 分区与 §3 账户对齐)、JetsamEvent-*.ips(reason+30s 快照)、vmmap/leaks/heap。配套 repro-all.sh 一键复现全书实验+CHECKSUM 校验SRC。
/proc/meminfo、/proc/pid/smaps(逐 VMA RSS/PSS/匿名/swap 分解——精细度超过 vmmap)、/proc/pid/status、vmstat/slabtop、tracepoint+kprobe、eBPF(任意内核事件的可编程观测——给 bcc/bpftrace 写脚本实时聚合)、perf。GEN
| XNU 链 | Linux 链 | |
|---|---|---|
| 一致性 | 强(Instruments/账本/查杀同口径) | 弱(各工具口径要自己对齐) |
| 精细度 | 中(官方给多少看多少) | 高(smaps 逐 VMA、BPF 任意事件) |
| 可编程性 | 无(dtrace 受限) | eBPF 图灵完备 |
| 闭环 | 观测→调优→复现一体(repro 脚本) | 工具多但组合靠人 |
三总纲:① 水位类 vs 累计类是两种问题("事件已发生"vs"内存还占着");② free ⊇ speculative(host.c:815:free_count=vm_page_free_count+speculative_count——"Pages free"含投机页,严格水位分析要 free−speculative)SRC;③ 平台分支陷阱(iOS 的 wire_count 只含 vm_page_wire_count,active 含 per-CPU 本地队列,fb/anon 是 pageable 口径不含 wired)。22 字段逐一给了填充点/增减点/实测验证EXP。
/proc/meminfo:MemFree 不含 page cache(脏净都不含);MemAvailable 是内核估算的"再不换页的前提下还能给多少"(把可回收 cache、可换出 anon 按水位折算)——语义上等价于 XNU 的"free+speculative 的诚实版"。GEN
| vm_stat | /proc/meminfo | |
|---|---|---|
| "可用内存"字段 | Pages free(含投机页——陷阱) | MemAvailable(自解释估算) |
| 陷阱程度 | 高(free 语义被重载) | 低(MemFree/MemAvailable 分工明确) |
| 累计计数 | 11 个(faults/COW/zero-fill/压缩…) | 分散在 /proc/vmstat(pgfault/pgmajfault…) |
| 平台一致性 | iOS/macOS 字段口径有分支差异 | 跨架构一致 |
测量矩阵(已验证的实验直接复用)+ 压测脚本三个常见错误(累计/水位混读、采样自身污染、episode 边界误判)+ repro-all.sh 一键复现 + audit.py 14 项自动验算SRC。
stress-ng(内存/缺页/COW/swap 全模式压测)、/proc/sys/vm/drop_caches(清页缓存——注意 XNU 无直接等价,投机页要走保护期)、PSI 阈值压测、vm.min_free_kbytes 水位调参实验GEN。
| 系统 | 锚点 | 含义 |
|---|---|---|
| XNU | pmap.c:10419 / :10645 / :1068 / :10802 / :10704 / :10768-10780 / :10840 | REFFAULT 状态机全链(arm_force_fast_fault / SET_REFFAULT_PAGE / PP_ATTR_REFFAULT / WRITE_PTE_STRONG 唯一置 AF 处 / arm_clear_fast_fault / 收割 REFERENCED / arm_fast_fault_internal) |
| XNU | proc_reg.h:1492-1494 | FSC_ACCESS_FLAG_FAULT_L1/L2/L3 = 0x09/0x0A/0x0B(ARM64 ISA 强制故障) |
| XNU | vm_pageout.c:2950 / :2786-2788 / :2806-2810 / :227 | 老化 1:1 预算 / 50% 自限 / NOFLUSH 注释 / inactive 目标 1/2 |
| XNU | vm_resident.c:3173 / :3351 / :360-361 | vm_page_wait / free_min 唤醒 / 本地队列 250/500 |
| XNU | vm_compressor_algorithms.c:296 | metacompressor(WKdm→LZ4 混合,lz4_threshold=2048) |
| XNU | zalloc.c:476-493 + zalloc_internal.h:294-314 | 四安全选项默认全开(SEQUESTER/SUBMAP/KEXT/STRICT_IOKIT) |
| XNU | kern_memorystatus.c:412/:413/:418/:1310-1326 | aging 10s/30s / extended 1800MB / 双限额写 ledger |
| XNU | vm_purgeable(:821-1000) | purgeable 丢弃顺序遍历 |
| XNU | host.c:815 / :829-831 / :860-861 | free ⊇ speculative / wire_count 平台分支 / fb-anon pageable 口径 |
| Linux | rmap.c:850 / :767 / :788 | page_referenced / page_referenced_one / ptep_clear_flush_young_notify |
| Linux | vmscan.c:1017 / :2742-2766 / :3149 / :3262 / :1939 | referenced_ptes>1 升级 / 水位 / 直接回收节流 / 直接回收 / 批次 32 |
| Linux | vmscan.c:4589 / :1876(v6.1)+ mmzone.h:384-385 | lru_gen_look_around 正扫 / 驱逐仍走 try_to_unmap / MIN_LRU_BATCH=64 |
| Linux | arm64 pgtable.h:103/:222 + fault.c:759-761 | pte_young=PTE_AF / pte_mkold / access flag fault 接入 |
| Linux | kernel/freezer.c:62-90 | __refrigerator = schedule() 循环(只停 CPU 不搬内存) |
| Linux | mm/madvise.c:743+ | MADV_FREE 仅匿名 VMA(懒释放提示) |
| Linux | drivers/block/zram/zram_drv.c:210 / :1356 | page_same_filled 同页去重 |
| Linux | mm/workingset.c:40-132 | refault distance 设计注释(回收质量反馈) |
本页是 ios-memory-book(v2.50)第 1-17 章的对照阅读伴侣:书负责把 XNU 讲透,本页负责把每一章放进 Linux 镜像前重估一遍。与 ios-memory-book / ios-bigmem-test 同系列。生成于 2026-08-30。