阅读随笔 | 深入Linux GPU ep5 再探内核态图形驱动:从DRM到AMDGPU
本文深入解析了Linux环境下AMD GPU的渲染与AI技术实现机制。首先介绍了用户态与内核态交互的关键桥梁libdrm,随后重点剖析了AMDGPU驱动在DRM框架下的工作流程。文章还梳理了AMDGPU驱动栈的模块组成,特别是核心模块amdgpu的功能特性及其与DRM框架的协作关系。
本文是《深入Linux GPU AMD GPU渲染与AI技术实践》读书笔记,以通俗简洁的方式重述知识。
干货长文预警,建议您做好准备。
关键词: Linux GPU 图形栈
导读
书接上文,我们已经几乎完成了用户态中的所有工作。剩下的最后一步,就是通过libdrm将用户态的指令发送到内核中,由DRM接受。在前文已经初步讲解了内核态中的渲染框架,本文将继续深入,
libdrm
libdrm 是一个用户空间 (user-space) 的库,它是DRM在用户空间对应的接口封装。它的主要作用是为用户空间的图形应用程序(如 Mesa 3D、X Server、Wayland Compositors 等)提供一套标准化的 API,使它们能够安全地与内核中的 DRM 驱动程序进行交互,而无需直接处理复杂的内核系统调用和设备文件操作。
工作机制
libdrm 的工作机制可以概括为以下几个关键步骤和组件:
1. 设备发现与打开 (Device Discovery and Open)
- 用户空间的图形应用程序(例如,一个 3D 游戏或一个图形库)首先会尝试通过 libdrm 库来找到并打开相应的 DRM 设备文件。
- DRM 设备文件通常位于
/dev/dri/目录下,例如/dev/dri/card0、/dev/dri/renderD128等。 - libdrm 封装了标准的
open()系统调用,用于打开这些设备文件,并获取一个文件描述符 (File Descriptor, FD)。这个 FD 是后续所有与 DRM 交互的基础。
2. 命令与数据传输 (I/O Control - ioctl)
这是 libdrm 的核心机制。
libdrm 中的每个函数调用(例如,请求分配 GPU 内存、设置显示模式、提交命令缓冲区等)最终都会被转换成一个或多个对 DRM 设备 FD 的 ioctl() (Input/Output Control) 系统调用。
| 用户空间操作 (libdrm API) | 内核空间操作 (DRM Driver) |
|---|---|
drmModeSetCrtc() (设置显示控制器) |
→ ioctl \xrightarrow{\text{ioctl}} ioctl 驱动接收并执行模式设置命令 |
drmIoctl(fd, DRM_IOCTL_GEM_CREATE, ...) (创建 GEM 对象) |
→ ioctl \xrightarrow{\text{ioctl}} ioctl 驱动在 GPU 内存中分配缓冲 |
drmIoctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, ...) (导出/共享缓冲) |
→ ioctl \xrightarrow{\text{ioctl}} ioctl 驱动处理缓冲句柄转换 |
3. 驱动特定功能 (Driver-Specific Functionality)
虽然 libdrm 提供了许多通用的 DRM API,但它也支持特定于硬件驱动程序的功能。
libdrm 通过一组驱动程序特定的 ioctl 接口(例如 DRM_IOCTL_I915_GEM_EXECBUFFER2 用于 Intel 显卡)来访问这些独特的功能。这保证了底层硬件的特殊能力可以被上层应用利用,同时保持 libdrm 接口的相对统一。
4. 内存管理 (Memory Management - GEM/TTM)
libdrm 提供了与内核内存管理机制交互的接口:
- 应用程序通过 libdrm 提供的 API (例如用于 GEM 的
drmPrimeFDToHandle或drmModeGetFB) 调用相应的ioctl,请求内核分配、映射、导入、导出或同步图形缓冲区。 - libdrm 负责在用户空间和内核空间之间传递缓冲区的句柄 (handle) 或文件描述符 (FD),从而实现内存的共享和访问。
5. 模式设置 (Mode Setting - KMS)
KMS (Kernel Mode Setting) 是 DRM 的一个重要功能,用于控制显示输出的分辨率、刷新率和显示器连接等。
- 应用程序(通常是显示服务器/合成器,如 Wayland/Xorg)使用 libdrm 的 KMS API(如
drmModeGetResources、drmModeSetCrtc、drmModeAddFB等)来:- 查询:获取连接的显示器、可用的显示模式 (Mode)、CRTC (显示控制器) 和编码器 (Encoder) 等信息。
- 设置:请求内核改变显示器的模式。
- libdrm 将这些请求转换为
ioctl命令,内核中的 DRM 驱动根据请求配置 GPU 和显示硬件。
从DRM到AMDGPU
AMDGPU 是 AMD 为其现代 GPU(如 GCN、RDNA 架构)开发的官方 Linux 内核驱动程序。它是一个复杂的 DRM 驱动,意味着它构建在 DRM 框架之上,并实现了 DRM 框架定义的所有核心接口。
工作流程
当一个 Vulkan 应用想要绘制一个三角形时,协作流程如下:
- 用户空间 (Mesa/Vulkan 驱动): 应用程序调用 Vulkan API,Mesa/Vulkan 驱动将渲染命令打包成一个或多个 AMDGPU 硬件指令格式 的命令缓冲区。
- libdrm 接口: Mesa 驱动调用 libdrm 提供的特定于 AMDGPU 的 API (例如,封装了
DRM_IOCTL_AMDGPU_CS的 ioctl)。 - 内核入口 (DRM 核心): DRM 核心接收到
ioctl请求,进行基本的安全检查(如 FD 是否有效)。 - AMDGPU 驱动处理:
- 验证: AMDGPU 驱动解析 ioctl 参数,验证命令缓冲区引用的内存对象是否有效、是否已准备好 (同步)。
- 调度: 将命令缓冲区添加到其内部的调度队列中。
- 硬件编程: 驱动程序将命令缓冲区放入 GPU 的命令环形缓冲区 (Ring Buffer),并通过中断或硬件寄存器通知 GPU 开始执行。
- GPU 执行: AMD GPU 执行命令,渲染三角形。
- 同步/完成: 渲染完成后,GPU 产生一个中断。AMDGPU 驱动处理该中断,更新相应的 Fence 或 Timeline 状态,通知等待结果的用户空间应用程序。
DRM协作机制
AMDGPU 驱动通过实现 DRM 框架提供功能,与内核 DRM 核心进行协作:
内核模式设置 (KMS: Kernel Mode Setting)
KMS 是 DRM 中负责管理显示输出的部分。显示服务器(如 Wayland/Xorg)通过 libdrm 调用 KMS API,AMDGPU 驱动在内核中安全、原子地完成显示设置。
AMDGPU (KMS 实现) 实现了这些抽象接口的硬件特定逻辑
- 探测:识别连接的显示器。
- 模式验证:确保请求的分辨率和时序 (timing) 是硬件支持的。
- 编程硬件:直接向 AMD GPU 的显示控制器寄存器写入数据,以设置分辨率、刷新率和像素格式等。
图形执行管理 (GEM: Graphics Execution Manager)
后续文章会单独提及内存管理、TTM、GEM。此处仅做介绍。
DRM 核心 提供 GEM 框架接口。 定义了 GEM 对象 (代表一块 GPU 内存) 的基本创建、映射、句柄管理等通用操作。
AMDGPU (GEM/内存管理) 使用 TTM (Translation Table Manager) 作为其底层内存管理机制。
- 分配:根据应用请求,在 VRAM 或系统内存中分配实际的 GPU 缓冲区。
- 映射:将这块内存映射到 GPU 的地址空间和/或 CPU 的地址空间,以便 CPU 和 GPU 都能访问。
- 页面错误处理:处理 GPU 访问错误内存时的中断。
💡 注意: AMDGPU 使用 TTM,这是一个比 GEM 更复杂的内存管理器,但它对外仍通过 GEM 接口暴露给用户空间。
命令提交与调度 (Command Submission and Scheduling)
关于这一部分,后续文章也会介绍。此处仅作简单介绍。
这是 3D 渲染和计算工作的核心。应用程序(通过 Mesa/Vulkan 驱动)将渲染命令打包成 命令缓冲区 (Command Buffer),通过 AMDGPU ioctl 提交给内核进行执行。
DRM 核心 定义了通用的 ioctl 接口用于命令提交。 提供了 上下文 (Context) 和 文件描述符 (FD) 的关联机制。
AMDGPU (命令提交) 实现了特定的命令提交 ioctl。
- Ring Buffer:维护一个或多个命令环形缓冲区,用于将用户提交的命令发送给 GPU 硬件。
- 调度器 (Scheduler):公平地调度来自不同应用程序的命令流,确保 GPU 资源高效利用。
- Fence/Timeline:使用同步对象 (如 Fence 或 Timeline) 来跟踪命令的执行状态,实现 CPU-GPU 间的同步和 GPU 内部任务间的依赖管理。 |
固件组成
AMDGPU 驱动栈在 Linux 内核中主要由一个核心模块及其紧密的依赖模块构成。以下是对这些主要内核模块的详细介绍:
amdgpu
amdgpu 是整个 AMDGPU 驱动栈的核心。它是基于 DRM (Direct Rendering Manager) 框架构建的主驱动程序。当系统启动时,它负责识别、初始化和管理现代 AMD GPU 硬件(包括 GCN、RDNA 和最新的 CDNA 架构)。
| 特性 | 描述 |
|---|---|
| 主要功能 | 图形渲染 (GFX): 处理所有 3D 渲染和图形命令提交。 显示管理 (KMS): 实现内核模式设置 (KMS),负责控制显示输出、设置分辨率和刷新率。 设备初始化: 加载所有必要的固件(如 PSP、SMC、VCN 固件)。 |
| 内存管理 | 依赖于 ttm 模块,负责分配和管理 GPU 专用的显存 (VRAM) 和通过 GART 访问的系统内存 (GTT)。 |
| 命令调度 | 包含 GPU 调度器 (Scheduler),负责公平地分配 GPU 资源,管理来自不同进程的命令流。 |
| 核心 API | 实现 AMDGPU 专有的 ioctl 接口,供用户空间的 Libdrm 和 Mesa/Vulkan 驱动调用。 |
| 依赖关系 | 它本身依赖于 drm 和 drm_kms_helper 模块。 |
amdkfd
amdkfd (AMD Kernel Fusion Device) 是一个可选但至关重要的模块,主要用于启用 AMD 的通用计算 (GPGPU) 功能,特别是支持 ROCm 和 HSA (Heterogeneous System Architecture)。
在 APU(集成 CPU 和 GPU)或专为计算设计的 GPU 上,amdkfd 提供了 CPU 和 GPU 之间高效协作的机制。
| 特性 | 描述 |
|---|---|
| 主要功能 | 异构内存管理: 管理 CPU 和 GPU 之间共享的内存区域,支持零拷贝 (Zero-Copy) 和更大、统一的虚拟地址空间。 |
| 用户模式队列 (UMQ): | 允许用户空间的计算应用程序直接将命令放入 GPU 的硬件队列(Ring Buffer),绕过内核的命令提交路径,减少延迟。 |
| 进程/设备抽象: | 抽象化计算设备,并管理计算进程的生命周期,包括处理 GPU 内存访问错误(Page Faults)。 |
| 启用条件 | 只有在内核配置了对 KFD 支持,并且在 amdgpu 模块加载时传入了相应的参数(如 amdgpu.vm_segment_size=... 或启用了 ROCm 支持)时,该模块才会被加载和激活。 |
ttm
ttm (Translation Table Manager) 是一个通用的 DRM 组件,但对于 AMDGPU 而言是其内存管理系统的基石。
| 特性 | 描述 |
|---|---|
| 主要功能 | GPU 内存管理: 负责复杂的图形内存对象管理,包括分配、迁移和锁定。 |
| 内存域管理: | 管理 VRAM(显存)、GTT(图形地址重映射表,映射系统内存)等不同内存域之间的缓冲区对象 (BO) 移动。 |
| 与 AMDGPU 关系 | AMDGPU 驱动通过 TTM 来实现其 GEM (Graphics Execution Manager) 接口,它是 AMDGPU 实现高性能显存管理的基础。 |
加载过程
AMDGPU 驱动需要加载多种类型的固件,包括:
- SMC/PSP 固件: 用于系统管理控制器 (System Management Controller) 和平台安全处理器 (Platform Security Processor),负责电源管理、时钟门控和安全启动。
- GC (Graphics Command Processor) 固件: 用于图形命令处理器,负责处理用户提交的渲染命令。
- SDMA (System DMA) 固件: 用于异步数据传输引擎。
- VCN/UVD/VCE 固件: 用于视频编解码硬件加速单元。
固件加载主要通过 Linux 内核的 request_firmware 机制 完成。
1. 驱动请求 (Driver Request)
- 时机: 当 AMDGPU 驱动程序在初始化过程中识别到某个特定的 AMD GPU 硬件(例如,通过 PCI ID),并且需要启动特定的硬件模块(如 SDMA 引擎、VCN 单元)时,就会触发固件加载。
- API 调用: AMDGPU 驱动会调用内核提供的 API:
其中:request_firmware(&fw, "amdgpu/[asic_name]_*.bin", dev);fw是一个结构体指针,用于存储加载后的固件数据。"amdgpu/[asic_name]_*.bin"是请求的固件文件名(例如,amdgpu/navi10_gc_4.0.0.bin)。dev是设备结构体指针,用于关联到特定的 GPU 设备。
2. 内核搜索 (Kernel Search and U-event)
内核接收到 request_firmware 请求后,会执行以下操作:
-
文件系统搜索: 内核首先会在其内部固件搜索路径中查找请求的文件。主要的搜索路径通常是:
/lib/firmware//lib/firmware/updates//lib/firmware/amdgpu/(这是 AMDGPU 固件最常见的位置)
-
用户空间通知 (U-event): 如果内核在自己的搜索路径中没有找到所需的固件文件,它不会立刻失败,而是会向用户空间发送一个 U-event (udev event) 通知。
- 这个 U-event 通知用户空间的程序(例如
udevd或systemd-udevd):“内核需要文件名为amdgpu/xxx.bin的固件。”
- 这个 U-event 通知用户空间的程序(例如
3. 用户空间加载 (Userspace Loading)
udev处理:udevd进程接收到这个 U-event 后,它会运行一个特定的助手程序(通常是/sbin/hotplug或其等效物)。- 固件定位与传输: 这个助手程序在文件系统(通常是
/lib/firmware/)中定位到所需的固件文件,并将固件的原始二进制数据通过一个特殊的管道或机制传输回内核。 - 内核等待: 在用户空间处理 U-event 的这段时间内,驱动程序的初始化线程会处于阻塞等待状态。
4. 驱动接收与硬件编程 (Driver Receive and Programming)
- 固件数据: 内核接收到用户空间传输回来的固件二进制数据后,将其存储在
request_firmware调用的fw结构体中。 - 驱动验证: AMDGPU 驱动在接收到固件后,会对其进行完整性检查和版本验证。
- 硬件上载: 驱动程序将固件数据加载到 GPU 内部的 SRAM 或特定的硬件寄存器/内存区域。例如,PSP 固件会加载到 AMD 的安全处理器中。
- 启动硬件: 一旦固件加载完成并被硬件识别,AMDGPU 驱动就可以发送命令启动相应的硬件单元(如初始化 SDMA 引擎、启动图形核心)。
5. 资源释放
- 固件加载完成后,AMDGPU 驱动会调用
release_firmware()API,通知内核释放用于存储固件二进制数据的内存,完成整个加载流程。
更多推荐

所有评论(0)