linux drm 内存管理,linux DRM GEM 笔记

在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;

};

提供以下几点管理:

一、对象自己的建立销毁管理,引用计数等。

二、vma管理,主要是配合用户层的调用mmap()时会用到。

三、shmem文件描述符获取

四、在PRIME中用于import/export操做

五、同步操做

六、回调函数提供平台差别实现

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的来获取。

这篇笔记写的潦草了一点,但愿之后有时间能补全。

参考连接:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值