1. 核心概念
drm_gem_object是DRM子系统中用于管理显存对象的核心抽象。其设计解决了文件描述符数量限制的问题:
- 使用自定义的 handle(32位整数)代替文件描述符
- 每个进程维护独立的 handle 到对象的映射表(idr)
- 对象本身通过引用计数管理生命周期
1.1 核心数据结构
struct drm_gem_object {
struct kref refcount; // 内核引用计数
unsigned handle_count; // handle引用计数
struct drm_device *dev; // 所属设备
struct file *filp; // shmem后端(可选)
struct drm_vma_offset_node vma_node; // mmap offset节点
size_t size; // 对象大小
int name; // 全局名称(0表示无名)
struct dma_buf *dma_buf; // 关联的dma-buf
struct dma_buf_attachment *import_attach; // 导入的附件
struct dma_resv *resv; // 同步保留对象
const struct drm_gem_object_funcs *funcs; // 驱动回调
};
进程私有数据:
struct drm_file {
struct idr object_idr; // handle → drm_gem_object 映射
spinlock_t table_lock; // 保护 object_idr
struct drm_prime_file_private prime; // PRIME 缓存(dma-buf)
};
2. BO 生命周期与用户态 IOCTL 接口
GEM Buffer Object 的完整生命周期包括:创建 → 映射 → 使用 → 共享 → 释放。本节按此顺序介绍相关的用户态接口。
2.1 阶段一:BO 创建
BO 对象由驱动程序特定的 IOCTL 创建(如 DRM_IOCTL_MODE_CREATE_DUMB、DRM_IOCTL_I915_GEM_CREATE 等),创建后返回 handle。
Handle 创建内核实现:
int drm_gem_handle_create(struct drm_file *file_priv,
struct drm_gem_object *obj,
u32 *handlep)
{
mutex_lock(&obj->dev->object_name_lock);
return drm_gem_handle_create_tail(file_priv, obj, handlep);
}
int drm_gem_handle_create_tail(struct drm_file *file_priv,
struct drm_gem_object *obj,
u32 *handlep)
{
struct drm_device *dev = obj->dev;
u32 handle;
int ret;
WARN_ON(!mutex_is_locked(&dev->object_name_lock));
// 增加 handle 引用计数
drm_gem_object_handle_get(obj);
// 从 idr 分配新 handle
idr_preload(GFP_KERNEL);
spin_lock(&file_priv->table_lock);
ret = idr_alloc(&file_priv->object_idr, NULL, 1, 0, GFP_NOWAIT);
spin_unlock(&file_priv->table_lock);
idr_preload_end();
mutex_unlock(&dev->object_name_lock);
handle = ret;
// 授予 VMA 访问权限
ret = drm_vma_node_allow(&obj->vma_node, file_priv);
// 调用驱动 open 回调
if (obj->funcs->open)
ret = obj->funcs->open(obj, file_priv);
// 插入对象到 idr
spin_lock(&file_priv->table_lock);
obj = idr_replace(&file_priv->object_idr, obj, handle);
spin_unlock(&file_priv->table_lock);
*handlep = handle;
return 0;
}
2.2 阶段二:BO 映射 (mmap)
创建 BO 后,用户态通过 mmap() 系统调用将其映射到进程地址空间。
2.2.1 获取 mmap offset
DRM_IOCTL_MODE_MAP_DUMB 或驱动特定 IOCTL 用于获取 mmap 的 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 (drm_gem_is_imported(obj)) {
ret = -EINVAL;
goto out;
}
ret = drm_gem_create_mmap_offset(obj);
*offset = drm_vma_node_offset_addr(&obj->vma_node);
return ret;
}
Offset 生成:
int drm_gem_create_mmap_offset(struct drm_gem_object *obj)
{
struct drm_device *dev = obj->dev;
return drm_vma_offset_add(dev->vma_offset_manager, &obj->vma_node,
obj->size / PAGE_SIZE);
}
2.2.2 执行 mmap
用户态调用 mmap(/dev/dri/card0, offset) 后,内核执行:
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;
drm_vma_offset_lock_lookup(dev->vma_offset_manager);
// 根据 offset 查找对象
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);
// 检查引用计数,防止 use-after-free
if (!kref_get_unless_zero(&obj->refcount))
obj = NULL;
}
drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
// 验证访问权限
if (!drm_vma_node_is_allowed(node, priv)) {
drm_gem_object_put(obj);
return -EACCES;
}
ret = drm_gem_mmap_obj(obj, drm_vma_node_size(node) << PAGE_SHIFT, vma);
drm_gem_object_put(obj);
return ret;
}
设置 VMA:
int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
struct vm_area_struct *vma)
{
int ret;
// 检查大小
if (obj_size < vma->vm_end - vma->vm_start)
return -EINVAL;
// 增加引用(由 vm_close 释放)
drm_gem_object_get(obj);
vma->vm_private_data = obj;
vma->vm_ops = obj->funcs->vm_ops;
// 调用驱动 mmap 回调
if (obj->funcs->mmap) {
ret = obj->funcs->mmap(obj, vma);
if (ret)
goto err_drm_gem_object_put;
WARN_ON(!(vma->vm_flags & VM_DONTEXPAND));
} else {
if (!vma->vm_ops) {
ret = -EINVAL;
goto err_drm_gem_object_put;
}
// 设置默认 VMA 属性
vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
}
return 0;
}
2.3 阶段三:BO 共享
2.3.1 现代方式:PRIME/DMA-BUF 共享(推荐)
**导出:DRM_IOCTL_PRIME_HANDLE_TO_FD **
将本地 handle 导出为可跨进程传递的 DMA-BUF 文件描述符。
struct drm_prime_handle {
__u32 handle; // 输入:GEM handle
__u32 flags; // 输入:DRM_CLOEXEC | DRM_RDWR
__s32 fd; // 输出:DMA-BUF 文件描述符
};
int drm_gem_prime_handle_to_fd(struct drm_device *dev,
struct drm_file *file_priv,
uint32_t handle,
uint32_t flags,
int *prime_fd)
{
struct dma_buf *dmabuf;
int fd = get_unused_fd_flags(flags);
if (fd < 0)
return fd;
// 导出为 dma-buf
dmabuf = drm_gem_prime_handle_to_dmabuf(dev, file_priv, handle, flags);
if (IS_ERR(dmabuf)) {
put_unused_fd(fd);
return PTR_ERR(dmabuf);
}
fd_install(fd, dmabuf->file);
*prime_fd = fd;
return 0;
}
导出核心逻辑:
struct dma_buf *drm_gem_prime_handle_to_dmabuf(struct drm_device *dev,
struct drm_file *file_priv,
uint32_t handle,
uint32_t flags)
{
struct drm_gem_object *obj;
struct dma_buf *dmabuf;
mutex_lock(&file_priv->prime.lock);
obj = drm_gem_object_lookup(file_priv, handle);
if (!obj) {
dmabuf = ERR_PTR(-ENOENT);
goto out_unlock;
}
// 检查缓存,避免重复导出
dmabuf = drm_prime_lookup_buf_by_handle(&file_priv->prime, handle);
mutex_lock(&dev->object_name_lock);
// 处理 re-export(导入后再导出)
if (obj->import_attach) {
dmabuf = obj->import_attach->dmabuf;
get_dma_buf(dmabuf);
goto out_have_obj;
}
if (obj->dma_buf) {
get_dma_buf(obj->dma_buf);
dmabuf = obj->dma_buf;
goto out_have_obj;
}
// 调用驱动导出函数
dmabuf = export_and_register_object(dev, obj, flags);
out_have_obj:
// 缓存 handle ↔ dma_buf 映射
drm_prime_add_buf_handle(&file_priv->prime, dmabuf, handle);
mutex_unlock(&dev->object_name_lock);
mutex_unlock(&file_priv->prime.lock);
return dmabuf;
}
**导入:DRM_IOCTL_PRIME_FD_TO_HANDLE **
接收 DMA-BUF fd,在本进程创建对应的 GEM handle。
int drm_gem_prime_fd_to_handle(struct drm_device *dev,
struct drm_file *file_priv,
int prime_fd,
uint32_t *handle)
{
struct dma_buf *dma_buf;
struct drm_gem_object *obj;
int ret;
dma_buf = dma_buf_get(prime_fd);
if (IS_ERR(dma_buf))
return PTR_ERR(dma_buf);
mutex_lock(&file_priv->prime.lock);
// 检查是否已经导入(避免重复)
ret = drm_prime_lookup_buf_handle(&file_priv->prime, dma_buf, handle);
if (ret == 0)
goto out_put;
// 调用驱动导入函数
mutex_lock(&dev->object_name_lock);
if (dev->driver->gem_prime_import)
obj = dev->driver->gem_prime_import(dev, dma_buf);
else
obj = drm_gem_prime_import(dev, dma_buf);
if (IS_ERR(obj)) {
ret = PTR_ERR(obj);
goto out_unlock;
}
// 关联 dma_buf
if (obj->dma_buf) {
WARN_ON(obj->dma_buf != dma_buf);
} else {
obj->dma_buf = dma_buf;
get_dma_buf(dma_buf);
}
// 创建 handle
ret = drm_gem_handle_create_tail(file_priv, obj, handle);
drm_gem_object_put(obj);
if (ret)
goto out_put;
// 缓存映射
ret = drm_prime_add_buf_handle(&file_priv->prime, dma_buf, *handle);
mutex_unlock(&file_priv->prime.lock);
dma_buf_put(dma_buf);
return 0;
out_unlock:
mutex_unlock(&dev->object_name_lock);
out_put:
mutex_unlock(&file_priv->prime.lock);
dma_buf_put(dma_buf);
return ret;
}
共享流程图:
进程A 进程B
| |
| PRIME_HANDLE_TO_FD(handle) |
|----> fd = dma-buf |
| |
| UNIX socket传递fd (安全) |
|--------------------------------->|
| | PRIME_FD_TO_HANDLE(fd)
| |----> handle
2.4 阶段四:BO 释放
**DRM_IOCTL_GEM_CLOSE **
释放进程对 GEM 对象的引用,当所有引用释放后,对象被销毁。
struct drm_gem_close {
__u32 handle; // 要关闭的对象 handle
__u32 pad;
};
int drm_gem_close_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_gem_close *args = data;
return drm_gem_handle_delete(file_priv, args->handle);
}
关键实现:
int drm_gem_handle_delete(struct drm_file *filp, u32 handle)
{
struct drm_gem_object *obj;
spin_lock(&filp->table_lock);
// 从 idr 中移除 handle 映射
obj = idr_replace(&filp->object_idr, NULL, handle);
spin_unlock(&filp->table_lock);
// 调用驱动回调、撤销 VMA 权限、减少引用计数
drm_gem_object_release_handle(handle, obj, filp);
spin_lock(&filp->table_lock);
idr_remove(&filp->object_idr, handle);
spin_unlock(&filp->table_lock);
return 0;
}
释放流程:
static int drm_gem_object_release_handle(int id, void *ptr, void *data)
{
struct drm_file *file_priv = data;
struct drm_gem_object *obj = ptr;
// 调用驱动 close 回调
if (obj->funcs->close)
obj->funcs->close(obj, file_priv);
// 清理 PRIME 缓存
drm_prime_remove_buf_handle(&file_priv->prime, id);
// 撤销 VMA 权限
drm_vma_node_revoke(&obj->vma_node, file_priv);
// 减少 handle 引用计数
drm_gem_object_handle_put_unlocked(obj);
return 0;
}
文件关闭时自动清理:
void drm_gem_release(struct drm_device *dev, struct drm_file *file_private)
{
// 遍历所有 handle,逐个释放
idr_for_each(&file_private->object_idr,
&drm_gem_object_release_handle, file_private);
idr_destroy(&file_private->object_idr);
}
2.5 完整生命周期示例
// 用户态代码示例
int fd = open("/dev/dri/card0", O_RDWR);
// 1. 创建 BO
struct drm_mode_create_dumb create = {
.width = 1920,
.height = 1080,
.bpp = 32,
};
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
uint32_t handle = create.handle;
// 2. 映射 BO
struct drm_mode_map_dumb map = {
.handle = handle,
};
ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
void *ptr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
// 3. 使用 BO
memset(ptr, 0xff, create.size);
// 4. 共享 BO(现代方式)
struct drm_prime_handle prime = {
.handle = handle,
.flags = DRM_CLOEXEC,
};
ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime);
// 通过 UNIX socket 将 prime.fd 发送给其他进程
// 5. 释放 BO
munmap(ptr, create.size);
struct drm_gem_close close_args = {
.handle = handle,
};
ioctl(fd, DRM_IOCTL_GEM_CLOSE, &close_args);
close(fd); // 关闭文件会自动释放所有未关闭的 handle
3. 并发保护
关键锁机制
| 锁 | 保护范围 | 使用场景 |
|---|---|---|
dev->object_name_lock | 全局名称 idr、handle_count | FLINK/OPEN、handle 创建/释放 |
file_priv->table_lock | 进程 handle idr | handle 分配/查找/删除 |
file_priv->prime.lock | PRIME 缓存 | 导入/导出 dma-buf |
obj->resv (dma_resv) | 对象内容同步 | GPU 访问同步 |
典型锁顺序
1. file_priv->prime.lock
2. dev->object_name_lock
3. file_priv->table_lock (短期持有)
4. 关键设计要点
-
双层引用计数:
refcount:内核对象引用handle_count:用户态 handle 引用
-
进程隔离:
- 每个进程独立的 handle 空间
- VMA 权限控制访问
-
缓存优化:
- PRIME 缓存避免重复导入/导出
- 检测 self-import
-
并发安全:
- 多层锁保护
- 处理 race condition(如 handle_count 检查)
-
资源清理:
- 文件关闭时自动释放所有 handle
- 断开引用循环防止内存泄漏
作为用户态接口,理解gem bo的整个生命周期的操作和使用过程是必要的,这也串起来后面所有的技术实现。
1294

被折叠的 条评论
为什么被折叠?



