参考内核版本:Linux-6.1.8

HMM

待更新......

dumb buffer create/map

在AMDGPU的Graphics业务中,用到了GEM(Graphics Execution Manager),它是用于内核内部管理图形缓冲区,用户空间进程可以通过GEM来创建、处理和销毁GPU中视频内容的内存对象。如下是AMD GPU注册得drm_driver回调

static const struct drm_driver amdgpu_kms_driver = {.......dumb_create = amdgpu_mode_dumb_create,.dumb_map_offset = amdgpu_mode_dumb_mmap,......
};

其中:DRM_IOCTL_MODE_CREATE_DUMB中调用amdgpu_mode_dumb_create来分配dumb buffer。这里需要注意amdgpu_mode_dumb_create最终仍然是调用了TTM来分配内存。

int drm_mode_create_dumb(struct drm_device *dev,struct drm_mode_create_dumb *args,struct drm_file *file_priv)
{......return dev->driver->dumb_create(file_priv, dev, args);
}
......
int drm_mode_create_dumb_ioctl(struct drm_device *dev,void *data, struct drm_file *file_priv)
{return drm_mode_create_dumb(dev, data, file_priv);
}

当然除了使用TTM外,drm中还提供了基于CMA(Contiguous Memory Allocator)的GEM的API来分配和释放内存,如下所示,另外这这个场景中的dumb该怎么理解?只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景?

drm_gem_cma_dumb_create() - create a dumb buffer object
drm_gem_cma_free() - free resources associated with a CMA GEM
.......

对于dumb buffer操作还提供了dumb_map_offset,看代码可以发现其在DRM_IOCTL_MODE_MAP_DUMP的ioctl中被调到。


int drm_mode_mmap_dumb_ioctl(struct drm_device *dev,void *data, struct drm_file *file_priv)
{struct drm_mode_map_dumb *args = data;if (!dev->driver->dumb_create)return -ENOSYS;if (dev->driver->dumb_map_offset)return dev->driver->dumb_map_offset(file_priv, dev,args->handle,&args->offset);elsereturn drm_gem_dumb_map_offset(file_priv, dev, args->handle,&args->offset);
}

那为什么需要这个ioctl呢?假设用户DRM_IOCTL_MODE_CREATE_DUMB申请了多个dumb buffer,那么要对哪一个做mmap()系统调用操作呢?如何才能让mmap()知道对哪一个dumb buffer进程操作呢?只能通过其offset参数来workaround来告诉mmap()具体要操作哪个。而上面这个DRM_IOCTL_MODE_MAP_DUMP就是为了为了完成这个工作的,传入create dumb buffer的handle后,它最终会返回一个offset(DRM文档称其为fake offset)到用户,然后用户拿这个offset就可以进程mmap()操作了。也由此可见用户mmap()时传进去的offset并不是真正的内存偏移量,而是一个gem object的索引值。通过该索引,drm驱动就可以准确找到当前要操作的是哪个gem object,进而获得相对应的物理buffer, 并对其做真正的mmap()操作。

amdkfd memory alloc/map

不仅在上面的AMD Graphics业务,在AMD Compute业务中也同样用到了同样的机制,其代码实现思路与上述相似:

/* AMDKFD_IOC_ALLOC_MEMORY_OF_GPU时通过TTM来分配一块物理地址范围(只是做标记),
最终也会返回给用户一个handle, 然后AMDKFD_IOC_MAP_MEMORY_TO_GPU会根据
handle找到对应的物理地址范围并为GPU建立页表,这样GPU就能访问这块内存了。
但是当CPU想访问的话怎么操作?只需要在用户层open renderDx,接着在mmap()时,
传入AMDKFD_IOC_ALLOC_MEMORY_OF_GPU返回的offset即可 */AMDKFD_IOCTL_DEF(AMDKFD_IOC_ALLOC_MEMORY_OF_GPU,kfd_ioctl_alloc_memory_of_gpu, 0),AMDKFD_IOCTL_DEF(AMDKFD_IOC_MAP_MEMORY_TO_GPU,kfd_ioctl_map_memory_to_gpu, 0),/* 在amdkfd中有二种方式来为GPU建立页表,一种是通过CPU来建立,一种是过DMA来建立。*/amdgpu_vm_cpu_update - helper to update page tables via CPUamdgpu_vm_sdma_update - execute VM update

对于GTT和VRAM这二种类型的内存,在通过AMDKFD_IOC_ALLOC_MEMORY_OF_GPU分配BO的时候可以调用drm_vma_offset_add 将bo的范围加载到ttm_device(bdev) 红黑树,然后得到一个fake offset,并返回给用户层。

然后在用户层mmap()时传入renderDx的fd和这个offset。这样mmap()就会调用到drm_driver所注册的mmap回调(fb->mmap),在该回调中可以注册vm_ops的回调。这样当在用户层访问 mmap() 返回的虚拟地址va时候,会发生缺页异常,然后会调用 vma_ops->fault 函数,完成CPU侧的映射。

prime

在AMDGPU的Graphics业务中用到了prime,prime在DRM中其实是一种buffer共享机制,它是基于dma-buf实现的

static const struct drm_driver amdgpu_kms_driver = {.......prime_handle_to_fd = drm_gem_prime_handle_to_fd,.prime_fd_to_handle = drm_gem_prime_fd_to_handle,.gem_prime_import = amdgpu_gem_prime_import,.gem_prime_mmap = drm_gem_prime_mmap,......
};

除了Graphics业务,在Compute业务(amdkfd)也同样使用了dma_buf。

 AMDKFD_IOCTL_DEF(AMDKFD_IOC_GET_DMABUF_INFO,kfd_ioctl_get_dmabuf_info, 0),AMDKFD_IOCTL_DEF(AMDKFD_IOC_IMPORT_DMABUF,kfd_ioctl_import_dmabuf, 0),

mmu noitify

从下面截取Linux-6.0内核版本的AMD GPU内核代码,从AMD提供了amdgpu_mn_unregister和amdgpu_mn_unregister的二个API(一般用在UMD userptr类型的memory中)实现可以看出,在HSA(Compute,for amdkfd)和GFX(Graphics)业务中都注册了mmu_notify回调。当使用到的CPU页表发生改变时都会调用该回调,如果使用时userpter类型的memory都是pinned(注意pin意思是不会被swap出去,而reserved是预留下来别人申请不到)的,cpu页表就不会变,也就调不到这里了,若是pin住的话,除非是页面的一些读写权限属性的改变才会调用。

/*** amdgpu_mn_invalidate_gfx - callback to notify about mm change* @mni: the range (mm) is about to update* @range: details on the invalidation* @cur_seq: Value to pass to mmu_interval_set_seq()** Block for operations on BOs to finish and mark pages as accessed and* potentially dirty.*/
static bool amdgpu_mn_invalidate_gfx(struct mmu_interval_notifier *mni,const struct mmu_notifier_range *range,unsigned long cur_seq)
{struct amdgpu_bo *bo = container_of(mni, struct amdgpu_bo, notifier);struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);long r;if (!mmu_notifier_range_blockable(range))return false;mutex_lock(&adev->notifier_lock);mmu_interval_set_seq(mni, cur_seq);r = dma_resv_wait_timeout(bo->tbo.base.resv, DMA_RESV_USAGE_BOOKKEEP,false, MAX_SCHEDULE_TIMEOUT);mutex_unlock(&adev->notifier_lock);if (r <= 0)DRM_ERROR("(%ld) failed to wait for user bo\n", r);return true;
}/*** amdgpu_mn_invalidate_hsa - callback to notify about mm change** @mni: the range (mm) is about to update* @range: details on the invalidation* @cur_seq: Value to pass to mmu_interval_set_seq()** We temporarily evict the BO attached to this range. This necessitates* evicting all user-mode queues of the process.*/
static bool amdgpu_mn_invalidate_hsa(struct mmu_interval_notifier *mni,const struct mmu_notifier_range *range,unsigned long cur_seq)
{struct amdgpu_bo *bo = container_of(mni, struct amdgpu_bo, notifier);struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);if (!mmu_notifier_range_blockable(range))return false;mutex_lock(&adev->notifier_lock);mmu_interval_set_seq(mni, cur_seq);amdgpu_amdkfd_evict_userptr(bo->kfd_bo, bo->notifier.mm);mutex_unlock(&adev->notifier_lock);return true;
}static const struct mmu_interval_notifier_ops amdgpu_mn_hsa_ops = {.invalidate = amdgpu_mn_invalidate_hsa,
};static const struct mmu_interval_notifier_ops amdgpu_mn_gfx_ops = {.invalidate = amdgpu_mn_invalidate_gfx,
};/*** amdgpu_mn_register - register a BO for notifier updates** @bo: amdgpu buffer object* @addr: userptr addr we should monitor** Registers a mmu_notifier for the given BO at the specified address.* Returns 0 on success, -ERRNO if anything goes wrong.*/
int amdgpu_mn_register(struct amdgpu_bo *bo, unsigned long addr)
{if (bo->kfd_bo)return mmu_interval_notifier_insert(&bo->notifier, current->mm,addr, amdgpu_bo_size(bo),&amdgpu_mn_hsa_ops);return mmu_interval_notifier_insert(&bo->notifier, current->mm, addr,amdgpu_bo_size(bo),&amdgpu_mn_gfx_ops);
}/*** amdgpu_mn_unregister - unregister a BO for notifier updates** @bo: amdgpu buffer object** Remove any registration of mmu notifier updates from the buffer object.*/
void amdgpu_mn_unregister(struct amdgpu_bo *bo)
{if (!bo->notifier.mm)return;mmu_interval_notifier_remove(&bo->notifier);bo->notifier.mm = NULL;
}
enum mmu_notifier_event {MMU_NOTIFY_UNMAP = 0,MMU_NOTIFY_CLEAR,MMU_NOTIFY_PROTECTION_VMA,MMU_NOTIFY_PROTECTION_PAGE,MMU_NOTIFY_SOFT_DIRTY,MMU_NOTIFY_RELEASE,MMU_NOTIFY_MIGRATE,MMU_NOTIFY_EXCLUSIVE,
};

其中若用户想对userpter类型的memory做pin操作可以,可以自己使用内核提供的get_user_pages或get_user_pages_fast来执行pin操作。会增加PAGE的计数(调用get_page()),所以此PAGE不会被swap out的。

long get_user_pages(unsigned long start, unsigned long nr_pages,unsigned int gup_flags, struct page **pages,struct vm_area_struct **vmas)
{......
}
int get_user_pages_fast(unsigned long start, int nr_pages,unsigned int gup_flags, struct page **pages)
{......
}

比如,Linux内核态使用get_user_pages_fast将用户态进程使用的内存在内核态分配一块虚拟地址进行页表映射,此时相当于有两个页表映射同一块物理内存,如果此时用户态进程将该页表换出 内核访问的这片内存不就出错了 如何保证内核使用完之前内存不被换出呢?

答:get_user_pages_fast() 会通过调用get_page()增加PAGE的计数,所以此PAGE不会被换出的(这里需要注意没有显式get_page()的page都有可能被迁移)。

最简单的情况是try_to_unmap()是会返回SWAP_AGAIN。这个PAGE就又被放回LRU了。

就是PTE已经被换成SWAP了,也没有关系。下次PAGE FAULT时,再从SWAP CACHE中找出这个页。

TLB invalidate

flush tlb/使tlb条目无效,对Driver来说只需要配置寄存器即可,比如对于compute业务的amdkfd来说直接使用如下函数来invalidate tbl。

void kfd_flush_tlb(struct kfd_process_device *pdd, enum TLB_FLUSH_TYPE type)

IOMMU/ATS

        大多数GPU设备一般自身是带有类似MMU的地址转换单元的,即使不使用IOMMU也是可以正常进行GVA到SPA之间的地址转换的。所以可以在这类GPU设备中不使能IOMMU/ATS。

使用IOMMU/ATS来进程GVA到SPA的转换

IOMMU用途之一便是对设备向系统内存发起的DMA进程地址转换,IOMMU支持legacy mode和scalable mode,其中scalable mode可以支持比较高级的PASID和nested translate功能(但是目前大部分主板都不支持scalable mode?)

其实只需要IOMMU就已经可以正常工作了,为什么还要开ATS(Address Translation Service)呢?ATS的提出主要是为了缓解iommu硬件iova转换的压力。尤其是当设备上有大量的DMA working sets时,ATS能够有效减少因为PCIe链路压力过大导致的设备性能抖动。ATS由位于PCIe设备上的ATC(Address Translation Cache) 和 Translaion Agent(TA,通常也是位于iommu硬件上)组成。ATC的作用可以跟cpu端的TLB来做类比,因此它也经常被称为Device TLB。ATC里面存储的主要是iova到hpa的映射关系,当ATC发生miss的时候需要跟TA之间进行一些交互。

如何在驱动中使能IOMMU/ATS?

/* device_iommu_mapped - Returns true when the device DMA is
translated by an IOMMU */
if (device_iommu_mapped(&mdev->pdev->dev)) {pr_info("%s: Non-passthrough IOMMU detected, Enable ATS!\n", __func__);mdev->ats_enable = true;
} else {pr_info("%s: Disable ATS!\n", __func__);mdev->ats_enable = false;
}...........................................................................
/* enable/disable ATS */
if (mdev->ats_enable)pci_enable_ats(mdev->pdev, PAGE_SHIFT);
elsepci_disable_ats(mdev->pdev); ...........................................................................
/* GPU自身页表PTE设置、地址转换单元的设置等,比如设置不对GVA进行翻译,直接把GVA发往PCIe,让IOMMU/ATS来翻译 */
if (mdev->ats_enable) {//PTE设置;//地址转换单元相关寄存器设置;
}

reference:

Linux x86-64 IOMMU详解(二)——SWIOTLB(软件IOMMU)_C is My Best Friend的博客-CSDN博客_swiotlb

https://lists.freedesktop.org/archives/amd-gfx/2021-January/057943.html

AMD GPU内存管理(1):概览相关推荐

  1. AMD GPU电源管理

    GPU有的子IP的电源管理是可以由FW独立完成,有的是需要FW和Driver配和完成的.比如:Video Codec的Enter/Exit DeepSleep的电源管理工作,一般都是由FW独立完成不需 ...

  2. nvidia cuda windows下gpu内存管理

    mxnet 出现错误 RuntimeError: CUDA out of memory. Tried to allocate windows下可以这样做:打开cmd窗口,输入nvidai-smi查看显 ...

  3. 深度学习中的内存管理问题研究综述

    点击上方蓝字关注我们 深度学习中的内存管理问题研究综述 马玮良1,2, 彭轩1,2, 熊倩1,2, 石宣化1,2, 金海1,2 1 华中科技大学计算机科学与技术学院,湖北 武汉 430074 2 华中 ...

  4. linux异构内存,Linux内核添加异构内存管理(HMM)将带来加速GPU的新方式,还有可能带来其他类型的机器学习硬件。...

    一项旨在让机器学习或其他基于GPU的应用得以大幅提升性能的内存管理功能已开发了很长一段时间,不过现在它即将进入到Linux内核的某下一个版本中. 异构内存管理(HMM)让设备的驱动程序可以为受制于自身 ...

  5. 【GPU】Nvidia CUDA 编程基础教程——利用基本的 CUDA 内存管理技术来优化加速应用程序

    博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发. 在5G早期负责终端数据业务层.核心网相关的开发工作,目前牵头6G ...

  6. [人工智能-深度学习-39]:环境搭建 - 训练主机硬件选择全指南(CPU/GPU/内存/硬盘/电源)

    作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:https://blog.csdn.net/HiWangWenBing/article/detai ...

  7. chrome/chromium 上的内存管理模块-allocator介绍

    本文介绍chromium在不同平台上 malloc/new 是如何封装调用的. 从代码中很容易发现,chromium的基础代码并不是仅仅使用"malloc"来分配内存 例如:    ...

  8. 深入理解Linux内存管理(0.3)

    学习方法论 写作原则 标题括号中的数字代表完成度与完善度 0.0-1.0 代表完成度,1.1-1.5 代表完善度 0.0 :还没开始写 0.1 :写了一个简介 0.3 :写了一小部分内容 0.5 :写 ...

  9. Android内存管理机制官方详解文档

    很早之前写过一篇<Android内存管理机制详解>点击量已7万+,现把Google官方文档整理输出一下,供各位参考. 一.内存管理概览 Android 运行时 (ART) 和 Dalvik ...

最新文章

  1. [置顶]2010年东北大学ACM程序设计竞赛冬季校赛题解
  2. java keytool 导入证书_java - Keytool无法导入证书 - SO中文参考 - www.soinside.com
  3. java学习笔记16--I/O流和文件
  4. python web框架django_Python Web应用框架 Django
  5. [LeetCode] Invert Binary Tree - 二叉树翻转系列问题
  6. 数据中心细节_当细节很重要时数据不平衡
  7. curl命令使用介绍
  8. 信息学奥赛一本通(1235:输出前k大的数)——堆排序
  9. 数控系统数据采集协同架构,集成马扎克(mazak)、西门子(Siemens)、海德汉(heidenhain)、广数、凯恩帝(knd)、三菱、海德汉、兄弟、哈斯、宝元、新代、发那科(Fanuc)、华中
  10. python word 表格复制_python实现同一word中的表格分别提取并保存到不同文件下
  11. 新版百度地图的覆盖物描述
  12. 【STM32】HAL库 SPI DMA UART驱动开发
  13. python公司分析_Python分析6000家破产IT公司
  14. Kratos,RPC服务调用过程
  15. 转载 如何用示波器进行UART串口数据分析
  16. 有没有计算机的毕业设计选题好点子?
  17. Win10不小心删除环境变量怎么恢复
  18. Android深色模式适配原理分析,android应用开发
  19. 当配置微信网页授权域名出现下载的文件不匹配的问题
  20. 《酒店管理系统》项目总结

热门文章

  1. 以太坊合并 你需要熟悉的两个PoS概念
  2. 功能测试和自动化测试的优缺点
  3. 周红c语言答案,C语言程序设计期末复习.ppt
  4. VUE element-ui之上传身份证照片正反面详细代码
  5. 国内CDN现状与美国对比
  6. Tiny Video Networks翻译
  7. android 按钮回弹效果,Android仿IOS回弹效果 支持任何控件
  8. 每月成长一步步_如何搭建个人云盘(NextCloud搭建篇,适用于Ubuntu)
  9. 本周三、五,武汉专场丨华为鲲鹏校企人才双选会火热来袭!
  10. centos7搭建主从DNS服务器