自定义应用 phasedmem 模拟相机的分阶段内存申请行为:
| 阶段 | 动作 | 模拟对象 |
|---|---|---|
| STAGE1 | 启动即申请 1GB = 512MB 匿名 zero-fill + 512MB mlock wired | IOSurface pageable 形态 + 驱动 wired 形态 |
| STAGE2 | 4 步 × 256MB(128 匿名 + 128 wired),步间隔 5s → 2GB | 相机管线扩容 |
| SETTLE | 静置 30 秒 | 稳态观察 |
| RELEASE | munlock 全部 wired → munmap 全部 | 用户态释放效率 |
采样:0.5s 粒度整机 vm_stat 全字段 + appscan 进程级 footprint 轨迹;工具自带毫秒级自计时。
| 字段 | 起点 | 终点 | Δ | 解读 |
|---|---|---|---|---|
| Pages free | 181,040 | 181,626 | +9MB | 净零——申请的都还回去了 |
| wired down | 44,780 | 44,835 | +0 | 1GB wired 全额摘除 |
| compressor stored | 81,809 | 86,190 | +68MB | 系统背景噪声(其它 App churn) |
前置:拉起 抖音 / 淘宝 / 京东 / 网易云音乐 / bilibili 五 App 至满载(free 跌至 2,310 页 = 36MB)。
| 供给来源 | 数量 | 机制 |
|---|---|---|
| 五 App 匿名页压缩 | −1,277MB | vm_pageout 同步压缩供货(场景期间 comp stored +1,048MB 内容,物理占用仅 +340MB → 压缩比 3.1:1) |
| file-backed 摘除 | −199MB | 代码页/缓存页直接丢弃(可从磁盘回读) |
| free 池消耗 | −21MB | 几乎没动——本来就只有 36MB |
| 击杀进程 | 0 个 | 五 App 全部存活,纯页级腾挪 |
| App | footprint | 内驻 | 压缩态 | 压缩占比 |
|---|---|---|---|---|
| 抖音 Aweme | 533.8MB | 1.3MB | 503.5MB | 94% |
| 淘宝 | 315.4MB | 0.8MB | 286.3MB | 91% |
| 京东 | 130.8MB | 2.3MB | 128.1MB | 98% |
| 网易云音乐 | 289.3MB | 157.8MB | 137.6MB | 48% |
| bilibili(番剧版) | 233.4MB | 151.5MB | 81.1MB | 35% |
注:网易云/bili 的内驻偏高,因实验结束后部分页已被访问解压回驻。
| 操作 | 场景一(free 2.83GB) | 场景二(free 36MB) | 退化幅度 |
|---|---|---|---|
| STAGE1 1GB(512a+512w) | 259ms(3.95GB/s) | 392ms(2.61GB/s) | ×1.52 |
| — touch 匿名 512MB | 137ms(3738MB/s) | 108ms(4732MB/s) | 更快* |
| — touch+wired 512MB | 231ms(wire 6491MB/s) | 167ms(wire 7962MB/s) | 更快* |
| grow 每步 256MB | 61~76ms(3.3~4.1GB/s) | 58~95ms(复测) | ≈持平 |
| STAGE2 全程(4×256MB) | 20.3s(含 5s 间隔) | 20.4s(含 5s 间隔) | 持平 |
| 释放 2048MB | 144ms | 143ms | 完全一致 |
| — munlock 1024MB | 95ms(10.3GB/s) | 95ms(10.3GB/s) | — |
| — munmap 2048MB | 49ms(41.8GB/s) | 48ms(41.8GB/s) | — |
* 场景二 touch 更快的原因:五 App 的页预先已在压缩器中"热身",zero-fill 直接消耗刚释放的 free 页,无需唤醒 pageout 扫描器。STAGE1 总时长变慢在于首秒同步压缩五 App 供货的等待。
应用侧:启动即 1GB(512MB 匿名 zero-fill surface 模拟 + 512MB wired 驱动模拟),增长到 2GB(各 1GB)。进程 footprint 精确匹配(1025.6MB → 2050.1MB,与申请量差 <0.1%)。
系统侧供给量:场景一全从 free 池划扣(free 2.83GB 充足,申请后仍剩 825MB);场景二 free 仅 36MB,系统从"压缩五 App 匿名页(−1,277MB)+ 摘 file-backed(−199MB)"腾出 1.5GB+ 供货,压缩器物理占用净增仅 +340MB(内容 1,048MB,压缩比 3.1:1)。内存类型上:匿名页走 zero-fill 分配(计 zero_fill 计数器),wired 走 mlock→vm_page_wire(不可回收、全记账、释放前纹丝不动)。
1GB 启动申请 259ms(空闲)/ 392ms(满载),综合 2.6~4.0GB/s;分解后 touch 匿名 3.7~4.7GB/s、wire 6.5~8.0GB/s。free 不足时的供给机制:同步压缩——mlock 触发的缺页路径上,vm_pageout 直接压缩五 App 的冷匿名页(无需先写盘、无需杀进程),每步 128MB wired 供给等待 <100ms。整个 2GB 满载过程 零击杀、五 App 全存活。
| 统计值 | 场景一变化 | 场景二变化 | 合理性判定 |
|---|---|---|---|
| Pages free | 181k→181k(净零) | 3k→91k(+1.4GB) | ✓ 一:释放即回吐;二:释放后五 App 未完全回驻,页暂存 free |
| wired down | +1,024MB 后归零 | +1,024MB 后归零 | ✓ mlock 全额记账、munlock 全额摘除,两场景分毫不差 |
| Anonymous | −116MB(净) | −1,277MB | ✓ 二:五 App 页被压后从 anon 转入 compressor 记账 |
| compressor stored | +68MB(噪声) | +1,048MB 内容/+340MB 物理 | ✓ 压缩比 3.1:1(WKdm 典型值) |
| File-backed | 持平 | −199MB | ✓ 系统摘冷代码页供货(可磁盘回读,最便宜来源) |
| 进程 footprint | 峰值 2050.1MB 全解压 | 峰值 2050.1MB(33% 压缩态) | ✓ 同为 2GB 但驻留形态不同——footprint 含压缩态 |
当 free 不足时,系统回收文件页的顺序是什么?本节用源码(xnu-7195.141.2)+ 两个专门实验回答:文件页由哪些成分组成、每种成分的回收优先级、以及地板保护在哪里生效。
| 成分 | 性质 | 回收成本 | 本机实测占比 |
|---|---|---|---|
| 投机页(speculative) | 预读簇读入但从未被访问(clustered read-ahead 超出请求范围的部分) | 零——直接丢弃,无 I/O | 基线 25,474 页(398MB)= fb 的 21%(压力后重建池约 3,000 页) |
| 冷文件页(inactive external) | 代码段(dylib 文本,来自 dyld 共享缓存)、mmap 的资源文件、字体文件、资产目录——曾被访问后老化 | 低——clean 直接丢弃;被再次访问时从磁盘 pagein 回来(约 200μs/页) | fb − spec ≈ 95,259 页(1.49GB)= 79% |
| 脏文件页(dirty) | mmap 可写映射被写过的页 | 高——必须先写回(pageout)变 clean 进 cleaned 队列才能回收 | 本实验工作负载下接近 0(两场景 Pageouts 仅 +73 页 / +1MB) |
| 活跃文件页(active) | 近期访问过的代码/资源(热路径) | 最高——需先老化降级到 inactive 才可回收 | 计入 active,不单列 |
free_count = vm_page_free_count + vm_page_speculative_count);"File-backed" = speculative + 活跃文件页 + 冷文件页 + cleaned。所以 speculative 是文件页的子集,也是"可用内存"的隐藏分量。pageout 扫描器 vm_pageout_scan 每轮从多个队列按优先级挑选牺牲页(xnu-7195.141.2 osfmk/vm/vm_pageout.c):
| 优先级 | 队列 | 源码锚点 | 语义 |
|---|---|---|---|
| ① 最优先 | cleaned Q | vm_pageout.c:2359-2365 | 脏页已经写回变 clean 的页——I/O 成本已付,直接摘除("Try for a clean-queue inactive page... Pick them up now that they are clean") |
| ② 次优先 | speculative aged Q | vm_pageout.c:2367-2378 | 预读但从未被触摸、且已度过 10×500ms=5 秒老化保护期的投机页——零 I/O 直接丢 |
| ③ | background Q | vm_pageout.c:2380-2419 | 后台模式(darkwake)下的 external 页(CONFIG_BACKGROUND_QUEUE) |
| ④ | inactive external Q | vm_pageout.c:2448+ | 普通冷文件页——但受 filecache_min 地板保护:低于地板时改 reactivate 放回(:2475-2490,计数器 vm_pageout_filecache_min_reactivated) |
| ⑤ 兜底 | inactive internal Q | vm_pageout.c:2505+ | 匿名页——送压缩器(不是丢弃) |
三个调节器(同一文件):
1.5GB 匿名压力在 0.5 秒内施加(工具 anonpress.c,ios-probe/cli),单采样窗口内的供给来源分解:
投机池在单个 0.5s 窗口内从 25,474 页被排空到 239 页(−398MB)——这正是源码里 speculative aged Q 排在 inactive external 之前的实测映照:零成本牺牲品先死。同时 fb 从 120,733 → 79,161(−650MB),其中 398MB 来自投机池、255MB 来自冷文件页。
逐级加压 6 × 512MB(每级稳 7 秒)观察 fb 的阶梯响应——用于区分"fb 不降"是地板保护还是"不再需要回收":
| 累计压力 | free | spec | file-backed | fb 较上级 | 压缩次数(累计Δ) | reactivated(Δ) | 供给主力 |
|---|---|---|---|---|---|---|---|
| 512MB | 92,633 | 3,156 | 82,627 | +2MB | 0 | 10 | free 池 |
| 1,024MB | 59,616 | 3,221 | 82,715 | +1MB | 0 | 54 | free 池 |
| 1,536MB | 26,606 | 3,261 | 82,795 | +1MB | 0 | 60 | free 池 |
| 2,048MB | 3,080 | 620 | 76,659 | −95MB | 3,464 | 1,090 | free 枯竭 → 投机池排空(3,261→620) |
| 2,560MB | 2,810 | 628 | 54,948 | −339MB | 17,038 | 11,652 | 冷文件页大额摘除 + 压缩器启动 |
| 3,072MB | 3,010 | 598 | 54,821 | −1MB | 50,010 | 11,741 | fb 钉死地板,全部靠压缩器 |
三个决定性信号同时出现:
vm_pageout_filecache_min_reactivated 路径(vm_pageout.c:2480-2490)的签名——扫描器选中了地板以下的文件页,但拒绝偷走、放回活跃队列,转头去压缩匿名页。| 实验 | file-backed 变化 | 解释(用本节顺序链) |
|---|---|---|
| 场景一(free 2.83GB 充足) | 持平(+71MB 噪声) | free 池全额支付,回收链根本未被唤醒——供给顺序第 0 级 |
| 场景二(五 App 满载 free 36MB) | −199MB | 首波冲击的冷文件页摘除(顺序链 ④ 级)+ 投机池排空;之后 fb 钉在 97k 页不再降——同为 filecache_min 地板 |
| 本节瞬时冲击(1.5GB/0.5s) | −650MB | 投机池全额(−398MB)+ 冷文件页(−255MB)单窗内依次支付 |
| 本节阶梯加压(3GB) | −432MB | 自由段(1.5GB 内)不动 → 投机池 → 冷文件页 → 856MB 硬地板后全靠压缩器 |
场景二的数据里有一个扎眼的数字:测试用例自身的匿名页有 33%(671MB / 2048MB)被压进了压缩器——footprint 轨迹显示 STAGE1 结束(启动后 0.4 秒!)就已压掉 65MB,2GB 满载时达到 671MB。内核怎么知道哪些页"冷"可以压、哪些页"热"要留着?wired 的 1GB 又为什么纹丝不动?本节用源码 + 专门的冷热判别实验回答。
XNU 不做 periodic 页表扫描,冷热识别分三层(xnu-7195.141.2):
| 层 | 动作 | 源码锚点 | 效果 |
|---|---|---|---|
| ① 缺页激活 | 每次缺页把页标记"热":vmp_reference = TRUE,入 active 队列;zero-fill/COW 缺页先进 per-CPU 本地队列(软限 250 页/CPU、硬限 500),免全局锁,攒够再批量并入全局 active 队列 | vm_fault.c:2999-3175(vm_fault_enqueue_page);vm_resident.c:4850-4934(vm_page_activate,:4927 置引用位);本地队列 vm_fault.c:3085-3130、vm_resident.c:360-362 | 新页一律视为热;被再次缺页的页自动回热 |
| ② 老化降级 | 当 inactive+speculative 低于目标(=(active+inactive+spec)/2)时,从 active 队列头取页:撤销 PTE 里的硬件 AF 访问位 + 清软件引用位,降入 inactive 队列 | vm_pageout.c:2769-2824(vm_page_balance_inactive);:2786-2788 目标公式;:2806-2810 撤 AF(故意不做 TLB flush——接受"远端 TLB 缓存漏掉一次引用"的误差,注释 :2797-2808) | 只按队列 FIFO 顺序降级,不预测——热的判定交给下一步的硬件反馈 |
| ③ 回收时二次机会 | pageout 扫描器从 inactive 取到候选页时,读软件引用位;若期间被访问过(见下),reactivate 送回 active(reactivations 计数器 +1);确认冷的才回收(匿名→压缩器,文件→丢弃) | vm_pageout.c:3359-3410;reactivate 上限 VM_PAGE_REACTIVATE_LIMIT = inactive_target/2(:273,注释"别的 CPU 可能在我们清位和遍历之间访问得更快,所以限制 reactivate 数量") | LRU 二次机会:访问过的页自动免责 |
tmplate |= ARM_PTE_AF; pa_set_bits(pa, PP_ATTR_REFERENCED))。VM 层读的 pmap_get_refmod(:10082)就是读这个软件位。这样即使页已驻留、访问不产生 VM 缺页,硬件也能替内核记账——这就是"热页无需自己申报,访问行为本身会留痕"的实现。两个同尺寸进程,唯一区别是触摸后的行为——一个沉睡(冷),一个每秒全量回访(热)——施加同样的外部压力(anonpress 2.5GB),对比压缩结果:
| 进程 | 行为 | footprint | 内驻(internal) | 被压缩 | 压缩占比 |
|---|---|---|---|---|---|
| cold 384MB | 触摸一次后 sleep | 385.2MB | 300.5MB | 84.4~99.0MB | 22~26% |
| hot 384MB | 触摸一次后每秒回访全部 24,576 页 | 385.3MB | 384.7MB | 0.0~0.1MB | 0.03% |
两个决定性信号:
实验中把热进程 kill -STOP(停止回访)并追加 1GB 压力,预期热页变冷被压——结果没有翻转(热页 55 秒后仍 0.0MB 压缩)。整机队列采样解释了原因:
机制解释(源码 :2790 的 while 条件):老化只在 inactive+speculative < (active+inactive+spec)/2 时进行。压力稳态下两队列各占一半、恰好达标,老化暂停——active 队列上的页(无论冷热)都安全。回收扫描器只从 inactive/speculative 取页,够不到 active 队列。实测铁证是 reactivated 计数器在 35 秒里纹丝不动(+0):没有降级就没有"降级后回访",二次机会循环整体停摆。
| 观测 | 机制解释 |
|---|---|
| STAGE1 结束(0.4s)就有 65MB 被压 | 触摸 512MB 用时 ~110ms,循环开头触摸的页到结束时已"100ms 未回访";free=36MB 的极限局里 pageout 扫描器全程全速运转(每轮 vm_page_balance_inactive(1),vm_pageout.c:2950),老化-压缩流水线跟得上触摸速度——头部页在工具还在跑的时候就被压了 |
| 2GB 满载时 671MB(33%)被压 | phasedmem 的触摸模式是 touch-once(每页写一次后永不回访)——正是 6.2 里 cold 探针的行为。被压掉的 671MB = 触摸较早、已老化降级的页;存活的 1,377.9MB = 触摸较新 + 6.3 的均衡保护(增长步之间的 5s 间隔让 active:inactive 达到 1:1 后老化暂停) |
| wired 1GB 全程零压缩 | vm_page_activate/deactivate 都有 VM_PAGE_WIRED 早退分支(vm_resident.c:4741-4747)——mlock 的页根本不进老化游戏,wire_count 是比"热"更强的豁免 |
| 场景一(free 2.83GB)同样 touch-once 却 0% 被压 | free 充足时 pageout 扫描器不运转(vm_pageout.c:207-213 注释:free 高于 target 时 pageout 不启动)——老化降级从未发生,touch-once 的页全部留在 active 队列。冷热识别本身依赖压力:没有回收需求就没有冷热判定 |
| 事件 | 队列迁移 | 记账变化 |
|---|---|---|
| 缺页(zero-fill/COW) | → 本地队列 → active | Translation faults/zero_fill/cow +1,vmp_reference=TRUE |
| 再次访问(含无 VM 缺页的 TLB 命中后 AF 收割) | inactive → active(reactivate) | reactivated +1 |
| 老化(inactive 不足半数时) | active 头 → inactive 尾 | 撤销 AF + 清引用位(无计数器,纯状态) |
| 回收判定:冷匿名页 | inactive → 压缩器 c_segment | compressions +1,anonymous −1,compressor stored +1 |
| 回收判定:冷 clean 文件页 | inactive → free | 无(见 §5 顺序链) |
| 回收判定:冷 dirty 文件页 | inactive → pageout 写回 → cleaned → 摘除 | pageouts +1 |
| 解压回访(访问已压缩页) | 压缩器 → active | decompressions +1 |
一句话:iOS 的冷热管理 = "缺页即热 + 硬件 AF 位留痕 + 老化降级 + 回收前二次机会"的 LRU 近似——不做页表周期扫描、不预测访问模式,把"谁热"的判定交给硬件访问位在两次检查之间的自然积累;再叠一层 inactive 目标均衡,让稳态下的页获得结构性保护。touch-once 工作负载(本实验)恰好是这个机制的最差情况:触摸即热的待遇只有一瞬,之后就按冷页处理。
核心思想同源——两级 LRU + 硬件访问位 + 二次机会(Linux 2.6 借鉴自 Solaris/BSD 系,XNU 继承 Mach/BSD 血统)——但实现哲学几乎每个环节都分叉。对照基线:XNU xnu-7195.141.2 vs Linux v5.10(含 v6.1 MGLRU 演化)。
| 维度 | XNU(iOS) | Linux |
|---|---|---|
| 引用位收割 | 故障时收割:老化撤 PTE 的 AF 位(故意不做 TLB flush,vm_pageout.c:2806-2810 注释明说接受远端 TLB 缓存漏记一次引用的误差)→ 后续访问触发 access-flag fault → 故障处理里写进 per-page 软件位 PP_ATTR_REFERENCED(pmap.c:10768-10780) | 回收时收割:page_referenced() 走反向映射(rmap)找到该页全部 PTE,逐个 ptep_clear_flush_young_notify 读+清 young 位(rmap.c:850、:767 page_referenced_one),带 TLB flush |
| 回收时单页成本 | O(1)——读一个软件位(vm_pageout.c:3359-3365) | O(映射数)——每次候选判定都要 rmap 遍历;多映射页(共享库)代价高 |
| 代价转嫁 | 摊到每次访问的故障路径(降级后首次访问付一次轻量故障,微秒级) | 摊到每次回收扫描(TLB flush + 遍历) |
| 均衡目标 | 固定 50%:(active+inactive+spec)×1/2(vm_pageout.c:208、:2786-2788);到达即老化暂停(本报告实测:reactivated 冻结 35s +0) | 容量自适应 inactive_ratio 查表(vmscan.c:2187-2219):1GB→3:1、10GB→10:1、100GB→31:1,int_sqrt(10×GB);6GB 设备 ≈ 7:1,inactive 仅 ~12.5% |
| 缓冲池含义 | 6GB 机型的二次机会窗口约为 Linux 的 4 倍——iOS 宁可少备牺牲品也要防误杀(压缩机兜底使回收错误便宜) | 内存越大观察窗口占比越小——swap 昂贵(毫秒级 I/O),宁可小窗口也要多备可回收页 |
| 冷页去向 | 内存压缩机(一等公民 pager):WKdm 774-793MB/s、压缩比 2-3:1、无 swapfile;回收错误 = 解压成本(微秒级)→ 老化-压缩敢全速跑(实测 50,010 次/秒级窗口) | swap 块设备 I/O(zram/zswap 为可选用途):错误 = 磁盘往返(毫秒级)→ 需要额外谨慎机制 |
| 工作集理论 | 无对应物——只有每次调用内的 reactivate 上限 VM_PAGE_REACTIVATE_LIMIT(vm_pageout.c:273) | workingset_refault:驱逐历史 shadow entry 算 refault 距离估计工作集大小(vmscan.c:903、:929 附近),动态调整扫描倾向 |
| 队列生态 | 特化队列群:speculative 投机池(10 bin × 500ms)、cleaned(预付写回插队)、throttled(无 swap 脏匿名限流)、background(darkwake)、per-CPU 本地队列(zero-fill/COW 免锁 250/500 页) | 每 lruvec 四条通用链(file/anon × active/inactive)+ memcg 隔离 + unevictable 链(mlock 页留在 LRU 上但跳过回收;XNU wired 页彻底出队 q_state=WIRED) |
| 批量优化 | per-CPU 本地队列蓄积激活(vm_fault.c:3085-3130) | pagevec 蓄积 LRU 入队(同目不同层) |
| 演化方向 | iOS 稳定迭代(speculative 池、cleaned 队列等持续微调) | v6.1 MGLRU(CONFIG_LRU_GEN,v6.1 vmscan 中 207 处引用):放弃两级链表改代数编号 + 按 mm 批量扫页表清 young 位(range 级 PTE 扫描代替逐页 rmap)——与 XNU"故障时收割、回收时零遍历"是同一经济压力下的两种收敛解 |
回看 phasedmem 的数据:wire 阶段 6,491~7,962 MB/s,而 anon touch 只有 3,013~4,732 MB/s——wired 快了近一倍。这个差异是真的,但"为什么"值得拆开。新工具 wireperf.c 用三种模式把"缺页/清零/wire 记账"三个成本正交分离(256MB,三轮中位):
| 模式 | 操作序列 | 耗时 | 吞吐 | 内核路径 |
|---|---|---|---|---|
| A. anon | mmap → touch | 50ms | 5,100 MB/s | vm_fault 全路径:分配页 + zero-fill(bzero_phys 清零 16KB) + pmap_enter + 入 active 队列 |
| B. premmap | mmap → touch → mlock | 50ms + 27ms | 9,500 MB/s(wire 段) | mlock 对已驻留页:vm_fault_wire_fast 快路径——只做摘队 + wire 记账 + PTE 改写,零物理清零 |
| C. wirefirst | mmap → mlock → touch | 47ms + 1ms | 5,450 MB/s | mlock 对 absent 页:wire_fast 查不到页 GIVE_UP → vm_fault_internal 全路径(分配+清零+wire+PTE 一次做完);之后 touch 零成本(全驻留,~270GB/s) |
源码(xnu-7195.141.2)里 mlock 的调用链是 vm_map_wire → vm_fault_wire(逐页循环)→ 每页先试 vm_fault_wire_fast(vm_fault.c:6045-6052)。快路径只有四步(vm_fault.c:6278-6460):
它跳过的东西(普通缺页 vm_fault 约 3,500 行 vs wire_fast 约 200 行):① zero-fill 的物理清零——普通匿名缺页必经 vm_fault_zero_page → vm_page_zero_fill → pmap_zero_page → bzero_phys(16KB)(vm_fault.c:806-864、vm_resident.c:5357-5369、pmap.c:11042-11047),每页 16KB 的内存带宽是匿名分配的最大单项成本;② 复杂映射链遍历(shadow/copy 对象链);③ zero-fill 页的 per-CPU 本地队列入队(vm_fault.c:3085-3130)。wire 对已驻留页是纯元数据操作——队列摘除 + 计数器 + PTE 属性位,全部 cache-line 级工作。
模式 C 证明:对未驻留页,mlock 退回全路径,分配 + 清零的钱一分不少(5,450 vs 5,100 MB/s,wire 记账只多 7%)。phasedmem 里 driver-wired 的计时(touch 152ms + wire 79ms)正是"模式 A + 模式 B"的串联:touch 阶段付清零的钱(和 anon 同价),wire 阶段只付记账的钱(所以单独看 6-8GB/s)。用户感知的"wired 快很多"来自把 wire 段吞吐与含清零的 anon 吞吐直接对比——同口径对比应该是:
| 同口径对比 | 数字 | 结论 |
|---|---|---|
| 纯 wire 记账(页已驻留)vs 纯匿名缺页 | 9,500 vs 5,100 MB/s | wire 快 1.86×(无清零成本) |
| 完整分配 1GB(含驻留形成)anon vs wired | 场景二复测:108ms vs 103+64ms(合计 167ms) | wired 反而慢 55%——多一道 mlock 通行 |
| 释放:munlock vs munmap | 10.3 GB/s vs 41.8 GB/s | munmap 更快(批量 TLB 击落 vs 逐页记账) |
zero-fill 的 bzero_phys 无法跳过——这是安全语义:匿名页必须保证不泄露上一个使用者的数据(内核页缓存、其它进程的堆)。wire_fast 敢跳过清零是因为它只处理已经驻留且内容已就绪的页(此前那次驻留已经付过清零的钱)。所以"wire 快"的本质是账单分期:把清零成本留在 touch 阶段(或此前的驻留),wire 阶段只剩元数据操作。真正的设计收益在另一个方向:wired 页从此退出所有队列游戏(不老化、不压缩、不进回收候选——vm_resident.c:4741-4747 的 VM_PAGE_WIRED 早退分支),这是拿运行时灵活性换确定性延迟。
uiopen 静默失败(设备老毛病),重启 SpringBoard 无效后整机重启——Taurine 半不受限越狱丢失,人工重新越狱后恢复(工具在 /var/mobile 重签 ent.plist 后继续)。tv.danmaku.bilianime,进程名 bili-universal。