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内存管理相关推荐

  1. Linux图形子系统

    转载网址:http://www.wowotech.net/graphic_subsystem/graphic_subsystem_overview.html 前言 图形子系统是linux系统中比较复杂 ...

  2. Linux任督二脉之内存管理(三) PPT

    五节课的第三节课-进程的内存消耗和泄漏 *进程的VMA. *进程内存消耗的4个概念:vss.rss.pss和uss *page fault的几种可能性,major和minor *应用内存泄漏的界定方法 ...

  3. Linux任督二脉之内存管理(二) PPT

    五节课的第二节课-内存的动态申请和释放 * slab.kmalloc/kfree./proc/slabinfo和slabtop * 用户空间malloc/free与内核之间的关系 * mallopt ...

  4. Linux内核源代码情景分析-内存管理

    用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3 ...

  5. linux物理内存虚拟内存一致,Liunx内存管理的调用和实现

    下面我们探讨一下关于内存管理的系统调用方式.事实上,POSIX 并没有给内存管理指定任何的系统调用.然而,Linux 却有自己的内存系统调用,主要系统调用如下 系统调用描述s = brk(addr)改 ...

  6. 【Linux 内核】Linux 内核体系架构 ( 进程调度 | 内存管理 | 中断管理 | 设备管理 | 文件系统 )

    文章目录 一.进程调度 二.内存管理 三.中断管理 四.设备管理 五.文件系统 一.进程调度 进程调度 : 进程 是 系统中 进行 资源分配 的 基本单位 ; 每个进程 在 运行时 , 都 感觉自己占 ...

  7. Linux 0.12内核的内存管理基础

    在Linux 0.12内核中,为了有效地使用机器中的物理内存,在系统初始化阶段内存被划分成几个功能区域. Linux内核程序占据在物理内存的开始部分,接下来是供硬盘或软盘等块设备使用的高速缓冲区部分, ...

  8. Linux内存管理的设计与实现,Linux内核设计与实现-内存管理学习小结

    一,页 内存管理以页为基本单位,用 struct page 表示系统中的每个物理页.系统中的每个结构都要分配这样一个结构体,假定系统的物理页为4kb,系统有128M的物理内存,那么系统仅需1MB的内存 ...

  9. Linux用户态进程的内存管理

    上一篇我们了解了内存在内核态是如何管理的,本篇文章我们一起来看下内存在用户态的使用情况,如果上一篇文章说是内核驱动工程师经常面对的内存管理问题,那本篇就是应用工程师常面对的问题. 相信大家都知道对用户 ...

最新文章

  1. vb 搜索指定目录下的指定类型文件
  2. Xposed源码剖析——概述
  3. apriori算法c++_关联分析——基于Apriori算法实现
  4. python 获取文件大小_第41p,超级重要,Python中的os库
  5. 接口自动化测试_Python自动化测试学习路线之接口自动化测试「模块四」
  6. 加密解密概述及openssl应用及其创建CA和签发证书的实现
  7. Java i18n - Java中的国际化
  8. Android备份onedrive,三星Note10正将OneDrive集成到Android相册应用中
  9. NHibernate之映射文件配置说明
  10. 长春技师学院计算机系,长春技校排名前五十
  11. HyperLeger Composer 重启 | 进入play ground | 进入 couchdb
  12. 精译丨美国2017年最值得投资的7大共同基金
  13. 前端项目实战5:聊天对话框
  14. 统计素数并求和python_Python练习题4.2统计素数并求和
  15. 6. 文本分类——transformer模型
  16. Flixel横板游戏制作教程(十)—Pickups(拾取道具)
  17. codeforces E. Placing Rooks
  18. 相声评书戏曲大全(安卓)
  19. 干得漂亮!微信封禁大量色情账号
  20. ubuntu linux qq

热门文章

  1. 解决 Starting MySQL ERROR The server quit without updating PID file
  2. HC-SR04超声波测距块讲解(附32单片机源码)
  3. springboot 小程序微信支付
  4. Android 之 Project Butter 详细介绍
  5. gyp: No Xcode or CLT version detected!
  6. android 地铁地图api,入门指南-地铁图 JS API | 高德地图API
  7. android 流播放器开发,GitHub - youcoding98/FastVideo: 基于Android平台的移动流媒体播放器的开发...
  8. MEM/MBA数学基础(04)方程 函数 不等式
  9. netmap pkt-gen程序代码分析
  10. video标签的使用