第11章:AMDGPU-VRAM分配流程

章节概述

本章深入探讨AMDGPU驱动中VRAM分配的完整流程,从用户态应用程序到底层Buddy分配器的调用链。通过理解实际的驱动集成,可以看到前面章节学习的算法如何在真实环境中应用。

难度级别 : 🟡🔴 中高级
预计阅读时间 : 50分钟
前置知识: 核心算法篇、TTM框架基础


📋 本章学习目标

完成本章学习后,你将能够:

  • ✅ 理解VRAM分配请求的来源和触发条件
  • ✅ 掌握AMDGPU VRAM Manager的初始化流程
  • ✅ 追踪从TTM到Buddy的完整调用链
  • ✅ 分析Buffer Object创建时的内存分配
  • ✅ 理解分配失败的处理机制

11.1 VRAM分配请求的来源

11.1.1 三大来源

在AMDGPU驱动中,VRAM分配请求主要来自三个方面:

c 复制代码
/**
 * 来源1: Buffer Object (BO) 创建
 * - 用户态应用通过DRM ioctl创建GEM对象
 * - Vulkan/OpenGL分配显存纹理和缓冲区
 * - 最常见的分配路径
 */
int amdgpu_gem_create_ioctl(struct drm_device *dev, void *data,
                            struct drm_file *filp)
{
    union drm_amdgpu_gem_create *args = data;
    // 解析用户参数
    // 调用amdgpu_gem_object_create()
}

/**
 * 来源2: 内核内部分配
 * - 驱动初始化时的固件缓冲区
 * - Ring buffer、IB pool等
 * - Page table和Page directory
 */
int amdgpu_bo_create_kernel(struct amdgpu_device *adev,
                           unsigned long size,
                           int align,
                           u32 domain,
                           struct amdgpu_bo **bo_ptr,
                           u64 *gpu_addr,
                           void **cpu_addr)
{
    // 内核使用的BO,通常需要CPU映射
}

/**
 * 来源3: Display相关分配
 * - Framebuffer
 * - Cursor buffer
 * - Display plane
 */
// 通过DRM框架调用,最终到amdgpu_display_gem_fb_create()

11.1.2 分配标志

不同的应用场景需要不同的分配特性:

c 复制代码
/**
 * AMDGPU GEM创建标志
 */
#define AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED   (1 << 0)  // CPU需要访问
#define AMDGPU_GEM_CREATE_NO_CPU_ACCESS         (1 << 1)  // CPU不访问
#define AMDGPU_GEM_CREATE_CPU_GTT_USWC          (1 << 2)  // GTT使用USWC
#define AMDGPU_GEM_CREATE_VRAM_CLEARED          (1 << 3)  // VRAM需要清零
#define AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS       (1 << 9)  // 连续VRAM
#define AMDGPU_GEM_CREATE_VM_ALWAYS_VALID       (1 << 10) // VM始终有效
#define AMDGPU_GEM_CREATE_VRAM_WIPE_ON_RELEASE  (1 << 13) // 释放时擦除

/**
 * TTM placement标志
 */
#define TTM_PL_FLAG_CONTIGUOUS  (1 << 0)  // 需要连续内存
#define TTM_PL_FLAG_TOPDOWN     (1 << 1)  // 从高地址分配

11.1.3 实际应用场景

c 复制代码
/**
 * 场景1: Vulkan纹理分配
 * - 大小: 4K纹理可能需要32MB
 * - 对齐: 通常4KB或更大
 * - 特性: 不需要CPU访问,非连续OK
 */
flags = AMDGPU_GEM_CREATE_NO_CPU_ACCESS;
domain = AMDGPU_GEM_DOMAIN_VRAM;

/**
 * 场景2: Display Framebuffer
 * - 大小: 4K@60Hz需要约8MB
 * - 对齐: 64KB或更大
 * - 特性: 必须在Visible VRAM,可能需要连续
 */
flags = AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED |
        AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
domain = AMDGPU_GEM_DOMAIN_VRAM;

/**
 * 场景3: Shader缓冲区
 * - 大小: 通常较小,KB到MB级别
 * - 对齐: 256字节对齐
 * - 特性: 频繁更新,可能CPU访问
 */
flags = AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED;
domain = AMDGPU_GEM_DOMAIN_VRAM | AMDGPU_GEM_DOMAIN_GTT;

11.2 AMDGPU VRAM Manager初始化

11.2.1 初始化时机

VRAM Manager在驱动初始化的早期阶段创建:

c 复制代码
/**
 * AMDGPU驱动初始化流程
 */
amdgpu_driver_load_kms()
  → amdgpu_device_init()
    → amdgpu_ttm_init()
      → amdgpu_vram_mgr_init()  // ← VRAM Manager初始化

/**
 * 调用栈示例
 */
[1] amdgpu_driver_load_kms         (amdgpu_kms.c)
[2]   amdgpu_device_init           (amdgpu_device.c)
[3]     amdgpu_ttm_init            (amdgpu_ttm.c)
[4]       amdgpu_vram_mgr_init     (amdgpu_vram_mgr.c)

11.2.2 初始化函数详解

c 复制代码
/**
 * amdgpu_vram_mgr_init - 初始化VRAM管理器
 * @adev: amdgpu设备指针
 */
int amdgpu_vram_mgr_init(struct amdgpu_device *adev)
{
    struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr;
    struct ttm_resource_manager *man = &mgr->manager;

    // 1. 注册DRM cgroup(资源控制)
    man->cg = drmm_cgroup_register_region(adev_to_drm(adev), 
                                         "vram", 
                                         adev->gmc.real_vram_size);

    // 2. 初始化TTM资源管理器
    ttm_resource_manager_init(man, &adev->mman.bdev,
                             adev->gmc.real_vram_size);

    // 3. 初始化VRAM Manager特有的数据结构
    mutex_init(&mgr->lock);
    INIT_LIST_HEAD(&mgr->reservations_pending);
    INIT_LIST_HEAD(&mgr->reserved_pages);
    INIT_LIST_HEAD(&mgr->allocated_vres_list);
    mgr->default_page_size = PAGE_SIZE;

    // 4. 设置操作函数表
    man->func = &amdgpu_vram_mgr_func;

    // 5. 初始化DRM Buddy分配器
    err = drm_buddy_init(&mgr->mm, man->size, PAGE_SIZE);

    // 6. 注册到TTM
    ttm_set_driver_manager(&adev->mman.bdev, TTM_PL_VRAM, &mgr->manager);
    ttm_resource_manager_set_used(man, true);

    return 0;
}

11.2.3 关键数据结构

c 复制代码
/**
 * struct amdgpu_vram_mgr - AMDGPU VRAM管理器
 */
struct amdgpu_vram_mgr {
    struct ttm_resource_manager manager;  // TTM资源管理器基类
    struct drm_buddy mm;                   // Buddy分配器实例
    struct mutex lock;                     // 保护分配器的互斥锁
    
    // 预留内存管理
    struct list_head reservations_pending; // 待处理的预留
    struct list_head reserved_pages;       // 已预留的页面
    
    // 分配跟踪
    struct list_head allocated_vres_list;  // 所有已分配资源列表
    
    // 统计信息
    atomic64_t vis_usage;                  // Visible VRAM使用量
    u64 default_page_size;                 // 默认页面大小
};

/**
 * struct amdgpu_vram_mgr_resource - 分配的VRAM资源
 */
struct amdgpu_vram_mgr_resource {
    struct ttm_resource base;         // TTM资源基类
    struct list_head blocks;          // Buddy分配的块链表
    struct list_head vres_node;       // 在allocated_vres_list中的节点
    unsigned long flags;              // 分配标志
    
    // 任务跟踪(调试用)
    struct {
        pid_t pid;
        char comm[TASK_COMM_LEN];
    } task;
};

11.2.4 初始化后的状态

复制代码
初始化完成后的结构:

amdgpu_device
  └── mman (内存管理器)
      └── vram_mgr (VRAM管理器)
          ├── manager (TTM资源管理器)
          │   ├── size: 8GB (总VRAM大小)
          │   ├── usage: 0 (初始未使用)
          │   └── func: &amdgpu_vram_mgr_func
          ├── mm (DRM Buddy)
          │   ├── size: 8GB
          │   ├── chunk_size: 4KB (PAGE_SIZE)
          │   ├── max_order: 21 (8GB/4KB = 2^21)
          │   ├── avail: 8GB (全部可用)
          │   └── free_list[0..21]: 初始化的空闲列表
          ├── lock: mutex (未锁定)
          ├── reservations_pending: 空列表
          ├── reserved_pages: 空列表
          ├── allocated_vres_list: 空列表
          ├── vis_usage: 0
          └── default_page_size: 4KB

此时Buddy分配器已准备好处理分配请求

11.3 从TTM到Buddy的调用链

11.3.1 完整调用链

c 复制代码
/**
 * 用户态GEM对象创建的完整流程
 */

// 1. 用户态ioctl入口
DRM_IOCTL_AMDGPU_GEM_CREATE
  ↓
amdgpu_gem_create_ioctl()                 // amdgpu_gem.c
  ↓
amdgpu_gem_object_create()
  ↓
// 2. BO创建
amdgpu_bo_create()                        // amdgpu_object.c
  ↓
amdgpu_bo_create_restricted()
  ↓
amdgpu_bo_do_create()
  ↓
// 3. TTM层
ttm_bo_init_reserved()                    // ttm_bo.c (TTM核心)
  ↓
ttm_bo_validate()
  ↓
ttm_bo_mem_space()
  ↓
ttm_resource_manager_first_pass()
  ↓
// 4. 驱动特定的资源管理器
man->func->alloc()
  ↓
amdgpu_vram_mgr_new()                     // amdgpu_vram_mgr.c
  ↓
// 5. Buddy分配器
drm_buddy_alloc_blocks()                  // drm_buddy.c
  ↓
alloc_from_freelist()
  ↓
split_block() / mark_allocated()

11.3.2 关键接口函数

c 复制代码
/**
 * amdgpu_gem_object_create - 创建GEM对象
 */
int amdgpu_gem_object_create(struct amdgpu_device *adev,
                            unsigned long size,
                            int alignment,
                            u32 initial_domain,
                            u64 flags,
                            struct amdgpu_bo **bo_ptr)
{
    struct amdgpu_bo_param bp;
    
    // 准备BO参数
    memset(&bp, 0, sizeof(bp));
    bp.size = size;
    bp.byte_align = alignment;
    bp.domain = initial_domain;
    bp.flags = flags;
    bp.type = ttm_bo_type_device;
    bp.resv = NULL;
    
    // 创建BO
    return amdgpu_bo_create(adev, &bp, bo_ptr);
}

/**
 * amdgpu_bo_create - 创建Buffer Object
 */
int amdgpu_bo_create(struct amdgpu_device *adev,
                    struct amdgpu_bo_param *bp,
                    struct amdgpu_bo **bo_ptr)
{
    // 选择内存域
    if (bp->domain & AMDGPU_GEM_DOMAIN_VRAM)
        bp->domain = AMDGPU_GEM_DOMAIN_VRAM;
    else if (bp->domain & AMDGPU_GEM_DOMAIN_GTT)
        bp->domain = AMDGPU_GEM_DOMAIN_GTT;
    
    // 调用实际创建函数
    return amdgpu_bo_create_restricted(adev, bp->size,
                                       bp->byte_align,
                                       bp->domain,
                                       bp->flags,
                                       ...);
}

11.4 BO创建时的内存分配

11.4.1 amdgpu_vram_mgr_new函数详解

这是连接TTM和Buddy分配器的关键函数:

c 复制代码
/**
 * amdgpu_vram_mgr_new - 分配VRAM资源
 * @man: TTM资源管理器
 * @tbo: TTM buffer object
 * @place: placement描述(内存域、标志等)
 * @res: 输出分配的资源
 */
static int amdgpu_vram_mgr_new(struct ttm_resource_manager *man,
                               struct ttm_buffer_object *tbo,
                               const struct ttm_place *place,
                               struct ttm_resource **res)
{
    struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
    struct amdgpu_device *adev = to_amdgpu_device(mgr);
    struct amdgpu_bo *bo = ttm_to_amdgpu_bo(tbo);
    struct amdgpu_vram_mgr_resource *vres;
    struct drm_buddy *mm = &mgr->mm;
    u64 size, remaining_size, lpfn, fpfn;
    u64 vis_usage = 0, max_bytes, min_block_size;
    unsigned long pages_per_block;

    // 1. 解析范围限制(fpfn/lpfn)
    lpfn = (u64)place->lpfn << PAGE_SHIFT;
    if (!lpfn || lpfn > man->size)
        lpfn = man->size;
    
    fpfn = (u64)place->fpfn << PAGE_SHIFT;

    // 2. 检查可用空间
    max_bytes = adev->gmc.mc_vram_size;
    if (tbo->type != ttm_bo_type_kernel)
        max_bytes -= AMDGPU_VM_RESERVED_VRAM;
    
    if (ttm_resource_manager_usage(man) > max_bytes)
        return -ENOSPC;

    // 3. 计算对齐要求
    if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS) {
        pages_per_block = ~0ul;  // 连续分配
    } else {
        pages_per_block = HPAGE_PMD_NR;  // 2MB大页
        pages_per_block = max_t(u32, pages_per_block,
                               tbo->page_alignment);
    }

    // 4. 分配vres结构
    vres = kzalloc(sizeof(*vres), GFP_KERNEL);
    ttm_resource_init(tbo, place, &vres->base);
    INIT_LIST_HEAD(&vres->blocks);

    // 5. 设置Buddy分配标志
    if (place->flags & TTM_PL_FLAG_TOPDOWN)
        vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
    
    if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS)
        vres->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
    
    if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CLEARED)
        vres->flags |= DRM_BUDDY_CLEAR_ALLOCATION;
    
    if (fpfn || lpfn != mgr->mm.size)
        vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;

    // 6. 分配循环(可能需要多次分配)
    remaining_size = (u64)vres->base.size;
    mutex_lock(&mgr->lock);
    
    while (remaining_size) {
        // 计算min_block_size(对齐)
        if (tbo->page_alignment)
            min_block_size = (u64)tbo->page_alignment << PAGE_SHIFT;
        else
            min_block_size = mgr->default_page_size;
        
        size = remaining_size;
        
        // 调整为2MB对齐(如果可能)
        if ((size >= (u64)pages_per_block << PAGE_SHIFT) &&
            !(size & (((u64)pages_per_block << PAGE_SHIFT) - 1)))
            min_block_size = (u64)pages_per_block << PAGE_SHIFT;

        // 调用Buddy分配器
        r = drm_buddy_alloc_blocks(mm, fpfn, lpfn,
                                   size, min_block_size,
                                   &vres->blocks,
                                   vres->flags);
        
        if (unlikely(r))
            goto error_free_blocks;
        
        if (size > remaining_size)
            remaining_size = 0;
        else
            remaining_size -= size;
    }

    // 7. 记录分配任务信息
    vres->task.pid = task_pid_nr(current);
    get_task_comm(vres->task.comm, current);
    list_add_tail(&vres->vres_node, &mgr->allocated_vres_list);

    mutex_unlock(&mgr->lock);

    // 8. 计算起始地址和可见VRAM使用
    vres->base.start = 0;
    list_for_each_entry(block, &vres->blocks, link) {
        unsigned long start;
        
        start = amdgpu_vram_mgr_block_start(block) +
                amdgpu_vram_mgr_block_size(block);
        start >>= PAGE_SHIFT;
        
        vres->base.start = max(vres->base.start, start);
        vis_usage += amdgpu_vram_mgr_vis_size(adev, block);
    }

    // 9. 检查是否连续
    if (amdgpu_is_vram_mgr_blocks_contiguous(&vres->blocks))
        vres->base.placement |= TTM_PL_FLAG_CONTIGUOUS;

    // 10. 更新统计
    atomic64_add(vis_usage, &mgr->vis_usage);
    
    *res = &vres->base;
    return 0;

error_free_blocks:
    drm_buddy_free_list(mm, &vres->blocks, 0);
    mutex_unlock(&mgr->lock);
    ttm_resource_fini(man, &vres->base);
    kfree(vres);
    return r;
}

11.4.2 分配流程可视化

复制代码
输入参数:
  - size: 16MB
  - alignment: 2MB
  - domain: VRAM
  - flags: TOPDOWN

步骤1: 解析参数
  fpfn = 0, lpfn = 8GB (全范围)
  pages_per_block = 512 (2MB / 4KB)

步骤2: 设置Buddy标志
  vres->flags = DRM_BUDDY_TOPDOWN_ALLOCATION

步骤3: 分配循环
  remaining_size = 16MB
  
  循环1:
    size = 16MB
    min_block_size = 2MB (对齐)
    调用 drm_buddy_alloc_blocks(mm, 0, 8GB, 16MB, 2MB, ...)
    
    Buddy分配器可能返回:
      - [8MB @ offset 1GB] 
      - [8MB @ offset 1GB+8MB]
    或者:
      - [16MB @ offset 2GB] (如果有连续的)
    
    remaining_size = 0 (分配完成)

步骤4: 构建vres
  vres->blocks: [Block1] → [Block2] → NULL
  vres->base.start: 计算最高地址
  vres->task.pid: 当前进程PID
  vres->task.comm: 进程名称

步骤5: 返回
  *res = &vres->base
  TTM现在可以使用这些块

11.4.3 多块分配示例

c 复制代码
/**
 * 场景: 分配3MB,2MB对齐
 * VRAM当前没有3MB的连续块
 */

// 输入
size = 3MB = 3145728 bytes
min_block_size = 2MB = 2097152 bytes
flags = 0 (非连续OK)

// Buddy分配器执行
remaining_size = 3MB

// 第一次循环
size = 3MB
计算order: 需要能容纳3MB且对齐2MB
  → 分配2MB块
remaining_size = 3MB - 2MB = 1MB

// 第二次循环  
size = 1MB
min_block_size仍然是2MB(对齐要求)
  → 分配2MB块(但只使用1MB)
remaining_size = 0

// 结果
vres->blocks:
  [Block1: 2MB @ offset X]
  [Block2: 2MB @ offset Y] (只使用1MB)

总分配: 4MB (有1MB内部碎片)
实际使用: 3MB

11.5 分配失败处理

11.5.1 失败的原因

c 复制代码
/**
 * VRAM分配可能失败的原因
 */

// 原因1: 空间不足
if (ttm_resource_manager_usage(man) > max_bytes)
    return -ENOSPC;

// 原因2: 无法找到符合条件的块
r = drm_buddy_alloc_blocks(...);
if (r == -ENOSPC) {
    // 没有足够大的空闲块
    // 或者范围限制内没有空间
}

// 原因3: 连续分配失败
if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS) {
    // 需要连续块,但VRAM碎片化
    return -ENOSPC;
}

// 原因4: Visible VRAM耗尽
if (need_visible_vram && vis_usage_too_high)
    return -ENOMEM;

11.5.2 Fallback机制

c 复制代码
/**
 * amdgpu_vram_mgr_new中的fallback逻辑
 */

// 尝试1: 连续分配
if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS) {
    pages_per_block = ~0ul;  // 尝试单个大块
    vres->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
}

r = drm_buddy_alloc_blocks(...);

// Fallback: 如果连续分配失败,尝试非连续
if (unlikely(r == -ENOSPC) && 
    pages_per_block == ~0ul &&
    !(place->flags & TTM_PL_FLAG_CONTIGUOUS)) {
    
    // 移除连续分配标志
    vres->flags &= ~DRM_BUDDY_CONTIGUOUS_ALLOCATION;
    
    // 使用2MB块
    pages_per_block = max_t(u32, 2UL << (20UL - PAGE_SHIFT),
                           tbo->page_alignment);
    
    continue;  // 重试
}

11.5.3 TTM层的Fallback

c 复制代码
/**
 * TTM提供更高层的fallback机制
 */

// placement描述多个可选内存域
struct ttm_placement placement = {
    .num_placement = 2,
    .placement = places,
};

places[0].mem_type = TTM_PL_VRAM;   // 优先VRAM
places[1].mem_type = TTM_PL_TT;     // Fallback到GTT

// TTM会依次尝试
ttm_bo_mem_space() {
    for (i = 0; i < placement->num_placement; i++) {
        r = man->func->alloc(...);  // 尝试VRAM
        if (!r)
            return 0;
    }
    // VRAM失败,尝试GTT
}

11.5.4 错误处理和清理

c 复制代码
/**
 * amdgpu_vram_mgr_new的错误路径
 */

error_free_blocks:
    // 释放已分配的块
    drm_buddy_free_list(mm, &vres->blocks, 0);
    mutex_unlock(&mgr->lock);

error_fini:
    // 清理TTM资源
    ttm_resource_fini(man, &vres->base);
    
    // 释放vres结构
    kfree(vres);
    
    return r;

/**
 * 调用者的处理
 */
int amdgpu_bo_create(...) {
    r = ttm_bo_init_reserved(...);
    if (r) {
        // 分配失败
        if (r == -ENOSPC) {
            dev_err(adev->dev, "VRAM allocation failed: out of space\n");
        } else if (r == -ENOMEM) {
            dev_err(adev->dev, "VRAM allocation failed: no memory\n");
        }
        
        // 尝试其他内存域或返回错误
        return r;
    }
}

11.5.5 实际应用中的处理

c 复制代码
/**
 * 用户态应用的错误处理
 */

// Vulkan示例
VkResult vkAllocateMemory(...) {
    int ret = amdgpu_bo_create(...);
    
    if (ret == -ENOSPC) {
        // VRAM不足
        // 1. 尝试回收不用的资源
        // 2. 尝试使用GTT内存
        // 3. 降低纹理质量
        // 4. 返回VK_ERROR_OUT_OF_DEVICE_MEMORY
    }
}

/**
 * 驱动内部的处理
 */
int amdgpu_init_firmware_buffer(...) {
    // 内核分配必须成功
    ret = amdgpu_bo_create_kernel(...);
    if (ret) {
        dev_err(adev->dev, "Failed to allocate firmware buffer\n");
        return ret;  // 驱动初始化失败
    }
}

11.6 释放流程

11.6.1 完整释放链

c 复制代码
/**
 * VRAM释放的调用链
 */

// 1. 用户态关闭GEM对象
close(gem_fd)
  ↓
drm_gem_object_release()
  ↓
// 2. BO引用计数归零
amdgpu_bo_unref()
  ↓
ttm_bo_put()
  ↓
ttm_bo_release()
  ↓
ttm_resource_free()
  ↓
// 3. 资源管理器释放
man->func->free()
  ↓
amdgpu_vram_mgr_del()           // amdgpu_vram_mgr.c
  ↓
// 4. Buddy释放
drm_buddy_free_list()           // drm_buddy.c
  ↓
drm_buddy_free_block()
  ↓
__drm_buddy_free()

11.6.2 amdgpu_vram_mgr_del函数

c 复制代码
/**
 * amdgpu_vram_mgr_del - 释放VRAM资源
 * @man: TTM资源管理器
 * @res: 要释放的资源
 */
static void amdgpu_vram_mgr_del(struct ttm_resource_manager *man,
                               struct ttm_resource *res)
{
    struct amdgpu_vram_mgr_resource *vres = 
        to_amdgpu_vram_mgr_resource(res);
    struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
    struct amdgpu_device *adev = to_amdgpu_device(mgr);
    struct drm_buddy *mm = &mgr->mm;
    struct drm_buddy_block *block;
    uint64_t vis_usage = 0;

    mutex_lock(&mgr->lock);

    // 1. 从分配列表中移除
    list_del(&vres->vres_node);
    memset(&vres->task, 0, sizeof(vres->task));

    // 2. 计算释放的visible VRAM
    list_for_each_entry(block, &vres->blocks, link)
        vis_usage += amdgpu_vram_mgr_vis_size(adev, block);

    // 3. 释放块到Buddy
    if (vres->flags & DRM_BUDDY_CLEAR_ALLOCATION)
        drm_buddy_free_list(mm, &vres->blocks, DRM_BUDDY_CLEARED);
    else
        drm_buddy_free_list(mm, &vres->blocks, 0);

    mutex_unlock(&mgr->lock);

    // 4. 更新统计
    atomic64_sub(vis_usage, &mgr->vis_usage);

    // 5. 清理TTM资源
    ttm_resource_fini(man, res);
    
    // 6. 释放vres结构
    kfree(vres);
}

11.7 实战示例

11.7.1 追踪一次实际分配

bash 复制代码
# 启用调试
echo 0x1ff > /sys/module/drm/parameters/debug
echo 1 > /sys/module/amdgpu/parameters/debug_mask

# 运行应用
vulkaninfo

# 查看分配信息
cat /sys/kernel/debug/dri/0/amdgpu_vram_mm

# 示例输出
VRAM mm
man size:8388608 pages, page size:4096 bytes
allocated size:2097152 pages
free size:6291456 pages

Order 0 (4KB):    1024 blocks
Order 1 (8KB):    512 blocks
Order 2 (16KB):   256 blocks
...
Order 11 (8MB):   8 blocks
Order 12 (16MB):  4 blocks

11.7.2 Debug打印分析

c 复制代码
/**
 * 添加调试打印
 */
static int amdgpu_vram_mgr_new(...) {
    ...
    
    pr_info("VRAM alloc: pid=%d comm=%s size=%lluKB align=%lluKB flags=0x%lx\n",
            current->pid, current->comm,
            size >> 10, min_block_size >> 10, vres->flags);
    
    r = drm_buddy_alloc_blocks(...);
    
    if (!r) {
        pr_info("VRAM alloc: success, blocks=%d\n",
                count_blocks(&vres->blocks));
    } else {
        pr_err("VRAM alloc: failed, error=%d\n", r);
    }
    
    ...
}

/**
 * 示例输出
 */
[  10.123] VRAM alloc: pid=1234 comm=vulkan size=16384KB align=2048KB flags=0x2
[  10.125] VRAM alloc: success, blocks=2
[  15.456] VRAM alloc: pid=1234 comm=vulkan size=8192KB align=256KB flags=0x0
[  15.458] VRAM alloc: success, blocks=1

🎯 最佳实践

✅ 应该做的

  1. 合理设置分配标志

    c 复制代码
    // ✓ Display buffer需要CPU访问
    flags = AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED;
    
    // ✓ Texture不需要CPU访问
    flags = AMDGPU_GEM_CREATE_NO_CPU_ACCESS;
  2. 使用TOPDOWN分配

    c 复制代码
    // ✓ 普通分配从高地址开始
    places[0].flags = TTM_PL_FLAG_TOPDOWN;
    
    // 保留低地址(Visible VRAM)给Display使用
  3. 正确处理分配失败

    c 复制代码
    // ✓ 提供fallback路径
    ret = amdgpu_bo_create(adev, &bp, &bo);
    if (ret == -ENOSPC) {
        // 尝试GTT内存
        bp.domain = AMDGPU_GEM_DOMAIN_GTT;
        ret = amdgpu_bo_create(adev, &bp, &bo);
    }

❌ 不应该做的

  1. 不要过度使用连续分配

    c 复制代码
    // ❌ 不必要的连续分配
    flags = AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
    // 增加碎片,降低成功率
    
    // ✓ 只在必要时使用
    if (hardware_requires_contiguous)
        flags = AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
  2. 不要忽略分配失败

    c 复制代码
    // ❌ 忽略错误
    amdgpu_bo_create(adev, &bp, &bo);
    // 继续使用bo → 可能崩溃
    
    // ✓ 检查返回值
    if (amdgpu_bo_create(adev, &bp, &bo)) {
        handle_error();
        return -ENOMEM;
    }

💡 实践练习

练习1: 追踪分配流程

编写一个内核模块,追踪VRAM分配:

c 复制代码
/**
 * 追踪VRAM分配的探针
 */
static int trace_vram_alloc(struct amdgpu_device *adev,
                           u64 size, u64 alignment)
{
    // TODO: 实现此函数
    // 提示:
    // 1. 记录分配前的空闲空间
    // 2. 调用amdgpu_bo_create
    // 3. 记录分配后的状态
    // 4. 打印统计信息
}

练习2: 模拟分配失败

模拟VRAM不足的情况:

c 复制代码
/**
 * 测试分配失败处理
 */
void test_allocation_failure(struct amdgpu_device *adev)
{
    // TODO: 实现此函数
    // 1. 分配大量小块占用VRAM
    // 2. 尝试分配大块(应该失败)
    // 3. 释放一些小块
    // 4. 重试大块分配
}

📚 本章总结

核心要点

  1. 分配来源

    • 用户态GEM对象创建
    • 内核内部分配
    • Display相关分配
  2. 调用链

    • ioctl → GEM → BO → TTM → VRAM Mgr → Buddy
    • 每层有不同的职责和抽象
  3. 关键函数

    • amdgpu_vram_mgr_init() - 初始化
    • amdgpu_vram_mgr_new() - 分配
    • amdgpu_vram_mgr_del() - 释放
  4. 错误处理

    • Fallback到GTT
    • 连续→非连续降级
    • 资源清理

关键数据结构

c 复制代码
struct amdgpu_vram_mgr {
    struct drm_buddy mm;           // Buddy分配器
    struct mutex lock;             // 互斥锁
    struct list_head allocated_vres_list;  // 分配列表
    atomic64_t vis_usage;          // Visible VRAM使用
};

struct amdgpu_vram_mgr_resource {
    struct list_head blocks;       // Buddy块链表
    unsigned long flags;           // 分配标志
};

🔗 导航


相关推荐
DeeplyMind15 天前
06 - Buddy分配算法
drm_buddy
DeeplyMind16 天前
07 - Buddy释放与合并算法
drm_buddy
DeeplyMind19 天前
05 - AMDGPU中的VRAM管理器
drm·amdgpu·drm_buddy·ttm
DeeplyMind23 天前
04 - 核心数据结构详解
drm·drm_buddy·vram
DeeplyMind25 天前
03 - DRM子系统与AMDGPU架构
drm·drm_buddy·vram分配
DeeplyMind3 个月前
Linux DRM 内存管理子系统的概念关系理解:gem、ttm、drm_buddy
drm·tm·drm_buddy