4. DRM与KMS核心机制

在Linux图形栈中,DRM(Direct Rendering Manager)负责GPU与显存管理,而KMS(Kernel Mode Setting)负责显示管线的配置与输出。理解KMS的对象模型,是分析显示性能、排查花屏/闪烁/撕裂、以及优化多屏合成路径的基础。KMS把一条显示通路拆成若干可独立配置的对象:Framebuffer、Plane、CRTC、Encoder、Connector,它们通过原子提交(Atomic Commit)一次性生效,避免中间状态导致的显示异常。

核心对象与职责

  • Framebuffer(FB):描述一块显存中的像素数据,包含格式、宽高、pitch、修饰符(modifier)等信息。它是“图像源”,本身不决定显示位置。
  • Plane:图层,负责把某个Framebuffer按裁剪、缩放、旋转、混合等属性叠加到CRTC上。常见类型有Primary、Cursor、Overlay。Plane决定“怎么合成”。
  • CRTC:显示控制器通道,负责扫描输出时序(mode)、刷新率、背景色,并把若干Plane合成后的结果送出。CRTC决定“以什么时序扫描”。
  • Encoder:把CRTC的像素流转换为特定接口信号,如HDMI、DisplayPort、DSI、eDP。Encoder决定“用什么编码/接口”。
  • Connector:物理输出端口及其连接状态,如HDMI-A-1、DP-1,包含EDID、DPMS、链路训练状态等。Connector决定“接到什么显示器”。
显示通路可以概括为:Framebuffer → Plane → CRTC → Encoder → Connector → 显示器。原子提交则把这条链上的所有属性打包成一次事务,要么全部生效,要么全部回滚。
KMS 对象模型与原子提交数据流 Framebuffer 格式 / 宽高 / pitch modifier / 显存对象 Plane 裁剪 / 缩放 / 旋转 混合 / zpos CRTC mode / 刷新率 扫描时序 / 背景色 Encoder HDMI / DP / DSI 像素流编码 Connector EDID / DPMS 链路训练 / 热插拔 显示器 面板 / 时序接收 原子提交(Atomic Commit) 一次 ioctl 打包所有对象属性,统一校验与生效 DRM_MODE_ATOMIC_TEST_ONLY DRM_MODE_ATOMIC_NONBLOCK DRM_MODE_PAGE_FLIP_EVENT 校验失败 → 整单回滚;校验通过 → 在垂直消隐期切换 避免中间态闪烁、撕裂与部分更新不一致 属性绑定 图像源 合成层 扫描时序 接口编码 物理端口 显示终端 原子提交标志

原子提交模式:从“逐步设置”到“一次事务”

传统KMS使用一系列独立ioctl分别设置CRTC、Plane、Connector,容易在切换过程中出现中间状态,例如先改了Plane再改CRTC,导致一帧内出现旧图层与新时序混用。原子提交把一次显示更新抽象为属性集合:每个对象(CRTC/Plane/Connector)都有一组可读写属性,用户态通过drmModeAtomicReq把属性变更逐条加入请求,最后调用一次drmModeAtomicCommit()提交。

drmModeAtomicReq *req = drmModeAtomicAlloc();

drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id);
drmModeAtomicAddProperty(req, plane_id, prop_src_x, 0);
drmModeAtomicAddProperty(req, plane_id, prop_src_y, 0);
drmModeAtomicAddProperty(req, plane_id, prop_src_w, width << 16);
drmModeAtomicAddProperty(req, plane_id, prop_src_h, height << 16);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_x, 0);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_y, 0);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_w, width);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_h, height);

uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK |
                 DRM_MODE_PAGE_FLIP_EVENT |
                 DRM_MODE_ATOMIC_ALLOW_MODESET;

int ret = drmModeAtomicCommit(fd, req, flags, user_data);
drmModeAtomicFree(req);

其中DRM_MODE_ATOMIC_TEST_ONLY只做校验不生效,适合在正式提交前探测配置是否合法;DRM_MODE_ATOMIC_NONBLOCK表示异步提交,配合DRM_MODE_PAGE_FLIP_EVENT可在页面翻转完成时收到事件,用于精确控制帧节奏;DRM_MODE_ATOMIC_ALLOW_MODESET允许修改时序等需要重新训练链路的属性。

典型场景:多图层合成与页面翻转

假设一个视频播放器要在4K屏幕上同时显示视频层和字幕层。视频帧放入Overlay Plane,字幕放入另一个Overlay或Primary Plane,两者都绑定到同一个CRTC。用户态每帧更新视频Plane的FB_ID,同时保持字幕Plane不变,然后一次原子提交。内核在垂直消隐期完成Plane切换,避免撕裂。若使用TEST_ONLY先校验,还能提前发现缩放比例超出硬件能力、格式不支持等问题,避免提交失败导致黑屏。

对象 关键属性 性能关注点
Framebuffer format、modifier、pitch 带宽、压缩、tiling 是否匹配显示控制器
Plane fb_id、crtc_id、src/dst、zpos、alpha 缩放单元能力、图层数量上限、混合开销
CRTC mode、active、background_color 刷新率、时序裕量、多屏共享资源
Encoder possible_crtcs、possible_clones 接口带宽、时钟限制、克隆模式
Connector CRTC_ID、DPMS、EDID、link_status 热插拔响应、链路训练失败、带宽协商

误区与复盘

常见误区一:认为“改了Plane就立刻显示”。实际上属性写入请求后,必须调用原子提交才会生效;未提交的请求不会影响显示。误区二:忽略TEST_ONLY,直接提交复杂配置,失败后难以定位是哪个属性不合法。误区三:在多线程中共享同一个drmModeAtomicReq,导致属性交叉污染。建议每个提交线程独立分配请求对象,提交后立即释放。

排查显示异常时,可以按对象链路逐级检查:先用modetest或drm_info确认Connector是否连接、CRTC是否active、Plane是否绑定到正确CRTC;再检查Framebuffer的modifier是否被当前硬件支持;最后确认原子提交的flags是否包含必要的ALLOW_MODESET。理解这五个对象与原子提交的关系,就能把“显示不正常”拆解为可验证的属性问题,而不是盲目调整分辨率或刷新率。