相关参考:
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原则)
内核文档中描述:从根本上讲,GEM涉及以下几种操作:
- 内存分配和释放
- 命令执行
- 执行命令时的光圈管理(???)
缓冲区对象分配相对简单,主要由Linux的shmem层提供,后者提供了用于备份每个对象的内存。
GEM由三部分组成:
- DUMB:只支持连续物理内存的buffer类型,基于kernel中通用CMA API实现,多用于小分辨率简单场景
- PRIME:连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用 于大内存复杂场景
- fence:buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题
DUMP和PRIME的来源可以参考:关于 DRM 中 DUMB 和 PRIME 名字的由来
GEM Initialization
首先 struct drm_driver中driver_features必须包含DRIVER_GEM属性
DRM core会在load之前初始化GEM core。
drm_dev_alloc
drm_dev_init
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,需要驱动自己分配内存
struct drm_gem_priv_object{ // 私有GEM结构体
.....
stuct drm_gem_object gem_obj;
..... // 自己的GEM拓展 信息
};
drm_gem_object_init
filp = shmem_file_setup("drm mm object", size, VM_NORESERVE); // 申请一个共享内存
obj->filp = filp;
or
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销毁时减去这一引用
amdgpu_gem_create_ioctl
int r = drm_gem_handle_create(filp, gobj, &handle);
/* drop reference from allocate - handle holds it now */
drm_gem_object_put_unlocked(gobj);
GEM代码分析
drm_driver中的gem相关接口
struct drm_driver {
// 与GEM相关
u32 driver_features; // 需要使能DRIVER_GEM
// 以下函数已被drm_gem_object_funcs 替代,但大部分还是这么用的,所以列出来
#if 0.
// drm_gem_objects的析构函数,已不使用
gem_free_object / gem_free_object_unlocked
gem_open_object / gem_close_object
gem_print_info
#endif
gem_create_object // *用于分配GEM对象结构,供CMA和SHMEM GEM帮助程序使用。
// prime buffer操作函数
prime_handle_to_fd / prime_fd_to_handle // prime基于DMA-BUF,fd和handle转换 ***
gem_prime_export / gem_prime_import // DMA-BUF的export和import
gem_prime_pin / gem_prime_unpin // TBD ???
gem_prime_get_sg_table / gem_prime_import_sg_table
gem_prime_vmap / gem_prime_vunmap / gem_prime_mmap
// dumb buffer使用
dumb_create / dumb_map_offset / dumb_destroy
};
// 5.7版本建议将部分操作接口分离出来:
struct drm_gem_object {
......
/* Optional GEM object functions. If this is set, it will be used instead of the
* corresponding &drm_driver GEM callbacks. */
const struct drm_gem_object_funcs *funcs; // 新版本推荐使用该方法,替代drm_driver
}
struct drm_gem_object_funcs {
void (*free)(struct drm_gem_object *obj);
int (*open)(struct drm_gem_object *obj, struct drm_file *file);
void (*close)(struct drm_gem_object *obj, struct drm_file *file);
void (*print_info)(struct drm_printer *p, unsigned int indent,
const struct drm_gem_object *obj);
struct dma_buf *(*export)(struct drm_gem_object *obj, int flags);
int (*pin)(struct drm_gem_object *obj);
void (*unpin)(struct drm_gem_object *obj);
struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
void *(*vmap)(struct drm_gem_object *obj);
void (*vunmap)(struct drm_gem_object *obj, void *vaddr);
int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
const struct vm_operations_struct *vm_ops; // 这是可选的,但对于mmap支持是必需的。
}
/*** 这部分推荐参考: struct drm_gem_object_funcs drm_cma_gem_default_funcs ***/
gem初始化drm_gem_init
struct drm_vma_offset_manager {
rwlock_t vm_lock;
struct drm_mm vm_addr_space_mm;
};
drm_dev_init->
drm_gem_init
struct drm_vma_offset_manager *vma_offset_manager = kzalloc;
dev->vma_offset_manager = vma_offset_manager
// 管理到用户空间的线性映射 TBD ???
drm_vma_offset_manager_init(vma_offset_manager, DRM_FILE_PAGE_OFFSET_START, DRM_FILE_PAGE_OFFSET_SIZE);
// #define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFFUL >> PAGE_SHIFT) + 1) // 1M
// #define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFFUL >> PAGE_SHIFT) * 256) // 255M
// start is 0x100000, offset is 0xfffff00
rwlock_init(&mgr->vm_lock);
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_file是怎么来的?
static struct drm_driver mygem_driver = {
.driver_features = DRIVER_GEM,
.fops = &mygem_fops,
}
static const struct file_operations mygem_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
...
}
.open = drm_open
drm_open_helper(filp, minor);
priv = drm_file_alloc(minor); // 就这里申请的
if (dev->driver->open) {
ret = dev->driver->open(dev, file); // 这里如果自定了open函数,在这里调用(不推荐)
filp->private_data = priv;
unlocked_ioctl = drm_ioctl,
drm_ioctl
is_driver_ioctl = nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END; // 允许用户自定义命令(这里用不到)
ioctl = &drm_ioctls[nr];
func = ioctl->func;
drm_ioctl_kernel(filp, func, kdata, ioctl->flags);
struct drm_file *file_priv = file->private_data; // 就是open时申请的文件
retcode = func(dev, kdata, file_priv);
使用了DUMP buffer进行操作
1.创建dumb buffer
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
调用内核中
drm_ioctl->drm_ioctls
static const struct drm_ioctl_desc drm_ioctls[]
的
drm_mode_create_dumb_ioctl 函数
drm_mode_create_dumb(dev, data, file_priv);
dev->driver->dumb_create(file_priv, dev, args);
最终调用自己driver的dumb_create函数
参考代码中dumb_create = drm_gem_cma_dumb_create 直接使用 CMA helper 函数实现
drm_gem_cma_dumb_create
drm_gem_cma_create_with_handle
drm_gem_cma_create
__drm_gem_cma_create(drm, size);
drm_gem_object_init(drm, gem_obj, size); // init object对象 ***
drm_gem_create_mmap_offset(gem_obj); // mmap映射 ***
dma_alloc_wc(drm->dev, size, &cma_obj->paddr // 这才是实际分配内存的地方
// 也可以在mmap中缺页中断分配
drm_gem_handle_create // 创建handle,这里会drm_gem_object_get 一次
drm_gem_object_put_unlocked(gem_obj); // 释放drm_gem_handle_create的句柄
##### 申请并初始化gem_obj
drm_gem_object_init(drm, gem_obj, size)
drm_gem_private_object_init // initialize an allocated private GEM object
filp = shmem_file_setup("drm mm object", size, VM_NORESERVE); // ???
obj->filp = filp;
#### mmap映射使用
drm_gem_create_mmap_offset(gem_obj);
#### desoty使用
struct drm_driver中dumb_destroy默认使用drm_gem_dumb_destroy()
drm_mode_destroy_dumb_ioctl
drm_mode_destroy_dumb
if (dev->driver->dumb_destroy)
return dev->driver->dumb_destroy(file_priv, dev, handle);
else
return drm_gem_dumb_destroy(file_priv, dev, handle);
handle管理
##### handle部分使用
drm_gem_handle_create + drm_gem_object_put_unlocked
#if 0
先看下handle,倒叙追下
drm_mode_create_dumb中第二个参数:
struct drm_mode_create_dumb *args,
使用drm_ioctl_kernel 函数 void *kdata参数
drm_ioctl 中 kdata = stack_kdata;
注:最后会把这部分导入用户空间:copy_to_user((void __user *)arg, kdata, out_size)
#endif
***再次整理下drm_gem_handle_create代码***:*(重点:handle获取方式)
{ // 用户态代码
struct drm_mode_create_dumb create_req = {};
create_req.bpp = 32;
create_req.width = 240;
create_req.height = 320;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
}
{ // 内核态代码
drm_ioctl
kdata = stack_kdata; // or kdata = kmalloc(ksize, GFP_KERNEL);
copy_from_user(kdata, (void __user *)arg, in_size) // 获取用户态参数
drm_ioctl_kernel(filp, func, kdata, ioctl->flags)
drm_mode_create_dumb(dev, data, file_priv);
.....
drm_gem_handle_create(); //获取到用户句柄******
copy_to_user((void __user *)arg, kdata, out_size) // 将handle返回到用户态
map管理
2.map dumb buffer
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)
调用内核中
drm_mode_mmap_dumb_ioctl
这里没实现dev->driver->dumb_map_offset,所以调用:
drm_gem_dumb_map_offset
根据handle 来获取offset
3.用户空间map后操作
mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset)
mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
...使用
直接调用内核
drm_gem_mmap
4.释放
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
drm_mode_destroy_dumb_ioctl
drm_mode_destroy_dumb
if (dev->driver->dumb_destroy)
return dev->driver->dumb_destroy(file_priv, dev, handle);
else
return drm_gem_dumb_destroy(file_priv, dev, handle);
// struct drm_driver中dumb_destroy默认使用drm_gem_dumb_destroy(),或者自定义释放函数
PRIME内存
从最简单的 dma-buf 驱动程序 可以知道dma_buffer的基本过程:
dma-buf 的出现就是为了解决各个驱动之间 buffer 共享的问题,因此它本质上是 buffer 与 file 的结合,即 dma-buf 既是块物理 buffer,又是个 linux file。buffer 是内容,file 是媒介,只有通过 file 这个媒介才能实现同一 buffer 在不同驱动之间的流转
查看Linux 下DMA-BUF使用情况:
sudo cat /sys/kernel/debug/dma_buf/bufinfo
Dma-buf Objects:
size flags mode count exp_name ino
10485760 00000000 00080005 00000003 i915 01030576
Exclusive fence: i915 signaled signalled
Shared fence: i915 signaled signalled
Attached Devices:
Total 0 devices attached
.......
DMA-BUF最小化实现
dma_buf_ops # dma-buf回调接口
DEFINE_DMA_BUF_EXPORT_INFO # 定义struct dma_buf_export_info 对象
dma_buf_export() # 初始化struct dma_buf_export_info
GEM中的DMA-BUF
struct drm_device {
......
/* prime: */
.prime_handle_to_fd = drm_gem_prime_handle_to_fd
.prime_fd_to_handle = drm_gem_prime_fd_to_handle
.gem_prime_export // export GEM -> dmabuf 自定义dma_buf的export和import接口
.gem_prime_import // import dmabuf -> GEM
};
// drm_ioctl.c中
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),
drm_prime_handle_to_fd_ioctl
struct drm_driver->prime_handle_to_fd(); // 调用自己实现的prime_handle_to_fd接口
drm_prime_fd_to_handle_ioctl
obj = drm_gem_object_lookup(file_priv, handle); // 调用自己实现的prime_fd_to_handle接口
drm_gem_prime_handle_to_fd
// 先判断当前drm_file是否已经注册过dma-buf
dmabuf = drm_prime_lookup_buf_by_handle(&file_priv->prime, handle);
if (obj->import_attach) ;
if (obj->dma_buf);
// 没注册过,需要注册
dmabuf = export_and_register_object(dev, obj, flags);
dmabuf = dev->driver->gem_prime_export(dev, obj, flags);
// 这个接口,就是dma_buf得export接口实现,可以参考 amdgpu_gem_prime_export
obj->dma_buf = dmabuf;
get_dma_buf(obj->dma_buf);
// 添加到 import list,和handle绑定
ret = drm_prime_add_buf_handle(&file_priv->prime,
dmabuf, handle);
// dma buf转fd描述符
dma_buf_fd(dmabuf, flags);
// 释放
dma_buf_put(dmabuf); // 在export_and_register_object中get过
// import
drm_gem_prime_fd_to_handle
obj = dev->driver->gem_prime_import(dev, dma_buf); // armada_gem_prime_import