Skip to content

05_屏幕显示与显卡底层操作

屏幕显示本质上是对显存(Video RAM)的读写操作。

一、 硬件基础:POST 与显存映射

在电脑启动时,固化在 ROM 中的 POST (Power On Self Test, 上电自检) 代码会被执行。POST 的一项核心工作,就是为显卡内存和系统主存建立映射关系 (Mapping)。

CPU 无法直接向显示器发送像素,它只能向特定的内存地址写入数据。硬件会将这些特定地址映射到显卡上的物理显存。

  • (1) 文本方式下:主存空间 B800:0 ~ B800:7FFF (物理地址 0xB8000~0xBFFFF) 与显卡内存建立映射。

  • (2) 图形方式下:主存空间 A000:0 ~ A000:FFFF (物理地址 0xA0000~0xAFFFF) 与显卡内存建立映射。


二、 16位实模式:文本模式下的显卡直接操作

在 DOS 实模式下,标准彩色文本模式的分辨率为 80 列 × 25 行。屏幕上的每一个字符占用显存中的 2 个连续字节。

1. 显存的双字节结构与属性字节

偶数地址存放字符的 ASCII 码,奇数地址存放该字符的属性字节 (Attribute Byte)。

属性字节(共 8 位)精细控制了字符的颜色和状态,按高位到低位分为背景和前景:

  • 背景属性 (高 4 位,Bit 7~4):

    • Bit 7:闪烁 (Blink) 或 高亮度 (Intensity)。

    • Bit 6~4:背景色的 RGB (红绿蓝) 通道。

  • 前景属性 (低 4 位,Bit 3~0):

    • Bit 3:字符亮度 (Intensity)。

    • Bit 2~0:字符色的 RGB (红绿蓝) 通道。

颜色二进制组合示例:

  • 010 (二进制) = 2 = 绿色。

  • 100 (二进制) = 4 = 红色。

2. 屏幕坐标与内存偏移地址的换算公式

屏幕左上角坐标为 (0, 0),X 轴水平向右,Y 轴垂直向下。

要在屏幕的第 y 行、第 x 列显示字符,其对应的内存偏移量 bx 计算公式为:

bx = (y * 80 + x) * 2

(注:乘以 80 是因为每行有 80 个字符;乘以 2 是因为每个字符占 2 个字节)。

3. 汇编代码实现

以下代码演示如何在坐标 (x, y) 处打印一个白底绿字的字符 'A':

mov ax, 0B800h      ; 文本模式显存段基址
mov ds, ax          ; 初始化数据段寄存器指向显存
mov bx, 0           ; 假设 x=0, y=0,则 bx = (0*80 + 0)*2 = 0

mov byte ptr ds:[bx], 'A'    ; 写入 ASCII 字符 'A' (偶数地址)
mov byte ptr ds:[bx+1], 72h  ; 写入属性字节 (奇数地址)。72h = 0111 0010b (白底绿字)

(注:72h 拆解:高四位 0111 代表白/浅灰背景,低四位 0010 代表绿色前景)。


三、 16位实模式:VGA 图形模式

在 DOS 实模式下,最经典的图形模式是 VGA Mode 13h。它抛弃了文本模式的字符矩阵限制,允许程序员精确控制屏幕上的每一个像素点。由于其内存映射的极致简洁性,它是早期 PC 游戏开发的黄金标准。

1. 核心机制与内存映射架构

  • 物理映射段基址:显存在图形模式下被主板固件(POST)映射到物理地址 0xA0000。在汇编中,对应的段寄存器(通常是 ES)必须初始化为 0xA000。

  • 分辨率与颜色深度:320 像素(宽) × 200 像素(高),支持同时显示 256 种颜色(8-bit 颜色深度)。

  • 完美匹配 64KB 限制:

    • 1 个字节 = 1 个像素。

    • 整个屏幕需要的内存总量:\(320 \times 200 = 64,000\) 字节。

    • 16 位架构下一个段的最大容量是 \(65,536\) 字节 (64KB)。

    • 架构优势:64,000 字节完美装入了一个 64KB 的段中(从 A000:0000 到 A000:FA00)。程序员不需要像高分辨率模式那样进行极其繁琐的“显存分页/换页(Bank Switching)”操作,只需改变偏移量即可遍历全屏。

2. 颜色机制:调色板索引 (Palette Index)

与文本模式下直接用位来代表 RGB 通道不同,Mode 13h 采用索引颜色机制。

  • 写入显存的 1 个字节(值范围 00h ~ FFh,即 0~255)并不是真实的 RGB 颜色值,而是 VGA 硬件调色板(DAC 寄存器)的索引号。

  • 例如:写入 0Fh(15)默认显示纯白色,写入 28h(40)默认显示标准红色。

3. 屏幕坐标与内存偏移地址的换算

屏幕左上角坐标为 (0, 0),右下角坐标为 (319, 199)。

要在屏幕坐标 (x, y) 处绘制一个像素,计算内存偏移量 DI(或 BX)的公式为:

Offset = y * 320 + x

底层性能优化技巧(乘法替换):

在早期的 8086/80286 CPU 上,MUL(乘法)指令极其极其缓慢。由于 320 不是 2 的幂次方,不能直接用 1 次移位完成。利用数学拆分:\(320 = 256 + 64\)。

\(y \times 320 = y \times 256 + y \times 64 = (y \ll 8) + (y \ll 6)\)

这样仅通过两条极快的 SHL (左移) 指令和一条 ADD 指令,就能瞬间算出 y * 320 的结果,极大地提升了屏幕渲染的帧率。

4. 汇编实践:初始化图形模式并画点

以下是一段完整的标准流程模板,展示如何进入图形模式、画一个红点,然后安全退出。

; 1. 进入 VGA 320x200 256色 图形模式 (Mode 13h)
mov ax, 0013h       ; AH=00h (设置视频模式), AL=13h (VGA 320x200 256色)
int 10h             ; 调用 BIOS 视频服务中断

; 2. 初始化显存段基址
mov ax, 0A000h      ; 图形模式显存基址
mov es, ax          ; 将 ES 寄存器指向显存

; 3. 计算目标坐标的偏移量 (假设画在中心点 x=160, y=100)
; 公式: Offset = 100 * 320 + 160 = 32160 = 7DA0h
mov di, 32160

; 4. 画点 (写入调色板索引)
mov byte ptr es:[di], 28h ; 28h (十进制 40) 在默认调色板中是红色

; 5. 暂停等待用户按键,防止程序直接结束导致画面一闪而过
mov ah, 00h         ; INT 16h 的 00h 功能:阻塞读取按键
int 16h

; 6. 恢复标准文本模式 (Mode 03h),否则 DOS 命令行会花屏
mov ax, 0003h       ; AH=00h, AL=03h (标准 80x25 彩色文本模式)
int 10h

; 7. 退出程序
mov ax, 4c00h
int 21h

四、 32位与64位保护模式:显卡操作的现代化演进

进入 32 位/64 位保护模式(Windows/Linux)后,实模式下的直接写显存技术彻底失效。

1. 权限隔离与硬件抽象 (Ring 3 限制)

  • 现代操作系统在分页机制和权限级别 (Ring 0~3) 的保护下,用户态程序 (Ring 3) 无权访问物理地址 0xB8000 或 0xA0000。

  • 任何尝试像 DOS 时代那样硬编码段基址并写入内存的操作,都会立刻触发操作系统的段错误 (Segmentation Fault / Access Violation)。

2. 现代控制台输出 (文字模式的替代)

现代汇编不再直接操纵字符属性字节,而是通过操作系统提供的 系统调用 (System Calls) 或 API 将字符串交给终端模拟器渲染:

  • Linux (64位):通过 SYSCALL 调用 sys_write (系统调用号 RAX=1),将数据输出到标准输出文件描述符 (RDI=1,即 stdout)。终端程序负责解析 ANSI 转义序列(如 \x1b[32m)来实现颜色控制。

  • Windows:调用 Win32 API 中的 WriteConsole 或 printf 包装函数。颜色控制通过调用 SetConsoleTextAttribute API 实现,该 API 在底层模拟了类似 DOS 的属性字节逻辑。

3. 现代图形渲染 (帧缓冲与 GPU 驱动)

如果要在现代操作系统中画图,汇编程序员有以下途径:

  • 内核帧缓冲 (Framebuffer):在 Linux 字符界面下,可以通过 open 打开 /dev/fb0 设备文件,并使用 mmap 将其映射到用户态的虚拟内存中。随后,像操作一个巨大的像素数组一样读写这块虚拟内存,由内核驱动同步到物理显存。

  • GPU 图形 API:现代显卡拥有独立的架构和数百兆/几 GB 的显存,不再通过古老的 A000 窗口映射。必须通过调用 OpenGL、DirectX 或 Vulkan 的动态链接库 (.dll/.so),将顶点数据和着色器 (Shader) 甚至 GPU 汇编指令打包提交给显卡驱动,由 GPU 硬件完成光栅化和像素输出。