3. Vblank中断机制
Vblank(Vertical Blanking,垂直消隐期)中断是显示子系统最核心的节拍源。每当一帧图像扫描完成、进入消隐区间时,显示控制器(如 DRM/KMS 中的 CRTC)会产生一次 Vblank 中断。它既是帧同步的时间基准,也是 DMA-BUF 缓冲区切换、页面翻转(page flip)完成的确认信号。理解 Vblank 中断从硬件触发到内核回调的完整链路,是后续做 Vblank 与 DMA-BUF 联合调优的前提。
一、核心原理:从硬件事件到软件回调
Vblank 中断的触发流程可以拆成四层:硬件层、中断控制器层、内核通用中断层、驱动处理层。显示控制器在扫描到消隐区起点时置位中断状态寄存器,通过 GIC(Generic Interrupt Controller)向 CPU 发出 IRQ 信号。CPU 响应后进入内核通用中断入口,读取中断号并查找注册在该 IRQ 上的处理函数,最终调用显示驱动注册的 Vblank 中断处理程序。
关键点在于:Vblank 中断频率等于刷新率。60Hz 屏幕每秒产生 60 次中断,120Hz 则是 120 次。这个频率看似不高,但如果上半部(top half)里做了耗时操作,就会直接阻塞其他中断,造成系统卡顿甚至丢帧。因此内核把中断处理拆成上半部与下半部,是 Vblank 机制必须掌握的设计。
二、上半部与下半部的职责划分
上半部运行在硬中断上下文,此时本地 CPU 中断被关闭,任何睡眠、内存分配、互斥锁操作都是禁止的。它的职责只有三件事:读取中断状态寄存器确认是 Vblank 事件、清除中断标志防止重复触发、记录时间戳并调度下半部。这三步必须在微秒级完成。
下半部则运行在软中断或工作队列上下文,可以睡眠、可以竞争锁、可以访问用户空间数据。Vblank 场景下,下半部通常负责:更新 DRM 的 vblank 计数、唤醒等待 vblank 的进程、触发 page flip 完成回调、通知 DMA-BUF 的 release 回调释放前一帧缓冲区。如果下半部处理过慢,会导致缓冲区释放延迟,进而让生产者(如 GPU 渲染)拿不到空闲 buffer,形成整条流水线的阻塞。
调优视角:Vblank 中断的“上半部要短、下半部要稳”是联合调优的第一原则。上半部超时会丢中断,下半部超时会拖慢 DMA-BUF 周转。两者共同决定了帧缓冲的复用效率。
三、常用 API 与调试节点
Linux DRM 子系统为 Vblank 提供了标准 API,驱动和用户态都可以使用。下表列出最常用的接口及其用途。
| API / 节点 | 所在层 | 用途 |
|---|---|---|
drm_crtc_handle_vblank() |
内核驱动 | 驱动在中断中调用,递增 vblank 计数并唤醒等待者 |
drm_crtc_vblank_get() / put() |
内核驱动 | 引用计数控制 Vblank 中断的使能与关闭 |
drm_vblank_init() |
内核驱动 | 初始化 CRTC 的 vblank 支持 |
drmWaitVBlank() |
用户态 libdrm | 阻塞等待指定次数的 Vblank,用于帧同步 |
/sys/class/drm/card0-Vblank |
sysfs 调试 | 查看 Vblank 计数与时间戳 |
/proc/interrupts |
procfs 调试 | 查看 Vblank IRQ 触发次数与 CPU 分布 |
trace_vblank_event |
ftrace | 跟踪 Vblank 事件时序,定位延迟 |
实际调试中,最直接的手段是观察 /proc/interrupts 中 Vblank 中断号的计数增长是否与刷新率一致。若计数明显偏低,说明中断被屏蔽或上半部处理超时;若计数正常但帧率不稳,则问题多半出在下半部或 DMA-BUF 释放路径。
# 查看 Vblank 中断计数
cat /proc/interrupts | grep -i vblank
# 查看 DRM Vblank 时间戳
cat /sys/class/drm/card0-Vblank
# 使用 ftrace 跟踪 vblank 事件
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_vblank_event/enable
cat /sys/kernel/debug/tracing/trace_pipe
四、案例场景与常见误区
某车载仪表项目在 60Hz 屏幕上出现周期性卡顿。排查发现 /proc/interrupts 中 Vblank 中断计数正常,但下半部工作队列中执行了 DMA-BUF 的 dma_buf_put() 和内存回收,单次耗时超过 8ms,接近一帧预算(16.6ms)的一半。优化方案是把缓冲区释放改为异步 work,并在上半部只做时间戳记录,最终卡顿消失。
常见误区:很多开发者把 Vblank 中断当成“帧率计数器”,只关心计数是否正确,却忽略了中断处理路径对 DMA-BUF 生命周期的直接影响。Vblank 中断延迟会直接转化为缓冲区释放延迟,进而影响整条显示流水线的吞吐。
另一个高频误区是在上半部里调用 drm_crtc_handle_vblank() 之外还做了大量日志打印。printk 在中断上下文可能触发控制台锁竞争,导致中断处理时间从几微秒膨胀到毫秒级。调试阶段应使用 tracepoint 而非 printk。
复盘要点可以归纳为三条:第一,上半部只做状态确认、清中断、调度下半部;第二,下半部避免长时间持锁和同步内存回收;第三,用 /proc/interrupts 与 ftrace 建立“中断计数—时间戳—帧完成”的对应关系,才能把 Vblank 与 DMA-BUF 的联合调优落到实处。