从零写OS内核 | 显示器——从Framebuffer到GPU
你有没有想过:一个像素是怎么被点亮在屏幕上的? 明明 CPU 只发送了几个字节的数据,显示器却能渲染出完整的 Windows 桌面或游戏画面。这中间的链路——Framebuffer、DRM/KMS、GPU 指令——比你想象的深得多。
本文从显示的基本原理出发,拆解 Framebuffer、显示时序、DRM/KMS 架构,最后给出 Linux 下可复制的调试命令。
一、Framebuffer:最底层的显示内存
Framebuffer 是显示硬件中最简单的抽象:一段内存区域,每个像素对应一个或多个字节,CPU 写内存即写屏幕。
像素坐标 (0,0) 像素坐标 (1,0) 像素坐标 (2,0)
┌──────────┐ ┌──────────┐ ┌──────────┐
│ R G B │ │ R G B │ │ R G B │
│08 16 24b │ │08 16 24b │ │08 16 24b │
└──────────┘ └──────────┘ └──────────┘
←─── 水平扫描线 (scanline) ───→
分辨率 1920×1080 @ 60Hz
→ 每行 1920 个像素,每帧 1080 行
→ 每秒刷新 60 帧
→ 带宽:1920×1080×3×60 ≈ 373 MB/s (24bpp)Framebuffer 的内存布局(RGB888,24bpp):
0x0000: [B0][G0][R0][B1][G1][R1]...
←───── 一个像素 3 字节 ─────→在 Linux 中查看 Framebuffer:
$ cat /dev/fb0 | head -c 100 # 读 Framebuffer 内容(原始像素数据)
$ fbset -s # 查看当前显示模式
mode "1920x1080-60"
geometry 1920 1080 1920 1080 32
timings 162 240 80 30 3 104 0
accel true rgba 8/16,8/8,8/0,0/0
$ ls /dev/fb*
/dev/fb0 /dev/fb1二、显示时序:HSync、VSync 和 Pixel Clock
显示器不是直接显示像素的——它按照固定时序逐行扫描,这个时序由多个信号控制:
Horizontal Timing(一行):
───────────────────────────────────────────────────────
│ Front │ Sync │ Back │ Active Video │
│ Porch │ Pulse │ Porch │ (有效像素) │
───────────────────────────────────────────────────────
│ ← HFP → │← HSP →│← HBP → │←─────── 1920 ─────→│
Vertical Timing(一帧):
───────────────────────────────────────────────────────
│ VFP │ VSync │ VBP │ Active Video │ 总计 1080 行
───────────────────────────────────────────────────────时序参数解释:
HFP/HBP/HSP:水平前肩/后肩/同步脉冲VFP/VBP/VSP:垂直前肩/后肩/同步脉冲Pixel Clock:每个像素需要的时钟周期数
计算带宽:
Pixel Clock = 162 MHz(时序中的 162)
水平总像素 = 1920 + 240 + 80 + 104 = 2344
垂直总行数 = 1080 + 30 + 3 + 23 = 1136
刷新率 = 162000000 / (2344 × 1136) ≈ 60.0 Hz ✓三、DRM/KMS:Linux 最新的显示驱动框架
Linux 2.6+ 引入了 DRM(Direct Rendering Manager) 和 KMS(Kernel Mode Setting),取代了旧的 fbdev(Framebuffer)驱动框架。
DRM 的核心对象:
┌──────────────────────────────────────────────────────┐
│ DRM Device (/dev/dri/card0) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CRTC │ │ Encoder │ │ Connector│ │
│ │ (扫描控制)│──→│(信号转换) │──→│(物理接口) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ └──→│ Plane │──→│ Frame- │◄───┘ │
│ │ │ (叠加层) │ │ buffer │ │
│ │ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Gem │ │ Fence │ │
│ │(内存对象) │ │(同步对象)│ │
│ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────┘DRM 的对象链路(以连接 HDMI 显示器为例):
- CRTC:扫描控制器,读取 framebuffer,按时序输出像素到 Encoder
- Encoder:把 CRTC 信号转换成 HDMI/DP/DSI 等协议
- Connector:物理接口(HDMI 座子),检测显示器插拔
- Plane:叠加层(如光标、OSD、背景)
Linux 查看 DRM 信息:
$ cat /sys/class/drm/card0/status
connected
$ cat /sys/class/drm/card0-HDMI-A-1/status
connected
$ cat /sys/class/drm/card0-HDMI-A-1/edid | parse-edid
# 读显示器的 EDID(分辨率、能力)
$ ls /sys/class/drm/
card0/ card0-HDMI-A-1/ card0-HDMI-A-2/ renderD128/四、GPU 架构:CUDA Core、Shader 和 Render Engine
现代 GPU 是一个高度并行的计算阵列,它和 CPU 的架构完全不同:
CPU 架构 GPU 架构
┌───────────────┐ ┌──────────────────────┐
│ 少数大核心 │ │ 数千个小计算单元 │
│ (4-16 cores) │ │ (CUDA/Stream Proc) │
│ 深流水线 │ │ 浅流水线 │
│ 低延迟 │ │ 高吞吐 │
│ 通用计算 │ │ SIMT(单指令多线程) │
└───────────────┘ └──────────────────────┘
适用:串行逻辑 适用:数据并行(像素/向量)GPU 的渲染管线:
顶点输入 → 顶点着色器 → 图元组装 → 光栅化 →
片段着色器 → 深度测试 → 混合 → 帧缓冲区
│
└→ Render Engine(渲染引擎)负责执行Linux 查看 GPU 信息:
$ lspci | grep -i vga
00:02.0 VGA compatible controller: Intel Corporation UHD Graphics 620
$ cat /sys/class/drm/card0/device/uevent
DRIVER=i915
PCI_SLOT_NAME=0000:00:02.0
$ dmesg | grep -i "i915|drm|gpu"
[ 2.345] i915 0000:00:02.0: Device: Intel Corporation UHD Graphics 620
[ 2.346] i915 0000:00:02.0: GuC firmware version: 55.0五、wandos 的显示子系统
wandos 目前在 arch/x86/core/display.cpp 实现了简单的 VGA 文本模式和 VBE(VESA BIOS Extensions)图形模式:
// wandos display.cpp(简化)
void set_vesa_mode(uint16_t width, uint16_t height, uint8_t bpp) {
// 调用 VBE 中断 0x10,AX=0x4F02
// 设置线性帧缓冲区模式
}
void draw_pixel(uint32_t x, uint32_t y, uint32_t color) {
uint32_t *fb = (uint32_t *)framebuffer_base;
fb[y * screen_width + x] = color;
}wandos 尚未实现:
- DRM/KMS 子系统
- GPU 加速(GEM/TTM 内存管理)
- 多显示器热插拔(Connector 检测)
- 图形桌面合成器(Wanda compositor)
公众号 footer
相关阅读:
- 《从零写OS内核 | 总线与地址空间——CPU 是怎么找到每个设备的》(343)
- 《从零写OS内核 | 晶体管基础——从 MOSFET 到 CPU》(342)
动手环节:
运行 cat /sys/class/drm/card*/status,看你的显示器有没有被正确识别。如果有独显,运行 lspci | grep VGA 看是什么型号。
仓库地址:
github.com/golang12306/os-kernel-from-scratch
关注公众号「AI不着急」,回复”资料”获取内核学习路线图。
评论