AMD rocr-libhsakmt分析系列3-1: Apertures

1. 概述

前文已经给出了aperture的定义。在 AMD GPU 的 HSA运行时中,Aperture(孔径)是一个核心概念,用于管理和组织不同类型的内存区域。libhsakmt 库通过精心设计的 aperture 机制,实现了 CPU 和 GPU 之间高效、灵活的内存管理。本文档详细阐述 libhsakmt 中 aperture 的类型、设计原理和使用场景。

2. Aperture 的核心概念

2.1 什么是 Aperture

Aperture 本质上是虚拟地址空间的一个连续区域,具有特定的起始地址和结束地址。每个 aperture 管理特定类型的内存,并提供该内存的分配、释放和映射功能。通过 aperture,系统可以将不同属性的内存隔离管理,确保内存访问的正确性和效率。

2.2 基本数据结构

aperture_t(简单 aperture)

  • base:aperture 的起始地址
  • limit:aperture 的结束地址
  • 只记录地址范围,不进行主动管理

manageable_aperture_t(可管理 aperture)

  • 继承 aperture_t 的 base 和 limit
  • align:内存对齐要求
  • guard_pages:保护页数量,用于检测越界访问
  • vm_ranges:空闲地址区域链表
  • tree 和 user_tree:红黑树,用于快速查找已分配的内存对象
  • fmm_mutex:线程同步互斥锁
  • is_cpu_accessible:标识 CPU 是否可直接访问
  • ops:操作函数指针,支持不同的内存管理策略

3. Aperture 类型详解

3.1 SVM Aperture(共享虚拟内存孔径)

SVM aperture 是最重要的内存区域,用于实现 CPU 和 GPU 之间的统一地址空间。

设计目标

  • 实现 CPU 和 GPU 共享同一虚拟地址,简化编程模型
  • 支持细粒度和粗粒度两种内存一致性模型

具体实现

  1. 双 aperture 设计

    • SVM_DEFAULT:用于粗粒度(非一致性)内存,占据 SVM 空间的后 3/4
    • SVM_COHERENT:用于细粒度(一致性)内存,占据 SVM 空间的前 1/4
  2. 地址空间分配策略

    • 优先尝试使用未预留的虚拟地址空间(通过 mmap_aperture_ops)
    • 如果失败,则预留固定的地址空间(通过 reserved_aperture_ops)
    • 支持最大 47 位地址空间(128TB)
  3. 适用场景

    • dGPU(独立显卡)的主要内存管理区域
    • 支持 HSA 统一内存模型的应用
    • 需要 CPU-GPU 零拷贝数据共享的场景

3.2 GPUVM Aperture(GPU 虚拟内存孔径)

GPUVM aperture 用于 APU(集成显卡)环境,管理非规范地址空间的 GPU 内存。

设计特点

  • 仅在非规范地址模式下存在(地址 < 2^47)
  • 每个 GPU 拥有独立的 GPUVM aperture
  • 在初始化时预留一小部分空间,避免空指针问题

使用场景

  • APU 系统的 GPU 显存管理
  • 传统 GPU 内存分配(非 SVM 模式)
  • 需要独立 GPU 地址空间的应用

3.3 Scratch Aperture(临时内存孔径)

Scratch aperture 是为 GPU 计算任务提供的临时工作空间。

功能特性

  • 每个 GPU 独立的 scratch 地址空间
  • 用于存储 shader 程序的临时变量
  • 按需分配,支持动态映射

物理实现

  • scratch_aperture:虚拟地址范围
  • scratch_physical:对应的物理内存(从 dgpu_aperture 分配)

典型应用

  • GPU kernel 执行时的栈空间
  • 线程局部存储(Thread Local Storage)
  • 寄存器溢出时的临时存储

3.4 LDS Aperture(本地数据共享孔径)

LDS(Local Data Share)aperture 管理 GPU 工作组内共享的高速内存。

核心特征

  • 只读的虚拟地址范围(由内核驱动管理)
  • 工作组内的所有工作项可共享访问
  • 访问速度远快于全局内存

使用限制

  • 大小固定且有限(通常 64KB 每个计算单元)
  • 生命周期仅限于工作组执行期间
  • 不能跨工作组访问

3.5 MMIO Aperture(内存映射 I/O 孔径)

MMIO aperture 提供对 GPU 寄存器和特殊硬件资源的访问。

设计要点

  • 映射 GPU 的硬件寄存器到 CPU 可访问的地址空间
  • 通常只有一个页面大小(4KB)
  • 需要支持 SVM 的 GPU 才会创建

应用场景

  • Doorbell 寄存器访问(队列通知)
  • 硬件计数器读取
  • 调试和性能监控

3.6 CPUVM Aperture(CPU 虚拟内存孔径)

CPUVM aperture 用于追踪系统内存中不需要 GPU 驱动管理的部分。

特点

  • 管理 APU 上 GPU 不直接访问的系统内存
  • 每个进程独立的追踪空间
  • 使用 mmap 进行地址分配

使用情况

  • 用户态程序分配的普通内存
  • 不需要 GPU 访问的缓冲区
  • CPU 专用的数据结构

3.7 Memory Handle Aperture(内存句柄孔径)

Memory handle aperture 为没有有效虚拟地址的内存分配提供句柄空间。

设计目的

  • 为纯 GPU 内存(无 CPU 映射)生成标识符
  • 地址范围位于非规范地址空间(47 位以上)
  • 47 位地址空间大小

典型应用

  • 仅 GPU 可访问的显存(mflags.ui32.NoAddress)
  • 跨进程内存共享的句柄
  • 内存导出和导入操作

4. Aperture 管理机制

4.1 双操作策略

libhsakmt 为 aperture 设计了两种内存管理策略,通过函数指针实现多态:

reserved_aperture_ops(预留式管理)

  • 预先通过 mmap 预留整块地址空间
  • 使用链表管理空闲区域
  • 分配时从空闲链表中查找合适的"洞"
  • 适用于地址范围固定且可控的场景

这是老的实现,在新硬件上基本不用了。而是用下面的mmap_aperture_ops实现。

mmap_aperture_ops(按需映射)

  • 不预留地址空间,按需调用 mmap
  • 依赖操作系统的地址分配
  • 更灵活但可能碎片化
  • 适用于 48 位地址空间的现代 GPU

4.2 内存对象追踪

每个分配的内存都关联一个 vm_object_t 结构:

  • start:内存起始地址
  • size:实际分配大小
  • handles:KFD 驱动句柄数组
  • mflags:内存属性标志
  • registered_device_id_array:注册的设备列表
  • mapped_device_id_array:已映射的设备列表

通过红黑树(rbtree)实现快速查找:

  • tree:按虚拟地址索引
  • user_tree:按用户指针索引(用于 userptr 注册)

4.3 内存对齐优化

libhsakmt 实现了智能的内存对齐策略:

  1. 基础对齐:至少满足 aperture 的 align 要求
  2. 大小自适应对齐:大缓冲区向上对齐到下一个 2 的幂次,最大到 GPU_HUGE_PAGE_SIZE
  3. TLB 优化:通过对齐减少 TLB 缺失,提升访问性能
  4. 保护页:每个对象后添加 guard pages,检测越界访问

4.4 NUMA 感知

在系统内存分配时,libhsakmt 会:

  • 识别 GPU 的 NUMA 节点
  • 使用 mbind 将内存绑定到最近的 CPU 节点
  • 减少跨 NUMA 访问延迟
  • 支持 NoSubstitute 标志强制绑定

5. 初始化流程

5.1 整体初始化

hsakmt_fmm_init_process_apertures() 是 aperture 系统的入口:

  1. 解析环境变量

    • HSA_DISABLE_CACHE:禁用 GPU 缓存
    • HSA_USERPTR_FOR_PAGED_MEM:使用 userptr 管理分页内存
    • HSA_SVM_GUARD_PAGES:设置保护页数量
  2. 创建 GPU 内存管理结构

    • 为每个 GPU 节点分配 gpu_mem_t
    • 打开 DRM render 设备文件
    • 初始化 aperture 的互斥锁
  3. 从内核获取 aperture 信息

    • 调用 AMDKFD_IOC_GET_PROCESS_APERTURES
    • 获取每个 GPU 的 LDS、Scratch、GPUVM 地址范围
  4. 初始化 SVM aperture

    • 计算所有 GPU 的公共地址空间
    • 预留或映射 SVM 区域
    • 设置内存策略(一致性/非一致性)
  5. 创建辅助 aperture

    • CPUVM aperture:追踪系统内存
    • Memory handle aperture:生成内存句柄

5.2 SVM Aperture 初始化策略

现代 GPU(GFXv9+)

  • 支持完整 47 位地址空间
  • 优先使用 mmap_aperture(按需分配)
  • 测试分配一页内存,成功则使用该策略

传统 GPU 或受限场景

  • 预留固定的地址空间(最大 40 位,1TB)
  • 使用多轮尝试,每次减半尝试大小
  • 最小保证 4GB 可用空间
  • 创建两个 aperture:1/4 为 coherent,3/4 为 default

6. 典型使用场景

6.1 分配设备内存

当应用请求分配 GPU 内存时:

  1. 根据 GPU ID 选择对应的 aperture(SVM 或 GPUVM)
  2. 从 aperture 中分配虚拟地址
  3. 调用 AMDKFD_IOC_ALLOC_MEMORY_OF_GPU 分配物理内存
  4. 创建 vm_object 追踪该内存
  5. 如需 CPU 访问,建立 mmap 映射

6.2 注册用户内存(Userptr)

用户可以将自己分配的内存注册到 GPU:

  1. 验证地址范围的有效性
  2. 在 SVM aperture 中创建 vm_object(使用 userptr 字段)
  3. 调用 KFD 驱动的 SVM API 或传统 userptr 注册
  4. 插入到 user_tree 中以便查找

6.3 内存映射到 GPU

内存分配后需要映射到特定 GPU:

  1. 查找内存对应的 vm_object
  2. 确定要映射的 GPU 列表(默认全部或指定部分)
  3. 调用 AMDKFD_IOC_MAP_MEMORY_TO_GPU
  4. 更新 mapped_device_id_array
  5. 增加映射计数器

7. 设计亮点

7.1 统一的内存模型

通过 SVM aperture,实现 CPU 和 GPU 使用相同的虚拟地址,消除了地址转换的复杂性。应用程序可以在 CPU 上分配内存,直接传递指针给 GPU 使用,无需额外的拷贝或映射操作。

7.2 灵活的策略切换

通过操作函数指针(ops),同一套代码可以支持预留式和按需式两种内存管理策略。系统根据 GPU 能力和运行环境自动选择最优策略,无需应用层感知。

7.3 细粒度的内存属性

每个 aperture 可以配置不同的属性:

  • 内存一致性(CoarseGrain vs FineGrain)
  • CPU 可访问性
  • 对齐要求
  • 保护页配置

7.4 高效的内存查找

使用红黑树实现 O(log n) 的内存对象查找,支持大规模内存分配场景。同时维护两棵树(按地址和按 userptr),满足不同的查询需求。

7.5 NUMA 优化

自动识别 GPU 的 NUMA 拓扑,将内存绑定到最近的 CPU 节点,最小化跨节点访问延迟,提升整体系统性能。

8. 与 HSA 标准的对应

libhsakmt 的 aperture 设计完整实现了 HSA 规范的内存模型:

  • Global Memory:通过 SVM aperture 和 GPUVM aperture 实现
  • Group Memory (LDS):通过 LDS aperture 暴露
  • Flat Address Space:SVM aperture 提供统一的平坦地址空间
  • Memory Regions:不同 aperture 对应不同的内存区域属性

9. 总结

9.1 Aperture 类型对比

下表总结了七种 aperture 的核心特征和差异:

Aperture 类型 主要用途 地址空间范围 管理策略 CPU 可访问 GPU 可访问 适用场景
SVM Aperture CPU-GPU 统一地址空间 最大 47 位 (128TB) reserved/mmap 自适应 dGPU 主内存区域,零拷贝共享
GPUVM Aperture GPU 专用虚拟内存 非规范地址 (< 2^47) reserved × APU 显存,传统 GPU 内存
Scratch Aperture GPU 临时工作空间 每 GPU 独立范围 reserved 部分(调试) Kernel 栈,寄存器溢出
LDS Aperture 工作组共享内存 固定小范围 只读(内核管理) × 工作组内高速共享
MMIO Aperture 硬件寄存器访问 单页 (4KB) 直接映射 - Doorbell,硬件计数器
CPUVM Aperture 系统内存追踪 47 位用户空间 mmap × 用户态普通内存
Memory Handle Aperture 内存句柄生成 非规范地址 (47 位) reserved × × 纯 GPU 内存标识符

9.2 Aperture 特性对比

不同 aperture 的管理特性对比:

特性 SVM GPUVM Scratch LDS MMIO CPUVM Mem Handle
可分配内存 × ×
支持 mmap × × ×
红黑树追踪 × ×
NUMA 绑定 × × × ×
保护页机制 × ×
动态映射 × × × ×
跨进程共享 × × × ×

9.3 使用场景决策树

选择合适的 aperture 类型:

复制代码
分配内存需求
├─ 需要 CPU-GPU 零拷贝共享?
│  └─ 是 → SVM Aperture (dGPU) 或 GPUVM Aperture (APU)
│
├─ 仅供 GPU kernel 临时使用?
│  └─ 是 → Scratch Aperture
│
├─ 工作组内共享?
│  └─ 是 → LDS Aperture
│
├─ 访问 GPU 硬件寄存器?
│  └─ 是 → MMIO Aperture
│
├─ 追踪用户态系统内存?
│  └─ 是 → CPUVM Aperture
│
└─ 纯 GPU 内存无 CPU 映射?
   └─ 是 → Memory Handle Aperture

理解libhsakmt中设计的各种aperture是理解后续memory相关实现的前提,因为memory的alloc/free、map/unmap、register/deregister、share/unshare的都涉及到选择合适的aperture来操作。

相关推荐
无奈笑天下19 小时前
银河麒麟桌面OS使用分区编辑器将/backup分区删除并扩容至根分区参考教程
linux·数据库·经验分享·编辑器
CheungChunChiu1 天前
Linux 内核设备模型与驱动框架解析 ——以 rk-pcie 为例
linux·运维·ubuntu
列逍1 天前
Linux进程(三)
linux·运维·服务器·环境变量·命令行参数
水天需0101 天前
VS Code Ctrl+Shift+V 预览 Markdown 无效的解决方案
linux
赖small强1 天前
【Linux C/C++开发】Linux 平台 Stack Protector 机制深度解析
linux·c语言·c++·stack protector·stack-protector·金丝雀机制
陌路201 天前
Linux42 守护进程
linux
liteblue1 天前
DEB包解包与打包笔记
linux·笔记
minji...1 天前
Linux 基础IO(一) (C语言文件接口、系统调用文件调用接口open,write,close、文件fd)
linux·运维·服务器·网络·数据结构·c++
赖small强1 天前
【Linux内存管理】Linux虚拟内存系统详解
linux·虚拟内存·tlb