news 2026/10/19 2:03:07

4.2 drm_gem_dma_object:基于 CMA 的物理连续后备

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
4.2 drm_gem_dma_object:基于 CMA 的物理连续后备

4.1 概述里我们把轻量后备分成了两支:需要物理连续的走drm_gem_dma_object,可换出的走drm_gem_shmem_object。本章从更简单的那一支讲起——drm_gem_dma_object。它是整个第四章里结构最小、流程最短的后备方案,很适合用来先把「一个 GEM 对象的后备存储到底要做哪几件事」这条最小闭环走通,再去理解 shmem 为了「可换出」多付出的那些复杂度。

前置知识:本节会用到内核 DMA API(dma_alloc_wc/dma_alloc_coherent)与CMA(Contiguous Memory Allocator,连续内存分配器)。只需记住一句话:CMA 是内核预留的一块可动态分配的物理连续内存池,供那些要求连续 DMA 缓冲的设备使用;dma_alloc_*在没有 IOMMU 时底层正是从它取内存。
如果想深入 DMA APIs 的设计请移步:从硅到用户态:DMA 全链路解析。


1、它解决什么:不支持 scatter-gather 的设备要「连续」

shmem 后备交给设备的是一批散落的页(靠sg_table描述)。但有一类设备吃不下散页——它们的 DMA 引擎不支持 scatter-gather,只能对着「一段连续地址」搬数据。内核源码的DOC: dma helpers把适用场景讲得很清楚(drm_gem_dma_helper.c):

  • 无 IOMMU 的设备:靠CMA分配物理连续的 buffer;
  • 有(紧耦合)IOMMU 的设备:页可以物理分散,但映射进 IOVA 后对设备呈现为连续——所以「连续」的真正含义是「设备视角(dma_addr)连续」,不一定物理连续。

典型用户是显示控制器(display controller)、简单 SoC 的 framebuffer、v4l2 视频缓冲等——它们扫描输出一帧画面时,需要一段地址连续、属性可控(通常 write-combine)的内存。


2、结构drm_gem_dma_object:四个字段,仅此而已

先看它的全貌,定义在drm_gem_dma_helper.h:

structdrm_gem_dma_object{structdrm_gem_objectbase;// 内嵌的 GEM 基类dma_addr_tdma_addr;// 后备内存的 DMA 地址(设备视角)structsg_table*sgt;// 仅 PRIME 导入时使用(保证连续)void*vaddr;// 后备内存的内核虚拟地址bool map_noncoherent;// 是否使用非一致性内存};#defineto_drm_gem_dma_obj(gem_obj)\container_of(gem_obj,structdrm_gem_dma_object,base)

和 shmem 一样,它在头部内嵌drm_gem_object,用to_drm_gem_dma_obj反推。但真正值得注意的是它没有什么:

  • 没有pages[]—— 后备是一整块连续内存,不需要页数组;
  • 没有pages_use_count/pages_pin_count/vmap_use_count—— 内存分配即常驻、直到释放,不换入换出、不需要分层引用计数;
  • 没有madv/madv_list—— 不参与 shrinker,不可回收;
  • vaddr在分配时就已拿到,不是惰性 vmap 的结果。

一句话:drm_gem_dma_object用「一次分配、钉死到底」换来了结构上的极简。它只需记住三样东西——「内核怎么访问(vaddr)、设备怎么访问(dma_addr)、以及(导入时)它的 sg 描述(sgt)」。


3、创建:drm_gem_dma_create—— 一次性分配,分配即到位

创建入口是drm_gem_dma_create,核心就两步:先建壳,再一把分配连续内存。

structdrm_gem_dma_object*drm_gem_dma_create(structdrm_device*drm,size_tsize){structdrm_gem_dma_object*dma_obj;size=round_up(size,PAGE_SIZE);// ① 对齐到页dma_obj=__drm_gem_dma_create(drm,size,false);// ② 建壳 + 伪 offsetif(IS_ERR(dma_obj))returndma_obj;if(dma_obj->map_noncoherent){dma_obj->vaddr=dma_alloc_noncoherent(...,size,&dma_obj->dma_addr,DMA_TO_DEVICE,GFP_KERNEL|__GFP_NOWARN);}else{dma_obj->vaddr=dma_alloc_wc(...,size,// ③ 默认 write-combine&dma_obj->dma_addr,GFP_KERNEL|__GFP_NOWARN);}...}

三个要点,恰好对照 shmem 的「惰性」:

  • ② 建壳:__drm_gem_dma_create负责drm_gem_object_init与drm_gem_create_mmap_offset(伪 offset 的分配,与第三章 3.3 的drm_vma_offset_manager是同一套机制)。它也支持 driver 用gem_create_object插入自己的外层对象——和 shmem 一样的「内嵌可扩展」套路;
  • ③ 分配即到位:dma_alloc_wc/dma_alloc_noncoherent一次性拿到连续内存,同时得到vaddr(内核地址)与dma_addr(设备地址)。没有get_pages、没有按需换入——这是与 shmem 最根本的流程差异:shmem 的页要到「第一次用到」才分配,而 DMA-CMA 在 create 时就全部就位、并钉死到释放;
  • 默认 write-combine:普通路径用dma_alloc_wc,适配「CPU 顺序写入、设备读取」的 framebuffer 访问模式;需要非一致性语义时才走map_noncoherent分支。

释放是对称的一把回收——drm_gem_dma_free调dma_free_wc/dma_free_noncoherent直接归还,没有「计数归零才释放」的层层退栈。


4、访问:mmap 全量映射、vmap 直接返回(无缺页、无 pin)

这是 DMA-CMA「简」得最明显的地方。

内核态vmap:直接返回缓存地址

drm_gem_dma_vmap整个函数只有一行实质代码:

intdrm_gem_dma_vmap(structdrm_gem_dma_object*dma_obj,structiosys_map*map){iosys_map_set_vaddr(map,dma_obj->vaddr);// 直接给出已有的 vaddrreturn0;}

因为dma_alloc_*返回的vaddr本就在内核虚拟地址空间里,内核要访问直接用即可——不像 shmem 还要pin+vmap把散页拼成连续地址。

用户态mmap:一次性映射整个对象

drm_gem_dma_mmap的关键是清掉VM_PFNMAP、一次性把整块 buffer 映射进用户态:

intdrm_gem_dma_mmap(structdrm_gem_dma_object*dma_obj,structvm_area_struct*vma){...vma->vm_pgoff-=drm_vma_node_start(&obj->vma_node);// 伪 offset 归零vm_flags_mod(vma,VM_DONTDUMP|VM_DONTEXPAND,VM_PFNMAP);if(dma_obj->map_noncoherent)ret=dma_mmap_pages(...);elseret=dma_mmap_wc(...,dma_obj->vaddr,dma_obj->dma_addr,vma->vm_end-vma->vm_start);// 整段一次映射...}

内核注释一句话点破差异:“immediately faults in the entire object instead of using on-demand faulting”——整个对象在 mmap 时就立即全部映射,而不是按需缺页。这也是为什么它的vm_ops里根本没有.fault(drm_gem_dma_vm_ops只有.open/.close):内存连续且常驻,无需逐页填充。对照 shmem 的drm_gem_shmem_fault(4.3.5 会讲)按需逐页补页,这里的「简」一目了然。


5、回调表与驱动集成:几乎零代码接入

helper 预置了一张默认回调表drm_gem_dma_default_funcs:

staticconststructdrm_gem_object_funcsdrm_gem_dma_default_funcs={.free=drm_gem_dma_object_free,.print_info=drm_gem_dma_object_print_info,.get_sg_table=drm_gem_dma_object_get_sg_table,.vmap=drm_gem_dma_object_vmap,.mmap=drm_gem_dma_object_mmap,.vm_ops=&drm_gem_dma_vm_ops,};

__drm_gem_dma_create在 driver 没有自带funcs时会自动挂上这张表(if (!gem_obj->funcs) gem_obj->funcs = &drm_gem_dma_default_funcs;)。再配合drm_driver层的快捷宏DRM_GEM_DMA_DRIVER_OPS,driver 只需一行就能把dumb_create(走drm_gem_dma_dumb_create,给 KMS framebuffer 分配 dumb buffer)和 PRIME 导入接好:

staticconststructdrm_driverxxx_driver={...DRM_GEM_DMA_DRIVER_OPS,// dumb_create + gem_prime_import_sg_table 一把到位};

这正呼应 4.1 说的「helper 而非 framework」——复用成本极低。


6、共享:get_sg_table 与 PRIME 导入(必须连续)

要导出成 dma-buf,需要一张sg_table。drm_gem_dma_get_sg_table用dma_get_sgtable从vaddr/dma_addr直接生成——因为内存连续,这张表通常就是一段。

导入方向有个硬约束:drm_gem_dma_prime_import_sg_table会用drm_prime_get_contiguous_size校验导入的 sg_table 是否物理连续(单一连续段),否则直接-EINVAL。这与它的定位一致:DMA-CMA 对象天生只接受连续内存,不像 shmem 那样能容纳任意散页。


7、与 shmem 对比:为什么它是「最简样例」

把两支轻量后备并排看,差异全部源于一句话——DMA-CMA 一次分配、钉死到底,shmem 惰性分配、可换出可 pin:

维度drm_gem_dma_object(4.2)drm_gem_shmem_object(4.3~)
后备内存CMA 物理连续内存shmem 可换出匿名页(散)
分配时机create 时一次性分配惰性,get_pages首次用时分配
能否换出/回收否(钉死,不进 shrinker)是(madv+ shrinker)
引用计数无(无需分层)pages_use_count/pin_count/vmap_use_count
内核vmap直接返回已有vaddr先 pin +vmap()拼连续地址
用户态mmap全量一次映射,无.fault空映射 + 按需缺页.fault
sg_table通常单段连续多段散页
PRIME 导入要求物理连续接受任意散页
典型用户display controller、无 IOMMU 的 SoC、v4l2virtio-gpu、iGPU、SoC GPU

读到这里应建立的认知是:DMA-CMA 把「对象 + 后备页 + 用户态/内核态访问 + 共享」这条链路做到了最短。理解了它,再看 shmem,你会发现 shmem 的绝大多数额外字段与流程,都是在为一个它不具备的能力买单——可换出。


8、本节小结

  • drm_gem_dma_object面向不支持 scatter-gather(或经紧耦合 IOMMU)的设备,提供设备视角连续的后备内存,底层无 IOMMU 时由CMA供给;
  • 结构只有dma_addr/vaddr/sgt/map_noncoherent四个字段,一次分配、钉死到底,不换出、不 pin、不计数;
  • 访问路径极短:vmap直接返回vaddr,mmap全量映射且无缺页回调;
  • 它是第四章「最简后备样例」,为理解 shmem 的复杂度提供了对照基线。

下一节(4.3):我们转向更通用的 shmem 后备。进入代码之前,先回答一个问题:BO 对后备存储有哪些要求,为什么恰好是 shmem 文件能全部满足。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 2:02:01

biliTickerBuy B站会员购抢购辅助工具怎么用:上手实操手册

biliTickerBuy B站会员购抢购辅助工具怎么用:上手实操手册 【免费下载链接】biliTickerBuy b站会员购购票辅助工具 项目地址: https://gitcode.com/GitHub_Trending/bi/biliTickerBuy 限量商品发售时,你得在开售的那一秒守在页面,差一…

作者头像 李华
网站建设 2026/10/19 2:00:34

当 MySQL 学会“读懂业务“:从底层原理到生产落地的完整指南

重要前置说明:原生 VECTOR 列类型是 MySQL 9.0 社区版​ 即可使用的;而库内 LLM 推理、ML_RAG 检索生成、ML_TRAIN AutoML 等完整 AI 流水线,属于 MySQL HeatWave GenAI / 企业本地 MySQL AI​ 的能力。下文分层讲解,你可以按自己…

作者头像 李华
网站建设 2026/10/19 2:00:17

ABAP Cloud 中的 OLAP 分析应用,从 CDS 多维模型到 InA 服务与实时业务决策

SAP 官方的航班预订分析教程里,一份预订记录不只用来显示航班号、客户和票价。它还可以回答各航空公司的预订量有多少、不同旅行社贡献了多少收入、客户分布是否发生变化。教程把航空公司、客户、航线和旅行社建成分析维度,把预订数据组织成分析模型,再通过 InA 服务交给 SA…

作者头像 李华
网站建设 2026/10/19 1:59:51

从姜公胆的连击看 ABAP,怎样让一次业务指令完成两次可靠处理

一张销售订单刚保存,仓库需要收到发货准备指令,财务还需要收到信用占用信息。业务人员只按了一次保存,后台却有两件事要办。这种「一次指令,连续产生两次效果」的画面,很容易让人想到《轩辕剑叁外传天之痕》里的姜公胆。不过,真正值得类比的并非简单地把同一段代码执行两…

作者头像 李华
网站建设 2026/10/19 1:57:53

嵌入式视觉部署实战:从图像采集到模型推理的完整链路与优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华