从零写OS内核 | 显示器——从Framebuffer到GPU

你有没有想过:一个像素是怎么被点亮在屏幕上的? 明明 CPU 只发送了几个字节的数据,显示器却能渲染出完整的 Windows 桌面或游戏画面。这中间的链路——Framebuffer、DRM/KMS、GPU 指令——比你想象的深得多。

本文从显示的基本原理出发,拆解 Framebuffer、显示时序、DRM/KMS 架构,最后给出 Linux 下可复制的调试命令。


一、Framebuffer:最底层的显示内存

Framebuffer 是显示硬件中最简单的抽象:一段内存区域,每个像素对应一个或多个字节,CPU 写内存即写屏幕

Bash
  像素坐标 (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×60373 MB/s (24bpp)

Framebuffer 的内存布局(RGB888,24bpp):

Bash
0x0000: [B0][G0][R0][B1][G1][R1]...
       ←───── 一个像素 3 字节 ─────→

在 Linux 中查看 Framebuffer:

Bash
$ 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

显示器不是直接显示像素的——它按照固定时序逐行扫描,这个时序由多个信号控制:

Bash
  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:每个像素需要的时钟周期数

计算带宽:

Bash
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 的核心对象:

Bash
  ┌──────────────────────────────────────────────────────┐
  │                    DRM Device (/dev/dri/card0)         │
  │                                                       │
  │  ┌──────────┐   ┌──────────┐   ┌──────────┐       │
  │  │  CRTC    │   │  Encoder  │   │ Connector│       │
  │  │ (扫描控制)│──→│(信号转换) │──→│(物理接口) │       │
  │  └──────────┘   └──────────┘   └──────────┘       │
  │        │                                   │          │
  │        │  ┌──────────┐   ┌──────────┐    │          │
  │        └──→│  Plane   │──→│  Frame-  │◄───┘          │
  │        │   │ (叠加层) │   │  buffer  │               │
  │        │   └──────────┘   └──────────┘               │
  │  ┌──────────┐   ┌──────────┐                        │
  │  │  Gem     │   │   Fence  │                        │
  │  │(内存对象) │   │(同步对象)│                        │
  │  └──────────┘   └──────────┘                        │
  └──────────────────────────────────────────────────────┘

DRM 的对象链路(以连接 HDMI 显示器为例):

  1. CRTC:扫描控制器,读取 framebuffer,按时序输出像素到 Encoder
  2. Encoder:把 CRTC 信号转换成 HDMI/DP/DSI 等协议
  3. Connector:物理接口(HDMI 座子),检测显示器插拔
  4. Plane:叠加层(如光标、OSD、背景)

Linux 查看 DRM 信息:

Bash
$ 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 的架构完全不同:

Bash
  CPU 架构                          GPU 架构
  ┌───────────────┐                ┌──────────────────────┐
  │  少数大核心    │                │  数千个小计算单元     │
  │  (4-16 cores) │                │  (CUDA/Stream Proc)  │
  │  深流水线     │                │  浅流水线            │
  │  低延迟      │                │  高吞吐              │
  │  通用计算    │                │  SIMT(单指令多线程) │
  └───────────────┘                └──────────────────────┘

  适用:串行逻辑                    适用:数据并行(像素/向量)

GPU 的渲染管线:

Bash
  顶点输入 → 顶点着色器 → 图元组装 → 光栅化 → 
  片段着色器 → 深度测试 → 混合 → 帧缓冲区
       │
       └→ Render Engine(渲染引擎)负责执行

Linux 查看 GPU 信息:

Bash
$ 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)图形模式:

Cpp
// 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不着急」,回复”资料”获取内核学习路线图。

最后修改: 2024年3月6日

作者

评论

发表评论

您的邮箱地址不会被公开。