Appearance
内核与驱动:把应用意图交给设备
系统栈总览 / 下一节:平台图形栈、引擎与应用 / 排障:跨层故障链
应用的绘图 API 不会直接写 GPU 寄存器,触摸屏也不会直接调用应用回调。内核和驱动负责把不可信、并发的进程请求转换为设备工作,同时隔离地址空间、约束设备访问范围、管理 buffer 所有权,并在完成或失败时通知上层。
这层要同时完成四件事:输入交付、CPU 调度、数据搬运、异步同步。客户端“已经改了状态但还没显示”“GPU 已画完却没赶上本次刷新”“画面卡住但 CPU 不高”,都要从这四类机制中区分。
两条路径与一个回路
控制路径决定谁做什么;数据路径搬运像素和资源;完成回路让下游知道何时可以消费结果。
text
控制:应用/引擎 -> 图形 API -> 用户态驱动 -> 内核 DRM/GPU driver -> GPU queue
数据:CPU 内存/文件/相机 buffer --DMA--> RAM 或显存 --GPU--> render target
回路:GPU fence / display vblank / input IRQ -> 内核事件 -> compositor 或应用Linux DRM/KMS 是本页的主例子。Windows 的 WDDM/DXGI、macOS 的 IOKit/Metal 与 WindowServer、Android 的 SurfaceFlinger 有不同对象和权限模型,但也必须解决同一组问题:谁拥有 buffer,谁可写它,何时可复用,何时真正显示。
1. 输入:IRQ 不是应用已经收到事件
鼠标、键盘、触摸屏控制器产生报告后,通常以中断(IRQ)通知 CPU。内核驱动确认中断来源、读取设备数据,再经输入子系统输出按键、相对位移或触摸坐标。高频设备的复杂处理可能延后到软中断或工作队列,避免长时间占据硬中断上下文。
text
触摸控制器报告坐标
-> IRQ
-> 内核驱动读取与确认
-> 输入子系统形成事件
-> 窗口系统/浏览器读取并路由
-> 坐标转换、命中测试、应用事件处理器三个边界必须分开:
- IRQ 只说明设备需要服务;目标进程仍要等其线程获得 CPU。
- 原始设备坐标到应用坐标会经过屏幕旋转、缩放、多屏、窗口和 viewport 变换;“点位偏移”要逐段检查。
- 事件送达不等于画面出现。事件处理、状态变更、渲染提交、合成与扫描至少还跨一次帧边界。
2. 调度:内核不理解“一帧”
调度器只在可运行线程间分配 CPU:依据调度策略、优先级、运行队列、CPU 亲和性和等待状态。它不理解 DOM、Widget 或动画。浏览器主线程、Flutter 的 UI isolate、栅格线程、GPU 服务线程和系统合成器都在竞争 CPU。
text
runnable: 已在 run queue,等待 CPU
running: 正在某核执行
blocked: 等锁、IPC、I/O、page fault 或 fence,暂不参与竞争因此页面延迟并不总是“JS 慢”:主线程可能被长 task 占住,也可能阻塞在锁、进程间消息、缺页或 GPU fence;CPU 利用率高也不能推出 GPU 忙。诊断需要线程时间线和等待原因,而不是仅看总 CPU。
3. 虚拟内存、缺页与进程隔离
进程使用虚拟地址。CPU 的 MMU 按页表翻译到物理页;内核据此隔离进程、延迟分配内存,并把共享 buffer 映射给协作进程或设备。渲染会涉及应用堆、纹理、图块、command buffer、共享内存和显示 buffer。
text
应用虚拟地址 -> TLB 或页表遍历 -> 物理 RAM
-> 设备映射/IOMMU 翻译 -> DMA 或 GPU 读写page fault 不必然是错误。首次触页时内核可能只建立映射;也可能要分配页、读文件或在内存压力下回收页。后几类若发生在输入到呈现的关键路径,会造成长尾卡顿。独立显存、统一内存和不同驱动的资源驻留策略差别很大,不能用应用 RSS 推断一张纹理是否已可用。
图形 API 调用通常先由用户态驱动编码命令,再在创建上下文、映射 buffer、提交队列等步骤进入内核。浏览器还会经 GPU 进程/服务使用 IPC 隔离不可信网页与复杂驱动。系统调用和 IPC 返回只说明请求被接受或排队,不能说明 GPU 已执行、更不能说明屏幕已显示。
4. DMA 与 IOMMU:设备如何安全地访问数据
DMA 让设备直接在内存与设备间传输数据,避免 CPU 逐字节搬运。驱动将经过授权的 buffer 映射给设备,并处理生命周期与缓存一致性;IOMMU 可进一步限制设备能访问的地址范围。应用的普通指针不能直接作为 GPU 可访问地址。
“零拷贝”也不等于零成本。相机或视频 buffer 可被共享,省掉一次 CPU 拷贝,却仍可能发生格式转换、缓存同步、跨设备等待或带宽竞争。是否应优化,先从 trace 确认拷贝和等待是否在关键路径上。
5. GPU 命令:从 command buffer 到执行队列
GPU 不理解应用状态,只执行已编码的图形或计算工作。引擎把 draw、dispatch、纹理上传、资源屏障等写进 command buffer,提交到 queue。典型职责如下:
- 用户态驱动编码命令、维护 API 资源状态。
- 内核/驱动校验权限和对象,建立内存映射,接收提交。
- GPU 调度器在多个 context/进程间安排可运行工作。
- GPU 读取资源、执行 shader、写入 render target。
- 完成或出错时信号 fence,交给等待的 queue、compositor 或 CPU。
text
display list / scene
-> graphics backend 编码 command buffer
-> submit(queue, waits, signal fence)
-> driver 调度 -> GPU 执行 -> fence signaledsubmit 很快返回是正常的,它只说明工作进入队列。CPU 提交、GPU 开始、GPU 完成、compositor 采用 buffer、面板扫描到像素是不同时间点。没有这些时间戳,就无法区分 CPU 编码慢、队列积压、GPU 饱和和 presentation 延迟。
6. Fence、buffer ownership 与 vblank
CPU、GPU、合成器和显示控制器并行工作。没有同步,消费者会读到半成品,生产者会覆盖正在显示的画面。同步对象把依赖关系变成可等待、可观察的契约。
| 机制 | 表达的事实 | 例子 |
|---|---|---|
| fence | 一批异步工作完成 | GPU 写完 render target,compositor 才可读取 |
| semaphore/event | 后继 queue 可开始依赖任务 | 合成 pass 等待栅格化结果 |
| acquire/release | buffer 的生产者与消费者切换所有权 | 相机 -> GPU -> 系统合成器 |
| vblank | 显示器到刷新边界 | 在安全时机 page flip,避免扫描中替换画面 |
三缓冲能让显示、合成、GPU 并行,提高吞吐;但过深的 buffer queue 会把最新输入排在旧帧后面,增加延迟。双缓冲队列浅,却更容易因生产来不及而重复旧帧。这里没有固定最优值,交互系统在吞吐、稳定性和延迟间取舍。
7. Linux DRM/KMS:从结果到 scanout
DRM 管理图形设备对象与权限,KMS 管理 connector、CRTC、plane、framebuffer 和 modeset。一个用户态 compositor 组合各个 surface 的 buffer,选择 GPU 合成或硬件 plane,然后以 atomic commit 请求内核在合适的 vblank 更新显示状态。显示控制器从可扫描 buffer 逐行读取,最终送到面板。
text
GPU render target 完成
-> compositor 取得 buffer
-> GPU 合成或分配 hardware planes
-> DRM atomic commit
-> next vblank -> scanout -> 屏幕这解释三类常见现象:
- 撕裂:扫描中切换内容,屏幕上下来自不同帧;同步到 vblank 可避免,但会增加等待。
- jank:新 buffer 未在刷新截止前就绪,显示器复用旧帧或跳过中间状态;状态未必丢失,但运动不连续。
- 黑屏/花屏:可能是 shader、纹理格式、buffer 生命周期、plane 限制、驱动复位或显示链路;先证实最终提交的 buffer 与 KMS/系统合成器事件。
macOS 的应用通常经 Metal/CAMetalLayer 与 WindowServer 合成,Windows 通常经 Direct3D、DXGI 和 DWM;它们不暴露 Linux 的 DRM/KMS 文件接口,也不应直接用 CRTC/plane 来解释问题。可迁移的仍是 command queue、surface/buffer、同步和 presentation 的因果关系。
8. 设备复位与可观测性
GPU 可能因驱动 bug、非法/超时工作、硬件故障或系统电源事件而被复位。复位会丢失或使 GPU context、资源内容失效;上层可能收到 device lost/removed,浏览器或应用要重建资源、降级或重启相关进程。不能把“某次提交没返回”简单归为应用卡死。
排障从一条可复现的时间线开始,保留事件时间、线程运行/阻塞、command submit、fence 完成和 presentation:
| 观察到的证据 | 优先怀疑 | 下一步 |
|---|---|---|
| 输入处理器很晚才开始 | run queue、长 task、锁、缺页 | 查主线程调度和系统 trace |
| handler 很快返回却等 fence | GPU queue、资源上传、shader、栅格 | 查 GPU/driver 轨道与资源压力 |
| fence 已完成但晚一帧显示 | compositor、vblank、buffer queue | 查 presentation 和系统合成器 |
| device lost、GPU reset 或黑屏 | 驱动/硬件/资源恢复 | 查内核日志、应用错误路径和重建策略 |
浏览器先使用 DevTools Performance 看 input delay、主线程、raster、compositor/GPU 轨道;原生 Linux 再结合系统 trace、DRM 事件、内核日志和厂商 GPU 工具。工具会变,证据结构不变。
上层开发者应保留的边界
- DOM mutation、
setState或 draw call 都只说明上层描述变了,不说明已经上屏。 - 不要在输入回调里同步解码大资源、触发大规模布局或等待 GPU;先提交可见反馈,再安排非关键工作。
- 不要凭直觉堆图层、纹理和离屏 buffer;它们会成为显存、带宽、栅格和合成压力。
- 不要用固定延时等待“渲染完成”。用框架或平台的帧、fence、presentation 回调表达依赖;用 trace 证明真实路径。
- “本地已提交”“已展示”“远端已确认”必须是不同状态。内核和驱动能证明设备完成工作,不能替服务端确认业务事实。
资料与边界
- Linux 输入机制:Input subsystem(访问于 2026-07-30)。
- 内存、DMA 与 IOMMU:Memory Management、DMA-API、IOMMU(访问于 2026-07-30)。
- DRM/KMS 与同步:DRM Internals、KMS、DRM UAPI(访问于 2026-07-30)。
- 图形队列与显式同步的 API 语义:Vulkan Specification(访问于 2026-07-30)。
本文不承诺某个浏览器、手机或操作系统的线程名、buffer 数和驱动实现。它们随硬件、内核、后端和版本变化;跨平台稳定的是隔离、异步队列、资源所有权、同步与最终 presentation 这几个问题。