目录核心概念数据结构struct resource树的组织方式两棵全局资源树树的操作 API真实系统中的 iomem 树/proc/iomem 的输出原理GPU VRAM 的 DEVICE_PRIVATE 占位devm_request_free_mem_region 完整流程设计哲学总结1. 核心概念Linux 内核用一棵**资源树resource tree**来管理整个系统的物理地址空间。每一段物理地址——无论是实际的 DRAM、PCI 设备的 MMIO BAR、ACPI 保留区域、还是 GPU VRAM 的虚拟占位——都是这棵树中的一个节点。这棵树的根节点就是全局变量iomem_resource定义在kernel/resource.cstructresourceiomem_resource{.namePCI mem,.start0,.end-1,// 即 0xFFFFFFFFFFFFFFFF覆盖整个地址空间.flagsIORESOURCE_MEM,};iomem_resource是物理地址空间 [0, 2^64) 的总管。系统启动时BIOS/UEFI 通过e820 表报告内存布局内核据此在树中插入System RAM、Reserved、ACPI Tables等节点。随后PCI 枚举阶段插入各设备的 BAR 资源驱动加载时插入设备特定区域。为什么需要树物理地址空间的分配存在天然的嵌套关系一段 PCI Bus 区间下面可以嵌套多个设备的 BAR一个设备 BAR 下面可以嵌套具体的功能子区域CXL Window 下面可以嵌套 System RAM扁平的链表无法表达这种层次关系所以必须用树。2. 数据结构struct resource/* include/linux/ioport.h */structresource{resource_size_tstart;// 起始物理地址inclusiveresource_size_tend;// 结束物理地址inclusiveconstchar*name;// 显示名称出现在 /proc/iomemunsignedlongflags;// 资源类型 状态标志unsignedlongdesc;// 资源描述符IORES_DESC_*structresource*parent;// 父节点外层包含区间structresource*sibling;// 同级兄弟同一父节点下的下一个区间structresource*child;// 第一个子节点被本区间包含的区间};2.1 关键字段解析字段说明start,end闭区间 [start, end]表示这段资源占用的物理地址范围name人类可读名称如 “System RAM”、“0000:03:00.0”、“xhci-hcd”flags位掩码主要标志见下表desc枚举值标识特殊资源类型parent/sibling/child构成左孩子-右兄弟表示的多叉树2.2 常用 flags标志值含义IORESOURCE_MEM0x200I/O 内存资源vs. I/O 端口IORESOURCE_IOIORESOURCE_BUSY0x80000000已被驱动声明占用request_region设置IORESOURCE_SYSRAM0x01000000系统 RAM 修饰符IORESOURCE_PREFETCH0x2000可预取PCI BAR 的特性IORESOURCE_EXCLUSIVE0x08000000用户态不可 mmap2.3 desc 描述符enum{IORES_DESC_NONE0,IORES_DESC_CRASH_KERNEL1,IORES_DESC_ACPI_TABLES2,IORES_DESC_ACPI_NV_STORAGE3,IORES_DESC_PERSISTENT_MEMORY4,IORES_DESC_PERSISTENT_MEMORY_LEGACY5,IORES_DESC_DEVICE_PRIVATE_MEMORY6,// ← GPU VRAM 虚拟占位IORES_DESC_RESERVED7,IORES_DESC_SOFT_RESERVED8,IORES_DESC_CXL9,};3. 树的组织方式资源树使用经典的**左孩子-右兄弟left-child right-sibling**表示法iomem_resource (root: 0x0 - 0xFFFFFFFFFFFFFFFF) ├── child ──→ System RAM [0x1000-0x9FFFF] │ sibling ──→ Reserved [0xA0000-0xFFFFF] │ child ──→ PCI Bus 0000:00 [0xA0000-0xDFFFF] │ sibling ──→ System ROM [0xF0000-0xFFFFF] │ sibling ──→ System RAM [0x100000-...] │ sibling ──→ ...3.1 核心不变量子区间完全包含在父区间内child.start parent.start child.end parent.end同级兄弟不重叠且按地址排序sib[i].end sib[i1].start同级间存在间隙是允许的间隙 未分配/空闲区域3.2 嵌套示例一个 PCI 总线区间 [0x80000000, 0xDFFFFFFF] 下嵌套子总线和设备 BARPCI Bus 0000:00 [80000000-dfffffff] ├── PCI Bus 0000:0e [df000000-df5fffff] │ ├── 0000:0e:00.0 [df000000-df1fffff] ← 设备 BAR │ ├── 0000:0e:00.4 [df200000-df2fffff] │ │ └── xhci-hcd [df200000-df2fffff] ← 驱动声明的子区域 │ └── ... ├── PCI Bus 0000:01 [df600000-df9fffff] │ └── PCI Bus 0000:02 → PCI Bus 0000:03 │ └── 0000:03:00.0 [df700000-df7fffff] ← GPU BAR └── ...4. 两棵全局资源树Linux 维护两棵独立的全局资源树树根节点管理对象用户态文件iomem_resource[0, 2^64)物理地址空间RAM、MMIO、device-private 等/proc/iomemioport_resource[0, IO_SPACE_LIMIT]I/O 端口空间x86 legacy I/O ports/proc/ioports对于 GPU VRAM 和现代设备我们只关心iomem_resource。5. 树的操作 API所有操作都由全局resource_lock读写锁保护。5.1 请求占位操作// 在 parent 下请求 [start, startn-1] 区间structresource*__request_region(structresource*parent,resource_size_tstart,resource_size_tn,constchar*name,intflags);核心算法__request_resourcestaticstructresource*__request_resource(structresource*root,structresource*new){resource_size_tstartnew-start;resource_size_tendnew-end;structresource*tmp,**p;// 边界检查必须在 parent 范围内if(endstart)returnroot;if(startroot-start)returnroot;if(endroot-end)returnroot;proot-child;for(;;){tmp*p;if(!tmp||tmp-startend){// 找到插入点tmp 为空或 tmp 在 new 之后new-siblingtmp;*pnew;new-parentroot;returnNULL;// 成功无冲突}ptmp-sibling;if(tmp-endstart)continue;// tmp 在 new 之前继续returntmp;// 冲突返回冲突的资源}}关键逻辑遍历兄弟链表寻找一个空隙能容纳新资源。如果某个已有资源的区间与新资源重叠返回冲突资源。5.2 插入操作允许嵌套structresource*insert_resource_conflict(structresource*parent,structresource*new);与request不同insert允许新资源包裹已有资源——已有资源会变成新资源的子节点。这用于表达如 “CXL Window 包含 System RAM” 的嵌套关系。__insert_resource的算法先尝试__request_resource如果成功则直接插入如果失败且冲突资源完全被新资源包含则将冲突资源及其相邻兄弟收编为新资源的子节点如果存在部分重叠冲突资源部分超出新资源范围返回错误5.3 释放操作void__release_region(structresource*parent,resource_size_tstart,resource_size_tn);从树中移除匹配的节点。如果被移除节点有子节点子节点上移到被移除节点的父节点下。5.4 冲突检测staticint__region_intersects(structresource*parent,resource_size_tstart,size_tsize,unsignedlongflags,unsignedlongdesc);// 返回值REGION_DISJOINT / REGION_INTERSECTS / REGION_MIXED遍历 parent 的子树判断 [start, startsize) 是否与任何已有资源相交。这是devm_request_free_mem_region寻找空闲区间的核心判断依据。6. 真实系统中的 iomem 树以下是一台配备 AMD GPU (0000:03:00.0) 的真实系统的/proc/iomem输出已精简注释# 低端内存BIOS/UEFI 报告的 e820 布局 00001000-0009ffff : System RAM # 640KB 常规内存 000a0000-000fffff : Reserved # VGA 兼容区 ROM 00100000-099fefff : System RAM # 扩展内存起始 # 主内存区 0b021000-64fa2fff : System RAM # 约 1.4GB 65154000-65765fff : System RAM 65767000-6b0a9fff : System RAM # ACPI 和固件保留 6b0aa000-718a6fff : Reserved 718a7000-71a46fff : ACPI Tables 71a47000-73a46fff : ACPI Non-volatile Storage # 32位 PCI MMIO 窗口 [2GB-3.5GB] 80000000-dfffffff : PCI Bus 0000:00 df600000-df9fffff : PCI Bus 0000:01 df600000-df8fffff : PCI Bus 0000:02 df600000-df8fffff : PCI Bus 0000:03 df700000-df7fffff : 0000:03:00.0 # GPU 32位 BAR # PCI ECAM (配置空间 MMIO) e0000000-efffffff : PCI ECAM 0000 [bus 00-ff] # 4GB 以上高端物理内存 100000000-c5dd7ffff : System RAM # 约 47GB 主内存 23e800000-23fa0d00f : Kernel code 23fc00000-2402effff : Kernel rodata 240400000-24084b53f : Kernel data # 64位 PCI MMIO 窗口 c80000000-fcffffffff : PCI Bus 0000:00 f800000000-fc0fffffff : PCI Bus 0000:01 # GPU 64位 BAR 链 f800000000-fc0fffffff : PCI Bus 0000:02 f800000000-fc0fffffff : PCI Bus 0000:03 f800000000-fbffffffff : 0000:03:00.0 # GPU VRAM BAR (16GB) fc00000000-fc0fffffff : 0000:03:00.0 # GPU doorbell BAR # GPU DEVICE_PRIVATE 虚拟占位 afc00000000-affffffffff : 0000:03:00.0 # ZONE_DEVICE 占位 (~2GB)6.1 地址空间分布示意物理地址 0 4GB ~48GB ~64GB ├────────────────────┼──────────────────────┼──────────────────┤ │ 低端内存 32位 MMIO │ System RAM │ 64位 PCI MMIO │ │ (e820 PCI) │ (~47GB) │ GPU BAR 等 │ └────────────────────┴──────────────────────┴──────────────────┘ ~700GB │ ┌───────┴───────┐ │ 0000:03:00.0 │ │ DEVICE_PRIVATE│ │ ~2GB 占位 │ └───────────────┘7. /proc/iomem 的输出原理/proc/iomem的实现在kernel/resource.cstaticint__initioresources_init(void){proc_create_seq_data(iomem,0,NULL,resource_op,iomem_resource);return0;}输出逻辑r_show函数staticintr_show(structseq_file*m,void*v){structresource*rootpde_data(file_inode(m-file));structresource*rv,*p;intdepth;// 计算缩进深度从当前节点到根的层数for(depth0,pr;depthMAX_IORES_LEVEL;depth,pp-parent)if(p-parentroot)break;// 非特权用户看到的地址为 0if(file_ns_capable(m-file,init_user_ns,CAP_SYS_ADMIN)){startr-start;endr-end;}else{startend0;}seq_printf(m,%*s%0*llx-%0*llx : %s\n,depth*2,,// 每层缩进2空格width,start,width,end,r-name);}遍历方式是前序遍历next_resource先深度后兄弟所以输出天然反映了树的嵌套结构用缩进表示父子关系。安全提示非 root 用户读/proc/iomem只能看到名称地址全为 0。这是为了防止 KASLR 等信息泄露。8. GPU VRAM 的 DEVICE_PRIVATE 占位8.1 问题为什么需要占位Linux 的migrate_vmaAPI 要求每一页设备内存都有对应的struct page。而struct page由内存模型sparsemem管理必须绑定到一个物理地址范围。但对于 discrete GPUVRAM 不在 CPU 物理地址空间——CPU 无法直接访问这些地址。所以需要在iomem_resource树中虚拟地占用一段地址仅用于给struct page一个 PFN页帧号让pfn_to_page()/page_to_pfn()能工作标记为MEMORY_DEVICE_PRIVATECPU 访问时触发 fault → 回迁到 RAM这段地址不对应任何真实的可访问硬件纯粹是簿记用途。8.2 与 Coherent 设备的区别特性MEMORY_DEVICE_PRIVATEMEMORY_DEVICE_COHERENTCPU 可直接访问❌ 不可以✅ 通过 aperture地址来源虚拟占位devm_request_free_mem_regionGPU aperture BAR 的真实物理地址典型场景Discrete GPUPCIeXGMI 连接的 APU/GPUCPU 访问时触发migrate_to_ramfault直接通过 CXL/XGMI 互联在amdgpu_svm_migration_init中的分支if(adev-gmc.xgmi.connected_to_cpu){// Coherent使用 GPU 的真实 aperture 地址pgmap-range.startadev-gmc.aper_base;pgmap-range.endadev-gmc.aper_baseadev-gmc.aper_size-1;pgmap-typeMEMORY_DEVICE_COHERENT;}else{// Private在 iomem 树中找空位做虚拟占位resdevm_request_free_mem_region(adev-dev,iomem_resource,size);pgmap-range.startres-start;pgmap-range.endres-end;pgmap-typeMEMORY_DEVICE_PRIVATE;}9. devm_request_free_mem_region 完整流程9.1 调用链devm_request_free_mem_region(dev, iomem_resource, size) └── get_free_mem_region(dev, baseiomem_resource, size, align, name, desc, flags) │ ├── 1. 对齐 size 到 GFR_DEFAULT_ALIGN │ x86_64: align 2^27 128MB (sparsemem section) │ ├── 2. 分配 struct resource devres 追踪结构 │ ├── 3. 从高地址向低地址扫描 ──────────────────────────┐ │ for (addr DIRECT_MAP_PHYSMEM_END - size; │ │ addr base-start; │ │ addr - size) │ │ │ │ ┌─ 4. 检查冲突 ────────────────────────────┐ │ │ │ __region_intersects(base, addr, size) │ │ │ │ 遍历 iomem_resource 的子节点检查 │ │ │ │ [addr, addrsize) 是否与任何已有资源 │ │ │ │ (System RAM, PCI BAR, Reserved...) 重叠 │ │ │ │ │ │ │ │ REGION_DISJOINT → 可用 │ │ │ │ REGION_INTERSECTS/MIXED → 跳过 │ │ │ └─────────────────────────────────────────┘ │ │ │ ├── 5. 占位注册 ────────────────────────────────────┘ │ __request_region_locked(res, iomem_resource, │ addr, size, name, 0) │ → 将 res 作为 iomem_resource 的子节点插入 │ → 设置 IORESOURCE_BUSY 标志 │ ├── 6. 设置描述符 │ res-desc IORES_DESC_DEVICE_PRIVATE_MEMORY │ ├── 7. 撤销已有映射 │ revoke_iomem(res) │ → 确保 /dev/mem 等不会映射到这段地址 │ └── 8. 注册 devres 回调 devres_add(dev, dr) → 设备移除时自动调用 devm_region_release() → 从 iomem_resource 树中移除占位9.2 对齐到 128MB 的原因x86_64 的 sparsemem 使用SECTION_SIZE_BITS 27即每个 section 128MB// arch/x86/include/asm/sparsemem.h#defineSECTION_SIZE_BITS27// 2^27 128MB// kernel/resource.c#defineGFR_DEFAULT_ALIGN(1ULPA_SECTION_SHIFT)// 1 27 128MBstruct page数组是按 section 粒度通过vmemmap分配的。如果占位的物理地址不对齐到section 边界devm_memremap_pages在创建struct page数组时会跨越 section 边界导致与其他 section 的struct page冲突。9.3 搜索方向为什么从高往低unsignedlongflagsGFR_DESCENDING|GFR_REQUEST_REGION;从高地址往低搜的原因低端地址密集System RAM、PCI 32 位 MMIO、ACPI 表等都集中在低 64GB 内高端地址空旷x86_64 物理地址空间高达 2^46 (64TB) 或 2^52 (4PB)高于实际 RAM 和 PCI 窗口的区域几乎全是空的避免碎片化不同 GPU 的占位从顶部向下排列不会与未来可能插入的 RAM 冲突9.4 搜索上界 DIRECT_MAP_PHYSMEM_END// arch/x86/include/asm/pgtable_64_types.h#defineDIRECT_MAP_PHYSMEM_ENDdirect_map_physmem_end// 4级页表: 2^46 64TB 5级页表: 2^52 4PB这是 CPU 直接映射区__va/__pa能覆盖的最大物理地址。ZONE_DEVICE 的struct page虽然不需要真实的物理访问但 PFN 必须在这个范围内否则pfn_to_page()的 vmemmap 映射会越界。9.5 devres 生命周期管理devm_前缀表示 device-managed resourcestructregion_devres{structresource*parent;// iomem_resourceresource_size_tstart;// 占位起始地址resource_size_tn;// 大小};staticvoiddevm_region_release(structdevice*dev,void*res){structregion_devres*thisres;__release_region(this-parent,this-start,this-n);}当 GPU 驱动 unbindrmmod amdgpu或设备热拔出时device_release_driver() → devres_release_all() → devm_region_release() // 自动从 iomem_resource 树中移除占位无需手动清理避免资源泄漏。10. 设计哲学总结10.1 统一抽象iomem_resource树将所有种类的物理地址使用者放在同一棵树中管理物理地址使用者 在树中的表现 ───────────── ──────────── 真实 DRAM System RAM (IORESOURCE_SYSTEM_RAM) PCI BAR MMIO PCI Bus / 设备名 (IORESOURCE_MEM) ACPI 保留 Reserved / ACPI Tables GPU VRAM 虚拟占位 设备名 (IORES_DESC_DEVICE_PRIVATE_MEMORY) PMEM 持久内存 Persistent Memory (IORES_DESC_PERSISTENT_MEMORY) CXL 扩展内存 CXL (IORES_DESC_CXL)这种统一管理确保了任何两个使用者不会占用相同的物理地址——这正是资源树存在的根本目的。10.2 嵌套表达树结构天然表达了 PCI 总线层次PCI Bus 0000:00 ⊃ PCI Bus 0000:01 ⊃ PCI Bus 0000:02 ⊃ 0000:03:00.0 ⊃ xhci-hcdinsert_resourceAPI 允许后插入的大区间收编已有的小区间为子节点比request_resource更灵活。10.3 并发安全所有树操作都由resource_lock读写锁保护/proc/iomem读取read_lockrequest_region/insert_resource/release_regionwrite_lock10.4 与内存子系统的桥梁iomem_resource是物理地址空间的地图而devm_memremap_pages是将这张地图上的一段区域与内核内存子系统struct page、sparsemem、ZONE_DEVICE连接起来的桥梁iomem_resource 树 内存子系统 ┌──────────────┐ ┌─────────────────┐ │ 占位区间 │ ──pgmap────→ │ devm_memremap_ │ │ [start,end] │ │ pages() │ │ DEVICE_ │ │ ↓ │ │ PRIVATE │ │ ZONE_DEVICE │ └──────────────┘ │ struct page[] │ │ ↓ │ │ folio-pgmap │ │ → dev_pagemap │ │ → drm_pagemap │ │ → migrate_vma │ └─────────────────┘这就是 GPU 驱动能够在不拥有真实物理内存的情况下参与 Linux 统一内存管理struct page、migrate_vma、mmu_notifier的底层基础设施。
Linux iomem_resource 树:物理地址空间的统一管理
目录核心概念数据结构struct resource树的组织方式两棵全局资源树树的操作 API真实系统中的 iomem 树/proc/iomem 的输出原理GPU VRAM 的 DEVICE_PRIVATE 占位devm_request_free_mem_region 完整流程设计哲学总结1. 核心概念Linux 内核用一棵**资源树resource tree**来管理整个系统的物理地址空间。每一段物理地址——无论是实际的 DRAM、PCI 设备的 MMIO BAR、ACPI 保留区域、还是 GPU VRAM 的虚拟占位——都是这棵树中的一个节点。这棵树的根节点就是全局变量iomem_resource定义在kernel/resource.cstructresourceiomem_resource{.namePCI mem,.start0,.end-1,// 即 0xFFFFFFFFFFFFFFFF覆盖整个地址空间.flagsIORESOURCE_MEM,};iomem_resource是物理地址空间 [0, 2^64) 的总管。系统启动时BIOS/UEFI 通过e820 表报告内存布局内核据此在树中插入System RAM、Reserved、ACPI Tables等节点。随后PCI 枚举阶段插入各设备的 BAR 资源驱动加载时插入设备特定区域。为什么需要树物理地址空间的分配存在天然的嵌套关系一段 PCI Bus 区间下面可以嵌套多个设备的 BAR一个设备 BAR 下面可以嵌套具体的功能子区域CXL Window 下面可以嵌套 System RAM扁平的链表无法表达这种层次关系所以必须用树。2. 数据结构struct resource/* include/linux/ioport.h */structresource{resource_size_tstart;// 起始物理地址inclusiveresource_size_tend;// 结束物理地址inclusiveconstchar*name;// 显示名称出现在 /proc/iomemunsignedlongflags;// 资源类型 状态标志unsignedlongdesc;// 资源描述符IORES_DESC_*structresource*parent;// 父节点外层包含区间structresource*sibling;// 同级兄弟同一父节点下的下一个区间structresource*child;// 第一个子节点被本区间包含的区间};2.1 关键字段解析字段说明start,end闭区间 [start, end]表示这段资源占用的物理地址范围name人类可读名称如 “System RAM”、“0000:03:00.0”、“xhci-hcd”flags位掩码主要标志见下表desc枚举值标识特殊资源类型parent/sibling/child构成左孩子-右兄弟表示的多叉树2.2 常用 flags标志值含义IORESOURCE_MEM0x200I/O 内存资源vs. I/O 端口IORESOURCE_IOIORESOURCE_BUSY0x80000000已被驱动声明占用request_region设置IORESOURCE_SYSRAM0x01000000系统 RAM 修饰符IORESOURCE_PREFETCH0x2000可预取PCI BAR 的特性IORESOURCE_EXCLUSIVE0x08000000用户态不可 mmap2.3 desc 描述符enum{IORES_DESC_NONE0,IORES_DESC_CRASH_KERNEL1,IORES_DESC_ACPI_TABLES2,IORES_DESC_ACPI_NV_STORAGE3,IORES_DESC_PERSISTENT_MEMORY4,IORES_DESC_PERSISTENT_MEMORY_LEGACY5,IORES_DESC_DEVICE_PRIVATE_MEMORY6,// ← GPU VRAM 虚拟占位IORES_DESC_RESERVED7,IORES_DESC_SOFT_RESERVED8,IORES_DESC_CXL9,};3. 树的组织方式资源树使用经典的**左孩子-右兄弟left-child right-sibling**表示法iomem_resource (root: 0x0 - 0xFFFFFFFFFFFFFFFF) ├── child ──→ System RAM [0x1000-0x9FFFF] │ sibling ──→ Reserved [0xA0000-0xFFFFF] │ child ──→ PCI Bus 0000:00 [0xA0000-0xDFFFF] │ sibling ──→ System ROM [0xF0000-0xFFFFF] │ sibling ──→ System RAM [0x100000-...] │ sibling ──→ ...3.1 核心不变量子区间完全包含在父区间内child.start parent.start child.end parent.end同级兄弟不重叠且按地址排序sib[i].end sib[i1].start同级间存在间隙是允许的间隙 未分配/空闲区域3.2 嵌套示例一个 PCI 总线区间 [0x80000000, 0xDFFFFFFF] 下嵌套子总线和设备 BARPCI Bus 0000:00 [80000000-dfffffff] ├── PCI Bus 0000:0e [df000000-df5fffff] │ ├── 0000:0e:00.0 [df000000-df1fffff] ← 设备 BAR │ ├── 0000:0e:00.4 [df200000-df2fffff] │ │ └── xhci-hcd [df200000-df2fffff] ← 驱动声明的子区域 │ └── ... ├── PCI Bus 0000:01 [df600000-df9fffff] │ └── PCI Bus 0000:02 → PCI Bus 0000:03 │ └── 0000:03:00.0 [df700000-df7fffff] ← GPU BAR └── ...4. 两棵全局资源树Linux 维护两棵独立的全局资源树树根节点管理对象用户态文件iomem_resource[0, 2^64)物理地址空间RAM、MMIO、device-private 等/proc/iomemioport_resource[0, IO_SPACE_LIMIT]I/O 端口空间x86 legacy I/O ports/proc/ioports对于 GPU VRAM 和现代设备我们只关心iomem_resource。5. 树的操作 API所有操作都由全局resource_lock读写锁保护。5.1 请求占位操作// 在 parent 下请求 [start, startn-1] 区间structresource*__request_region(structresource*parent,resource_size_tstart,resource_size_tn,constchar*name,intflags);核心算法__request_resourcestaticstructresource*__request_resource(structresource*root,structresource*new){resource_size_tstartnew-start;resource_size_tendnew-end;structresource*tmp,**p;// 边界检查必须在 parent 范围内if(endstart)returnroot;if(startroot-start)returnroot;if(endroot-end)returnroot;proot-child;for(;;){tmp*p;if(!tmp||tmp-startend){// 找到插入点tmp 为空或 tmp 在 new 之后new-siblingtmp;*pnew;new-parentroot;returnNULL;// 成功无冲突}ptmp-sibling;if(tmp-endstart)continue;// tmp 在 new 之前继续returntmp;// 冲突返回冲突的资源}}关键逻辑遍历兄弟链表寻找一个空隙能容纳新资源。如果某个已有资源的区间与新资源重叠返回冲突资源。5.2 插入操作允许嵌套structresource*insert_resource_conflict(structresource*parent,structresource*new);与request不同insert允许新资源包裹已有资源——已有资源会变成新资源的子节点。这用于表达如 “CXL Window 包含 System RAM” 的嵌套关系。__insert_resource的算法先尝试__request_resource如果成功则直接插入如果失败且冲突资源完全被新资源包含则将冲突资源及其相邻兄弟收编为新资源的子节点如果存在部分重叠冲突资源部分超出新资源范围返回错误5.3 释放操作void__release_region(structresource*parent,resource_size_tstart,resource_size_tn);从树中移除匹配的节点。如果被移除节点有子节点子节点上移到被移除节点的父节点下。5.4 冲突检测staticint__region_intersects(structresource*parent,resource_size_tstart,size_tsize,unsignedlongflags,unsignedlongdesc);// 返回值REGION_DISJOINT / REGION_INTERSECTS / REGION_MIXED遍历 parent 的子树判断 [start, startsize) 是否与任何已有资源相交。这是devm_request_free_mem_region寻找空闲区间的核心判断依据。6. 真实系统中的 iomem 树以下是一台配备 AMD GPU (0000:03:00.0) 的真实系统的/proc/iomem输出已精简注释# 低端内存BIOS/UEFI 报告的 e820 布局 00001000-0009ffff : System RAM # 640KB 常规内存 000a0000-000fffff : Reserved # VGA 兼容区 ROM 00100000-099fefff : System RAM # 扩展内存起始 # 主内存区 0b021000-64fa2fff : System RAM # 约 1.4GB 65154000-65765fff : System RAM 65767000-6b0a9fff : System RAM # ACPI 和固件保留 6b0aa000-718a6fff : Reserved 718a7000-71a46fff : ACPI Tables 71a47000-73a46fff : ACPI Non-volatile Storage # 32位 PCI MMIO 窗口 [2GB-3.5GB] 80000000-dfffffff : PCI Bus 0000:00 df600000-df9fffff : PCI Bus 0000:01 df600000-df8fffff : PCI Bus 0000:02 df600000-df8fffff : PCI Bus 0000:03 df700000-df7fffff : 0000:03:00.0 # GPU 32位 BAR # PCI ECAM (配置空间 MMIO) e0000000-efffffff : PCI ECAM 0000 [bus 00-ff] # 4GB 以上高端物理内存 100000000-c5dd7ffff : System RAM # 约 47GB 主内存 23e800000-23fa0d00f : Kernel code 23fc00000-2402effff : Kernel rodata 240400000-24084b53f : Kernel data # 64位 PCI MMIO 窗口 c80000000-fcffffffff : PCI Bus 0000:00 f800000000-fc0fffffff : PCI Bus 0000:01 # GPU 64位 BAR 链 f800000000-fc0fffffff : PCI Bus 0000:02 f800000000-fc0fffffff : PCI Bus 0000:03 f800000000-fbffffffff : 0000:03:00.0 # GPU VRAM BAR (16GB) fc00000000-fc0fffffff : 0000:03:00.0 # GPU doorbell BAR # GPU DEVICE_PRIVATE 虚拟占位 afc00000000-affffffffff : 0000:03:00.0 # ZONE_DEVICE 占位 (~2GB)6.1 地址空间分布示意物理地址 0 4GB ~48GB ~64GB ├────────────────────┼──────────────────────┼──────────────────┤ │ 低端内存 32位 MMIO │ System RAM │ 64位 PCI MMIO │ │ (e820 PCI) │ (~47GB) │ GPU BAR 等 │ └────────────────────┴──────────────────────┴──────────────────┘ ~700GB │ ┌───────┴───────┐ │ 0000:03:00.0 │ │ DEVICE_PRIVATE│ │ ~2GB 占位 │ └───────────────┘7. /proc/iomem 的输出原理/proc/iomem的实现在kernel/resource.cstaticint__initioresources_init(void){proc_create_seq_data(iomem,0,NULL,resource_op,iomem_resource);return0;}输出逻辑r_show函数staticintr_show(structseq_file*m,void*v){structresource*rootpde_data(file_inode(m-file));structresource*rv,*p;intdepth;// 计算缩进深度从当前节点到根的层数for(depth0,pr;depthMAX_IORES_LEVEL;depth,pp-parent)if(p-parentroot)break;// 非特权用户看到的地址为 0if(file_ns_capable(m-file,init_user_ns,CAP_SYS_ADMIN)){startr-start;endr-end;}else{startend0;}seq_printf(m,%*s%0*llx-%0*llx : %s\n,depth*2,,// 每层缩进2空格width,start,width,end,r-name);}遍历方式是前序遍历next_resource先深度后兄弟所以输出天然反映了树的嵌套结构用缩进表示父子关系。安全提示非 root 用户读/proc/iomem只能看到名称地址全为 0。这是为了防止 KASLR 等信息泄露。8. GPU VRAM 的 DEVICE_PRIVATE 占位8.1 问题为什么需要占位Linux 的migrate_vmaAPI 要求每一页设备内存都有对应的struct page。而struct page由内存模型sparsemem管理必须绑定到一个物理地址范围。但对于 discrete GPUVRAM 不在 CPU 物理地址空间——CPU 无法直接访问这些地址。所以需要在iomem_resource树中虚拟地占用一段地址仅用于给struct page一个 PFN页帧号让pfn_to_page()/page_to_pfn()能工作标记为MEMORY_DEVICE_PRIVATECPU 访问时触发 fault → 回迁到 RAM这段地址不对应任何真实的可访问硬件纯粹是簿记用途。8.2 与 Coherent 设备的区别特性MEMORY_DEVICE_PRIVATEMEMORY_DEVICE_COHERENTCPU 可直接访问❌ 不可以✅ 通过 aperture地址来源虚拟占位devm_request_free_mem_regionGPU aperture BAR 的真实物理地址典型场景Discrete GPUPCIeXGMI 连接的 APU/GPUCPU 访问时触发migrate_to_ramfault直接通过 CXL/XGMI 互联在amdgpu_svm_migration_init中的分支if(adev-gmc.xgmi.connected_to_cpu){// Coherent使用 GPU 的真实 aperture 地址pgmap-range.startadev-gmc.aper_base;pgmap-range.endadev-gmc.aper_baseadev-gmc.aper_size-1;pgmap-typeMEMORY_DEVICE_COHERENT;}else{// Private在 iomem 树中找空位做虚拟占位resdevm_request_free_mem_region(adev-dev,iomem_resource,size);pgmap-range.startres-start;pgmap-range.endres-end;pgmap-typeMEMORY_DEVICE_PRIVATE;}9. devm_request_free_mem_region 完整流程9.1 调用链devm_request_free_mem_region(dev, iomem_resource, size) └── get_free_mem_region(dev, baseiomem_resource, size, align, name, desc, flags) │ ├── 1. 对齐 size 到 GFR_DEFAULT_ALIGN │ x86_64: align 2^27 128MB (sparsemem section) │ ├── 2. 分配 struct resource devres 追踪结构 │ ├── 3. 从高地址向低地址扫描 ──────────────────────────┐ │ for (addr DIRECT_MAP_PHYSMEM_END - size; │ │ addr base-start; │ │ addr - size) │ │ │ │ ┌─ 4. 检查冲突 ────────────────────────────┐ │ │ │ __region_intersects(base, addr, size) │ │ │ │ 遍历 iomem_resource 的子节点检查 │ │ │ │ [addr, addrsize) 是否与任何已有资源 │ │ │ │ (System RAM, PCI BAR, Reserved...) 重叠 │ │ │ │ │ │ │ │ REGION_DISJOINT → 可用 │ │ │ │ REGION_INTERSECTS/MIXED → 跳过 │ │ │ └─────────────────────────────────────────┘ │ │ │ ├── 5. 占位注册 ────────────────────────────────────┘ │ __request_region_locked(res, iomem_resource, │ addr, size, name, 0) │ → 将 res 作为 iomem_resource 的子节点插入 │ → 设置 IORESOURCE_BUSY 标志 │ ├── 6. 设置描述符 │ res-desc IORES_DESC_DEVICE_PRIVATE_MEMORY │ ├── 7. 撤销已有映射 │ revoke_iomem(res) │ → 确保 /dev/mem 等不会映射到这段地址 │ └── 8. 注册 devres 回调 devres_add(dev, dr) → 设备移除时自动调用 devm_region_release() → 从 iomem_resource 树中移除占位9.2 对齐到 128MB 的原因x86_64 的 sparsemem 使用SECTION_SIZE_BITS 27即每个 section 128MB// arch/x86/include/asm/sparsemem.h#defineSECTION_SIZE_BITS27// 2^27 128MB// kernel/resource.c#defineGFR_DEFAULT_ALIGN(1ULPA_SECTION_SHIFT)// 1 27 128MBstruct page数组是按 section 粒度通过vmemmap分配的。如果占位的物理地址不对齐到section 边界devm_memremap_pages在创建struct page数组时会跨越 section 边界导致与其他 section 的struct page冲突。9.3 搜索方向为什么从高往低unsignedlongflagsGFR_DESCENDING|GFR_REQUEST_REGION;从高地址往低搜的原因低端地址密集System RAM、PCI 32 位 MMIO、ACPI 表等都集中在低 64GB 内高端地址空旷x86_64 物理地址空间高达 2^46 (64TB) 或 2^52 (4PB)高于实际 RAM 和 PCI 窗口的区域几乎全是空的避免碎片化不同 GPU 的占位从顶部向下排列不会与未来可能插入的 RAM 冲突9.4 搜索上界 DIRECT_MAP_PHYSMEM_END// arch/x86/include/asm/pgtable_64_types.h#defineDIRECT_MAP_PHYSMEM_ENDdirect_map_physmem_end// 4级页表: 2^46 64TB 5级页表: 2^52 4PB这是 CPU 直接映射区__va/__pa能覆盖的最大物理地址。ZONE_DEVICE 的struct page虽然不需要真实的物理访问但 PFN 必须在这个范围内否则pfn_to_page()的 vmemmap 映射会越界。9.5 devres 生命周期管理devm_前缀表示 device-managed resourcestructregion_devres{structresource*parent;// iomem_resourceresource_size_tstart;// 占位起始地址resource_size_tn;// 大小};staticvoiddevm_region_release(structdevice*dev,void*res){structregion_devres*thisres;__release_region(this-parent,this-start,this-n);}当 GPU 驱动 unbindrmmod amdgpu或设备热拔出时device_release_driver() → devres_release_all() → devm_region_release() // 自动从 iomem_resource 树中移除占位无需手动清理避免资源泄漏。10. 设计哲学总结10.1 统一抽象iomem_resource树将所有种类的物理地址使用者放在同一棵树中管理物理地址使用者 在树中的表现 ───────────── ──────────── 真实 DRAM System RAM (IORESOURCE_SYSTEM_RAM) PCI BAR MMIO PCI Bus / 设备名 (IORESOURCE_MEM) ACPI 保留 Reserved / ACPI Tables GPU VRAM 虚拟占位 设备名 (IORES_DESC_DEVICE_PRIVATE_MEMORY) PMEM 持久内存 Persistent Memory (IORES_DESC_PERSISTENT_MEMORY) CXL 扩展内存 CXL (IORES_DESC_CXL)这种统一管理确保了任何两个使用者不会占用相同的物理地址——这正是资源树存在的根本目的。10.2 嵌套表达树结构天然表达了 PCI 总线层次PCI Bus 0000:00 ⊃ PCI Bus 0000:01 ⊃ PCI Bus 0000:02 ⊃ 0000:03:00.0 ⊃ xhci-hcdinsert_resourceAPI 允许后插入的大区间收编已有的小区间为子节点比request_resource更灵活。10.3 并发安全所有树操作都由resource_lock读写锁保护/proc/iomem读取read_lockrequest_region/insert_resource/release_regionwrite_lock10.4 与内存子系统的桥梁iomem_resource是物理地址空间的地图而devm_memremap_pages是将这张地图上的一段区域与内核内存子系统struct page、sparsemem、ZONE_DEVICE连接起来的桥梁iomem_resource 树 内存子系统 ┌──────────────┐ ┌─────────────────┐ │ 占位区间 │ ──pgmap────→ │ devm_memremap_ │ │ [start,end] │ │ pages() │ │ DEVICE_ │ │ ↓ │ │ PRIVATE │ │ ZONE_DEVICE │ └──────────────┘ │ struct page[] │ │ ↓ │ │ folio-pgmap │ │ → dev_pagemap │ │ → drm_pagemap │ │ → migrate_vma │ └─────────────────┘这就是 GPU 驱动能够在不拥有真实物理内存的情况下参与 Linux 统一内存管理struct page、migrate_vma、mmu_notifier的底层基础设施。