原文链接:https://www.cnblogs.com/yaongtime/p/14418357.html

在GPU上的各类操作中涉及到多种、多个buffer的使用。
通常我们GPU是通过图像API来调用的,例如OPENGL、vulkan等,所以GPU上buffer的使用,实际上就是在这些图像API中被使用。
例如在opengl es中,vertex/fragment shader、vertex index、vertex buffer object、uniform buffer object、texture、framebuffer等都需要一块memory buffer来存储对应的内容。
而在vulkan中有提供明确的memory管理规则,它把memory分成两半来管理分别是:resource和backing memory。
resource有两种类型buffer和image。
Backing memory也被叫做device memory。
不同类型的resource对Backing memory的需求不一样,vulkan根据resource的属性来分配对应的backing memory。
所以memory的管理在整个GPU的操作中起着重要作用。
 
Memory buffer究其本质就是ram上的段内存空间可被表示为:address + size。
如果支持MMU,虚拟地址连续,但物理地址不连续的一段内存。
 
因为linux系统的特点,应用层不能直接访问物理地址等原因,所以需要linux kernel中提供一种方法来让用户层图像API访问device buffer。
GEM(Graphics Execution Manager)即是linux DRM中用于完成memory管理的内核基础设施(不止这一种)。
 
GEM作为一种内存管理方式,并未覆盖各种在userspace和kernel使用情况(use cases)。
GEM提供了一组标准的内存相关的操作给userspace,以及一组辅助函数给kernel drivers,kernel drivers还需要实现一些硬件相关的私有操作函数。
GEM所管理的memory具体类型、属性是不可知的,我们并不知道它所管理的buffer对象包含了什么。如果要获知GEM所管理的buffer对象的具体内容和使用目的,需要kernel drivers自己实现一组私有的ioctl来获取对应的信息。
 
一个实际的GEM对象所管理的memory类型与硬件平台密切相关,这里我们主要讨论嵌入式平台上的GPU的MEMORY管理。
嵌入式平台上GPU和CPU往往共享主存DDR,所以在本文中讨论GEM的backing memory往往就是DDR上的某段物理内存页(连续或非连续均可)。
这段物理内存会被CPU(分内核虚拟地址和用户层虚拟地址)、GPU(虚拟地址和物理地址)访问。
在CPU端访问时,当在用户层访问时,需要通过GEM的mmap()规则映射成用户层虚拟地址,在kernel中使用时需要映射成内核虚拟地址。
在GPU端访问时,如果GPU支持MMU,GPU也使用MMU映射后虚拟地址,如果不支持MMU,GPU直接访问物理地址。
 
userspace:
在userspace当需要创建一个新的GEM对象时,会通过调用driver私有的ioctl接口来获取。
虽然不同driver设计的icotl接口不一样,但是最终都通过返回一个handle给用户层,来作为kernel中一个GEM对象的引用,而这个handle就是一个u32的整数。
所以一个kernel中的GEM对象被抽象为一个不透明的u32整数值,所以userspace对一个GEM对象的操作均透过这个handle来进行。
 
如前所述,GEM本质上是做内存管理,而内存上最常规的操作就是读写。
而在对内存读写上我们需要增加各种限制条件,这些条件可以是,比如谁可以在什么时候写入什么地方,谁可以在什么时候从哪个地址读取等。
 
当在userspace需要访问GEM buffer内存时,通常通过mmap()系统调用来映射GEM对象所包含的物理地址。
因为在userspace一个handle就代表一个GEM对象,在映射前还需要通过driver私有的ioctl返回一个pg_offset,作为一个mmap()的“off_t offset”参数。
详细的讨论将在mmap节展开。
 
Kernel space:
本文主要讨论内容是kernel driver中对GEM的使用。
 
GEM对象的分配和它的backing memory的分配是分开的。
一个GEM对象通过struct drm_gem_object来表示,驱动程序往往需要把struct drm_gem_object嵌入到自己的私有数据结构中,主要用于内存对象的管理。
struct drm_gem_object对象中不包含内存分配的管理,Backing memory分配将在memory分配段讨论。
 
在kernel中struct drm_gem_object的被定义为:
struct drm_gem_object {
 
 
  struct kref refcount;
  unsigned handle_count;
  struct drm_device *dev;
 
 
  struct file *filp;
  struct drm_vma_offset_node vma_node;
 
 
  size_t size;
 
 
  int name;
 
 
  struct dma_buf *dma_buf;
  struct dma_buf_attachment *import_attach;
  struct dma_resv *resv;
  struct dma_resv _resv;
 
 
  const struct drm_gem_object_funcs *funcs;
};
 
提供如下几点管理:
1、对象本身的创建销毁管理,引用计数等。
2、vma管理,主要是配合用户层的调用mmap()时会用到。
3、shmem文件描述符获取
4、在PRIME中用于import/export操作
5、同步操作
6、回调函数提供平台差异实现
 
struct drm_gem_object中主要是包含了通用的部分,存在平台差异化的地方通过两个方法来解决。
一是通过一组回调函数接口,让drivers提供各自的实现版本。
二是通过嵌入struct drm_gem_object到各自私有的数据结构中,来扩展GEM对象的管理内容。
 
在一个GEM对象上涉及到的操作或者是提供的功能如下:
1.create
2.backing memory
3.mmap
4.import/export
5.sync
 
1.首需要创建一个GEM对象
2.GEM是管理一段内存,那么必然涉及到实际物理内存分配
3.GEM分配的内存要在用户层能访问,需要通过对mmap()的支持
4.GEM对象的内存可以来至driver自己的分配,同样可以从外部模块引入,也支持将GEM对象所管理的内存导出给其他模块使用
5.当GEM对象被多个模块使用时,就涉及到buffer数据的同步
 
GEM对象创建
一个GEM对象通常需要嵌入到driver私有数据结构中(类似于基类)。
目前的kernel中提供了helper函数,这些函数就是在嵌入了GEM对象的基础上实现的。
kernel中提供了几种常用到的GEM对象的扩展,我们会讨论到CMA、shmem这两种扩展,围绕这两者有相应的helper函数。
前文提到GEM把实际的内存配实际上留给了drivers自己实现,从CMA、shmem的名字即可知,这种扩展分别对应从CMA或shmem分配实际的物理内存。
 
物理内存分配
CMA(Contiguous Memory Allocator)是linux系统早期启动时,预留的一段内存池。
CMA用于分配大块的、连续的物理内存。
当GPU或display模块不支持MMU时,使用CMA来分配内存是不错的选择。
 
CMA作为GEM对象的内存分配:
struct drm_gem_cma_object {
  struct drm_gem_object base;
  dma_addr_t paddr;
  struct sg_table *sgt;
  void *vaddr;
};
 
base:GEM对象
paddr:分配的内存物理地址
sgt:通过PRIME导入的scatter/gather table,这个table上的物理地址必须保证是连续的。
vaddr:分配的内存虚拟地址
 
函数drm_gem_cma_create()通过调用__drm_gem_cma_create()完成一个struct drm_gem_cma_object对象分配和初始化,然后通过dma_alloc_wc()分配指定大小的内存。
 
struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
                          size_t size)
{
    struct drm_gem_cma_object *cma_obj;
    int ret;
 
 
    size = round_up(size, PAGE_SIZE);
 
 
    cma_obj = __drm_gem_cma_create(drm, size);
    if (IS_ERR(cma_obj))
        return cma_obj;
 
 
    cma_obj->vaddr = dma_alloc_wc(drm->dev, size, &cma_obj->paddr,
                      GFP_KERNEL | __GFP_NOWARN);
    if (!cma_obj->vaddr) {
        drm_dbg(drm, "failed to allocate buffer with size %zu\n",
             size);
        ret = -ENOMEM;
        goto error;
    }
 
 
    return cma_obj;
 
 
error:
    drm_gem_object_put(&cma_obj->base);
    return ERR_PTR(ret);
}
 
MMAP:
 
GEM对提供了mmap()的支持,通过映射后usersapce可以访问GEM对象的backing memory。
一种是完全由driver自己提供私有的ioctl实现。
 
GEM建议的方式是走mmap系统调用:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
 
前文提到一个GEM对象在usersapce看来就是一个u32的不透明handle值,这个handle值不能直接和mmap配合使用。
所以要如何通过mmap来映射一个GEM对象,使其能在usersapce被访问呢?
DRM是通过mmap的offset参数来识别出一个要被映射的GEM对象的。
在一个GEM对象能被mmap映射前,这个GEM对象会调用函数drm_gem_create_mmap_offset()去分配一个fake offset。
然后driver自身需要实现一个独有的ioctl(),将这个fake offset传递给usersapce。
当usersapce拿到这个fake offset后,作为参数传递给mmap 的offset参数。
当mmap执行在kernel中DRM部分时,DRM通过这个offset参数返回GEM对象,获取其上的backing memory,从而完成对这个GEM对象的映射。
 
如果GPU支持MMU,可以用shmem分配memory。
shmem分配的物理页可能不连续,因为GPU支持MMU,所以GPU也能访问这种不连续的物理页。使用shmem可以充分利用缺页异常分配memory的特性,真正建立页表是在用户空间对映射地址进行read/write时,触发缺页异常时,才执行虚拟地址到物理地址的映射。
而如果GEM对象的backing memory是CMA时,在mmap系统调用,进入kernel driver部分执行时,就需要完成用户虚拟地址到物理地址的映射。
 
static struct drm_gem_cma_object *
__drm_gem_cma_create(struct drm_device *drm, size_t size)
{
    struct drm_gem_cma_object *cma_obj;
    struct drm_gem_object *gem_obj;
    int ret;
 
 
    if (drm->driver->gem_create_object)
        gem_obj = drm->driver->gem_create_object(drm, size);
    else
        gem_obj = kzalloc(sizeof(*cma_obj), GFP_KERNEL);
    if (!gem_obj)
        return ERR_PTR(-ENOMEM);
 
 
    if (!gem_obj->funcs)
        gem_obj->funcs = &drm_gem_cma_default_funcs;
 
 
    cma_obj = container_of(gem_obj, struct drm_gem_cma_object, base);
 
 
    ret = drm_gem_object_init(drm, gem_obj, size);
    if (ret)
        goto error;
 
 
    ret = drm_gem_create_mmap_offset(gem_obj);
    if (ret) {
        drm_gem_object_release(gem_obj);
        goto error;
    }
 
 
    return cma_obj;
 
 
error:
    kfree(cma_obj);
    return ERR_PTR(ret);
}
 
 
PRIME IMPORT/EXPORT
 
GPU上完成一帧图像的渲染后,通常要送到display模块去显示,或是在有多个GPU的桌面机上,GPU间的buffer切换。
这都涉及到将本地内存对象共享给其他模块(本质上是让其他模块访问GPU渲染后的framebuffer)。
同样其他模块也可能指定一块buffer,让GPU把数据渲染在其之上,对GPU driver来说就需要引入某个内存buffer。
 
PRIME IMPORT/EXPORT是DRM的标准特性,GEM只是其中一个具体的实现的方式,因为本文只讨论GEM,所以后续均讨论不对这两者做区分。
 
这种导入/导出操作均由userspace来发起,由driver来提供具体的实现。
之所以要有userspace来发起,因为driver不能预先知道何时需要导入/导出,也不能预先知道要导入/导出的去向来路。
DRM提供了两个ioctl命令,分别对应导入和导出:
DRM_IOCTL_DEF(DRM_IOCTL_PRIME_HANDLE_TO_FD, drm_prime_handle_to_fd_ioctl, DRM_RENDER_ALLOW)
DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_RENDER_ALLOW)
 
导出GEM:
我们知道在userspace一个GEM对象通过一个handle来表示。
当要把这个GEM对象导出,我们通过ioctl传递这个handle值给driver,然后driver会返回一个fd。
这个fd就是一个文件描述符,和通过open()系统调用返回的fd是同一个东西。
而这个fd可以通过UNIX domain sockets在进程间传递。
 
从driver中返回这个fd是通过dma_buf来实现的。
dma_buf是专门设计来供多个模块间进行DMA共享的。
 
导入GEM:
GEM对象是由driver创建,但backing memory是通过dma_buf的来获取。
 
这篇笔记写的潦草了一点,希望以后有时间能补全。
 
参考链接:
 
 

最新文章

  1. 【swift学习笔记】四.swift使用Alamofire和swiftyJson
  2. APiCloud真机调试需要注意的几个问题
  3. String类实现
  4. 【巩固】CSS3的3D动画 ——3D旋转(1)
  5. 20135316王剑桥 linux第七周课实验笔记
  6. 零售业数据分析的媒介——BI工具
  7. dropdownlist值改变时调用js
  8. Catalyst揭秘 Day5 optimizer解析
  9. advanced dom scripting dynamic web design techniques Part One DOM SCRIPTING IN DETAIL CHAPTER 1 DO IT RIGHT WITH BEST PRACTICES
  10. Delphi判断进程是否存在(使用CreateToolhelp32Snapshot)
  11. Tomcat 改变localhost主页,映射到应用地址
  12. 关于使用srping @RequestParam 容易出错的地方
  13. linux 进程概念
  14. vue中使用动画vue-particles
  15. 关于 Java 中关于 数组的声明
  16. Hudson 打包部署到Was上特别慢
  17. 多媒体开发之ftp---一个很现实的需求把ftp转换成rtmp协议做点播
  18. Ubuntu 14.04(64bit)使用mentohust连接校园网
  19. μCOS-II系统之事件(event)的使用规则及Semaphore实例
  20. Google Chrome保存插件方法

热门文章

  1. 《我想进大厂》之Zookeeper夺命连环9问
  2. C链表-C语言入门经典例题
  3. P1046 陶陶摘苹果 Python实现
  4. dedecms织梦的安全问题解决办法
  5. 小白搭建WNMP详细教程---NGINX安装与设置
  6. springboot中扩展ModelAndView实现net mvc的ActionResult效果
  7. ogn1.MethodFailedException:Method "xxx" failed for object xxx
  8. 【bzoj 2163】复杂的大门(算法效率--拆点+贪心)
  9. Codeforces Round #501 (Div. 3) D. Walking Between Houses (思维,构造)
  10. GYM101810 ACM International Collegiate Programming Contest, Amman Collegiate Programming Contest (2018) M. Greedy Pirate (LCA)