第三章:GEM分析:3.2 用户态接口drm_gem_object

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_DUMBDRM_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_countFLINK/OPEN、handle 创建/释放
file_priv->table_lock进程 handle idrhandle 分配/查找/删除
file_priv->prime.lockPRIME 缓存导入/导出 dma-buf
obj->resv (dma_resv)对象内容同步GPU 访问同步

典型锁顺序

1. file_priv->prime.lock
2. dev->object_name_lock
3. file_priv->table_lock (短期持有)

4. 关键设计要点

  1. 双层引用计数

    • refcount:内核对象引用
    • handle_count:用户态 handle 引用
  2. 进程隔离

    • 每个进程独立的 handle 空间
    • VMA 权限控制访问
  3. 缓存优化

    • PRIME 缓存避免重复导入/导出
    • 检测 self-import
  4. 并发安全

    • 多层锁保护
    • 处理 race condition(如 handle_count 检查)
  5. 资源清理

    • 文件关闭时自动释放所有 handle
    • 断开引用循环防止内存泄漏

作为用户态接口,理解gem bo的整个生命周期的操作和使用过程是必要的,这也串起来后面所有的技术实现。

<think> 首先,用户的问题是关于解决编译错误:"致命错误:drm/drm_gem_cma_helper.h:没有那个文件或目录"。这看起来是Linux内核开发中的一个问题。 用户提供了几个引用,这些引用是关于DRM(Direct Rendering Manager)和GEM(Graphics Execution Manager)的。让我快速回顾这些引用: - 引用[1]: 描述了drm_gem.c文件的作用,它是DRM框架的核心部分,处理GEM模块。 - 引用[2]: 提到drm_gem_cma_create()函数,用于分配和初始化GEM对象。 - 引用[3]: 讨论了使用GEM的驱动需要设置DRIVER_GEM位。 - 引用[4]: 展示了drm_gem_cma_create_with_handle()函数的代码片段,用于创建DMA缓冲区。 用户的问题是:"致命错误:drm/drm_gem_cma_helper.h:没有那个文件或目录"。这表示在编译代码时,编译器找不到drm_gem_cma_helper.h头文件。 现在,我需要基于这些引用和一般知识来提供解决方法。尽管引用不是直接解决这个错误,但它们提供了上下文。 可能的原因和解决方法: 1. **缺少内核头文件或开发包**:这个头文件是Linux内核DRM框架的一部分。如果用户没有安装正确的内核头文件或开发包,就会报这个错误。 - 解决方法:安装Linux内核头文件或特定于DRM的开发包。例如,在Ubuntu上,可以安装`linux-headers-$(uname -r)`或`libdrm-dev`包。 - 引用中没有直接提到这个,但这是一个常见问题。 2. **内核版本不兼容**:drm_gem_cma_helper.h可能只在较新的内核版本中可用。如果用户使用的是旧内核,这个文件可能不存在。 - 解决方法:更新内核到支持该文件的版本。 - 从引用[2]和[4]中,提到了drm_gem_cma_create()函数,这表明CMA(Contiguous Memory Allocator)是DRM的一部分,可能需要较新的内核。 3. **路径问题**:代码中可能包含了错误的路径。头文件应该位于标准位置,如`/usr/include/drm/`。 - 解决方法:检查头文件是否安装在系统中。使用命令`find /usr/include -name drm_gem_cma_helper.h`来搜索。 4. **驱动特定问题**:如果用户正在编译自定义驱动,可能没有正确设置环境。 - 解决方法:确保编译环境正确配置,包括内核源代码路径。 基于引用: - 引用[2]提到"drm_gem_cma_create()",这与头文件相关。 - 引用[4]展示了代码,使用drm_gem_cma_create(),这需要drm_gem_cma_helper.h。 所以,解决方法应该包括安装必要的包。 标准步骤: - 在基于Debian的系统(如Ubuntu)上,运行:`sudo apt install linux-headers-$(uname -r) libdrm-dev` - 在基于RPM的系统(如Fedora)上,运行:`sudo dnf install kernel-devel libdrm-devel` 用户可能没有指定发行版,所以给出通用建议。 确保回答结构清晰,使用中文,并添加相关问题。 最后,生成相关问题。 回答结构: 1. **解释错误原因**:简要说明为什么会出现这个错误。 2. **提供解决方法**:逐步给出解决方案。 3. **引用支持**:自然地添加引用标识。 4. **生成相关问题**:在结束时添加相关问题部分。 </think>### 解决“致命错误:drm/drm_gem_cma_helper.h:没有那个文件或目录”的方法 这个错误通常发生在 Linux 内核开发或编译 DRM(Direct Rendering Manager)相关代码时,表示编译器无法找到 `drm_gem_cma_helper.h` 头文件。该文件是 DRM 框架中 GEM(Graphics Execution Manager)模块的一部分,主要用于管理连续内存分配(CMA),在引用[2]中提到的 `drm_gem_cma_create()` 函数就依赖于这个头文件[^2]。错误可能由以下原因引起: - **缺少内核头文件或开发包**:系统未安装对应的 Linux 内核头文件或 DRM 开发库。 - **内核版本不兼容**:`drm_gem_cma_helper.h` 在较旧的内核版本中可能不存在或已被更名(例如,在 Linux 5.11+ 中,CMA 功能被整合到更通用的接口中)。 - **编译环境配置错误**:编译时未正确指定内核源代码路径。 - **代码依赖问题**:引用的代码基于特定内核版本,而您的环境不匹配。 以下是逐步解决方法,请根据您的 Linux 发行版和内核版本操作(以 root 或 sudo 权限执行): #### 步骤 1: 安装必要的内核头文件和开发包 确保系统安装了当前内核版本的头文件和 DRM 开发库。这将提供缺失的 `drm_gem_cma_helper.h` 文件。 - **Ubuntu/Debian 系统**: ```bash sudo apt update sudo apt install linux-headers-$(uname -r) libdrm-dev ``` - **Fedora/CentOS/RHEL 系统**: ```bash sudo dnf update sudo dnf install kernel-devel libdrm-devel ``` - **Arch Linux/Manjaro 系统**: ```bash sudo pacman -Syu sudo pacman -S linux-headers libdrm ``` 安装后,头文件通常位于 `/usr/include/drm/` 目录下。使用命令验证文件是否存在: ```bash find /usr/include -name drm_gem_cma_helper.h ``` #### 步骤 2: 检查内核版本兼容性 如果步骤 1 无法解决问题,您的内核版本可能过旧或过新: - **查看内核版本**: ```bash uname -r ``` - **如果内核版本 < 5.0**:`drm_gem_cma_helper.h` 在旧版本中可能不可用。建议升级内核: - Ubuntu/Debian: `sudo apt install --install-recommends linux-generic` - Fedora: `sudo dnf upgrade kernel` - **如果内核版本 ≥ 5.11**:CMA 功能已被重构。在较新内核中,应改用 `drm/drm_gem.h` 或驱动特定头文件。例如,引用[1]提到 GEM 模块已抽象化,驱动需通过 `drm_gem_object_funcs` 扩展[^1]。检查您的代码,替换 `drm_gem_cma_helper.h` 为: ```c #include <drm/drm_gem.h> #include <drm/drm_gem_cma.h> // 如果可用,否则使用通用接口 ``` #### 步骤 3: 配置编译环境 如果编译自定义内核模块(如 DRM 驱动),确保 Makefile 正确引用内核源代码路径: - 在 Makefile 中添加内核路径: ```makefile KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build ``` - 然后运行: ```bash make -C $(KERNEL_DIR) M=$(PWD) modules ``` 如果问题持续,导出内核头文件路径: ```bash export CPATH=/usr/include/drm:$CPATH ``` #### 步骤 4: 验证和测试 - 重新编译代码,观察错误是否解决。 - 如果仍报错,检查代码是否依赖特定驱动实现。引用[4]显示,CMA 操作需要驱动支持(如 `drm_gem_cma_create`)[^4]。确保您的驱动在 `struct drm_driver` 中设置了 `DRIVER_GEM` 标志,如引用[3]所述[^3]。 ### 常见原因和预防 - **原因**:DRM GEM 模块在 Linux 内核中快速演进,头文件路径或名称可能随版本变化。 - **预防**:开发时使用与目标内核匹配的版本,并定期更新开发包。 如果以上步骤无效,请提供更多细节(如 Linux 发行版、内核版本和错误日志),以便进一步诊断。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值