Linux图形子系统之GEM内存管理
Linux图形子系统之GEM内存管理
- 引言
- 1 创建与映射
- 1.1 创建GEM
- 1.2 映射对象到文件
- 1.3 映射对象到用户空间
- 1.4 对象同步
- 2 内存分配
- 2.1 数据结构
- 2.1.1 内存管理结构
- 2.1.2 内存节点结构
- 2.2 分配算法
- 2.3 常见用法
引言
drm对内存使用抽象成GEM对象,用户空间通过句柄或文件映射的方式访问。
1 创建与映射
1.1 创建GEM
drm_mode_create_dumb_ioctl是DRM_IOCTL_MODE_CREATE_DUMB的处理函数,它直接调用了drm_mode_create_dumb函数,该函数通过参数解析和检查后,调用drm_driver的dumb_create回调函数。
dumb_create回调的常规实现如下:
int xxx_gem_dumb_create(struct drm_file *file,struct drm_device *dev,struct drm_mode_create_dumb *args)
{# 略略略... ...obj = kzalloc(sizeof(*obj), GFP_KERNEL);drm_gem_private_object_init(dev, &obj->base, args->size);obj->base.funcs = &xxx_gem_object_funcs;# 略略略... ...ret = drm_gem_object_create(file, obj->base, &handle);if (ret)return error;args->handle = handle;return 0;# 略略略... ...
}
整个函数流程概况:
- 首先,分配一块驱动的GEM扩展结构体(内部嵌套drm_gem_object),通过drm_gem_private_object_init对嵌套drm_gem_object的base字段初始化后,继续初始化扩展的自定义字段。
- 然后,通过drm_gem_object_create为对象创建一个句柄。内部实现为通过idr_alloc为对象分配一个句柄;如果驱动实现了drm_gem_object_funcs的open回调,则调用,反之,若驱动实现了drm_driver的gem_open_object回调,则调用该回调。
注:一般gem扩展结构体的字段包括对象对应的内存信息记录
1.2 映射对象到文件
drm_mode_mmap_dumb_ioctl是DRM_IOCTL_MODE_MAP_DUMB的处理函数,该函数优先调用drm_driver的dumb_map_offset回调,如果没有实现则调用默认的处理函数drm_gem_dumb_map_offset:
int drm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,u32 handle, u64 *offset)
{struct drm_gem_object *obj;int ret;obj = drm_gem_object_lookup(file, handle);if (!obj)return -ENOENT;/* Don't allow imported objects to be mapped */if (obj->import_attach) {ret = -EINVAL;goto out;}ret = drm_gem_create_mmap_offset(obj);if (ret)goto out;*offset = drm_vma_node_offset_addr(&obj->vma_node);
out:drm_gem_object_put_unlocked(obj);return ret;
}
整个函数流程概况:
- 首先,通过drm_gem_object_lookup函数在file中通过handle查找出drm_gem_object;
- 对于导入的对象(dma buffer导入),在默认函数中是禁止映射到文件;
- 然后,通过drm_gem_create_mmap_offset映射文件内偏移。将drm_gem_object的vma_node添加到drm_device的vma_offset_manager中管理;
- 最后,通过drm_vma_node_offset_addr获取对象vma_node的偏移。该偏移是相对于整个文件内的偏移。用户态用该偏移去mmap对应的drm file实现对gem内存的访问。
注:在对象释放的时候,需要通过drm_gem_free_mmap_offset去归还占用的offset
1.3 映射对象到用户空间
drm_gem_mmap函数是drm_file的mmap默认实现:
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{# 略略略... ...drm_vma_offset_lock_lookup(dev->vma_offset_manager);node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,vma->vm_pgoff,vma_pages(vma));if (likely(node)) {obj = container_of(node, struct drm_gem_object, vma_node);if (!kref_get_unless_zero(&obj->refcount))obj = NULL;}drm_vma_offset_unlock_lookup(dev->vma_offset_manager);# 略略略... ...ret = drm_gem_mmap_obj(obj, drm_vma_node_size(node) << PAGE_SHIFT,vma);drm_gem_object_put_unlocked(obj);return ret;
}
整个函数流程概况:
- 首先,通过vma->vm_pgoff获取node,转换为gem对象。vma->vm_pgoff为mmap调用时用户请求的文件偏移,该偏移是通过DRM_IOCTL_MODE_MAP_DUMB设置的;
- 然后,调用函数drm_gem_mmap_obj映射obj。在drm_gem_mmap_obj函数中,对vma相关字段赋值,其中包括:如果驱动实现了drm_gem_object_funcs的vm_ops回调,则将其设置为vma的vm_ops回调,反之,若驱动实现了drm_driver的gem_vm_ops回调,则用该回调设置vma的vm_ops回调;同时,将obj赋值给vma的vm_private_data字段。
gem对象或驱动需要实现vm_ops回调,该回调的open/close默认实现为drm_gem_vm_open/drm_gem_vm_close。vm_ops的fault实现过程如下:
vm_fault_t xxx_gem_fault(struct vm_fault *vmf)
{struct vm_area_struct *area = vmf->vma;struct drm_gem_object *obj = area->vm_private_data;struct drm_xxx_gem_object *xxx_obj = to_intel_bo(obj);pgoff_t page_offset;u64 page;page_offset = (vmf->address - area->vm_start) >> PAGE_SHIFT;page = gem_xxx_get_one_page(obj, page_offset);ret = vmf_insert_pfn(vma, vmf->address, page >> PAGE_SHIFT);if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}
注: 此外,还有如下实现方式:
- 在xxx_gem_fault中,通过vm_insert_page映射,返回VM_FAULT_NOPAGE;
- 在xxx_gem_fault中,获取struct page*并赋值给vmf->page,返回0;
- 在drm_file的mmap回调中,通过remap_pfn_range一次性将obj所有物理页映射到vma;
1.4 对象同步
在多个渲染上下文中,可能存在同时访问某个GEM对象的可能。为了解决资源竞争的问题,提供了两个函数:
- drm_gem_lock_reservations:用于对多个需要使用的GEM对象加锁;
- drm_gem_unlock_reservations:用于解锁占用的多个GEM对象;
注:本质上是加解锁gem对象的resv->lock,这是一个ww_mutex,用于保护渲染上下文对gem添加读写fence的过程。实际的使用过程就是:先获取上一个上下文添加的dma fence,等待其触发;然后根据访问方式,插入一个dma fence;过程中是支持共享读/互斥写。
2 内存分配
上面只是介绍了GEM对象的创建和映射。具体对内存的管理,一般的驱动要么用drm提供的内存块管理,或者自定义实现方式。
drm内部提供了drm_mm_init和drm_mm_insert_node/drm_mm_remove_node函数管理设备内存块。前者用于初始化内存块的范围;后者用于内存的申请和释放。
2.1 数据结构
2.1.1 内存管理结构
struct drm_mm {# 略略略... ...struct list_head hole_stack;struct drm_mm_node head_node;struct rb_root_cached interval_tree;struct rb_root_cached holes_size;struct rb_root holes_addr;unsigned long scan_active;
};
字段描述:
- head_node是drm_mm_init初始化的最大的空闲内存块,然后将所有空闲的和已分配的内存串成一个链表。
- holes_size/holes_addr、hole_stack表示所有的空闲内存结点。hole_stack按分配时间反序排列的链表;holes_size是空闲内存块的大小降序的红黑树;holes_addr是按空闲内存节点地址升序的红黑树;
- interval_tree是将所以分配的内存节点按地址升序的红黑树。
2.1.2 内存节点结构
struct drm_mm_node {unsigned long color;u64 start;u64 size;/* private: */struct drm_mm *mm;struct list_head node_list;struct list_head hole_stack;struct rb_node rb;struct rb_node rb_hole_size;struct rb_node rb_hole_addr;# 略略略... ...u64 hole_size;# 略略略... ...
};
字段描述:
- start/size定义了节点的起始地址和大小。特别地,对于初始化添加的内存块,start等于地址尾部、size为负数;
- node_list是用于串联从当前节点分配的内存结点;hole_stack用于串联到drm_mm的hole_stack;rb用于插入到drm_mm的interval_tree;rb_hole_size用于插入到drm_mm的holes_size;rb_hole_addr用于插入到drm_mm的rb_hole_addr;
- hole_size表示空闲内存的大小。
2.2 分配算法
整个内存分配主要实现在函数drm_mm_insert_node_in_range中,相关概况如下:
- 首先,找到一个块合适的空闲内存块。如果通过drm_mm_insert_node进入,则在合格的内存块中选择最小的空闲内存块。由于存在内存对齐等因素,所以可能会经过多次选择;
- 然后,从选取的空闲内存块中分配内存到传入的节点,设置相关字段的值。
- 然后,将已分配的内存块添加到空闲内存块的node_list链表后面,再将已分配的内存块添加到drm_mm的interval_tree中,继续将空闲内存块从drm_mm的holes_size、holes_addr、hole_stack中移除;
- 最后,如果已分配的内存块的开始地址大于空闲内存区间的开始地址,需要将选取的空闲内存放回drm_mm的holes_size、holes_addr、hole_stack中;同样,如果已分配的内存块的末端地址小于空闲内存区间的末端地址,则将已分配内存作为一个空闲内存,添加到drm_mm的holes_size、holes_addr、hole_stack中;
注:整个分配算法特别有意思的是struct drm_mm的head_node的size为负数,其答案就在函数add_hole中,通过这种巧妙的设计让空闲内存和已分配内存的数据结构统一。
2.3 常见用法
常规中,一般在驱动程序中扩展struct drm_device结构,使其内嵌一个struct drm_mm结构,然后设备创建的时候通过drm_mm_init对其初始化;在扩展的GEM对象中嵌套struct drm_mm_node,使用内存的时候通过drm_mm_insert_node分配内存,不再需要的时候,通过drm_mm_remove_node释放。
注:操作过程中,需要添加锁保护。
Linux图形子系统之GEM内存管理相关推荐
- Linux图形子系统
转载网址:http://www.wowotech.net/graphic_subsystem/graphic_subsystem_overview.html 前言 图形子系统是linux系统中比较复杂 ...
- Linux任督二脉之内存管理(三) PPT
五节课的第三节课-进程的内存消耗和泄漏 *进程的VMA. *进程内存消耗的4个概念:vss.rss.pss和uss *page fault的几种可能性,major和minor *应用内存泄漏的界定方法 ...
- Linux任督二脉之内存管理(二) PPT
五节课的第二节课-内存的动态申请和释放 * slab.kmalloc/kfree./proc/slabinfo和slabtop * 用户空间malloc/free与内核之间的关系 * mallopt ...
- Linux内核源代码情景分析-内存管理
用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3 ...
- linux物理内存虚拟内存一致,Liunx内存管理的调用和实现
下面我们探讨一下关于内存管理的系统调用方式.事实上,POSIX 并没有给内存管理指定任何的系统调用.然而,Linux 却有自己的内存系统调用,主要系统调用如下 系统调用描述s = brk(addr)改 ...
- 【Linux 内核】Linux 内核体系架构 ( 进程调度 | 内存管理 | 中断管理 | 设备管理 | 文件系统 )
文章目录 一.进程调度 二.内存管理 三.中断管理 四.设备管理 五.文件系统 一.进程调度 进程调度 : 进程 是 系统中 进行 资源分配 的 基本单位 ; 每个进程 在 运行时 , 都 感觉自己占 ...
- Linux 0.12内核的内存管理基础
在Linux 0.12内核中,为了有效地使用机器中的物理内存,在系统初始化阶段内存被划分成几个功能区域. Linux内核程序占据在物理内存的开始部分,接下来是供硬盘或软盘等块设备使用的高速缓冲区部分, ...
- Linux内存管理的设计与实现,Linux内核设计与实现-内存管理学习小结
一,页 内存管理以页为基本单位,用 struct page 表示系统中的每个物理页.系统中的每个结构都要分配这样一个结构体,假定系统的物理页为4kb,系统有128M的物理内存,那么系统仅需1MB的内存 ...
- Linux用户态进程的内存管理
上一篇我们了解了内存在内核态是如何管理的,本篇文章我们一起来看下内存在用户态的使用情况,如果上一篇文章说是内核驱动工程师经常面对的内存管理问题,那本篇就是应用工程师常面对的问题. 相信大家都知道对用户 ...
最新文章
- vb 搜索指定目录下的指定类型文件
- Xposed源码剖析——概述
- apriori算法c++_关联分析——基于Apriori算法实现
- python 获取文件大小_第41p,超级重要,Python中的os库
- 接口自动化测试_Python自动化测试学习路线之接口自动化测试「模块四」
- 加密解密概述及openssl应用及其创建CA和签发证书的实现
- Java i18n - Java中的国际化
- Android备份onedrive,三星Note10正将OneDrive集成到Android相册应用中
- NHibernate之映射文件配置说明
- 长春技师学院计算机系,长春技校排名前五十
- HyperLeger Composer 重启 | 进入play ground | 进入 couchdb
- 精译丨美国2017年最值得投资的7大共同基金
- 前端项目实战5:聊天对话框
- 统计素数并求和python_Python练习题4.2统计素数并求和
- 6. 文本分类——transformer模型
- Flixel横板游戏制作教程(十)—Pickups(拾取道具)
- codeforces E. Placing Rooks
- 相声评书戏曲大全(安卓)
- 干得漂亮!微信封禁大量色情账号
- ubuntu linux qq
热门文章
- 解决 Starting MySQL ERROR The server quit without updating PID file
- HC-SR04超声波测距块讲解(附32单片机源码)
- springboot 小程序微信支付
- Android 之 Project Butter 详细介绍
- gyp: No Xcode or CLT version detected!
- android 地铁地图api,入门指南-地铁图 JS API | 高德地图API
- android 流播放器开发,GitHub - youcoding98/FastVideo: 基于Android平台的移动流媒体播放器的开发...
- MEM/MBA数学基础(04)方程 函数 不等式
- netmap pkt-gen程序代码分析
- video标签的使用