基于源码逐行证据与同机实测(Apple M3:macOS 26.4 vs lima Linux VM)的深度对比
证据标签: [SRC] 源码行号可证 · [EXP-MAC] macOS 26.4 实测 · [EXP-LNX] Linux VM 实测 · [推导] 算术推导 · [知识] 通行结论(未逐行引用)
分析对象:XNU(xnu-7195.141.2 = iOS 14.8;xnu-11215.1.10 = iOS 18 世代;另参考 xnu main 分支)与 Linux(torvalds/linux v6.1 LTS,参考 5.10/6.12 存档及 Ubuntu 7.0 实测)。 实测环境(同机对照):Apple M3(8GB/8CPU/16KB 页,macOS 26.4 = Darwin 25.4,XNU-11215 世代)与其上运行的 lima/vz Linux 虚拟机(Ubuntu 7.0.0-34-generic aarch64,5GiB/4vCPU/4KB 页)。 证据标注:[SRC] = 本目录
src/存档源码行号可证;[EXP-MAC] = macOS 26.4 实测(empirical/zprint-macos-26.4.txt等);[EXP-LNX] = Linux 虚拟机实测;[推导] = 由源码常量做的算术;[知识] = 通行结论,未逐行引用。 版本纪律:所有行号引用同时指明文件与版本;跨版本行号必然漂移。
对比内核内存占用,第一个困难是口径:两个系统根本没有同名的"内核内存"计数器。
Linux 把内核占用拆成 /proc/meminfo 中若干显式字段,全部有源码出处 [SRC:linux-6.1/fs_proc_meminfo.c:L55-56 及各 show_val_kb 行]:
- Slab = NR_SLAB_RECLAIMABLE_B + NR_SLAB_UNRECLAIMABLE_B(对象缓存,含可回收部分 SReclaimable)
- KernelStack(每线程 16KB,按页记账进 lruvec/memcg)
- PageTables、Percpu、VmallocUsed、Buffers、CmaTotal 等
XNU 没有单一等价物,内核内存分散在四个桶里:
1. zalloc zone 堆(小对象,zprint 可见,macOS 26 实测 292 个 zone)[EXP-MAC]
2. 内核映射 kmem 分配(内核栈、kalloc 大块、IOKit wired),由 kmem tag 表述(VM_KERN_MEMORY_STACK、VM_KERN_COUNT_BOOT_STOLEN 等,tag 号定义见 [SRC:xnu-7195/vm_statistics.h:L672])[EXP-MAC]
3. pmap 页表与 per-pmap 结构(计入 boot-stolen 桶)
4. 压缩器(c_segment 缓冲与元数据,计入 VM_KERN_MEMORY_COMPRESSOR)
因此本文的做法是:能归一化的按单位比较(每线程、每 GB 内存、每进程、每对象),不能归一化的比较机制与系数(元数据比例、碎片率、安全税),并用结构分析弥补工作负载差异。
第二点是版本。本报告把 XNU 侧锚定在两个已验证版本:iOS 14.8(xnu-7195.141.2,行号与 test1 主书项目逐字节校验一致)与 iOS 18 世代(xnu-11215.1.10,与 macOS 26.4 实测同代);Linux 侧锚定 v6.1 LTS,演化讨论参考 5.10/6.12 存档与 Ubuntu 7.0 实测。行号引用一律给出文件与版本。
⚠️ 一个实测限制:无 root 权限时 macOS
zprint会把 zone 的 cur/max size 列置 0(安全遮蔽),但 elem size 与 cur inuse 是真实的 [EXP-MAC]。因此 XNU zone 占用以inuse × elem_size(活跃字节)计量,它是驻留的下界(不含 magazine 缓存、chunk 内空洞与外部位图)。Linux 侧Slab含全部已分配 slab 页。两侧口径差异在 §4.4 专门讨论。
同一颗 M3 芯片上:macOS 直跑(8GB 物理内存)与 lima/vz Linux 虚拟机(5GiB 内存切片)。
| 维度 | XNU (macOS 26.4, 8GB) | Linux (Ubuntu 7.0, 5GiB VM) | 备注 |
|---|---|---|---|
| 页大小 | 16KB [EXP-MAC: sysctl hw.pagesize] | 4KB [EXP-LNX: getconf] | 主流部署差异(Android 亦以 4K 为主) |
| 静态内核镜像(常驻) | 87.3MB(WIRED_STATIC_KERNELCACHE)[EXP-MAC] |
55.5MB(code 27.8+rodata 20.1+rwdata 6.3+bss 1.3),init 段 15.2MB 已释放后为 40.3MB [EXP-LNX: dmesg] | XNU 含全部桌面驱动 |
| 对象堆活跃字节 | 840.2 MiB(292 zones,Σ inuse×elem)[EXP-MAC] | 233.4 MiB(411 个有对象 cache 的 Σ num_objs×objsize)[EXP-LNX] | ⚠️ 工作负载完全不同,见 §2.2 |
| 其中可回收 | 几乎为 0(zone 设计上不可回收,§9) | 163.6MB SReclaimable(dentry/inode 等,占 Slab 65%)[EXP-LNX] |
弹性差异是结构性 |
| 内核栈 | 42.7MB(VM_KERN_MEMORY_STACK tag)[EXP-MAC] |
2.7MB + 0.7MB SCS(169 线程)[EXP-LNX] | 每线程两侧均为 16KB,见 §6 |
| 页表 | 未单列(混入 wired/boot-stolen) | 3.76MB(5GB/169 线程)[EXP-LNX] | XNU 16K 页 → 同覆盖页表量 ~1/4 |
| per-CPU 基础设施 | 无独立计数(zpercpu zone 很小,§4.3) | 6.72MB / 4 CPU = 1.68MB/CPU [EXP-LNX] | 量级差异 |
| 物理页元数据 | 21.7MB(474,236 页 × 48B)[EXP-MAC] | 80MB(1,310,720 页 × 64B,nr_memmap_boot_pages=20480)[EXP-LNX] |
归一化后差 ~6×,见 §7 |
| vmalloc/VA | kernel map VA 用量 832GB,zone map VA 152GB [EXP-MAC] | VmallocTotal 128TB,VmallocUsed 37MB [EXP-LNX] |
VA 无物理成本,仅作布局参考 |
| 压缩器 | tag 3.8MB 驻留 + compressor zone 族 80.4MB 元数据活跃 [EXP-MAC] | zram/zswap 未启用(Zswap: 0)[EXP-LNX] |
iOS 必开压缩器,Android 通常 zram |
Mac 是带 GUI 的桌面系统(5,464 个内核线程、873,374 条 VM map entry、413,126 个 vm_object);VM 是几乎空载的服务器(169 线程)。总量差异(840 vs 233 MiB)主要反映工作负载,不是内核效率。有意义的对比是单位成本:
| 单位成本 | XNU [EXP-MAC] | Linux [EXP-LNX] | 比值 |
|---|---|---|---|
| 每线程堆对象 | threads 2160B + threads_ro 56B ≈ 2.2KB | task_struct 8704B(每线程一个) | ~3.9× |
| 每线程内核栈 | 16KB(kern.stack_size=16384) | 16KB + SCS ~4KB ≈ 20KB | 1.25× |
| 每任务记账对象 | Per-task.ledger 1568B(~45 分账条目) | memcg objcg(按对象摊,非按任务) | 模型不同 |
| 每 GB RAM 页元数据 | 48B/16KB = 2.9MB/GB | 64B/4KB = 16.4MB/GB | 5.6× |
| 每条 VMA 元数据 | VM.map.entries 64B(+nodes 256B 按需) | vm_area_struct 192B(+anon_vma_chain 64B/anon_vma 104B) | ~2-3× |
这张表已经给出了结论的雏形:XNU 在"每单位"元数据上系统性地更省(页尺寸 + 结构更小),但把大量内存花在类型隔离与安全上;Linux 的单位成本更高,但换来了可回收性与通用性。以下逐维度展开。
Linux:Ubuntu 7.0 arm64 内核启动日志给出精确分解 [EXP-LNX: dmesg]:
Memory: 4271848K/5242880K available (27776K kernel code, 6298K rwdata,
20108K rodata, 15168K init, 1303K bss, 930176K reserved, 32768K cma-reserved)
Freeing unused kernel memory: 15168K
__init 段 15.2MB 用完即释放("Freeing unused kernel memory" 是 free_initmem 的输出)。XNU:macOS 26 的 kmem tag 表给出 VM_KERN_COUNT_WIRED_STATIC_KERNELCACHE = 89,296K ≈ 87.3MB [EXP-MAC];磁盘上 /System/Library/Kernels/kernel 19.0MB(通用)+ 每个 SoC 一份 kernel.release.tXXXX ≈ 16.6MB [EXP-MAC]。iOS 的 kernelcache 会显著小于 macOS(无桌面驱动栈),但结构相同:全部驱动 + IOKit + BSD + Mach 静态链接进单一镜像,无 __init 式整体释放、无运行时卸载。
VM_KERN_COUNT_BOOT_STOLEN = 609,856K(596MB) [EXP-MAC],含启动期偷取的页表、早期分配与"permanent" zone 存储。这部分在 Linux 里分散于 reserved/memblock,但 XNU 把它显式记账,是可观测性上的优势。这是两内核内存模型差异最大的一层。
XNU zalloc(iOS 14.8,xnu-7195.141.2):
- 每个 zone 是一种类型专属的大小类别,元素分配在 PAGE_SIZE 粒度(iOS arm64 = 16KB)的 chunk 上(chunk 语义见元数据注释)[SRC:xnu-7195/zalloc.c:L159-197]。
- 每 chunk 每页有 16 字节 zone_page_metadata:zone 索引、guard 标志、chunk 长度、以及使用位图(≤32 元素/页时内联 uint32_t zm_bitmap,更多时指向外部位图)[SRC:xnu-7195/zalloc.c:L159-197,static_assert(sizeof==16) 于 L197]。
- 元素本身无 per-element 头:空闲元素通过位图标记,per-CPU magazine(zone_magazine,深度 zc_mag_size)缓存热元素 [SRC:xnu-7195/zalloc.c:L227-240];magazine + recirculation depot + zone 三层缓存 [SRC:xnu-7195/zalloc.c:L259-274]。
- 最新分支的默认值:zc_mag_size = 8,每 CPU 每 zone 挂缓存上限 zc_pcpu_max = 128KB [SRC:xnu-main/zalloc.c:L613-621]。
- 回收:zone_gc()(L6580)只能回收完全空闲的 chunk,且仅对 collectable zone 生效 [SRC:xnu-7195/zalloc.c:L6562-6580]。
- zone map 预算:zone_phys_size_max() = 物理内存 1/4,LP64 再 ×1.5(=3/8 RAM),clamp 到 [CONFIG_ZONE_MAP_MIN, RAM/2, ZONE_MAP_MAX] [SRC:xnu-7195/zalloc.c:L8078-8107];平台上限 iOS 8GB / macOS 32GB(VA 预留 24GB/128GB)[SRC:xnu-main/zalloc.c:L164-170]。4GB iPhone 的 zone map 预算 ≈ 1.5GB。
Linux SLUB(v6.1):
- 对象紧密打包进 order-0..3 的 buddy 页(slub_max_order = PAGE_ALLOC_COSTLY_ORDER = 3,即 4KB 页内核上单 slab ≤ 32KB)[SRC:linux-6.1/mm_slub.c:L3885-3887]。
- 零 per-object 元数据:空闲链表指针就存在空闲对象体内(object + s->offset),CONFIG_SLAB_FREELIST_HARDENED 时用随机数与地址 XOR 编码 [SRC:linux-6.1/mm_slub.c:L349-423];s->offset 通常取对象中部,避免分配出去后残留可预测指针 [SRC:linux-6.1/mm_slub.c:L703]。
- slab 级元数据寄生在 struct page(空闲链表头、frozen 标志等),无独立 per-slab 头。
- 打包策略:优先保证 min_objects = 4 × (fls(nr_cpus) + 1) 个对象(8 核 = 20 个),浪费容忍度从 1/16 逐步放宽到 1/4 [SRC:linux-6.1/mm_slub.c:L3939-3985]。
- per-CPU:每个 cache 每 CPU 一份 kmem_cache_cpu(freelist/tid/slab/partial/local_lock,≈40B)[SRC:linux-6.1/include_linux_slub_def.h:struct kmem_cache_cpu],加 per-node partial 链。
给 24 字节的小对象(例如一个路径分量缓存):
kmalloc-32,4KB 页装 128 个对象,页内浪费 0;分摊的 struct page 元数据 64B/4096B = 1.56%(全 RAM 分摊)。实测本 VM 的 slab 利用率:Σ活跃对象字节 233.4MB / Slab 总量 253.3MB ≈ 92% [EXP-LNX,推导]。Percpu: 6720KB / 4 CPU [EXP-LNX],且 PCPU_MIN_UNIT_SIZE = 32KB(percpu 区最小单元)[SRC:linux-6.1/include_linux_percpu.h:L22];每个 slab cache 再挂 per-CPU kmem_cache_cpu。411 个有对象 cache × per-CPU 结构是隐性放大器:cache 数与 CPU 数相乘。z_stats。292 zone × 8 CPU × (magazine ~136B×2 + stats ~百字节) ≈ 数百 KB 量级 [推导,量级估计]。实测无法直接读出 per-CPU 总量——XNU 不提供 Percpu 等价字段。Slab 计入整页(含 slab 内空闲对象),且 SReclaimable 可在压力下收缩;XNU zone 的物理页全部 wired(不可换出),zone_gc 只能整 chunk 回收——XNU 的对象堆是"只涨不缩(除整块)"的刚性结构,靠 jetsam 砍任务间接回收(§13)。XNU iOS 14.8(embedded 64-bit 配置,27 个桶)[SRC:xnu-7195/kalloc.c:L199-236]:
16, 32, 48, 64, 80, 96, 128, 160, 192, 224, 256, 288, 368, 400, 512,
576, 768, 1024, 1152, 1280, 1664, 2048, 4096, 6144, 8192, 16384, 32768
小桶(≤512 与 1024)kzc_caching=true(启用 magazine 缓存)。这 27 桶 × 每个堆实例化一遍:iOS 14 有 default./data./kext. 三个堆 [SRC:xnu-7195/kalloc.c:L344-349,L354-400]。
iOS 18 世代(xnu-11215):桶表扩展为 29 类(arm64 增加 24576)并加上 kalloc_type 机制——每个 C 类型经 __kalloc_type 链接器节获得类型专属 zone,堆族变为 NONE/SHARED/DATA_BUFFERS/KT_VAR [SRC:xnu-11215/kalloc.c:L117,L168-173,L201,L424-431]。macOS 26 实测可见 data.kalloc.16 … data.kalloc.32768 与默认堆 kalloc.288/384/192 并存 [EXP-MAC]。
Linux v6.1:kmalloc 标准类 8/16/32…4096/8192(KMALLOC_MIN_SIZE = ARCH_DMA_MINALIGN)[SRC:linux-6.1/include_linux_slab.h:L226,L300-302],kmalloc_info[] 全表 [SRC:linux-6.1/mm_slab_common.c:L783];超过 KMALLOC_MAX_CACHE_SIZE 走页分配器/vmalloc。类型维度有 NR_KMALLOC_TYPES 组(NORMAL/DMA/CGROUP/RECLAIM)[SRC:linux-6.1/mm_slab_common.c:L673]。Ubuntu 7.0 实测出现 kmalloc-rnd-01 … rnd-15:每 size class ×15 个随机化实例(CONFIG_RANDOM_KMALLOC_CACHES 世代的加固),kmalloc 族 cache 总数 235 个 [EXP-LNX]——v6.1 源码中尚无此机制 [SRC:linux-6.1 全文无 rnd]。
ZONE_MAX_ALLOC_SIZE = 32KB(zone 上限)[SRC:xnu-7195/zalloc.c:L157];超过后 kalloc_large → kmem_alloc(KMA_ATOMIC|KMA_KOBJECT,kernel_map 上的 wired 分配;最新分支为带 guard 的 kmem_alloc_guard)[SRC:xnu-7195/kalloc.c:L746 起;xnu-main/kalloc.c:L2428]。macOS 实测 data.kalloc.24576 活跃 20.4MB、data.kalloc.32768 10.9MB [EXP-MAC]。VmallocUsed 37MB @5GB VM)[EXP-LNX];vmalloc 每 vmap_area 一条 VA 记录,物理页按需。| XNU kalloc | Linux kmalloc | |
|---|---|---|
| 目标 | 类型隔离(防 UAF 混用):kalloc_type 给每个 C 结构独立 zone;数据与指针分离堆 | 密度:少量全局类 + per-site 专用 cache(kmem_cache_create) |
| 桶数 | 27-29 类 × 多堆 × 无限类型 zone(iOS 16+) | ~13 类 × 4 类型组(×15 随机组,Ubuntu 7.0) |
| 实测堆活跃 | kalloc 族 175.8MB + data 族 165.2MB = 341MB(桌面负载)[EXP-MAC] | kmalloc+dma 族 45.5MB(251 cache,服务器负载)[EXP-LNX] |
| 后果 | 碎片与空闲 chunk 更多;类型混淆攻击面小 | 密度高;同 size 类内类型混杂(rnd-cache 缓解) |
kernel_stack_size = KERNEL_STACK_SIZE,macOS 26 实测 kern.stack_size = 16384 [EXP-MAC: sysctl]。分配走 kernel_memory_allocate(kernel_map, …, kernel_stack_size + 2×PAGE_SIZE, KMA_GUARD_FIRST|KMA_GUARD_LAST|KMA_KSTACK|KMA_KOBJECT|KMA_ZERO, VM_KERN_MEMORY_STACK)——双侧 guard 页,tag=8 [SRC:xnu-7195/stack.c:L121-123,L175-197;vm_statistics.h:L672]。空闲栈进 stack_free_list 缓存复用(不还 VM)[SRC:xnu-7195/stack.c:L161-171]。MIN_THREAD_SHIFT = 14 → THREAD_SIZE = 16KB,4KB 页内核上也是 16KB(VMAP_STACK 页粒度对齐)[SRC:linux-6.1/arm64_asm_memory.h:L83-99];VMAP_STACK 下按页记账 NR_KERNEL_STACK_KB [SRC:linux-6.1/kernel_fork.c:L488-504]。Ubuntu 7.0 另开 ShadowCallStack ~4KB/线程(ShadowCallStack: 704KB/169 线程 ≈ 4.2KB)[EXP-LNX]。结论:每线程 16KB 两侧完全一致;Linux 加 SCS 后 ~20KB。绝对量差异纯由线程数决定(5,464 vs 169)[EXP-MAC/EXP-LNX]。
macOS 26 实测(XNU 侧)[EXP-MAC]:
- threads = 2160B + threads_ro = 56B(读写分离的新设计)
- proc_task = 3640B(task+proc 合并分配)
- Per-task.ledger = 1568B(§12)
- 加 bank_task 160B、task_id_token 40B、ipc 簇(ipc.ports 144B×17 万)……
Linux 实测 [EXP-LNX]:
- task_struct = 8704B(每线程一个!)
- files_cache 704B、signal_cache 1152B、mm 相关见 §8
每线程堆对象 XNU ≈ 2.2KB vs Linux ≈ 8.7KB(3.9×);每进程 XNU ≈ 3.6+1.5+… ≈ 5.5KB vs Linux task_struct+mm_struct+…。Linux task_struct 的庞大来自其通用性(调度策略、cgroup、numa、安全键、追踪……全在一个结构里);XNU 把 task/thread 拆小并移出热字段(threads_ro)。
XNU struct vm_page(iOS 14.8 源码推导 + macOS 26 实测双重确认 ≈ 48B):
- 打包指针设计:vm_page_packed_t = uint32_t,队列链 vm_page_packed_queue_entry = {next,prev} = 8B,vm_page_object_t 也是 32 位 [SRC:xnu-7195/deep2_vm_page.h:L87-L99]。
- 字段求和:vmp_q_pageq(8) + vmp_listq(8) + vmp_backgroundq(8) + vmp_offset(8) + vmp_object(4) + wire:16 位域字(4) + vmp_next_m(4) + busy… 位域字(4) = 48B [SRC:xnu-7195/deep2_vm_page.h:L195-277,推导]。
- ARM 上数组内 vm_page 不存 ppnum:物理页号由数组下标线性推出(vm_first_phys_ppnum),只有"清扫回来的特殊页"才用带 ppnum 的变体(vm_page_with_ppnum,从 "vm pages" zone 分配)[SRC:xnu-7195/deep2_vm_page.h:L273-276,L409-413;vm_resident.c:L1668-1685]。
- "vm pages array" 是个只做报表的伪 zone("Nothing ever allocates or frees to this zone. It's just here for reporting purposes via zprint")[SRC:xnu-7195/vm_resident.c:L1642 及注释]——macOS 26 实测其 elem size = 48(正是 sizeof(struct vm_page)),inuse = 474,236 页 → 21.7MB [EXP-MAC]。
- 分配时机:启动时按 mem_sz / (PAGE_SIZE + sizeof(*vm_pages)) 自洽换算页数,pmap_steal_freeable_memory(npages × sizeof) 一次性偷出 [SRC:xnu-7195/vm_resident.c:L1390-1415]。
Linux struct page:
- 通用 64 位默认配置下 64B/页。实测闭环:VM present 页 = 589,824 + 720,896 = 1,310,720,×64B = 83,886,080B = 恰 20,480 个 4KB 页,与 /proc/vmstat 的 nr_memmap_boot_pages = 20480 精确相等 [EXP-LNX,推导]。
- vmemmap 是独立 VA 区(VMEMMAP_SIZE = (_PAGE_END(VA_BITS_MIN) - PAGE_OFFSET) >> VMEMMAP_SHIFT)[SRC:linux-6.1/arm64_asm_memory.h:L34]。
| 配置 | 每页元数据 | 占 RAM 比 | 4GB 设备 |
|---|---|---|---|
| XNU 16K 页 + 48B | 48/16384 | 0.29% | 12.6MB |
| Linux 4K 页 + 64B | 64/4096 | 1.56% | 64MB |
| Linux 16K 页 + 64B | 64/16384 | 0.39% | 16MB |
页尺寸贡献 4×,结构尺寸贡献 1.33×,合计 ~5.3×。 同样的杠杆作用于页表(16K 页 PTE 覆盖 4×地址)与 per-page 辅助结构。XNU 从 iPhone 起全线 16K 页;Linux 主流部署(Android/服务器)仍 4K 为主,16K 仅在部分新机型/服务器启用——这是部署策略差异而非纯技术差异,但客观上造成当前实际部署中 XNU 的元数据税更低。
两个内核对"进程地址空间"的建模完全不同,元数据成本结构随之分岔。
Linux:mm_struct(每进程)[SRC:linux-6.1/include_linux_mm_types.h:L512] → VMA 树(6.1 起默认 maple tree)每条映射一个 vm_area_struct(实测 192B [EXP-LNX],定义 [SRC:linux-6.1/include_linux_mm_types.h:L444])→ 匿名页走 rmap:每进程 anon_vma(104B)+ 每 VMA anon_vma_chain(64B)[EXP-LNX],页内 _mapcount 反查所有映射。文件页挂 inode 的 address_space xarray(radix_tree_node 584B,本 VM 活跃 13.1MB [EXP-LNX])。
XNU:每 task 一个 vm_map,区间用 vm_map_entry(7195 定义 [SRC:xnu-7195/deep2_vm_map.h:L255],~88B[推导];macOS 26 新设计拆成 VM.map.entries 64B + 按需 VM.map.nodes 256B [EXP-MAC])→ 每个映射源是一个 vm_object(256B,macOS 实测活跃 413,126 个 = 100.9MB [EXP-MAC]),COW 用 shadow chain(VM.map.copies 72B)。XNU 没有 rmap:回收按对象遍历队列,pmap 侧维护 PV 结构。
| XNU [EXP-MAC] | Linux [EXP-LNX] | |
|---|---|---|
| 区间条目 | 873,374 条 × 64B = 53.3MB(+83,957 nodes×256B=20.5MB) | 5,376 × 192B = 1.0MB(+anon_vma_chain 3,328×64B) |
| 映射源对象 | vm_object 413,126 × 256B = 100.9MB(每个匿名区一个对象) | address_space 内嵌 inode(文件);匿名无对象,rmap 链代替 |
| inode 侧 | APFS_INODES 424B × 119,201 = 48.2MB + vnodes 264B×120,054 + namecache 104B×128,527 | ext4_inode 1128B×65,279=70.2MB + inode_cache 616B + dentry 192B×142,128=26MB |
结构结论:XNU 为每个匿名区域付出一个 vm_object(256B),但省掉 rmap 与 per-VMA 链;Linux 的 VMA 更重(192B)+ anon_vma 族,但文件页缓存挂在全局 address_space 上(跨进程共享无需复制元数据),且有 dcache 这类"可牺牲缓存"(§9)。
PageTables 3.76MB @5GB/169 线程 [EXP-LNX];pgd 每进程一页(PGD_SIZE == PAGE_SIZE → __get_free_page)[SRC:linux-6.1/arm64_mm_pgd.c:L20-28]。KPTI(CONFIG_UNMAP_KERNEL_AT_EL0)需为每个用户 mm 维护剥离内核映射的用户侧 PGD,顶层页表近乎翻倍 [知识];Apple Silicon 不受 Meltdown 影响未启用。task_ledgers.page_table,phys_footprint 公式含 + page_table)[SRC:xnu-7195/task.c:L987-996,L1055]——内核向每个进程收取页表"内存税",直接影响 jetsam 排序。16K 页使同等映射量页表 ~1/4。Linux 的内核内存有相当部分是"弹性的":
- SReclaimable = 163.6MB(本 VM,占 Slab 65%)[EXP-LNX],主体是 dentry/inode 缓存:dentry_cache = KMEM_CACHE_USERCOPY(dentry, SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD|SLAB_ACCOUNT, …) [SRC:linux-6.1/fs_dcache.c:L3308]——同时可回收且memcg 记账。
- 注册 shrinker 的子系统在压力下按 LRU 收缩(zswap 也注册 shrinker [SRC:6.12 存档 mm_zswap.c:L125-128,L176])。
- page cache(Cached 1.9GB)整体可回收,MGLRU/传统 LRU 均有(存档:6.1 MGLRU lru_gen_look_around、5.10 传统 LRU 水位与直接回收——见 skill linux-vm-archive)。
XNU 的 zone 堆设计上不可回收:
- zone_gc 只回收全空 chunk 且需 collectable 标志 [SRC:xnu-7195/zalloc.c:L7391-7399];zone 物理页 wired。
- XNU 把"弹性"交给别的机制:purgeable 内存(token 状态机,用户态自愿牺牲,存档 vm_purgeable.c)、压缩器(把冷页压进 256KB segment)、以及最终手段 jetsam 砍进程(§13)。
- 文件缓存不存在独立 dentry/icache 大盘:namecache 12.8MB + vnodes 30.2MB(macOS 实测 [EXP-MAC]),文件页统一进 VM object 的页面队列(按对象记账到任务 ledger 的 external 项)。
净效果:Linux 内核占用里 ~2/3 的 slab + 全部 page cache 可在秒级让出;XNU 的 wired/zone 部分刚性,但 iOS 的封闭应用模型让"砍进程"成为可接受的全局回收器。一个用弹性换占用,一个用刚性换确定性。
XNU 压缩器(iOS 14.8):
- 组织:每 segment 一块 256KB 缓冲(C_SEG_BUFSIZE = 1024*256)[SRC:xnu-7195/vm_compressor.h:L46],C_SEG_MAX_PAGES = C_SEG_BUFSIZE/PAGE_SIZE(16K 页设备 = 16 页/段)[L47];段头 struct c_segment + 每压缩页一个 c_slot(定长数组起步,溢出转变长数组)[L192-247,L259];分配尺寸 sizeof(c_segment) + C_SEG_SLOT_VAR_ARRAY_MIN_LEN × sizeof(c_slot) [SRC:xnu-7195/vm_compressor.c:L818]。
- 每个被压缩页在 pager 侧留 4 字节 c_slot_mapping(22bit 段号 + 10bit 槽号)[SRC:xnu-7195/vm_compressor.h:L250-253]。
- 算法:WKdm/LZ4 混合(metacompressor,lz4_threshold 编译期 12288@16K 页——真机 sysctl 已闭环,见 skill 锚点)。
- macOS 26 实测:compressor_segment zone 184B × 219,071 段头活跃 + compressor_pager 64B × 318,102 + compressor_slots.{16,64,512} 族,合计 ~80.4MB 元数据活跃;数据缓冲 tag VM_KERN_MEMORY_COMPRESSOR 仅 3.8MB 驻留(swap 11GB 已换出,段缓冲释放)[EXP-MAC]。
- iOS 无 swap:压缩即换出,压缩器内存必然驻留且计入 kernel 侧。
Linux:zram/zswap 是可选块设备/前端,非内建:
- zram:每页 zram_table_entry 记录 handle/flags,same-filled 页去重(page_same_filled 直接存 8B 元素)[SRC:6.12 存档 zram_drv.c:L205,L1507],压缩数据进 zsmalloc 池 [L1546 zs_malloc]。
- zswap:pool + xarray 索引 + shrinker(写回 swap)[SRC:6.12 存档 mm_zswap.c:L125-128,L271]。
- Android 默认 zram 通常 = 50% RAM;服务器可完全不用。占用量是策略选择,不是内核结构必然。
两条路线都在加税,但税种不同。
XNU(类型隔离 + 区域隔离):
- ZSECURITY_OPTIONS:SEQUESTER(zone VA 隔离)、SUBMAP_USER_DATA(数据袋独立子映射)、SEQUESTER_KEXT_KALLOC、STRICT_IOKIT_FREE [SRC:xnu-7195/deep2_zalloc_internal.h:L294-310]。
- zone 子映射分桶:VM/RO/GEN(+/DATA)[SRC:xnu-11215/zalloc.c:L633-646];kalloc_type 类型专属 zone(§5)。
- 栈 guard 页、zone_page_metadata 防篡改、zleak。
- 税的形式:更多小 zone → 更多 magazine/chunk 空洞;只读 zone(threads_ro、proc_ro)需要 RO 子映射。
- 演化证据:ZC_ zone_create 安全标志 iOS 12→18 = 0→0→21→33→34→34(skill 六版本存档口径)——iOS 14 是安全税大涨的节点。
Linux(随机化 + 检测):
- freelist 指针 XOR+地址编码 [SRC:linux-6.1/mm_slub.c:L349-359];freelist 随机化(编译期)。
- CONFIG_RANDOM_KMALLOC_CACHES 世代:kmalloc 每 size class ×15 实例(Ubuntu 7.0 实测 235 个 kmalloc cache,活跃 45.5MB)[EXP-LNX]。
- ShadowCallStack:+4KB/线程(§6.1)。
- KASAN(调试构建):影子内存 = RAM/8,数百 MB 级*——生产不开 [知识];KFENCE 采样式,每样本 2 页。
- memcg:SLAB_ACCOUNT/objcg 按对象记账(struct obj_cgroup + per-memcg lruvec)[SRC:6.12 存档 mm_memcontrol.c:obj_cgroup 系列]。
- KPTI:用户 PGD 翻倍(§8.3)[知识]。
对比结论:XNU 的税是常态、结构性的(更多 zone、更多隔离 VA、RO 副本),Linux 的税是可配置的(rnd-caches/SCS/KASAN 按构建开关)。生产手机上两者税负都不小,但 XNU 税基更细(类型级),Linux 税基更粗(类级 × 随机化)。
XNU:ledger 体系(per-task 细粒度):
- 每 task 一个 ledger,~45 个分账条目(cpu_time、internal、internal_compressed、iokit_mapped、alternate_accounting、page_table、purgeable_、tagged_footprint(network/media/graphics/neural 四类 × footprint/nofootprint × compressed)……)[SRC:xnu-7195/task.c:L1018 起的 init_task_ledgers 全表];实测 Per-task.ledger zone = 1568B/task [EXP-MAC];ledger 条目结构 = flags/warn/limit/credit/debit + refill [SRC:xnu-7195/deep2_ledger.h:L73-79]。
- phys_footprint 公式*(jetsam 的排序依据):(internal - alternate_accounting) + (internal_compressed - alternate_accounting_compressed) + iokit_mapped + purgeable_nonvolatile(+_compressed) + page_table [SRC:xnu-7195/task.c:L987-996]。
- 观测:zprint(zone)、mach_task_info、ledger API、kmem tag 表(macOS:按 kmod/vm tag 的 wired 明细——本报告 [EXP-MAC] 的 wired 表即来自它)。
Linux:memcg + 全局计数器: - 全局:/proc/meminfo(字段计算源码 [SRC:linux-6.1/fs_proc_meminfo.c])、/proc/slabinfo、/sys/kernel/slab(本 VM 570 个 cache [EXP-LNX])、/proc/zoneinfo、/proc/vmstat。 - per-cgroup:memory.current 含内核内存(slab objcg 记账、KernelStack 按页入 lruvec [SRC:linux-6.1/kernel_fork.c:L494-496])。 - 没有 per-task 的"内核税"概念(task 的页表不记在任务账上,而在全局 PageTables)——与 XNU 把 page_table 计入 phys_footprint 形成鲜明对照。
memorystatus_available_pages 由 pageout 线程更新(memorystatus_pages_update(pages_avail))[SRC:xnu-7195/kern_memorystatus.c:L5066-5068];jetsam 按 band 表杀进程(JETSAM_PRIORITY 体系 [SRC:xnu-7195/kern_memorystatus.c:L100-104 表,L308 引用])。pageout 水位:iOS VM_PAGE_FREE_MIN = 10 + free/200、FREE_TARGET = 15 + free/100(macOS /80)[SRC:xnu-7195/vm_pageout.c:L226-245]。iOS 16+ 增加 sustained-pressure kill(六版本演化:0→0→0→3 处引用)。治理对象是进程,不是内核页。macOS 26 kmem tag 表(按驱动分列的 wired 物理内存)节选 [EXP-MAC]:
com.apple.iokit.IOGPUFamily 65744K (+API 7040K)
com.apple.iokit.IOSurface 45232K
com.apple.iokit.IOMobileGraphicsFamily 33984K (+DCP.DMA.disp0 29648K)
com.apple.driver.AppleAVD.DMA.avd 12032K
com.apple.driver.AppleA7IOP.DMA.dcp 32160K
com.apple.iokit.IOUSBHostFamily 6256K (+DMA 3+3MB)
...(合计:total 1950.86M of 3086.91M)
驱动+IOKit 的 wired 内存是 XNU 侧最大的单一类别(GPU/显示/USB/编解码器,数百 MB)。它们是驱动作者用 IOKit 显式 wired 的(IOMemoryDescriptor/IOBufferMemoryDescriptor 族,deep4 存档),内核只记账不治理。
Linux 侧等价物分散在:模块 text/data(本 VM 仅 6.6MB [EXP-LNX])、GEM/CMA(CmaTotal 32MB)、DMA buffer heaps(6.12 存档 dma-buf-heaps)。关键差异不是量级而是治理:Linux 驱动内存多走 buddy/CMA 可迁移页,XNU IOKit 默认 wired 常驻。iOS 上这部分被 Apple 的驱动团队严格审计(机器有限可穷举),Linux 上是长尾风险。
XNU(skill 六版本存档 + 本目录 11215): - zalloc 重构(8792+):zone 子映射(VM/RO/GEN/DATA)、z_pcpu_cache、depot [SRC:xnu-11215/zalloc.c:L2817-2818,L4714];vm_page 结构维持 48B 但拆出 SPTM/PPL 管理的 pmap 侧数据(macOS 26 实测 vm.pages.array=48B [EXP-MAC])。 - kalloc 类型化:iOS 14 的 3 堆(default/data/kext)[SRC:xnu-7195/kalloc.c:L344-349] → iOS 18 的 kalloc_type/__kalloc_type 节(NONE/SHARED/DATA_BUFFERS/KT_VAR)[SRC:xnu-11215/kalloc.c:L168-173,L424-431]。趋势:zone 数与隔离度上升,单位元数据成本下降,安全税上升。 - task/thread 拆分(threads+threads_ro、proc_task 合并):每线程堆对象从单一大结构变为 2.2KB 热区 + RO 冷区 [EXP-MAC]。 - freezer/sustained-pressure 演化:384→887 引用、iOS 16 起 sustained-pressure kill(skill 口径)。
Linux(5.10→7.0 存档+实测): - 5.10 传统 LRU → 6.1 MGLRU 默认(冷页识别成本下降);6.1 maple tree 取代 VMA rbtree(区间元数据降)。 - 5.11+ bpf 内存 memcg 记账;5.14+ kmalloc-rcl(可回收 kmalloc 类);6.6+ CONFIG_RANDOM_KMALLOC_CACHES(×15 随机 cache,实测已见)[EXP-LNX]。 - Ubuntu 7.0(实测):SCS、kmalloc-rnd、kmalloc-cg 已是默认——安全税在 Linux 上也在加速。 - folio(6.1+)降低大页路径的 per-page 操作成本。
共同趋势:两边都在"单位元数据更小 × 安全隔离更重"的对角线上移动;XNU 起点更省也更刚性,Linux 起点更胖但更弹性,差距在收敛而非发散。
以"4GB 手机类设备、相近功能负载"为假想基准,把两侧内核占用差距分解为可解释的成分:
| 成分 | 方向 | 量级(归一化) | 证据 |
|---|---|---|---|
| 页尺寸(16K vs 4K) | XNU 省 | 页表+页元数据 ~4× | §7.2、§8.3 |
| 页描述符(48B vs 64B)+ 线性 ppnum | XNU 省 | 元数据 1.33× + 省去 per-page ppnum | §7.1 |
| 每线程对象(2.2KB vs 8.7KB) | XNU 省 | 3.9×/线程 | §6.2 |
| VMA/对象模型 | 互有胜负 | XNU entry 更小但 vm_object 更多 | §8 |
| 类型隔离 zone 碎片 | XNU 费 | 更多小 zone/magazine/chunk 空洞 | §4、§5 |
| 刚性 wired(不可回收) | XNU 费(占用),Linux 省(弹性) | SReclaimable 65% vs ~0 | §9 |
| 压缩器内建 | XNU 费(必然驻留) | 元数据 80MB 级 @重度压缩 | §10 |
| 驱动 wired | XNU 费(桌面/手机全常驻) | 数百 MB tag | §14 |
| 模块化 | Linux 省(按需加载) | 本 VM 6.6MB | §3 |
| 安全税 | 双方都涨 | XNU 类型隔离;Linux rnd/SCS/KASAN | §11 |
| per-CPU 基础设施 | XNU 省 | 1.68MB/CPU vs 数百 KB | §4.3 |
一句话结论: 1. 微观(每单位):XNU 在页元数据、页表、线程对象上系统性更省(合计在 4GB 设备上 ~50-80MB 量级优势),根源是 16K 页 + 打包指针 + 封闭硬件下的结构精简。 2. 中观(堆结构):XNU 把省下的钱花在类型隔离与安全上(kalloc_type、zone 子映射、RO 副本),SLUB 则以密度优先——Linux 的 slab 利用率实测 92%,XNU 无法对称实测(可观测性差异本身即结论)。 3. 宏观(治理模型):Linux 内核"胖而有弹性"(可回收 slab/page cache/shrinker),XNU"省而刚性"(wired + zone 不可回收 + jetsam 兜底)。总占用差距更多由工作负载与驱动生态决定,单位成本差距由页尺寸与结构设计决定。 4. 对 iOS 而言:无 swap、有压缩器、jetsam 治理——这组约束使得 XNU 必须"省",也因此演化出 48B vm_page、2.2KB 线程对象这类极限压缩的设计;Linux 的服务器基因则允许它在 128TB vmalloc 空间里铺开更重的元数据,靠弹性消化。
kernel-mem-compare/
├── src/
│ ├── xnu-7195/ # xnu-7195.141.2 (iOS 14.8) ⚠️ deep2_*/deep3_* 前缀=深读校验版
│ │ ├── zalloc.c kalloc.c task.c vm_resident.c vm_pageout.c vm_compressor.c
│ │ ├── vm_fault.c vm_purgeable.c vm_map.c kern_memorystatus.c ledger.c stack.c
│ │ ├── vm_compressor.h proc.h startup.c machine_routines64.c thread.c pmap.c vm_unix.c
│ │ └── deep2_zalloc.h deep2_zalloc_internal.h deep2_kalloc.h deep2_ledger.h
│ │ deep2_vm_page.h deep2_vm_map.h deep3_vm_kern.c deep3_vm_init.c
│ ├── xnu-11215/ # xnu-11215.1.10 (iOS 18 世代)
│ │ └── zalloc.c kalloc.c zalloc_internal.h vm_page.h vm_resident.c thread.c
│ ├── xnu-main/ # xnu main 分支(macOS 26 代;仅 zalloc.c kalloc.c,用于
│ │ # zc_mag_size/zc_pcpu_max/ZONE_MAP_MAX 等仅在此版本核对的值)
│ └── linux-6.1/ # torvalds/linux v6.1(17 个文件,slub/slab_common/fork/meminfo/
│ # percpu/vmalloc/page_alloc/dcache/mm_types/sched/fs/pgd/mmu/…)
├── empirical/
│ ├── zprint-macos-26.4.txt # 396 行 zone 表 + kmem tag wired 表 + VA 表
│ ├── vm_stat-macos-26.4.txt sysctl-macos-26.4.txt
│ └── slabinfo-linux.txt # Ubuntu 7.0 aarch64 全量 slabinfo
├── NOTES-anchors.md # 全部行号锚点速查(含六版本演化数据)
└── 本报告
另引用 skill 存档(~/.dsh/skills/xnu-source-analysis/):linux-vm-archive/(5.10 rmap/vmscan、6.1 vmscan/mmzone/pgtable、6.12 zswap/zram/memcontrol/shrinker)、multi-version-archive/(iOS 12/13/14/16/17/18 四文件六版本)、session-archive/(7195 pmap/vm_fault/vm_pageout/vm_resident 校验版)。
复算入口:XNU zone 活跃字节 = Σ(inuse×elem)(zprint 第 1、7 列);Linux slab 活跃 = Σ(num_objs×objsize)(slabinfo 第 3、4 列);Linux memmap = present×64B(zoneinfo present 之和 × 4096B 换算页)。三组恒等式均可用 empirical/ 原始数据机械复核。