引言
在 Linux 内核庞大而复杂的子系统中,DRM(Direct Rendering Manager,直接渲染管理器)和 KMS(Kernel Mode Setting,内核模式设置)共同构成了现代图形栈的基石。它们不仅驱动着从嵌入式手机屏幕到 8K 专业显示器的所有显示设备,还为 GPU 硬件加速渲染、合成器输出以及多屏协同提供了底层基础设施。尽管 DRM/KMS 在桌面和移动端无处不在,但其内部架构——CRTC/Encoder/Connector 的硬件抽象模型、原子模式设置(Atomic Modesetting)的引入动机、Plane 的硬件合成机制、以及与现代 GPU 驱动(amdgpu / i915 / nouveau)的关系——对于大多数应用开发者仍然是一个"黑箱"。本文将深入 DRM/KMS 内核子系统,从硬件拓扑抽象、用户态交互、原子提交流水线、以及与现代 Wayland/Xorg 合成器的协作机制四个维度,完整揭示 Linux 图形显示管理的全貌。
1. DRM/KMS 架构总览
1.1 历史演进:从 XFree86 到 Atomic KMS
Linux 图形显示子系统的演化可以分为四个阶段:
- 1990s — XFree86 时代:用户态 X Server 通过直接映射显卡 MMIO 寄存器来控制显示模式(Mode Setting)。内核仅提供基本的帧缓冲(fbdev)抽象。这种用户态模式设置导致系统崩溃、恢复困难、安全性极差。
- 2004 — Kernel Mode Setting 起步:VESA 和早期 Intel i915 驱动在内核中接管显示模式设置,但架构混乱,缺乏统一抽象。
- 2008 — DRM 子系统正式化:Dave Airlie 创建统一的 DRM 子系统,引入 CRTC/Encoder/Connector/Plane 的硬件抽象模型。
- 2014 — Atomic KMS 引入:传统 modeset 存在中间状态(partial state),Atomic API 将所有显示变更打包为一个事务,要么全部成功要么全部回滚,彻底消除闪屏和中间错误状态。
- 2018+ — Universal Planes + HDR + Color Management:Universal Planes 将 Overlay/Cursor/Primary Plane 统一抽象,HDR 元数据和色彩管理加入 KMS 属性系统。
1.2 核心硬件抽象模型
DRM/KMS 将复杂的 GPU 显示管线抽象为四个核心对象类型和一组辅助属性:
┌────────────────── Pipeline Model ──────────────────┐
│ │
│ ┌─────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Plane │───▶│ CRTC │───▶│ Encoder │ │
│ │ (像素源) │ │ (扫描器) │ │ (信号编码) │──▶│──▶ Connector ──▶ Monitor
│ └─────────┘ └──────────┘ └───────────────┘ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │ Primary │ │ pixclk │ │ DP/HDMI │ │
│ │ Overlay │ │ h/v sync│ │ eDP/DSI │ │
│ │ Cursor │ │ margins │ │ VGA/CVBS│ │
│ └─────────┘ └────────┘ └─────────┘ │
│ │
└──────────────────────────────────────────────────────┘
1.3 四大核心对象类型
Plane(像素平面)
Plane 是像素数据的来源。GPU 通常包含多种类型的 Plane:
- Primary Plane:主显示平面,每个 CRTC 必须有且仅有一个 Primary Plane。它作为 "framebuffer 基底",全屏切换发生时主要由它驱动。
- Overlay Plane(硬件叠加层):硬件光标/视频叠加专用,支持 YUV 格式直接扫描输入,零拷贝视频回放的关键。现代 GPU 通常有 2-4 个 Overlay/Cursor Planes。
- Cursor Plane:硬件光标专用,通常支持固定小尺寸(64x64或256x256),在扫描过程中由显示控制器硬件叠加。
Plane 的关键属性包括:
struct drm_plane_properties {
FB_ID — 绑定的 framebuffer 对象 ID
CRTC_ID — 绑定的 CRTC(可切换)
SRC_X/Y/W/H — 源图像裁剪矩形(定点 16.16)
CRTC_X/Y/W/H — 在 CRTC 上的显示矩形
rotation — 旋转/翻转标志 (DRM_ROTATE_* / DRM_REFLECT_*)
alpha — 全局透明度 (0-65535)
pixel_blend_mode — 像素混合方式 (None/Premultiplied/Coverage)
zpos — Plane 深度优先级 (Atomic 新增)
IN_FORMATS — 支持的像素格式 (DRM Format Modifier)
COLOR_ENCODING — BT.601/BT.709/BT.2020
COLOR_RANGE — Limited/Full Range
};
CRTC(阴极射线管扫描控制器)
CRTC 是扫描输出引擎,负责从 Plane 读取像素数据、按照时序参数(pixclock、hdisplay、hsync、vdisplay、vsync、margins)生成扫描信号。每个 CRTC 独立驱动一个显示管线。
struct drm_crtc_properties {
ACTIVE — 是否激活模式输出
MODE_ID — 当前模式 (drm_mode_modeinfo)
OUT_FENCE_PTR — 帧完成 fence(用于同步)
VRR_ENABLED — Variable Refresh Rate (FreeSync/G-Sync)
GAMMA_LUT — Gamma 校正查找表
DEGAMC_LUT — sRGB 解码 Degamma 表
CTM — Color Transformation Matrix
OUTPUT_FORMAT — YCbCr/RGB 输出格式
};
/* 模式结构 (简化) */
struct drm_mode_modeinfo {
uint32_t clock; /* kHz */
uint16_t hdisplay, hsync_start, hsync_end, htotal;
uint16_t vdisplay, vsync_start, vsync_end, vtotal;
uint32_t flags; /* HSKEW | CSYNC | DOUBLESCAN | ... */
uint32_t type; /* DRM_MODE_TYPE_PREFERRED | ... */
char name[32]; /* e.g. "1920x1080@60" */
};
Encoder(信号编码器)
Encoder 将 CRTC 输出的像素流转换为物理信号协议:
- DAC Encoder:模拟 VGA 输出
- TMDS Encoder:DVI / HDMI 数字信号
- LVDS/eDP Encoder:笔记本内置屏
- DP/eDP:DisplayPort(主链路)
- DSI Encoder:MIPI DSI(嵌入式设备)
- Virtual Encoder:无物理连接的虚拟显示(虚拟 Weston、headless)
Encoder 的关键属性:
struct drm_encoder_properties {
possible_crtcs — 可路由的 CRTC 位掩码
possible_clones — 可克隆的 Encoder 位掩码
encoder_type — DRM_MODE_ENCODER_* 类型
DPMS — Power state: On/Suspend/Off
OUTPUT_FORMAT — DisplayPort DSC, HDMI FRL ...
HDMI Output Infoframe — AVI/DVI/SPD/HDMI/DRM Infoframe
};
Connector(连接器/显示器端口)
Connector 是 GPU 的物理输出接口连接点。它负责检测热插拔事件(HPD)、读取显示器 EDID 数据、管理显示电源状态。
struct drm_connector_properties {
EDID — 显示器 EDID (Binary blob)
DPMS — ON / SUSPEND / OFF
CONNECTOR_STATUS — connected / disconnected / unknown
WIDTH_MM/HEIGHT_MM — 显示器物理尺寸
subconnector — VGA-Composite / S-Video / ...
TILE — Mosaic/Tiled Display 信息
panel_orientation — Normal/Inverted (平板旋转)
non-desktop — 非桌面显示器 (VR/AR)
DITHERING — 色彩抖动配置
HDR_OUTPUT_METADATA — HDR 静态/动态元数据
CONTENT_PROTECTION — HDCP 状态
};
2. DRM 内存管理与 Buffer 共享
2.1 GEM(Graphics Execution Manager)
GEM 是 DRM 子系统中管理 GPU 显存的核心框架。它提供了 buffer 对象(BO)的生命周期管理和跨设备 buffer 共享。
/* GEM buffer 对象 */
struct drm_gem_object {
struct kref refcount;
uint32_t handle; /* 全局唯一的 32-bit handle */
const struct drm_gem_object_funcs *funcs;
size_t size;
struct dma_buf *imported; /* import 的外部 buffer */
struct dma_buf *dmabuf; /* export 给其他设备的 dma_buf */
struct file *filp; /* shmem backing 文件 */
};
/* GEM 操作 API */
int drm_gem_handle_create(struct drm_file *file_priv,
struct drm_gem_object *obj,
uint32_t *handle); /* 创建用户态 visible handle */
struct drm_gem_object *drm_gem_object_lookup(struct drm_file *file_priv,
uint32_t handle); /* 查找 BO */
int drm_gem_prime_fd_to_handle(struct drm_device *dev,
struct drm_file *file_priv,
int fd, uint32_t *handle); /* import 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); /* export dma_buf */
2.2 DMA-BUF 与零拷贝共享
DMA-BUF 是 Linux kernel 的 buffer 共享机制,允许 GPU 内核驱动(DRM)、V4L2 摄像头、视频编解码器等设备通过文件描述符(fd)共享内存 buffer,内核负责维护引用计数和缓存一致性。
/* 典型同步流程:生产者/消费者 fence */
struct dma_buf_sync {
unsigned long flags;
};
#define DMA_BUF_SYNC_READ (1 << 0) /* CPU/device 即将读取 */
#define DMA_BUF_SYNC_WRITE (1 << 1) /* CPU/device 即将写入 */
#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE)
#define DMA_BUF_SYNC_START (0 << 2) /* 同步开始标记 */
#define DMA_BUF_SYNC_END (1 << 2) /* 同步结束标记 */
2.3 DMA-BUF Heap 与 Allocator
Linux 5.6+ 引入了 dma-buf heaps 框架,为不同类型的 buffer 分配提供统一的接口:
- system heap:分配普通匿名内存(CACHED/UNCACHED 可选)
- cma heap:分配 CMA (Contiguous Memory Allocator) 连续物理内存,适用于 IOMMU 不存在或不覆盖的设备
- vendor heaps:特定供应商的受保护内存(安全摄像头、TEE 等)
2.4 DMA-BUF ioctl 扩展:隐式同步同步点
传统的 DMA-BUF 同步依赖 ioctl 显式调用(DMA_BUF_IOCTL_SYNC)。Linux 6.6+ 引入了 SYNC_FILE 和 DRM 时序 fence 的整合机制,让生产者通过 timeline fence 通知消费者,消费者通过 poll/epoll 等待 producer fence signal:
/* DMA buf 导入时携带 in-fences */
struct drm_prime_attachment {
struct dma_buf *dmabuf;
struct device *dev;
struct list_head obj_node;
enum dma_data_direction dir;
// 关键:import 时使用 in-fence 数组
};
/* 渲染完成后 export 的 out-fence 传递给显示控制器 */
struct drm_mode_fb_cmd2_with_fence {
struct drm_mode_fb_cmd2 mode_cmd;
__s32 fence_fd; /* 渲染完成 fence,NULL 表示同步完成 */
};
3. KMS 用户态接口:IOCTL 与属性系统
3.1 DRM IOCTL 家族
KMS 通过 /dev/dri/cardX 或 /dev/dri/renderD128 设备节点暴露给用户态。核心 IOCTL 包括:
/* 模式获取与配置 */
DRM_IOCTL_SET_MASTER — 申请 master 权限(用于 modesetting)
DRM_IOCTL_DROP_MASTER — 释放 master 权限
DRM_IOCTL_MODE_GETRESOURCES — 获取所有 Connector/Encoder/CRTC/Plane ID
DRM_IOCTL_MODE_GETCONNECTOR — 获取 connector 详情 + 模式列表
DRM_IOCTL_MODE_GETENCODER — 获取 encoder 详情
DRM_IOCTL_MODE_GETCRTC — 获取当前 CRTC 配置
DRM_IOCTL_MODE_GETPLANE — 获取 plane 详情(支持的格式等)
/* Page Flip 与 VBlank */
DRM_IOCTL_MODE_PAGE_FLIP — 异步换帧(VBlank 同步)
DRM_IOCTL_WAIT_VBLANK — 等待 VBlank 信号
/* Atomic Commit */
DRM_IOCTL_MODE_ATOMIC — 提交原子属性事务
DRM_IOCTL_MODE_OBJ_SETPROPERTY — 设置对象属性
DRM_IOCTL_MODE_CREATEPROPBLOB — 创建属性 blob (EDID/Gamma/Mode)
DRM_IOCTL_MODE_DESTROYPROPBLOB — 销毁属性 blob
/* 显存管理 */
DRM_IOCTL_GEM_CLOSE — 关闭 gem handle
DRM_IOCTL_GEM_FLINK — 获取全局名称(跨进程共享)
DRM_IOCTL_GEM_OPEN — 通过全局名称打开
DRM_IOCTL_PRIME_FD_TO_HANDLE — dma_buf fd → gem handle
DRM_IOCTL_PRIME_HANDLE_TO_FD — gem handle → dma_buf fd
/* Buffer 管理 */
DRM_IOCTL_MODE_ADDFB2 — 注册 framebuffer 对象
DRM_IOCTL_MODE_RMFB — 注销 framebuffer
DRM_IOCTL_MODE_MAP_DUMB — 映射 dumb buffer(CPU 访问)
DRM_IOCTL_MODE_CREATE_DUMB — 创建 dumb buffer(线性、非 GPU 友好)
DRM_IOCTL_MODE_DESTROY_DUMB — 销毁 dumb buffer
3.2 属性系统(Property System)
KMS 使用动态属性系统来管理对象的可配置参数。每个属性都有一个类型和值:
/* 属性类型 */
DRM_MODE_PROP_RANGE — 范围 [min, max]
DRMR_MODE_PROP_ENUM — 枚举值列表
DRM_MODE_PROP_BLOB — 二进制 blob(EDID/Gamma/Mode info)
DRM_MODE_PROP_BITMASK — 位掩码枚举
DRM_MODE_PROP_OBJECT — 指向另一个 KMS 对象的 ID
DRM_MODE_PROP_SIGNED_RANGE — 有符号范围
/* 属性 flags */
DRM_MODE_PROP_PENDING — 属性变更待审议
DRM_MODE_PROP_IMMUTABLE — 不可变属性
DRM_MODE_PROP_ATOMIC — 非 Atomic commit 下不可修改(Atomic only)
3.3 Dumb Buffer vs. GPU-Optimized Buffer
DRM 提供两种 buffer 分配方式:
- Dumb Buffer:线性(Linear)内存分配,适用于简单的 CPU 访问和软件渲染(fbdev 兼容)。不支持 GPU tile/compression 布局。
- GPU-Optimized Buffer:通过特定驱动的 allocator(AMDGPU TMZ/VRAM、i915 GGTT/LMEM/VRAM),支持 GPU 内部 tile mod compressed 布局(如 AMD DGPU 的 Swizzle Mode、Intel 的 TileY/X)。这种 buffer 对 CPU mmap 不友好,但 GPU 访问零开销。通常需要配合 GPU 命令流(CS)使用。
4. 原子模式设置(Atomic Modesetting)
4.1 为什么需要 Atomic
传统的 modeset 是逐步进行的:先设置 CRTC mode,再绑定 framebuffer,再设置 Plane 坐标,最后 enable CRTC。问题在于每一步都是独立的 ioctl,如果中间步骤失败,系统处于"半配置"状态,可能导致:
- 显示器信号中断(黑屏/闪屏)
- 设置了一半的属性(遗漏或无意义的中间态)
- 硬件处于不一致状态(需手动 rollback)
Atomic Modesetting 将所有变更打包成一个事务:所有属性先写入 staging state,只有所有验证通过后一次性提交到硬件。
4.2 Atom 提交流程
/* 1. 创建原子请求 */
drmModeAtomicReq *req = drmModeAtomicAlloc();
/* 2. 收集所有属性变更 */
drmModeAtomicAddProperty(req, crtc_id,
.prop = CRTC_MODE_ID,
value = mode_blob_id); /* blob 中的 mode */
drmModeAtomicAddProperty(req, crtc_id,
prop = CRTC_ACTIVE,
value = 1);
drmModeAtomicAddProperty(req, plane_id,
prop = PLANE_FB_ID,
value = fb_id);
drmModeAtomicAddProperty(req, plane_id,
prop = PLANE_CRTC_X, value = 0);
drmModeAtomicAddProperty(req, plane_id,
prop = PLANE_CRTC_Y, value = 0);
drmModeAtomicAddProperty(req, plane_id,
prop = PLANE_CRTC_W, value = 1920);
drmModeAtomicAddProperty(req, plane_id,
prop = PLANE_CRTC_H, value = 1080);
drmModeAtomicAddProperty(req, connector_id,
prop = CONNECTOR_CRTC_ID,
value = crtc_id);
/* 3. 提交事务 */
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET | /* 允许模式更改 */
DRM_MODE_PAGE_FLIP_EVENT; /* 请求页面翻转事件 */
ret = drmModeAtomicCommit(fd, req, flags, user_data);
/* 4. 释放请求对象 */
drmModeAtomicFree(req);
4.3 Atomic Flags
/* 提交 flags */
DRM_MODE_ATOMIC_ALLOW_MODESET — 允许全模式设置(如 CRTC mode 变更)
DRM_MODE_ATOMIC_NONBLOCK — 异步提交(不等待 VBlank)
DRM_MODE_ATOMIC_TEST_ONLY — 仅验证,不实际应用(用于提前检测错误)
DRM_MODE_PAGE_FLIP_EVENT — 监听 Page Flip 完成事件
DRM_MODE_ATOMIC_NO_UPDATE_BO — 不等待 buffer 释放事件
/* 典型组合:
* 仅更新 Plane: DRM_MODE_PAGE_FLIP_EVENT
* 完整 modeset: DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT
*/
4.4 TEST_ONLY 验证流程
Atomic 提交前,DRM 驱动会在 staging state 上执行验证链:
Validation Pipeline:
├── plane_atomic_check()
│ ├── check pixel format supported
│ ├── check source/crtc rect within bounds
│ ├── check rotation against plane caps
│ ├── check zpos/ordering consistency
│ └── check modifier/fb layout supported
├── crtc_atomic_check()
│ ├── check mode timings (hw clock range)
│ ├── check bandwidth requirements
│ ├── validate blob mode structure
│ └── check gamma/CTM/color pipeline consistency
├── encoder_atomic_check()
│ ├── validate signal format support
│ └── check bandwidth vs clock limits
├── connector_atomic_check()
│ ├── validate EDID compatibility
│ └── check HDCP/content protection state
└── TEST_ONLY 成功 → apply
TEST_ONLY 失败 → return -EINVAL / -ERANGE
5. VBlank、Page Flip 与帧同步
5.1 VBlank 中断与事件
显示器每帧回扫(Vertical Blanking Interval,VBlank)时,GPU 硬件产生中断。DRM 子系统利用此中断实现精确的帧时序同步:
/* 等待 VBlank */
struct drm_wait_vblank_request req = {
.request = {
.type = _DRM_VBLANK_RELATIVE,
.sequence = 1, /* 等下一个 vblank */
}
};
drmWaitVBlank(fd, &req);
/* 注册 VBlank 回调 */
typedef void (*drmVBlankHandler)(int fd, unsigned int sequence,
unsigned int tv_sec, unsigned int tv_usec,
void *user_data);
/* Page Flip 事件 */
drmEventContext ev = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = page_flip_handler,
.vblank_handler = vblank_handler,
};
drmHandleEvent(fd, &ev);
5.2 双重缓冲与 Page Flip
现代显示使用 Page Flip 实现无撕裂的帧切换。drmModePageFlip 在下一个 VBlank 时刻将 CRTC 的当前 framebuffer 切换到新 buffer,不会在扫描中途修改(避免 tearing):
/* Page Flip 流程 */
1. 渲染到 back buffer (fb_back)
2. drmModePageFlip(fd, crtc_id, fb_back->fb_id, flags, user_data)
3. 内核标记待处理 flip
4. 下一 VBlank 中断触发
5. CRTC 硬件从 fb_front 切换到 fb_back
6. 翻转事件回调触发(带 user_data)
7. fb_front 变为可用 back buffer,循环继续
6. 与现代显示栈的协作
6.1 Wayland 合成器
Wayland 合成器(Weston、Mutter、Sway、KWin)直接使用 KMS Atomic API 进行输出管理。weston 的典型显示管线:
┌─────────────────────────────────────────────┐
│ Compositor (Userspace) │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Surface │ │ Surface │ │ Surface │ │
│ │ Buffer │ │ Buffer │ │ Buffer │ │
│ │ wl_shm/ │ │ (DMA-BUF)│ │(DMA-BUF) │ │
│ │ zwp_linux │ └─────┬────┘ └─────┬────┘ │
│ │ _dmabuf) │ │ │ │
│ └─────────┘ ▼ ▼ │
│ ┌──────────────────────────┐ │
│ │ GL/Vulkan 渲染 │ │
│ │ (EGLStreams/GBM) │ │
│ └──────────┬───────────────┘ │
│ │ │
│ ┌──────────▼───────────────┐ │
│ │ Atomic Modeset Commit │ │
│ │ ┌─────┬──────┬──────┐ │ │
│ │ │Cur. │Prim. │Ovly. │ │ │
│ │ │Plane │Plane │Plane │ │ │
│ │ └──┬──┴──┬───┴──┬───┘ │ │
│ │ ▼ ▼ ▼ │ │
│ │ │ CRTC 引擎 │ │ │
│ │ └────┬────┘ │ │
│ └────────────┼──────────────┘ │
└──────────────────────────┼────────────────┘
▼
┌──────────┐
│ Monitor │
│ (DP/HDMI)│
└──────────┘
6.2 EGL 平台:GBM vs EGLStreams
两大 EGL Wayland platform 核心差异:
- gbm(Generic Buffer Management):Mesa 的跨平台方案。基于 DRM dumb buffer + GEM,通过 zwp_linux_dmabuf_v1 Wayland 协议直接向合成器提交 DMA-BUF。i915 / amdgpu / nouveau 均支持。
- EGLStreams:NVIDIA 的私有方案。在 NVIDIA PRO 驱动上通过 EGLStreamProducer/Consumer 将渲染 frame 传递给合成器。NVIDIA 现已转向 GBM 路线。
6.3 Xorg 适配层:xf86-video-modesetting
Xorg 合成器通过 xf86-video-modesetting 驱动适配 KMS。模式下设置借用了 libdrm 的 API,通过 DDX(Device Dependent X)层和 X-Server 的 Present Extension 实现 modern VSync 和 page flip。
7. DRM 驱动核心结构
7.1 驱动注册框架
/* 核心描述符 */
struct drm_driver {
const struct drm_driver *driver_features;
/* GEM hooks */
struct drm_gem_object *(*gem_create_object)(struct drm_device *dev, size_t size);
int (*prime_pin)(struct drm_gem_object *obj);
void (*prime_unpin)(struct drm_gem_object *obj);
struct sg_table *(*prime_get_sg_table)(struct drm_gem_object *obj);
struct drm_gem_object *(*prime_import_sg_table)(struct drm_device *dev,
struct dma_buf_attachment *attach, struct sg_table *sgt);
/* Display hooks */
int (*drm_mode_config_funcs)(struct drm_device *dev);
/* IOCTL hooks */
int (*ioctls[DRM_IOCTL_DEF_NR])(struct drm_device *dev, void *data,
struct drm_file *file_priv);
int ioctl_count;
/* Sysfs */
int (*debugfs_init)(struct drm_minor *minor, int minor_id);
};
/* 驱动注册流程 */
int drm_dev_init(struct drm_device *dev, struct drm_driver *driver,
struct device *parent);
int drm_dev_register(struct drm_device *dev, unsigned long driver_features);
void drm_dev_unregister(struct drm_device *dev);
void drm_dev_exit(struct drm_device *dev, unsigned long driver_features);
7.2 三大主流 DRM 驱动
| 驱动 | GPU 厂商 | 关键特性 | 内存管理架构 |
|---|---|---|---|
| amdgpu | AMD | GFX11/RDNA3、APU、GPU Reset、TMZ、FreeSync、SR-IOV | VRAM(System/Local/Visible) + GTT + TT Mov |
| i915 | Intel | Gen12/Xe/Arc、TileX/Y/LMEM/LMEM+Context、HDR、VRR | GGTT + LMEM(dGPU) + LLC(iGPU) |
| nouveau | NVIDIA | Re-clocking(受限)、GSP firmware(Turing+) | VRAM + GART + IOMMU |
7.3 AMDGPU 特有:Ring Buffer 与 GPU 调度
AMDGPU 驱动中,用户态提交 GPU 命令通过 6 个 Ring Buffer(gfx、compute、sdma、vce、uvd、vcn)实现:
/* GPU 命令提交路径 */
1. 用户态填充 IB (Indirect Buffer) 命令包
- PACKET3 (PM4) 格式
- 包含 DRAW_INDEX_2、CONTEXT_CONTROL、FRAME_CONTROL 等操作码
2. 通过 amdgpu_cs_ioctl 提交 job
-> amdgpu_cs_ioctl()
-> amdgpu_cs_parser_init() [CS 解析]
-> amdgpu_cs_sync [同步处理]
-> amdgpu_job_submit [加入调度队列]
3. 调度器 (drm_sched) 决定执行时机
- 按 VM id / priority 排队
- 高级调度:抢占(VM 切换)和恢复
4. 硬件 Ring Buffer 执行
- 写入 doorbell 寄存器通知硬件
- 硬件 DMA 读取 IB → 执行 → 写回状态
- 完成后发出 IRQ → 信号 fence
5. fence signal → 触发用户态同步回调
- libdrm: amdgpu_cs_syncobj_wait
- Wayland: zwp_linux_dmabuf_feedback_v1
8. GPU 重置与错误恢复(GPU Reset)
现代 DRM 驱动支持 "GPU Reset" —— 当 GPU 因命令提交挂起时,驱动可尝试重置单个进程或整个设备而非崩溃。机制包括:
- Job Hanging 检测:调度器超时(默认 50ms)后标记 job 为 hung
- TTM Memory Eviction:重置前尝试将 GPU 内存驱逐到系统 RAM
- HARD RESET:重置 GPU 硬件,恢复驱动状态,可能丢失上下文
- SOFT RESET(部分支持):仅终止出错进程,保留其他任务运行
8.1 amdgpu 的 GPU Reset 流程
GPU Hung Detection
↓
amdgpu_device_gpu_recover()
├── Step 1: 识别受影响进程(收集 guilty/non-guildly 标记)
├── Step 2: 通知用户态 DMA-BUF 不可访问
├── Step 3: TTM evict VRAM to system RAM
├── Step 4: 硬件 reset (PCIe FLR 或 SOC reset)
├── Step 5: 驱动状态恢复(重新加载 firmware/ring/IRQ)
└── Step 6: 通知等待 fence(signal error=-EIO)
用户态感知:
- 渲染 client 获取 GPU reset status (CONTEXT_RESET 事件)
- Wayland 合成器可重新初始化 EGLSurface/GBM Surface
- Xorg DDX 重启 GLAMOR 加速
9. DRM 调试工具与性能分析
9.1 kernel debugfs 节点
DRM 驱动导出丰富的 debugfs 信息:
/sys/kernel/debug/dri/0/
├── amdgpu_gtt_mm — GTT 内存分配详细状态
├── amdgpu_vram_mm — VRAM 内存分配列表
├── amdgpu_ring_gfx — GFX Ring Buffer 状态和近期命令
├── amdgpu_gpu_recov — GPU Reset 记录
├── amdgpu_fences — 当前活跃 fence 列表
├── i915_display_info — i915 display pipeline 详细状态
├── i915_gem_objects — 所有 GEM 对象和内存区域
├── i915_contexts — 活跃 GPU 上下文
├── i915_dmc — Display Micro Controller 状态
├── i915_dpst — 节能状态统计
└── force_reset — 写入触发模拟 reset
9.2 modetest:KMS 调试瑞士军刀
libdrm 自带的 modetest 工具可用于快速测试和调试:
# 列出所有 KMS 对象
$ modetest -M amdgpu
Encoders:
id=51 crtc=42 type=TV
Connectors:
id=52 encoder=51 status=connected modes:[3840x2160@60, 1920x1080@60, ...]
CRTCs:
id=42 3840x2160@60 mode connector=52 encoder=51 fb=57
Planes:
id=34 type=Primary formats:XRGB8888 ARGB8888
id=38 type=Overlay formats:XRGB8888 NV12 YUYV
id=42 type=Cursor formats:ARGB8888
# 测试 atomic modeset
$ modetest -M amdgpu -a -s 52:3840x2160
# 测试 page flip
$ modetest -M amdgmu -P 34@42:1920x1080 -v
9.3 GPU 调试工具链
- radeontop / intel_gpu_top / nvtop:实时 GPU 利用率展示
- perf top / perf stat:通过 perf_event_open 监控 GPU 硬件计数器
- AMD ROCprofiler / Intel GPA:从属厂商的性能分析工具
- Mesa tracers (e.g. MESA_DEBUG=1、apitrace):更上层 API 追踪
- DRM MC(MultiChannel):内核 framebuffer console
10. 用户态 DRM 编程实战
10.1 最小 KMS 应用程序:libdrm 示例
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
/* 假设设备节点 /dev/dri/card0 */
int main() {
int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
/* 设置 Universal Planes 和 Atomic */
uint64_t cap;
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
/* 获取资源 */
drmModeRes *res = drmModeGetResources(fd);
drmModePlaneRes *planes = drmModeGetPlaneResources(fd);
drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[0]);
/* 找到合适的 mode */
drmModeModeInfo *mode = &conn->modes[0]; /* 首选 */
/* 获取 CRTC */
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoder_id);
uint32_t crtc_id = enc->crtc_id;
/* 创建 dumb buffer */
struct drm_mode_create_dumb create_req = {
.width = mode->hdisplay,
.height = mode->vdisplay,
.bpp = 32,
};
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
/* 注册 framebuffer */
uint32_t fb_id;
drmModeAddFB(fd, mode->hdisplay, mode->vdisplay, 24, 32,
create_req.pitch, create_req.handle, &fb_id);
/* 映射 dumb buffer */
struct drm_mode_map_dumb map_req = { .handle = create_req.handle };
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
void *map = mmap(0, create_req.size, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, map_req.offset);
uint32_t color = 0xFF3366FF;
for (int i = 0; i < mode->hdisplay * mode->vdisplay; i++) {
((uint32_t*)map)[i] = color;
}
/* 首次 modeset */
drmModeSetCrtc(fd, crtc_id, fb_id, 0, 0,
&conn->connector_id, 1, mode);
sleep(3); /* 显示 3 秒 */
/* 清理 */
munmap(map, create_req.size);
struct drm_mode_destroy_dumb destroy_req = { .handle = create_req.handle };
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
drmModeRmFB(fd, fb_id);
drmModeFreeConnector(conn);
drmModeFreeEncoder(enc);
drmModeFreePlaneResources(planes);
drmModeFreeResources(res);
close(fd);
return 0;
}
/* 编译: gcc -o kms_test kms_test.c -ldrm */
10.2 高级模式:Atomic + Page Flip 双缓冲
/* 简化的 atomic page flip 双缓冲渲染循环 */
struct flip_context {
int fd;
uint32_t crtc_id, plane_id, conn_id;
uint32_t fb_front, fb_back;
drmModeAtomicReq *req;
drmModeModeInfo *mode;
};
void atomic_flip(struct flip_context *ctx) {
drmModeAtomicReq *req = ctx->req;
/* 渲染到 back buffer(先省略具体渲染代码) */
// render_to_back_buffer(ctx->fb_back_map);
/* 设置 plane 属性 */
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "FB_ID"), ctx->fb_back);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "CRTC_ID"), ctx->crtc_id);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "SRC_X"), 0);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "SRC_Y"), 0);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "SRC_W"), (uint64_t)ctx->mode->hdisplay << 16);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "SRC_H"), (uint64_t)ctx->mode->vdisplay << 16);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "CRTC_X"), 0);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "CRTC_Y"), 0);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "CRTC_W"), ctx->mode->hdisplay);
drmModeAtomicAddProperty(req, ctx->plane_id,
plane_property(req, "CRTC_H"), ctx->mode->vdisplay);
/* 提交:等待 page flip 事件 */
uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
drmModeAtomicCommit(ctx->fd, req, flags, ctx); /* user_data = ctx */
/* 交换前后 buffer */
uint32_t tmp = ctx->fb_front;
ctx->fb_front = ctx->fb_back;
ctx->fb_back = tmp;
/* 重置 atomic request */
drmModeAtomicFree(ctx->req);
ctx->req = drmModeAtomicAlloc();
}
/* Page Flip 完成回调 */
void page_flip_handler(int fd, unsigned int frame, unsigned int sec,
unsigned int usec, void *data) {
struct flip_context *ctx = data;
/* 后置处理:记录帧时间、触发下一帧 */
}
11. 安全与权限模型
11.1 DRM Master 权限
传统上,modesetting 需要 DRM Master 权限(通过 DRM_IOCTL_SET_MASTER 获取)。自 Linux 5.10 起,现代驱动支持非 Master 模式的 modesetting(如翻页),但部分关键操作仍需要 Master:
- DRM Master:持有 modesetting 最高权限,同一时间仅一个 Master
- 非 Master 渲染:通过 DRM_IOCTL_AUTH_MAGIC 认证的 fd 可提交 GPU 渲染命令
- renderD128:render-only 设备节点,无需 Master 即可提交渲染和计算
11.2 Render 节点隔离
DRM 子系统自 Linux 3.12 引入 render 节点,将渲染计算与 modesetting 权限分离:
/dev/dri/
├── card0 — Master + Render (传统接口,权限大)
├── renderD128 — Render-only (无 modesetting)
└── controlD64 — Control-only (极少使用)
安全应用:容器内只需 bind mount /dev/dri/renderD128 即可获得 GPU 渲染/计算能力,无需 modesetting 权限。
11.3 KMS 安全帽
DRM 子系统内置 Landlock + SECCOMP 集成:
- KMS fd 可被 seccomp 过滤危险 ioctl
- Wayland 合成器使用 PRCTL_SECCOMP_FILTER 防止 compositor 自身作恶
- capability(CAP_SYS_ADMIN) 检查 opens a new master 限制
12. 未来方向
12.1 显示虚拟化:SR-IOV + GPU-PV
AMDGPU 和 i915 已支持 SR-IOV(Single Root I/O Virtualization),可将物理 GPU 显示管线划分为多个虚拟 GPU,多虚拟机共享显示引擎输出。
12.2 Dynamic Display Multiplexer (DYNDMUX)
Intel Xe 引入 Dynamic Display Mux Switching,支持在 boot 时无缝切换 GPU(避免传统 dreaded "GPU Switch" 需要的重启 Xorg/Compositor)。
12.3 色彩管理标准化:Colorspace KMS API 完善
Sysfs ABI 和 KMS property 的规范化和文档化,让能为用户态和合成器提供完整、标准化的色彩管线控制。
12.4 DMA-BUF 与 FPGA/加速器的整合
随着异构计算兴起(Xilinx、Intel QAT、AMD/Xilinx Versal),DMA-BUF 正在成为 GPU-FPGA 零拷贝协作的中间件基石。Linux 6.10+ 引入了 dma_buf_import_device 来支持设备间 buffer import。
结语
Linux DRM/KMS 子系统是我们日常与计算机视觉交互的底层基石。从 CRTC/Encoder/Connector/Plane 的精妙硬件抽象,到 Atomic Modesetting 的事务性保证,再到与 Wayland/合成器的精密协作,DRM/KMS 以超过千万行代码的规模支撑着世界上最复杂的图形显示栈。理解 DRM/KMS 不仅对驱动开发者和嵌入式工程师必不可少,对于任何需要深入理解计算机系统全栈——从像素到显示器——的开发者而言,这都是一段值得投入的知识旅程。

发表评论 取消回复