Intel Discrete GPU - Memory - Alloc

• 10 分钟阅读 • intel gpu

在应用使用 GPU 渲染一帧图像的过程中,需要向 GPU 传递 Vertex/Texture 数据信息,渲染完成后应用将得到一帧图像的数据信息,而这些数据信息的承载者便是通过显存,显存的术语通常被叫做 BO (Buffer Object);

显存位置定义

先来了解一下几种描述显存存储位置的定义,

显存分配流程

以上介绍了 BO 可能存在的三种存储位置,实际上 BO 期望开辟的位置是由用户态驱动所指定,内核在 BO 创建时会借助__xe_bo_placement_for_flags来解析用户的请求。这个位置并非指定,它可以是多个,依照更佳适合 BO 存在的位置传递到内核。

用户期望 BO 开辟的位置最终会存储在bo->placements中,在当前 Xe 驱动中,期望开辟的位置最多有可以有三种,通过XE_BO_MAX_PLACEMENTS指定。

显存分配的入口为XE_GEM_CREATE,核心函数是ttm_bo_validate,它的职责不仅仅是起到显存分配作用,如它的命名一样,它用来核对当前 BO 的所在位置是否与预期placements匹配,否则将可能会涉及到显存的分配、迁移。

显存初次分配时是通过ttm_bo_init_reserved来实现的,完成xe_bo结构的初始化后会通过刚提及的核心函数ttm_bo_validate验证显存开辟位置,它通过一些逻辑比较bo->resourceplacement是否匹配,否则将通过ttm_bo_alloc_resource分配资源,继而通过ttm_bo_handle_move_mem做迁移。

起初这里的流程真是把我绕晕,为什么显存的分配的流程要涉及 validate 和 move ?怎么不是简简单单的 alloc 呢。
后面再以全局视角来看这里的话,是蕴藏着开发者的智慧在里面的。因为ttm_bo_validate在显存的 Alloc / Evict / Swap 等均有调用,它是一个通用接口。

在显存的初次分配时,bo->resource是不存在的,所以 validate 中的ttm_resource_compatible检查一定不符合。我们假设用户期望的显存开辟位置是 VRAM,那么显存的分配将会在ttm_bo_alloc_resource中完成,独显的内存分配是通过 DRM BUDDY 机制实现,分配的函数为drm_buddy_alloc_blocks,分配完成后会涉及 move 函数的调用,这里要注意的是,显存初次分配时并不会涉及实际的 move 操作,仅会调用ttm_bo_move_null来更新bo->resource,这时 resource 将代表显存的实际存在位置了,后续的 Evict / Swap 操作将借助它来进一步操作。

如果用户期望的显存开辟位置是 SYSTEM,显存的分配流程又是什么样呢?

关于 DRM_BUDDY

可以像内存子系统 Buddy 内存管理一样理解 Drm Buddy,它能够按照 order 管理 Pages,也能够 Split / Merge;

区别比较大的点是,

/* Order-zero must be at least SZ_4K */
#define DRM_BUDDY_MAX_ORDER (63 - 12)

一段 Pages 的分配状态在 Drm Buddy 通过drm_buddy_block来管理,其中header成员各个字段的定义如下,

初始化

核心接口 - drm_buddy_init

假如独显的内存大小是 4G,那么 drm buddy 初始化时 max_order 为 20,

mm->max_order = ilog2(size) - ilog2(chunk_size);

每个 order 均搭配有一个 Free Pages List,

mm->free_list = kmalloc_array(mm->max_order + 1,
                sizeof(struct list_head), GFP_KERNEL);

除了上述的 free_list 关联空闲 Pages 外,Drm Buddy 还提供了一个 roots 链表,记录 Buddy 初始化完成时的 block 信息,

mm->n_roots = hweight64(size);
mm->roots = kmalloc_array(mm->n_roots,
            sizeof(struct drm_buddy_block *), GFP_KERNEL);

假如独显的内存大小是 6G,基础页面大小为 4K,初始化时将通过一个 order 20 的 4G Block 和一个 order 19 的 2G Block 关联完整的独显空间,这两个 Block 都将串联至 roots 中。

内存分配

核心接口 - drm_buddy_alloc_blocks

Block 管理策略

大致流程

内存释放

核心接口 - drm_buddy_free_list

大致流程

关于 TTM_POOL

ttm_pool 是 TTM 架构的一个能力子集,它作为系统内存的缓冲器,为硬件加速提供内存推动力。
ttm_pool 的全局初始化接口为ttm_pool_init,一般情况下它在 TTM 初始化ttm_device_init中被调用。

每个 TTM 设备内部嵌着 pool 结构,每个 pool 中包含以下具体的内存池,

struct {
    struct ttm_pool_type orders[NR_PAGE_ORDERS];
} caching[TTM_NUM_CACHING_TYPES];

其中TTM_NUM_CACHING_TYPES当前共定义为以下三种 cache 类型,

当驱动中有具体的分配需求时,将从指定的 cache 类型池中获取到内存。

Xe 驱动中在初始化 ttm_pool 时默认禁用了 dma_alloc 及 dma32 相关属性,这时 ttm_pool 将启用全局的内存池作为缓冲,它们分别是

为什么没有 cached 的池子呢,正如 Code 中注释写道,

DGFX system memory is always WB / ttm_cached, since other caching modes are only supported on x86. DGFX GPU system memory accesses are always coherent with the CPU.

这难道不是说明更应该需要一个 cached 内存池,或者初始化 ttm 时应该默认使能 use_dma_alloc?

内存分配

对外接口 - ttm_pool_alloc

大致流程

内存释放

对外接口 - ttm_pool_free

大致流程

缓存释放

对外接口 - 注册 Shrinker,核心实现为ttm_pool_shrink

大致流程

显存关联场景

关联 DMABUF

系统中的进程间或硬件 IP 间均有共享 Buffer 的需求,DRM 中实现共享的机制称之为 Prime,它由 Dave Airlie 提出。

这个名字的由来也是故事的,Nvidia 最早实现双显卡,名为 Optimus, Dave 参考并实现后,有趣地将 Linux 下实现的机制命名为了 Prime;
Optimus Prime 结合在一起意味着什么?

Prime 当前的架构是借助 DMA-BUF 来实现的,具体的说 Prime 的使用场景主要是 fd 的导入与导出,导入与的导出的操作接口分别对应drm_prime_fd_to_handle_ioctldrm_prime_handle_to_fd_ioctl

在导入的场景中,DMA BUF 背后的内存可能来自于其它模块分配,Prime 先要判断这个 DMA BUF 是否已有 BO 关联,有则转换为 Handle,无则新建 BO 并关联;

在导出的场景中,用户会先分配一块 BO 内存,然后通过这个 BO 对应的 Handle 进一步将其转换为 fd;

关联 USER-PTR

用户 CPU 侧空间分配的内存也可以直接被 GPU 访问,这一部分使用场景的内存分配完全在用户层面,用户可以通过 Malloc 或 MMAP 分配,然后在 MAP 阶段映射到 GPU 页表中,具体操作后面篇章再介绍。

需要注意的是,用户通过 Malloc 或 MMAP 分配空间后,这仅代表 VA 已完成分配,实际上给到 GPU 映射时极有可能存在物理内存未分配完整的情况,这样该如何处理?

在 Xe 驱动的实现中,GPU 在映射 USER-PTR 类型内存时会通过xe_vma_userptr_pin_pages对内存页面做出 Pin 操作,确保物理内存完全分配。Pin 操作的核心是借助hmm_range_fault实现,这是 MM 子系统专门为异构处理器提供的一个内存分配手段,我们可以将它称之为 HMM

关于 HMM

文章标签: intel gpu

上一篇 : Intel Discrete GPU - Memory - Map / Page Fault
下一篇 : Intel Discrete GPU - 开篇
阅读进度 0%