原文链接: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的来获取。
 
这篇笔记写的潦草了一点,希望以后有时间能补全。
 
参考链接:
 
 
												
												
								- linux DRM GPU scheduler 笔记
		内核文档:   Overview   The GPU scheduler provides entities which allow userspace to push jobs into softw ... 
- Linux DRM KMS 驱动简介【转】
		转自:https://blog.csdn.net/yangkuanqaz85988/article/details/48689521 Whoops,上次写完<Linux DRM Graphic  ... 
- linux 2.6 驱动笔记(一)
		本文作为linux 2.6 驱动笔记,记录环境搭建及linux基本内核模块编译加载. 环境搭建: 硬件:OK6410开发板 目标板操作系统:linux 2.6 交叉编译环境:windows 7 + v ... 
- Linux内核分析课程笔记(一)
		linux内核分析课程笔记(一) 冯诺依曼体系结构 冯诺依曼体系结构实际上就是存储程序计算机. 从两个层面来讲: 从硬件的角度来看,冯诺依曼体系结构逻辑上可以抽象成CPU和内存,通过总线相连.CPU上 ... 
- Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)
		Linux进程间通信IPC学习笔记之同步二(SVR4 信号量) 
- Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
		Linux进程间通信IPC学习笔记之同步二(Posix 信号量) 
- Linux进程间通信IPC学习笔记之消息队列(SVR4)
		Linux进程间通信IPC学习笔记之消息队列(SVR4) 
- Linux进程间通信IPC学习笔记之有名管道
		基础知识: 有名管道,FIFO先进先出,它是一个单向(半双工)的数据流,不同于管道的是:是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)有一个与路径名关联的名 ... 
- Linux进程间通信IPC学习笔记之管道
		基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ... 
随机推荐
	
									- YOLOv1论文解读
			摘要 我们提出一种新的目标检测算法--YOLO.以前有关目标检测的研究将检测转化成分类器来执行.然而,我们将目标检测框架化为空间分隔的边界框及相关的类概率的回归问题.在一次评估中,单个神经网络直接从整 ... 
- idea使用maven的打包工具package不会打上主类解决方法
			
			
- Mybatis学习笔记1
			mybatis是一个orm持久化框架,mybatis专注于sql的操作从3.0开始名字改变了:ibatis-mybatis 对象关系映射(Object Relational Mapping) 一.My ... 
- Codeforces Round #678 (Div. 2)【ABCD】
			比赛链接:https://codeforces.com/contest/1436 A. Reorder 题解 模拟一下这个二重循环发现每个位置数最终都只加了一次. 代码 #include <bi ... 
- HDU5739 Fantasia【点双连通分量 割点】
			HDU5739 Fantasia 题意: 给出一张\(N\)个点的无向图\(G\),每个点都有权值\(w_i\),要求计算\(\sum_{i=1}^{N}i\cdot G_i % 1e9+7\) 其中 ... 
- 博弈论入门——Nim游戏引入
			说实话,我真的对这个游戏看得是一脸懵逼,因为(我太弱了)我没有明白一些变量的意思,所以一直很懵,现在才明白,这让我明白博弈论(还可以骗钱)博大精深; 以下是我自己思考的过程,也许不严谨,但是最终明白了 ... 
- C - Last Digit
			Description The function f(n, k) is defined by f(n, k) = 1k + 2k + 3k +...+ nk. If you know the valu ... 
- python爬取QQVIP音乐
			QQ音乐相比于网易云音乐加密部分基本上没有,但是就是QQ音乐的页面与页面之间的联系太强了,,导致下载一个音乐需要分析前面多个页面,找数据..太繁琐了 1.爬取链接:https://y.qq.com/  ... 
- Java基础(第一期)
			Java基础 1.注释 Java中注释有三种: 单行注释 // 多行注释 /* */ 文本注释(用的较少) /** */ 书写注释是一个非常好的习惯 BAT 平时写代码一定要注意规范 //有趣的代码注 ... 
- 鸟哥的linux私房菜——第十二章学习(Shell Scripts)
			第十二章  Shell Scripts 1.0).什么是shell scripts? script 是"脚本.剧本"的意思.整句话是说, shell script 是针对 shel ...