相关参考:

Linux中显存管理使用方法(使用)

DRM Memory Management(原理)

现代Linux系统需要⼤量的图形内存来动态存储帧缓冲区、纹理、顶点和其他与图形相关的数据,因此有效管理图形内存对于图形堆栈⾄关重要,并且在DRM基础架构中发挥着核⼼作⽤。

DRM核⼼包括两个内存管理器,即 转换表映射(TTM)和图形执⾏管理器(GEM)。

TTM是第⼀个开发的DRM内存管理器,并试图成为⼀种“千篇⼀律”的解决⽅案。它提供了⼀个单⼀
的⽤⼾空间API,可以满⾜所有硬件的需求,同时⽀持统⼀内存体系结构(UMA)设备和具
有专⽤视频RAM的设备(即⼤多数离散视频卡)。这导致了⼀个庞⼤,复杂的代码⽚段,结
果证明这些代码难以⽤于驱动程序开发。

GEM最初是由英特尔赞助的项⽬,以应对TTM的复杂性。它的设计理念是完全不同的:GEM 没有为每个与图形内存相关的问题提供解决⽅案,⽽是确定了驱动程序之间的通⽤代码,并创建了⼀个共享它的⽀持库。

与TTM相⽐,GEM的初始化和执⾏要求更简单,但它不具有视频RAM管理功能,因此仅限于NUMA设备。


TTM-The Translation Table Manager

已经不使用喽,放弃搬砖


GEM-the Graphics Execution Manage

GEM向用户空间公开了一组标准的与内存相关的操作,向驱动程序公开了一组帮助函数,并让驱动程序使用自己的私有API实现特定于硬件的操作

GEM 用户空间API:提供了一些列用户空间API的规则(虽然有些过时,但很好地概述了GEM API原则)

image.png

内核文档中描述:从根本上讲,GEM涉及以下几种操作

  • 内存分配和释放
  • 命令执行
  • 执行命令时的光圈管理(???)

缓冲区对象分配相对简单,主要由Linux的shmem层提供,后者提供了用于备份每个对象的内存。

GEM由三部分组成:

  • DUMB:只支持连续物理内存的buffer类型,基于kernel中通用CMA API实现,多用于小分辨率简单场景
  • PRIME:连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用 于大内存复杂场景
  • fence:buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题

DUMP和PRIME的来源可以参考:关于 DRM 中 DUMB 和 PRIME 名字的由来

image.png

GEM Initialization

首先 struct drm_driverdriver_features必须包含DRIVER_GEM属性

DRM core会在load之前初始化GEM core。

  1. drm_dev_alloc
  2. drm_dev_init
  3. if(DRIVER_GEM)drm_gem_init(dev); // GEM属性的要初始化gem,还记得这个吧

然后会创建 DRM Memory Manager objec 提供用于对象分配的address space pool。

在KMS配置中,如果硬件需要,驱动程序需要在核心GEM初始化之后分配和初始化命令环缓冲区。UMA设备通常有所谓的stolen 内存区域,它为初始framebuffer和设备所需的较大的连续内存区域提供空间。这个空间通常不是由GEM管理的,必须单独初始化到它自己的DRM MM对象中

GEM Objects Creation

struct drm_gem_object 表示一个GEM objects instance。

初始化方式一:
驱动调用 drm_gem_object_init() 进行初始化。
驱动负责调用shmem_read_mapping_page_gfp()做实际物理页面的申请,初始化GEM对象时驱动可以决定申请页面,或者延迟到需要内存时再申请
需要内存时是指:用户态访问内存发生缺页中断,或是驱动需要启动DMA用到这段内存。

初始化方式二:
匿名页面内存并不是一定需要的,嵌入式设备一般需要物理连续系统内存,驱动可以创建GEM对象儿没有shmfs,并调用 drm_gem_private_object_init() 初始化此私有GEM对象,不过这样的话,私有GEM对象的存储管理就需要驱动自行完成。

再次强调:drm_gem_object_init 函数不进行分配内存,只是创建shmfs,需要驱动自己分配内存

  1. struct drm_gem_priv_object{ // 私有GEM结构体
  2. .....
  3. stuct drm_gem_object gem_obj;
  4. ..... // 自己的GEM拓展 信息
  5. };
  6. drm_gem_object_init
  7. filp = shmem_file_setup("drm mm object", size, VM_NORESERVE); // 申请一个共享内存
  8. obj->filp = filp;
  9. or
  10. drm_gem_private_object_init() // 嵌入式设备分配连续内存

可以看到,GEM分配一个匿名的共享内存,然后struct drm_gem_object->filp指向这里,当图形硬件直接使用系统内存时,这块内存用作对象的主存储,或者作为后备存储。

GEM对象的生命周期

drm_gem_object_get() 加数-需要加锁
drm_gem_object_put() 减数-需要加锁
drm_gem_object_put_unlocked()也可以不持锁操作,当引用计数为0,则销毁

GEM Objects Mapping

drm_gem_create_mmap_offset
用户为了使用GEM对象内存,所以必须mmap映射到用户空间

mmap系统调用不能直接映射GEM对象,因为GEM对象没有自己的文件描述符, 所以:

  • 用户自己实现do_mmap函数(不推荐)
  • DRM文件描述符使用mmap直接映射GEM对象(推荐)

这里就用drm提供的mmap映射,
GEM对象没有文件描述符,但是通过mmap(void*addr,size_t length,int port,int flags,int fd, off_t offset)中的offset参数

为了能识别这个offset,驱动必须先对这个GEM对象执行了drm_gem_create_mmap_offset(),这样得到的offset值需要先通过驱动指定的方式传给用户态程序,然后就可以通过mmap传下来 。

GEM对象命名和句柄-handle

drm_gem_handle_create()创建GEM对象handle-32 bit int类型,获取一个局部唯一handle,
通过drm_gem_handle_delete()来删除,
drm_gem_object_lookup()可以由handle找出对应的GEM对象。
handle仅是一个对GEM对象的引用,在handle销毁时减去这一引用

  1. amdgpu_gem_create_ioctl
  2. int r = drm_gem_handle_create(filp, gobj, &handle);
  3. /* drop reference from allocate - handle holds it now */
  4. drm_gem_object_put_unlocked(gobj);

GEM代码分析

drm_driver中的gem相关接口

  1. struct drm_driver {
  2. // GEM相关
  3. u32 driver_features; // 需要使能DRIVER_GEM
  4. // 以下函数已被drm_gem_object_funcs 替代,但大部分还是这么用的,所以列出来
  5. #if 0.
  6. // drm_gem_objects的析构函数,已不使用
  7. gem_free_object / gem_free_object_unlocked
  8. gem_open_object / gem_close_object
  9. gem_print_info
  10. #endif
  11. gem_create_object // *用于分配GEM对象结构,供CMASHMEM GEM帮助程序使用。
  12. // prime buffer操作函数
  13. prime_handle_to_fd / prime_fd_to_handle // prime基于DMA-BUFfdhandle转换 ***
  14. gem_prime_export / gem_prime_import // DMA-BUFexportimport
  15. gem_prime_pin / gem_prime_unpin // TBD ???
  16. gem_prime_get_sg_table / gem_prime_import_sg_table
  17. gem_prime_vmap / gem_prime_vunmap / gem_prime_mmap
  18. // dumb buffer使用
  19. dumb_create / dumb_map_offset / dumb_destroy
  20. };
  21. // 5.7版本建议将部分操作接口分离出来:
  22. struct drm_gem_object {
  23. ......
  24. /* Optional GEM object functions. If this is set, it will be used instead of the
  25. * corresponding &drm_driver GEM callbacks. */
  26. const struct drm_gem_object_funcs *funcs; // 新版本推荐使用该方法,替代drm_driver
  27. }
  28. struct drm_gem_object_funcs {
  29. void (*free)(struct drm_gem_object *obj);
  30. int (*open)(struct drm_gem_object *obj, struct drm_file *file);
  31. void (*close)(struct drm_gem_object *obj, struct drm_file *file);
  32. void (*print_info)(struct drm_printer *p, unsigned int indent,
  33. const struct drm_gem_object *obj);
  34. struct dma_buf *(*export)(struct drm_gem_object *obj, int flags);
  35. int (*pin)(struct drm_gem_object *obj);
  36. void (*unpin)(struct drm_gem_object *obj);
  37. struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
  38. void *(*vmap)(struct drm_gem_object *obj);
  39. void (*vunmap)(struct drm_gem_object *obj, void *vaddr);
  40. int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
  41. const struct vm_operations_struct *vm_ops; // 这是可选的,但对于mmap支持是必需的。
  42. }
  43. /*** 这部分推荐参考: struct drm_gem_object_funcs drm_cma_gem_default_funcs ***/

gem初始化drm_gem_init

  1. struct drm_vma_offset_manager {
  2. rwlock_t vm_lock;
  3. struct drm_mm vm_addr_space_mm;
  4. };
  5. drm_dev_init->
  6. drm_gem_init
  7. struct drm_vma_offset_manager *vma_offset_manager = kzalloc;
  8. dev->vma_offset_manager = vma_offset_manager
  9. // 管理到用户空间的线性映射 TBD ???
  10. drm_vma_offset_manager_init(vma_offset_manager, DRM_FILE_PAGE_OFFSET_START, DRM_FILE_PAGE_OFFSET_SIZE);
  11. // #define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFFUL >> PAGE_SHIFT) + 1) // 1M
  12. // #define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFFUL >> PAGE_SHIFT) * 256) // 255M
  13. // start is 0x100000, offset is 0xfffff00
  14. rwlock_init(&mgr->vm_lock);
  15. drm_mm_init(&mgr->vm_addr_space_mm, page_offset, size); // initialize a drm-mm allocator

GPU的内存管理和使用-实战

看了上边的,还是一脸懵逼:

  • 显存怎么用?
  • 显存的MMU?
  • 显存怎么透过PCI?
  • PC和显卡是怎么通过GEM进行数据交互的 ?
  • 自己的代码怎么使用显存?
  • 传说中的dma-buf?

还是不咋会用GPU显存吧

DUMB内存

DRM GEM 驱动程序开发

我们先看下所有文件的drm_file是怎么来的?

  1. static struct drm_driver mygem_driver = {
  2. .driver_features = DRIVER_GEM,
  3. .fops = &mygem_fops,
  4. }
  5. static const struct file_operations mygem_fops = {
  6. .owner = THIS_MODULE,
  7. .open = drm_open,
  8. .release = drm_release,
  9. .unlocked_ioctl = drm_ioctl,
  10. ...
  11. }
  12. .open = drm_open
  13. drm_open_helper(filp, minor);
  14. priv = drm_file_alloc(minor); // 就这里申请的
  15. if (dev->driver->open) {
  16. ret = dev->driver->open(dev, file); // 这里如果自定了open函数,在这里调用(不推荐)
  17. filp->private_data = priv;
  18. unlocked_ioctl = drm_ioctl,
  19. drm_ioctl
  20. is_driver_ioctl = nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END; // 允许用户自定义命令(这里用不到)
  21. ioctl = &drm_ioctls[nr];
  22. func = ioctl->func;
  23. drm_ioctl_kernel(filp, func, kdata, ioctl->flags);
  24. struct drm_file *file_priv = file->private_data; // 就是open时申请的文件
  25. retcode = func(dev, kdata, file_priv);

使用了DUMP buffer进行操作

  1. 1.创建dumb buffer
  2. drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
  3. 调用内核中
  4. drm_ioctl->drm_ioctls
  5. static const struct drm_ioctl_desc drm_ioctls[]
  6. drm_mode_create_dumb_ioctl 函数
  7. drm_mode_create_dumb(dev, data, file_priv);
  8. dev->driver->dumb_create(file_priv, dev, args);
  9. 最终调用自己driverdumb_create函数
  10. 参考代码中dumb_create = drm_gem_cma_dumb_create 直接使用 CMA helper 函数实现
  11. drm_gem_cma_dumb_create
  12. drm_gem_cma_create_with_handle
  13. drm_gem_cma_create
  14. __drm_gem_cma_create(drm, size);
  15. drm_gem_object_init(drm, gem_obj, size); // init object对象 ***
  16. drm_gem_create_mmap_offset(gem_obj); // mmap映射 ***
  17. dma_alloc_wc(drm->dev, size, &cma_obj->paddr // 这才是实际分配内存的地方
  18. // 也可以在mmap中缺页中断分配
  19. drm_gem_handle_create // 创建handle,这里会drm_gem_object_get 一次
  20. drm_gem_object_put_unlocked(gem_obj); // 释放drm_gem_handle_create的句柄
  21. ##### 申请并初始化gem_obj
  22. drm_gem_object_init(drm, gem_obj, size)
  23. drm_gem_private_object_init // initialize an allocated private GEM object
  24. filp = shmem_file_setup("drm mm object", size, VM_NORESERVE); // ???
  25. obj->filp = filp;
  26. #### mmap映射使用
  27. drm_gem_create_mmap_offset(gem_obj);
  28. #### desoty使用
  29. struct drm_driverdumb_destroy默认使用drm_gem_dumb_destroy()
  30. drm_mode_destroy_dumb_ioctl
  31. drm_mode_destroy_dumb
  32. if (dev->driver->dumb_destroy)
  33. return dev->driver->dumb_destroy(file_priv, dev, handle);
  34. else
  35. return drm_gem_dumb_destroy(file_priv, dev, handle);

handle管理

  1. ##### handle部分使用
  2. drm_gem_handle_create + drm_gem_object_put_unlocked
  3. #if 0
  4. 先看下handle,倒叙追下
  5. drm_mode_create_dumb中第二个参数:
  6. struct drm_mode_create_dumb *args,
  7. 使用drm_ioctl_kernel 函数 void *kdata参数
  8. drm_ioctl kdata = stack_kdata;
  9. 注:最后会把这部分导入用户空间:copy_to_user((void __user *)arg, kdata, out_size)
  10. #endif
  11. ***再次整理下drm_gem_handle_create代码***:*(重点:handle获取方式)
  12. { // 用户态代码
  13. struct drm_mode_create_dumb create_req = {};
  14. create_req.bpp = 32;
  15. create_req.width = 240;
  16. create_req.height = 320;
  17. drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
  18. }
  19. { // 内核态代码
  20. drm_ioctl
  21. kdata = stack_kdata; // or kdata = kmalloc(ksize, GFP_KERNEL);
  22. copy_from_user(kdata, (void __user *)arg, in_size) // 获取用户态参数
  23. drm_ioctl_kernel(filp, func, kdata, ioctl->flags)
  24. drm_mode_create_dumb(dev, data, file_priv);
  25. .....
  26. drm_gem_handle_create(); //获取到用户句柄******
  27. copy_to_user((void __user *)arg, kdata, out_size) // handle返回到用户态

handle的创建 6.GEM显存使用-2 - 图3 6.GEM显存使用-2 - 图4

map管理

  1. 2.map dumb buffer
  2. drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)
  3. 调用内核中
  4. drm_mode_mmap_dumb_ioctl
  5. 这里没实现dev->driver->dumb_map_offset,所以调用:
  6. drm_gem_dumb_map_offset
  7. 根据handle 来获取offset
  8. 3.用户空间map后操作
  9. mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset)
  10. mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
  11. ...使用
  12. 直接调用内核
  13. drm_gem_mmap
  14. 4.释放
  15. drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
  16. drm_mode_destroy_dumb_ioctl
  17. drm_mode_destroy_dumb
  18. if (dev->driver->dumb_destroy)
  19. return dev->driver->dumb_destroy(file_priv, dev, handle);
  20. else
  21. return drm_gem_dumb_destroy(file_priv, dev, handle);
  22. // struct drm_driverdumb_destroy默认使用drm_gem_dumb_destroy(),或者自定义释放函数

PRIME内存

最简单的 dma-buf 驱动程序 可以知道dma_buffer的基本过程:

dma-buf 的出现就是为了解决各个驱动之间 buffer 共享的问题,因此它本质上是 buffer 与 file 的结合,即 dma-buf 既是块物理 buffer,又是个 linux file。buffer 是内容,file 是媒介,只有通过 file 这个媒介才能实现同一 buffer 在不同驱动之间的流转
image.png
查看Linux 下DMA-BUF使用情况:

  1. sudo cat /sys/kernel/debug/dma_buf/bufinfo
  2. Dma-buf Objects:
  3. size flags mode count exp_name ino
  4. 10485760 00000000 00080005 00000003 i915 01030576
  5. Exclusive fence: i915 signaled signalled
  6. Shared fence: i915 signaled signalled
  7. Attached Devices:
  8. Total 0 devices attached
  9. .......

DMA-BUF最小化实现

  1. dma_buf_ops # dma-buf回调接口
  2. DEFINE_DMA_BUF_EXPORT_INFO # 定义struct dma_buf_export_info 对象
  3. dma_buf_export() # 初始化struct dma_buf_export_info

GEM中的DMA-BUF

  1. struct drm_device {
  2. ......
  3. /* prime: */
  4. .prime_handle_to_fd = drm_gem_prime_handle_to_fd
  5. .prime_fd_to_handle = drm_gem_prime_fd_to_handle
  6. .gem_prime_export // export GEM -> dmabuf 自定义dma_buf的export和import接口
  7. .gem_prime_import // import dmabuf -> GEM
  8. };
  9. // drm_ioctl.c中
  10. DRM_IOCTL_DEF(DRM_IOCTL_PRIME_HANDLE_TO_FD, drm_prime_handle_to_fd_ioctl, DRM_RENDER_ALLOW),
  11. DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_RENDER_ALLOW),
  12. drm_prime_handle_to_fd_ioctl
  13. struct drm_driver->prime_handle_to_fd(); // 调用自己实现的prime_handle_to_fd接口
  14. drm_prime_fd_to_handle_ioctl
  15. obj = drm_gem_object_lookup(file_priv, handle); // 调用自己实现的prime_fd_to_handle接口
  16. drm_gem_prime_handle_to_fd
  17. // 先判断当前drm_file是否已经注册过dma-buf
  18. dmabuf = drm_prime_lookup_buf_by_handle(&file_priv->prime, handle);
  19. if (obj->import_attach)
  20. if (obj->dma_buf);
  21. // 没注册过,需要注册
  22. dmabuf = export_and_register_object(dev, obj, flags);
  23. dmabuf = dev->driver->gem_prime_export(dev, obj, flags);
  24. // 这个接口,就是dma_buf得export接口实现,可以参考 amdgpu_gem_prime_export
  25. obj->dma_buf = dmabuf;
  26. get_dma_buf(obj->dma_buf);
  27. // 添加到 import list,和handle绑定
  28. ret = drm_prime_add_buf_handle(&file_priv->prime,
  29. dmabuf, handle);
  30. // dma buf转fd描述符
  31. dma_buf_fd(dmabuf, flags);
  32. // 释放
  33. dma_buf_put(dmabuf); // 在export_and_register_object中get过
  34. // import
  35. drm_gem_prime_fd_to_handle
  36. obj = dev->driver->gem_prime_import(dev, dma_buf); // armada_gem_prime_import