4. DRM框架入门
DRM(Direct Rendering Manager)是Linux内核中负责图形显示与GPU渲染资源管理的子系统。它最初用于解决多进程直接操作显卡硬件带来的冲突问题,如今已演化为一个分层清晰、职责明确的显示框架。在Android系统中,SurfaceFlinger、HWC以及各类GPU驱动都通过DRM接口与内核显示硬件交互。理解DRM,核心是理解它如何把"一帧画面从内存送到屏幕"这件事拆解成若干可组合、可复用的对象。
一、KMS:内核模式设置
KMS(Kernel Mode Setting)是DRM中负责显示管线配置与控制的子系统。它把显示硬件抽象为一组对象,每个对象承担明确职责,并通过属性(Property)机制对外暴露可配置参数。KMS的核心对象包括CRTC、Encoder、Connector、Plane和Bridge。
- CRTC:显示控制器,代表一个扫描输出引擎。它从Framebuffer读取像素,按时序产生扫描信号。一个CRTC对应一路独立的显示输出,负责分辨率、刷新率、扫描时序等。
- Encoder:编码器,把CRTC输出的像素时序转换为Connector可接收的信号格式,例如RGB并行、LVDS、MIPI DSI、HDMI TMDS等。
- Connector:连接器,代表物理显示接口,如HDMI口、MIPI DSI排线座、eDP屏线。它负责读取EDID、检测热插拔、报告连接状态。
- Plane:图层,代表一个可独立叠加的像素来源。它关联Framebuffer,支持位置、缩放、旋转、Alpha混合、Z序等属性。硬件通常有Primary、Overlay、Cursor等Plane。
- Bridge:桥接器,用于连接非标准显示链路,例如DSI到LVDS转换芯片、MIPI DSI到HDMI桥、Type-C转接芯片等。Bridge把Encoder与Connector之间的信号做转换或中继。
这些对象之间形成一条清晰的信号链:Framebuffer → Plane → CRTC → Encoder → Bridge(可选)→ Connector → 物理屏幕。KMS的职责就是根据用户空间请求,把这条链路上的对象连接起来并配置属性。
二、GEM:显存与缓冲管理
GEM(Graphics Execution Manager)负责显存对象的分配、共享与生命周期管理。它不关心显示时序,只关心"一块内存如何被GPU、显示控制器和用户空间安全地共享"。
GEM的核心概念是GEM Object,它代表一块可被GPU访问的显存区域。用户空间通过DRM_IOCTL_GEM_CREATE创建对象,通过mmap映射到用户地址空间,通过PRIME机制在进程间或设备间共享。Android中常见的Dumb Buffer是一种最简单的GEM对象,用于CPU可访问的线性缓冲,常用于早期bring-up和简单测试。
/* 典型 GEM 创建与映射流程(伪代码) */
struct drm_mode_create_dumb create = {
.width = 1920,
.height = 1080,
.bpp = 32,
};
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
struct drm_mode_map_dumb map = { .handle = create.handle };
ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
void *addr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
GEM与KMS的分工非常清晰:GEM提供Framebuffer背后的内存对象,KMS把Framebuffer绑定到Plane,再由CRTC扫描输出。没有GEM,KMS就没有像素来源;没有KMS,GEM分配的内存也无法变成屏幕上的画面。
三、Atomic模式与属性配置
传统DRM使用Legacy ioctl逐个设置CRTC、Plane、Connector,容易出现中间状态和竞态。现代DRM推荐使用Atomic模式:用户空间一次性提交一组属性变更,内核先做原子校验(Atomic Check),全部通过后再提交生效(Atomic Commit)。
| 对象 | 核心职责 | 关键属性示例 |
|---|---|---|
| CRTC | 扫描输出与时序生成 | mode、active、out_fence_ptr |
| Plane | 图层叠加与像素来源 | fb_id、crtc_id、src_x/y、crtc_x/y、alpha、zpos |
| Connector | 物理接口与状态检测 | crtc_id、dpms、EDID、link_status |
| Encoder | 信号格式转换 | possible_crtcs、possible_clones |
| Bridge | 非标准链路转换 | bridge chain、timings、format |
四、典型场景:Android开机显示初始化
以Android设备开机为例,显示驱动通常经历以下阶段:
- 内核启动,DRM驱动probe,注册CRTC、Plane、Encoder、Connector、Bridge等对象。
- Connector检测到屏幕连接,读取EDID或使用Panel固定时序。
- Bootloader或内核早期通过简单Framebuffer显示开机Logo。
- SurfaceFlinger启动,通过DRM Atomic API接管显示,创建GEM Buffer并绑定到Plane。
- HWC根据图层合成策略,决定使用GPU合成还是Overlay Plane直出。
- 每次提交通过Atomic Commit生效,CRTC按VSync节奏扫描输出。
modetest -M xxx -p 可以列出所有CRTC、Encoder、Connector及其连接关系;modetest -M xxx -c 查看Connector状态;modetest -M xxx -a 查看所有属性。
五、常见误区与复盘
- 把Bridge当成Connector:Bridge是信号转换器,Connector才是物理接口。DSI到HDMI桥芯片应注册为Bridge,最终仍需要一个HDMI Connector。
- 认为一个CRTC只能接一个Connector:一个CRTC同一时刻只能驱动一个Connector,但可以通过possible_crtcs与多个Encoder关联,实现灵活路由。
- 忽略Plane的格式与缩放限制:并非所有Plane都支持任意格式和缩放比例,Atomic Check会拒绝不支持的配置,导致提交失败。
- 混淆GEM与Framebuffer:GEM Object是内存,Framebuffer是对内存的显示描述(格式、宽高、Stride)。一个GEM Object可以创建多个Framebuffer。
- 忘记处理Atomic Commit的Fence:异步提交需要等待out_fence,否则会出现撕裂或提交顺序错乱。
掌握DRM的对象模型,是后续进行Android HWC集成、多屏异显、Overlay优化和显示问题定位的基础。KMS负责"怎么显示",GEM负责"显示什么",Atomic API负责"如何安全地一次性提交",三者共同构成现代Linux显示管线的核心骨架。