Linux 内核 DRM/KMS 显示管理架构深度实战:从 CRTC/Encoder/Connector 到原子模式设置
在 Linux 内核的众多子系统中,DRM(Direct Rendering Manager)是最复杂、历史包袱最重、却又对现代桌面和移动端用户体验至关重要的图形栈核心。从古老的 XFree86 到今天的 Wayland 合成器,从嵌入式 MCU 屏幕到多 GPU 异构渲染,DRM/KMS 提供了一套统一的显示与渲染管理抽象。本文将从硬件拓扑建模出发,层层深入 CRTC/Encoder/Connector 核心对象模型、GEM/TTM 显存管理、KMS 原子模式设置、Vblank 事件同步、以及 DRM 驱动开发实战,为你揭开 Linux 图形栈内核侧的神秘面纱。
本文所有分析基于 Linux 6.6+ 内核源码(drivers/gpu/drm/),代码示例参考 i915/amdgpu/virtio-gpu 等主流 DRM 驱动实现。
一、DRM 子系统全景:从混乱到统一的历史演进
1.1 早期 Linux 图形栈的黑暗时代
在 DRM 出现之前,Linux 的图形管理处于完全的"丛林状态":用户态 X Server 直接通过 /dev/mem 或 ioperm() 操作显卡 MMIO 寄存器,没有内核级的资源隔离与冲突调解。这意味着:
- 两个图形应用同时写入同一组显卡寄存器会直接导致系统死机
- 没有统一的显存分配机制,X Server 自行管理全部 VRAM
- 没有 VSync 同步,画面撕裂(tearing)成为常态
- 没有模式设置(modesetting)抽象,切换分辨率需要直接操作显卡硬件
2003 年,Keith Packard 和 Eric Anholt 主导了 DRM 子系统的首次重构,引入了核心对象模型:drm_device 作为全局管理器,TTM(Translation Table Maps)作为统一的显存管理器。从 2.6.28 内核开始,DRM 正式成为 Linux 图形栈的内核基石。
1.2 从 DRI1 到 DRI3/Present:渲染与显示的解耦
Linux 图形栈的完整演进路径为:
- DRI1(2000):全局锁机制,所有应用共享锁,串行化渲染——性能灾难
- DRI2(2008):引入 GEM(Graphics Execution Manager),每个应用有独立渲染缓冲,SwapBuffer 仍通过 X Server 中转
- DRI3 + Present(2013):DRI3 让应用直接获取渲染缓冲的 GEM handle,Present 扩展让用户态直接将缓冲"present" 给显示控制器,绕过 X Server
- Wayland(2010+):合成器直接从应用接收 dma-buf,直接通过 KMS 上屏——DRM/KMS 成为 Wayland 合成的唯一后端
1.3 DRM 的两个侧面:Rendering 与 KMS
DRM 子系统实际上包含两个相对独立的功能域:
- Rendering(渲染侧):通过 GEM/TTM 管理显存,通过 GPU 调度器提交渲染命令(command submission),支持 fence 同步、dma-buf 跨设备共享
- KMS(Kernel Mode Setting,显示侧):管理显示管线(display pipeline),控制分辨率、刷新率、显示时序
关键洞察是:KMS 是显示硬件无关的抽象层,它用一套统一的对象模型描述所有类型的显示硬件——无论是嵌入式 LCD 控制器、手机 MIPI DSI 接口,还是桌面 DisplayPort 输出。
二、KMS 核心对象模型:CRTC、Encoder、Connector 三件套
2.1 显示管线(Display Pipeline)拓扑
KMS 将显示硬件抽象为三个核心对象和一组辅助对象:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Frame │────▶│ Plane │────▶│ CRTC │────▶│ Encoder │
│ Buffer │ │ (图层) │ │ (扫描器) │ │ (编码器) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐ ┌──────────┐
│ Panel │◀────│Connector │◀─── HDMI/eDP/DP
│ (显示器) │ │ (连接器) │
└──────────┘ └──────────┘
2.2 CRTC(Cathode Ray Tube Controller)
CRTC 是显示管线的"心脏",负责从帧缓冲读取像素数据并按照精确的时序向外发送(即"扫描输出")。每个 CRTC 对应一个独立的时序发生器(timing generator),可以通过 drmModeSetCrtc() 配置其输出模式。
关键数据结构:
struct drm_crtc {
struct drm_device *dev;
struct drm_mode_config *config;
// 显示模式
struct drm_display_mode mode; // 当前分辨率+时序
struct drm_display_mode hw_mode; // 硬件调整后的时序
// 像素扫描
struct drm_framebuffer *primary; // 主图层(primary plane)
uint32_t x, y; // 扫描起始位置
bool enabled; // CRTC 是否开启
// Gamma 校正与色彩管理
struct drm_color_lut *gamma_lut;
size_t gamma_size;
// 模式设置函数
struct drm_crtc_funcs *funcs;
void *helper_private;
};
一个 GPU 通常有 1~6 个 CRTC(Intel 集成显卡典型为 3 个),支持多路独立显示输出。
2.3 Encoder(编码器)
Encoder 负责将 CRTC 输出的像素数据编码为适合传输的物理信号:
- DSI Encoder:将像素转为 MIPI DSI 协议,用于手机/嵌入式 LCD 面板
- TMDS Encoder:用于 HDMI/DVI(i915 中的 intel_hdmi_encoder)
- DP Encoder:用于 DisplayPort(支持 MST 多流传输)
- TV Encoder:将像素转为模拟信号(复合视频),已渐淘汰
- Virtual Encoder:virtio-gpu 等虚拟化场景使用
struct drm_encoder {
struct drm_device *dev;
uint32_t possible_crtcs; // 位掩码:该编码器可连接的 CRTC
uint32_t possible_clones;// 位掩码:可克隆的编码器
// 编码函数
const struct drm_encoder_funcs *funcs;
const struct drm_encoder_helper_funcs *helper_funcs;
};
编码器与 CRTC 并非一一对应——有些硬件可以通过"复用器"(mux)让同一编码器向不同 CRTC 输出。possible_crtcs 位掩码表示了这种连接可能性。
2.4 Connector(连接器)与状态检测
Connector 代表一个物理显示输出接口(HDMI 端口、eDP 端口、DP 端口等)。它管理与显示器的连接状态和 EDID(Extended Display Identification Data)读取:
struct drm_connector {
struct drm_device *dev;
// 连接器类型
enum drm_connector_type connector_type;
// DRM_MODE_CONNECTOR_HDMIA / DRM_MODE_CONNECTOR_eDP /
// DRM_MODE_CONNECTOR_DisplayPort / DRM_MODE_CONNECTOR_DSI...
// 状态
enum drm_connector_status status;
// connector_status_connected / disconnected / unknown
// EDID
struct drm_edid *edid;
struct edid *full_edid; // 完整的 EDID 1.4 数据
// 可用模式列表
struct list_head modes; // drm_display_mode 链表
// 对应编码器
uint32_t encoder_id;
uint32_t possible_encoders;
// 热插拔处理
struct drm_hpd hpd;
bool polled; // 是否通过轮询检测连接状态
};
热插拔检测(HPD)是连接器的关键功能。当 HDMI 插入时,显卡上的 HPD 引脚触发中断,DRM 子系统执行以下流程:
- HPD 中断触发 → 调用
drm_helper_hpd_irq_event() - 读取 DDC 通道上的 EDID 数据块
- 解析 EDID → 构建可用模式列表(drm_add_edid_modes())
- 通过 udev 通知用户态(X11/Wayland)显示器状态变化
- 用户态请求模式设置 → KMS 原子提交
三、Plane 系统:从单一主图层到硬件叠加
3.1 Plane 类型与层次
现代 DRM/KMS 支持多种 Plane 类型,每个 Plane 对应显示管线中的一个独立硬件层(Hardware Overlay):
- Primary Plane(主图层):必须存在,对应 CRTC 的底层背景。Cursor 除外不可禁用
- Overlay Plane(叠加图层):硬件覆盖层,支持任意位置放置较小的帧缓冲
- Cursor Plane(光标图层):通常为 64x64 的小尺寸层,支持硬件鼠标光标
Plane 的核心价值在于硬件合成(Hardware Composition)——视频层在独立的 overlay plane 上显示、UI 层在 primary plane 上显示,两者由显示控制器硬件级合成,无需 GPU 参与,零带宽消耗。
3.2 Plane 属性系统
Linux 4.0 引入的 Atomic KMS 为 Plane 添加了一套通用属性(Property)机制:
struct drm_plane {
// 标准属性
struct drm_property *type_prop; // PRIMARY/OVERLAY/CURSOR
struct drm_property *alpha_prop; // 全局透明度 0-255
struct drm_property *blend_mode_prop; // 像素混合模式
struct drm_property *rotation_prop; // 旋转角度
struct drm_property *zpos_prop; // 层级顺序
struct drm_property *color_encoding_prop; // BT.601/BT.709/BT.2020
struct drm_property *color_range_prop; // Limited/Full Range
// 缩放与裁剪
bool has_scaling;
uint32_t max_downscale;
uint32_t max_upscale;
// 支持的像素格式
const uint32_t *format_modifiers;
uint32_t format_count;
};
四、GEM 显存管理与 dma-buf 跨设备共享
4.1 GEM:图形执行管理
GEM(Graphics Execution Manager)是 DRM 子系统的显存管理核心,由 Intel Tungsten Graphics 团队在 DRI2 时代引入。其设计目标是统一不同 GPU 厂商的显存分配 API。
GEM 的核心创新在于"命名对象"(Named Object)机制——通过 32-bit 全局唯一 handle 在进程间传递显存对象引用:
// 创建 GEM 缓冲
struct drm_gem_create create = { .size = 4096 * 1024 }; // 4MB
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
uint32_t gem_handle = create.handle;
// 获取 CPU 可访问的映射地址
struct drm_gem_map_off map = { .handle = gem_handle };
ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
void *vaddr = mmap(NULL, size, PROT_READ|PROCT_WRITE, MAP_SHARED, fd, map.offset);
// 将 GEM handle 传递给另一个进程(通过 Unix 域套接字)
struct drm_gem_open open = { .name = gem_name };
ioctl(other_fd, DRM_IOCTL_GEM_OPEN, &open);
uint32_t local_handle = open.handle;
4.2 TTM 与 GEM 的关系
许多新手混淆 GEM 与 TTM:实际上 GEM 是 API 层(用户态接口规范),TTM 是后端内存管理算法。TTM 负责:
- 在 VRAM、系统内存(GTT)、以及换出磁盘间动态迁移显存页
- 处理 GPU CPU 访问冲突
- 管理 placement 策略(
TTM_PL_VRAM/TTM_PL_TT)
// TTM placement 结构
struct ttm_place {
uint32_t mem_type; // TTM_PL_SYSTEM / TTM_PL_VRAM
uint32_t flags; // TTM_PL_FLAG_CMA / TTM_PL_FLAG_NO_EVICT
};
// Buffer 对象迁移
struct ttm_buffer_object {
struct ttm_mem_reg mem; // 当前位置
struct ttm_bo_type type; // kern/user/dma
struct ttm_resource *resource; // 当前资源
bool evictable; // 是否可被驱逐
kref_t kref;
};
4.3 dma-buf 跨设备共享机制
dma-buf 是 DRM 子系统导出为独立内核模块的 DMA 缓冲共享框架,解决了 GPU 间零拷贝数据传递问题:
// 导出 GEM 对象为 dma-buf
struct drm_prime_handle prime = {
.handle = gem_handle,
.flags = DRM_CLOEXEC | DRM_RDWR,
};
ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime);
int dma_buf_fd = prime.fd;
// 导入 dma-buf 为本地 GEM 对象
struct drm_prime_handle import = { .fd = dma_buf_fd };
ioctl(other_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import);
uint32_t imported_handle = import.handle;
典型的多 GPU 使用场景:NVIDIA dGPU 渲染完成后,将 dma-bfd 传递给 Intel iGPU 的 KMS 显示,实现 PRIME 渲染输出——笔记本 Optimus 技术的内核基础。
五、KMS 原子模式设置(Atomic Modesetting)深度剖析
5.1 从 Legacy 到 Atomic:为什么需要新 API
旧的 KMS 接口(Legacy Modesetting)的核心问题是"部分失败"(partial failure):
// Legacy KMS 的问题示例
drmModeSetCrtc(crtc_id, fb_id, 0, 0, &connector_id, 1, &mode);
// 如果 Connector 不支持该模式 → 已设置的 Plane 和 CRTC 进入了损坏状态
// 如果 CRTC 不支持该分辨率 → 画面直接黑屏,无法回滚
Legacy API 的每次调用都是独立提交——drmModeSetCrtc()、drmModeSetPlane()、drmModeSetCursor() 各自生效,中间状态可能导致画面撕裂、硬件挂起。
Atomic KMS 的解决之道:将所有显示状态打包为原子事务(Atomic Transaction),硬件要么全部应用、要么全部不应用。
5.2 原子提交的三大对象与属性
Atomic KMS 引入的新对象是 drm_modeset_lock + 属性(Property)系统,所有状态变更通过属性值设置:
// 创建 Atomic 请求
drmModeAtomicReq *req = drmModeAtomicAlloc();
// 1. 设置 CRTC 属性
drmModeAtomicAddProperty(req, crtc_id,
crtc_prop_mode_id, blob_id); // 目标分辨率模式
drmModeAtomicAddProperty(req, crtc_id,
crtc_prop_active, 1); // CRTC 启用
// 2. 设置 Connector 属性
drmModeAtomicAddProperty(req, connector_id,
connector_prop_crtc_id, crtc_id); // 绑定 CRTC
// 3. 设置 Plane 属性
drmModeAtomicAddProperty(req, plane_id,
plane_prop_fb_id, fb_id); // 帧缓冲
drmModeAtomicAddProperty(req, plane_id,
plane_prop_crtc_id, crtc_id); // 目标 CRTC
drmModeAtomicAddProperty(req, plane_id,
plane_prop_src_x, 0); // 源矩形 X
drmModeAtomicAddProperty(req, plane_id,
plane_prop_src_y, 0); // 源矩形 Y
drmModeAtomicAddProperty(req, plane_id,
plane_prop_src_w, 1920 << 16); // 源矩形宽(Q16.16 定点)
drmModeAtomicAddProperty(req, plane_id,
plane_prop_src_h, 1080 << 16); // 源矩形高
drmModeAtomicAddProperty(req, plane_id,
plane_prop_crtc_x, 0); // 屏幕位置 X
drmModeAtomicAddProperty(req, plane_id,
plane_prop_crtc_y, 0); // 屏幕位置 Y
drmModeAtomicAddProperty(req, plane_id,
plane_prop_crtc_w, 1920); // 屏幕宽
drmModeAtomicAddProperty(req, plane_id,
plane_prop_crtc_h, 1080); // 屏幕高
// 4. 提交(带标志控制同步行为)
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
drmModeAtomicCommit(fd, req, flags, NULL);
// 5. 清理
drmModeAtomicFree(req);
5.3 原子标志位详解
| 标志 | 含义 |
|---|---|
| DRM_MODE_ATOMIC_NONBLOCK | 非阻塞提交,通过 event 通知完成;Wayland 合成器常用 |
| DRM_MODE_ATOMIC_TEST_ONLY | 仅测试不变更,验证配置是否可行 |
| DRM_MODE_ATOMIC_ALLOW_MODESET | 允许真正的模式设置(硬件时序变更),闪屏 |
| DRM_MODE_PAGE_FLIP_EVENT | 请求 VBlank 事件通知,用于帧精确同步 |
5.4 内核侧原子提交流程
当用户态调用 DRM_IOCTL_MODE_ATOMIC 时,内核侧的关键流程如下:
// drivers/gpu/drm/drm_atomic.c
int drm_mode_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state,
uint32_t flags)
{
// 1. 复制当前状态(备份用于回滚)
drm_atomic_state_init(dev, state);
// 2. 遍历所有 Plane → 验证裁剪/缩放参数
for_each_plane_in_state_safe(state, plane, old_plane)
funcs->atomic_check(plane, state);
// 3. 遍历所有 CRTC → 验证显示模式
for_each_crtc_in_state_safe(state, crtc, old_crtc)
funcs->atomic_check(crtc, state);
// 4. 遍历所有 Connector → 验证链路
for_each_connector_in_state_safe(state, connector)
funcs->atomic_check(connector, state);
// 5. TEST_ONLY 标志 → 到此结束
if (flags & DRM_MODE_ATOMIC_TEST_ONLY)
return 0;
// 6. 非阻塞提交 → 将 commit 入队到 workqueue
if (flags & DRM_MODE_ATOMIC_NONBLOCK)
return commit->commit_entry.work.func(&worker);
// 7. 同步提交 → 执行完整 commit 流程
drm_atomic_helper_swap_state(dev, state); // 保存旧状态用于回滚
// 8. 调用各对象的 atomic_commit
for_each_plane_in_state(state, plane)
funcs->atomic_commit(plane, state);
for_each_crtc_in_state(state, crtc)
funcs->atomic_commit(crtc, state);
// 9. 等待 VBlank 后提交
drm_crtc_vblank_get(crtc);
funcs->atomic_flush(crtc, state);
}
六、Vblank 与垂直同步(VSync):告别画面撕裂
6.1 Vblank 中断机制
CRT 电子束从上到下扫描一行后需要回到左上角继续,这段时间称为 Vertical Blanking Interval(Vblank)。在此期间显示控制器不读取帧缓冲,是更新画面的完美窗口——这就是 VSync 同步的理论基础。
DRM 子系统将 Vblank 抽象为可计数的参考点:
// 获取当前 Vblank 计数
struct drm_vblank_crtc vblank;
ioctl(fd, DRM_IOCTL_VBLANK, &vblank);
// 请求在下一个 Vblank 时刻触发事件
struct drm_wait_vblank vbl_wait = {
.request = {
.type = _DRM_VBLANK_RELATIVE,
.sequence = 1, // 相对当前 +1
}
};
ioctl(fd, DRM_IOCTL_WAIT_VBLANK, &vbl_wait);
// Flip 事件:Page Flip 完成通知
struct drm_event_vblank vblank_event = {
.type = DRM_MODE_EVENT_VBLANK,
.sequence = current_vblank_count,
.tv_sec = kernel_time.tv_sec,
.tv_usec = kernel_time.tv_usec,
};
6.2 Page Flip:无撕裂画面更新
Page Flip 是 DRM VSync 同步的核心操作——在 Vblank 期间从一帧缓冲切换到另一帧缓冲,实现无撕裂画面更新:
// 用户态请求 Page Flip
drmModePageFlip(fd, crtc_id, new_fb_id,
DRM_MODE_PAGE_FLIP_EVENT, user_data);
// 内核侧处理流程
int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data)
{
// 1. 验证新帧缓冲与 CRTC 兼容
fb = drm_framebuffer_lookup(dev, data->fb_id);
// 2. 获取 Vblank 引用
drm_crtc_vblank_get(crtc);
// 3. 提交 Page Flip(入队等待 VBlank)
funcs->page_flip(crtc, fb, event, data->flags);
// 4. 硬件扫描当前帧缓冲,在下一个 Vblank 开始时切换到新帧
// 5. 发送 DRM_EVENT_VBLANK + DRM_EVENT_FLIP_COMPLETE
}
6.3 Adaptive Sync、FreeSync 与 G-SYNC
现代可变刷新率(VRR)技术让显示器不再固定在 60Hz 或 144Hz:
- FreeSync / Adaptive-Sync:DisplayPort 1.2a 标准,DRM 中通过
DRM_MODE_FLAG_SUPPORTS_VRR标记 - G-SYNC Compatible:NVIDIA 认证的 FreeSync 显示器,amdgpu 驱动自动支持
- DRM 实现:通过
drm_connector的vrr_capable属性 + CRTC 的VRR_ENABLED属性控制
七、色彩管理与 HDR 元数据
7.1 Linux 色彩管理管线
DRM 的色彩管理管线(Color Management Pipeline,CMP)支持硬件级的色调映射:
帧缓冲 → De-Gamma → CSC(Color Space Conversion) →
Gamma → [HDR Output] → Physical Display
标准 DRM Color Properties:
- CTM(Color Transformation Matrix):3x3 矩阵色彩变换,16.16 定点
- DEGAMMA_LUT:De-Gamma 查找表,将线性光转为非线性空间
- GAMMA_LUT:最终 Gamma 校正,适配显示器光电特性
- OUTPUT_COLORSPACE:BT.601 / BT.709 / BT.2020(DP 2.0+)
7.2 HDR 元数据传递
Linux 5.1+ 引入 HDR_OUTPUT_METADATA 属性,通过以下机制实现:
struct drm_hdr_output_metadata {
enum drm_colorspace colorspace;
// SMPTE 2086 静态 HDR
struct drm_hdr_metadata_type1 {
uint16_t display_primary[3][2]; // R/G/B 色域坐标
uint16_t white_point[2]; // 白点坐标
uint16_t max_cll; // 最大内容亮度 (nits)
uint16_t max_fall; // 最大帧平均亮度
} hdmi_metadata_type1;
// Dolby Vision / HDR10+ 动态 HDR
void *dynamic_metadata;
uint32_t dynamic_metadata_size;
};
八、同步与 Fence:跨设备渲染管线同步
8.1 DMA Fence 机制
DMA Fence 是 DRM 子系统的同步原语,用于 GPU 间、GPU-Display 间的操作序列化:
struct dma_fence {
struct kref refcount;
spinlock_t *lock;
// Fence 状态
uint64_t context; // Fence 上下文 ID
uint32_t seqno; // 序列号
// 回调注册
struct list_head cb_list;
signed long expires;
// 状态查询
bool signaled; // 是否已信号
// 调试支持
const char *ops_name;
char driver_name[32];
};
8.2 同步链(Sync Chain)工作流
// ① GPU 渲染完成后发 Fence
struct sync_file *render_fence = drm_sched_push_job(sched, job);
// ② 显示控制器等待 Fence 信号后再显示
drmModeAtomicAddProperty(req, plane_id,
plane_prop_in_fence_fd, render_fence_fd);
// ③ 合成器链(Wayland)
// app renders → export sync_fd
// compositor waits on sync_fd before import to KMS plane
// KMS ensures the buffer is ready before scanning out
8.3 Explicit Sync:从 Implicit 到 Explicit 的革命
传统 DRM 使用"隐式同步"(Implicit Sync)——每个 GPU 命令流隐式关联一个 fence,dma-buf 的读写者通过 resv(reservation object)协调:
// 隐式同步问题:不确定性
// 用户态不知道"何时"GPU 完成渲染 → 依赖内核判断 → 额外的同步开销
buf->resv->writers = sched->fence;
buf->resv->readers = (struct dma_fence **)(reader_count++);
Linux 6.8+ 主导的 Explicit Sync 改革:用户态直接传递 fence,内核不再需要猜测同步时机:
// 显式同步流程
struct sync_file *out_fence;
// App → Compositor(渲染完成 fenced buffer)
drm_syncobj_to_sync_file(sync_obj, &out_fence);
unix_fd_send(out_fence); // 通过 Unix Domain Socket 传递
// Compositor → KMS(显示前先确保渲染完成)
drmModeAtomicAddProperty(req, plane_id,
plane_sync_fd_prop, fence_fd);
// KMS 内部:dma_fence_wait_timeout(fence, 2s)
九、DRM 驱动开发实战:最小 DRM 驱动骨架
9.1 DRM 驱动注册过程
编写最小 DRM 驱动只需实现一组回调并注册到 DRM core:
#include <drm/drm_drv.h>
#include <drm/drm_device.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
static const struct drm_driver my_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &my_drm_fops,
.name = "my_drm",
.desc = "My Virtual DRM Driver",
.date = "20260101",
.major = 1,
.minor = 0,
// GEM 对象操作
.gem_create_object = my_gem_create,
._prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
// Dumb Buffer 接口(用于简易场景)
.dumb_create = my_dumb_create,
.dumb_map_offset = my_dumb_map_offset,
};
static int __init my_drm_init(void)
{
struct drm_device *ddev;
struct platform_device *pdev;
// 1. 分配设备
ddev = drm_dev_alloc(&my_drm_driver, &pdev->dev);
// 2. 注册 DRM 设备
drm_dev_register(ddev, 0);
// 3. 初始化显示管线(CRTC/Encoder/Connector/Plane)
my_drm_modeset_init(ddev);
}
module_init(my_drm_init);
9.2 实现 Dummy CRTC 与 Connector
// 定义 CRTC 操作
static const struct drm_crtc_funcs my_crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};
static const struct drm_crtc_helper_funcs my_crtc_helper_funcs = {
.mode_valid = my_crtc_mode_valid,
.atomic_check = my_crtc_atomic_check,
.atomic_begin = NULL,
.atomic_flush = my_crtc_atomic_flush,
.atomic_enable = my_crtc_atomic_enable,
.atomic_disable = my_crtc_atomic_disable,
};
// 定义 Encoder 操作
static const struct drm_encoder_funcs my_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
// 定义 Connector(虚拟显示器,用于测试)
static const struct drm_connector_funcs my_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_connector_helper_funcs my_connector_helper_funcs = {
.get_modes = my_connector_get_modes,
.best_encoder = my_connector_best_encoder,
.atomic_check = my_connector_atomic_check,
};
// 初始化显示管线
static int my_drm_modeset_init(struct drm_device *ddev)
{
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_plane *primary;
// 创建 Primary Plane
primary = drm_universal_plane_init(ddev, 0,
&my_plane_funcs,
my_formats, my_formats_count,
my_format_modifiers,
DRM_PLANE_TYPE_PRIMARY,
"primary");
// 创建 CRTC
drm_crtc_init_with_planes(ddev, crtc,
primary, NULL,
&my_crtc_funcs, "crtc");
crtc->helper_private = &my_crtc_helper_funcs;
// 创建 Encoder
drm_encoder_init(ddev, encoder,
&my_encoder_funcs,
DRM_MODE_ENCODER_VIRTUAL,
"encoder");
// 创建 Connector(虚拟固定连接器)
drm_connector_init(ddev, connector,
&my_connector_funcs,
DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(connector,
&my_connector_helper_funcs);
// 绑定 Encoder ↔ Connector
drm_connector_attach_encoder(connector, encoder);
}
9.3 帧缓冲与 Dumb Buffer 创建
// VBlank 回调
static int my_crtc_enable_vblank(struct drm_crtc *crtc)
{
// 启用 Vblank 中断
writel(INT_ENABLE_VBLANK, base + INT_ENABLE_REG);
return 0;
}
static void my_crtc_disable_vblank(struct drm_crtc *crtc)
{
writel(0, base + INT_ENABLE_REG);
}
// Dumb Buffer 模式(最常见的简易帧缓冲)
static int my_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
// args->width, args->height, args->bpp → args->pitch, args->size, args->handle
args->pitch = ALIGN(args->width * args->bpp / 8, 64);
args->size = args->pitch * args->height;
// 分配内存,通过 drm_gem_object 注册
return drm_gem_handle_create(file,
gem_object, &args->handle);
}
十、GPU 调度器:从 DRM Sched 到 EXECLUSIVE 优先级
10.1 DRM Scheduler 的演进
在 Linux 6.0 之前,各 GPU 厂商自行实现命令提交调度(i915 用 intel_execlists,amdgpu 用 drm_sched,lima/panfrost 各自实现)。6.0 起 DRM core 抽象出统一调度框架:
// DRM 调度器核心结构
struct drm_gpu_scheduler {
// Job 队列
struct list_head work_list[kernel_pipe_priority_count]; // HIGH/NORMAL/LOW
struct drm_sched_rq run_list;
// 工作线程
struct task_struct *thread;
// 超时处理
struct delayed_work work_tdr; // Timeout Detection Recovery
// Fence 机制
struct dma_fence *last_scheduled;
// 操作函数
const struct drm_sched_backend_ops {
.prepare_job = ...,
.run_job = ...,
.timedout_job = ...,
.free_job = ...,
} *ops;
};
// Job 提交流程
static void drm_sched_main(struct work_struct *w)
{
while (!kthread_should_stop()) {
// 1. 从优先级队列取 Job
job = drm_sched_select_job(sched);
// 2. 提交给 GPU 执行
sched->ops->run_job(job);
// 3. 注册 Fence 回调
dma_fence_add_callback(&job->hw_fence,
drm_sched_cb, job);
// 4. 处理 TDR(Timeout Detection Recovery)
mod_delayed_work(&sched->work_tdr,
msecs_to_jiffies(sched->timeout));
}
}
10.2 AMDGPU 的艺术:GFX 与 Compute 双引擎
AMDGPU 驱动是 DRM 子系统中代码最复杂(50万+ 行)的驱动之一,其核心创新在于多硬件队列调度:
// AMDGPU 的多引擎体系
struct amdgpu_device {
struct amdgpu_ring rings[AMDGPU_MAX_RINGS];
// GFX 引擎:图形渲染(OpenGL/Vulkan 绘图命令)
struct amdgpu_ring gfx_ring[AMDGPU_MAX_GFX_RINGS];
// SDMA 引擎:System DMA,用于 Copy Buffer
struct amdgpu_ring sdma_ring[AMDGPU_MAX_SDMA_RINGS];
// Compute 引擎:计算着色器(OpenCL/CUDA 替代)
struct amdgpu_ring compute_ring[AMDGPU_MAX_COMPUTE_RINGS];
// VCN 引擎:视频编解码
// UVD 引擎:旧版视频解码
};
Mesa/OpenGL 驱动调用链:glDrawArrays() → Mesa Gallium3D → RADV/AMD RadeonSI → libdrm_amdgpu → amdgpu_cs_submit() → drm_sched_entity_push_job() → 硬件 Ring Buffer → GPU 执行
十一、常见陷阱与调试技巧
11.1 原子提交的常见错误
❌ 错误 1:忘记设置 CRTC 的 plane_id
drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id); // 必须绑定 CRTC!
❌ 错误 2:分辨率超出 Connector 的 max_bpc × max_pixel_clock
// 使用 modetest 查询限制:
// modetest -M i915 -c | grep "modes"
❌ 错误 3:在 TEST_ONLY 检查通过后认为原子提交一定成功
// TEST_ONLY 只验证静态参数,运行时可能因显存不足或 TDR 失败
❌ 错误 4:非阻塞提交后过早释放帧缓冲
// NONBLOCK 提交后,用户态应等 FRAME_COMPLETE 事件再释放
// 否则正在扫描输出的 Frame Buffer 被释放 → GPU hang / 屏幕闪白
11.2 DRM 调试工具链
| 工具 | 用途 |
|---|---|
modetest -M <driver> -c | 查看 Connector/CRTC/Plane 拓扑与可用属性 |
modetest -M <driver> -s <crtc_id>:<resolution> | 测试模式设置(快速验证硬件支持) |
cat /sys/kernel/debug/dri/0/state | 读取 DRM 设备完整状态(debugfs) |
intel_gpu_top | Intel GPU 使用情况实时监控 |
amdgpu_top | AMD GPU 使用情况实时监控 |
radeontop | Radeon GPU 使用率显示(OpenGL/Vulkan) |
kernel tracing: /sys/kernel/debug/tracing/events/drm/ | DRM 子系统 ftrace 事件追踪 |
11.3 调试显示管线死锁
DRM 的一个棘手问题是 mode_config 大锁死锁。在 Atomic 路径中,drm_device.mode_config.lock 是一个全局 mutex,所有状态操作竞争此锁。典型死锁场景:
// 场景:App 持有 mode_config.lock(原子提交中)
// HPD 中断触发 → 也想获取 lock → 死锁
// 解决方案:HPD 事件使用 workqueue 异步处理
static void hpdr_work_fn(struct work_struct *work)
{
mutex_lock(&dev->mode_config.lock); // 安全获取,不发生死锁
drm_helper_hpd_irq_event(dev);
mutex_unlock(&dev->mode_config.lock);
}
十二、DRM 生态系统与前沿趋势
12.1 最新技术动向
- Intel Xe 驱动(2023+):全新 Intel 独立 GPU 驱动,取代传统的 i915 驱动
- NVIDIA Open GPU Kernel Module(2022+):NVIDIA 终于开源了内核模块(MIT/GPL 双协议),采用 GSP(GPU System Processor)架构,DRM 兼容层与 amdgpu 类似
- Asahi Linux(2022+):将 Apple Silicon GPU 的驱动逆向并引入主线内核,Apple AGX GPU 在 DRM 框架下运行 Mesa 驱动
- Panfrost/PanVK:ARM Mali GPU 开源驱动栈,完全基于 DRM/KMS
- VirtIO-GPU(virtio-gpu 1.2+):云桌面虚拟化显示支持,原生 Atomic KMS
- Qualcomm Adreno "Freedreno":移动 GPU 的开源 DRM 驱动
12.2 Explicit Sync 的完整实现
2024-2026 年 DRM 子系统最重要的演变是 Explicit Sync 的全面落地。这涉及 DRM Core、Wayland(wp_linux_drm_syncobj_v1协议)、Android(Sync Framework)的协调转换。核心变化:
// 旧:隐式同步
// 用户态看不到 Fence,一切由内核协调
// 新:显式同步
struct wp_linux_drm_syncobj_timeline_v1 {
// 用户态直接管理 timeline 值
uint64_t current_point;
uint64_t target_point;
};
// Wayland 合成器显式管理
zwp_linux_dmabuf_feedback_v1 →
main_device + tranche_flags +
target_device + sync_obj_timeline
// KMS 直接使用显式 fence
drmModeAtomicAddProperty(req, plane_id,
"IN_FENCE_FD", imported_fence_fd);
12.3 对 Rust 驱动支持的展望
随着 Rust-for-Linux 的成熟,DRM 子系统已出现首个 Rust 驱动实验——Apple Silicon GPU 驱动的部分组件(如 DMA-BUF 子系统封装)已在用 Rust 编写。AMDGPU 驱动中也有计划引入 Rust 实现的 GEM 管理和电源管理子系统。未来的趋势是 C/Rust 混合实现——性能关键路径用 C,复杂逻辑和状态管理用 Rust。
十二、总结:DRM 子系统哲学
DRM 子系统的设计哲学可以归纳为三个核心原则:
- 硬件无关的抽象:CRTC/Encoder/Connector/Plane 四原件模型统一了所有显示硬件的描述,用户态代码无需关心底层差异
- 状态打包与原子提交:从 Legacy 到 Atomic 的演进,本质是将"部分失败"变为"原子成功"——分布式一致性理论在内核的缩影
- 显式优于隐式:从 Implicit Sync 到 Explicit Sync、从 Legacy 到 Atomic、从 ioctl 到 Property,DRM 的长期趋势是将控制权还给用户态,内核只做最小化的仲裁
理解 DRM 不仅是理解 Linux 图形栈的必经之路,也是学习大规模内核子系统设计的绝佳案例——如何在硬件多样性中建立统一抽象,如何在高性能与安全性间取得平衡,如何让子系统渐进式演进而不破坏兼容性。从嵌入式 MCU 的多点触控屏幕,到服务器 BMC 的 BMC 图形输出,到 AI 训练机多卡渲染输出,DRM/KMS 正以其惊人的灵活性默默承载着这一切。

发表评论 取消回复