1. 背景与核心概念
在 Linux 图形驱动开发中,DRM(Direct Rendering Manager)子系统负责管理 GPU 资源、内存分配和用户空间与内核空间的交互。GEM(Graphics Execution Manager)是 DRM 的一套内存管理框架,主要用于分配、管理和映射显存对象(GEM objects)。
用户空间应用(如 X server、Wayland compositor 或 OpenGL/Vulkan 驱动)通常需要将 GPU buffer 映射到自己的地址空间,以便直接访问显存。这一过程通过 mmap
系统调用实现,而 DRM/GEM 通过一套虚拟 offset 机制(fake offset)来安全地管理和查找这些对象。drm_gem_mmap
就是核心的 mmap 入口函数。
要理解本文,首先要理解mmap的功能和原理,这个文章一抓一大把,本专栏里也水了一篇:linux mmap 的匿名映射和设备映射-CSDN博客。这里给出一个解释:mmap实现了物理内存上的文件内容映射到用户态空间(VMA),用户态可直接访问的效果;本质是以page为单位将VMA与物理page进行关联的机制。那问题就来了,根据该函数的传入参数:
-
要映射的文件:要映射的是设备文件,也就是drm_device打开后对应到drm_file;
-
映射的文件长度:文件长度通常是要访问的GEM对象的大小;
-
映射的文件偏移:这里就有疑问了,就不是一个真正的文件,哪有偏移? 这就是上面fake offset的作用了,这也是本篇文章要讲清楚的一个技术点。
2. drm_gem_mmap 实现流程详解
drm_gem_mmap
的主要职责是:根据用户空间传入的 mmap offset,查找对应的 GEM 对象,并完成内存映射。代码如下:
cpp
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct drm_file *priv = filp->private_data;
struct drm_device *dev = priv->minor->dev;
struct drm_gem_object *obj = NULL;
struct drm_vma_offset_node *node;
int ret;
if (drm_dev_is_unplugged(dev))
return -ENODEV;
// 1. 通过 offset 查找 GEM 对象
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));
drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
// 2. 权限检查
if (!drm_vma_node_is_allowed(node, priv)) {
drm_gem_object_put(obj);
return -EACCES;
}
// 3. 完成内存映射
ret = drm_gem_mmap_obj(obj,
drm_vma_node_size(node) << PAGE_SHIFT,
vma);
drm_gem_object_put(obj);
return ret;
}
代码实现的流程概述如下:
-
用户空间调用
mmap(fd, offset, size, ...)
,offset 为 DRM/GEM 分配的 fake offset。 -
内核进入 DRM 设备的
drm_gem_mmap
实现。 -
通过
vma_offset_manager
查找 offset 对应的vma_node(上面代码中步骤1)
。 -
通过
vma_node
反查 GEM 对象,检查引用计数和权限(上面代码中步骤2)
。 -
调用
drm_gem_mmap_obj
,设置 VMA 的回调和页表,完成映射(上面代码中步骤3)
。
下面具体描述。
2.1 通过 offset 查找 GEM 对象
-
drm_vma_offset_manager:DRM 设备的 VMA offset 管理器,负责管理所有 GEM 对象的虚拟 offset 区间。
-
drm_vma_offset_exact_lookup_locked :根据 mmap offset(
vma->vm_pgoff
)和映射页数,查找对应的drm_vma_offset_node
。vma->vm_pgoff
是如下函数返回的,可以看到就是drm_mm_node的start的偏移,因为start是以page为单位,所以左移了。
cpp
static inline __u64 drm_vma_node_offset_addr(struct drm_vma_offset_node *node)
{
return ((__u64)node->vm_node.start) << PAGE_SHIFT;
}
- drm_vma_offset_node :每个 GEM 对象都包含一个 drm_vma_offset_nodel类型的
vma_node成员
,记录其虚拟 offset 区间(start, size),用于 mmap 查找。
2.2 权限检查
- 检查当前文件(进程)是否有权限访问该对象(防止跨进程非法映射)。函数实现如下,就是在node->vm_files中查找。这些drm_file是在gem创建或者import过程中通过drm_vma_node_allow加入的node->vm_files中的。
cpp
bool drm_vma_node_is_allowed(struct drm_vma_offset_node *node,
struct drm_file *tag)
{
struct drm_vma_offset_file *entry;
struct rb_node *iter;
read_lock(&node->vm_lock);
iter = node->vm_files.rb_node;
while (likely(iter)) {
entry = rb_entry(iter, struct drm_vma_offset_file, vm_rb);
if (tag == entry->vm_tag)
break;
else if (tag > entry->vm_tag)
iter = iter->rb_right;
else
iter = iter->rb_left;
}
read_unlock(&node->vm_lock);
return iter;
}
2.3 完成映射
- 调用
drm_gem_mmap_obj
,设置 VMA 的回调、权限和页表等。
3. drm_vma_offset_node 的作用与机制
3.1 结构定义与核心字段
drm_vma_offset_node
是 DRM/GEM 内存管理的核心结构,定义如下(见 drm_vma_manager.h
):
cpp
struct drm_vma_offset_node {
rwlock_t vm_lock;
struct drm_mm_node vm_node; //实际物理地址和大小
struct rb_root vm_files; //存放可访问该node的drm_file
void *driver_private;
};
3.2 作用与设计动机
-
虚拟 offset 映射:用户空间 mmap 时,传入的 offset 并不是物理地址,而是 DRM/GEM 分配的"fake offset"。这样可以避免用户空间直接访问物理显存,提升安全性和灵活性。
-
对象查找:通过 offset 能快速定位到对应的 GEM 对象,无需遍历所有对象。
-
权限隔离:每个对象的 offset 区间唯一,防止跨进程、跨对象的非法访问。
-
高效管理:红黑树结构支持高效的插入、查找和删除操作,适合大量 buffer 管理。
3.3 生命周期与管理流程
-
分配 :GEM 对象创建时,调用
drm_gem_create_mmap_offset
,分配一个唯一的 offset 区间,并将vma_node
插入到vma_offset_manager
的红黑树中。 -
查找 :mmap 时,通过 offset 查找
vma_node
,进而定位 GEM 对象。 -
权限控制 :每个
vma_node
记录允许访问的文件(进程),通过drm_vma_node_is_allowed
检查。 -
释放 :对象销毁时,调用
drm_gem_free_mmap_offset
,从红黑树中移除vma_node
,释放 offset 区间。
4. 总结
drm_gem_mmap
和 drm_vma_offset_node
是 Linux DRM/GEM 子系统内存管理的基石。通过虚拟 offset 映射机制,内核实现了安全、高效、灵活的显存对象管理。驱动开发者在实现自定义 GEM 对象、支持 mmap 映射时,需充分理解 offset 管理机制、对象生命周期和权限控制,确保系统安全与性能。
**一句话总结:fake offset就是drm_mm_node的start值,mmap实现的是用户态VMA与GEM对象进行了绑定;**真正实现虚拟地址到物理页的页表填写可能并不在该函数中,可能是page_fault中进行的。这个可以看下何小龙的mmap的博客。
如有帮助,请三连:点赞、收藏、加关注。