自制工业控制系统:Intel集成显卡实战——从GOP帧缓冲接管显示输出
Contents
起因
工控机上电,操作工看到的第一样东西是画面。这篇记录的就是显示输出怎么接手。接手用的 GOP 有来历:UEFI 接 BIOS 班时,把实模式 INT 10h/VBE 那套点亮屏幕的调用换成了结构体加函数指针的图形输出协议,帧缓冲地址和模式信息从它手里直接拿。对面的 Intel 核显同样有故事——2010 年前后(Arrandale/Clarkdale 起)显示单元并进了 CPU 封装,从此在 Intel 平台长期占着 bus0:dev2:func0 的固定座位。固件用 GOP 把屏幕点亮、谈好模式,把帧缓冲的物理地址交出来;固件退场之后,显示引擎的 pipe/plane/PLL 配置原样留着继续扫描,Intel 核显在我们这儿就是一个已经点亮的扫描输出引擎,配置一行不用碰。剩下的活看着只有一件——往那块内存里写像素——但真写稳了才知道,像素格式、行距、这块内存的来路、为什么慢、怎么变快,一个都绕不过去。
一、GOP 交出来的到底是什么
locate_protocol(gEfiGraphicsOutputProtocolGuid) 拿到 GOP,有用的东西全在 Mode 指针里,逐字段过一遍:
| 字段 | 含义 | 实战备注 |
|---|---|---|
| FrameBufferBase | 帧缓冲物理基址 | Intel 核显上通常指向偷取内存或 GTT 映射窗口,见第三节 |
| FrameBufferSize | 字节总数 | 校验用:应 ≈ H × pitch × 4 |
| Info->HorizontalResolution / VerticalResolution | 分辨率 | 工控屏直接用面板原生模式 |
| Info->PixelsPerScanLine | 行距(像素单位) | **不是宽度!**对齐 padding 在每行末尾 |
| Info->PixelFormat | 像素格式 | 四种,见下 |
PixelFormat 的四种处理:
0PixelRedGreenBlueReserved8BitPerColor:内存字节序 R,G,R,x1PixelBlueGreenRedReserved8BitPerColor:内存字节序 B,G,R,x——Intel 核显几乎总是它,QEMU 也是。小端机直接写数值0xAARRGGBB,落到内存正好是 B,G,R,x,一个字节都不用换2PixelBitMask:给 R/G/B 各自的位掩码,按掩码移位拼色3PixelBltOnly:没有线性帧缓冲,只能调 GOP 的 Blt 函数——而 ExitBootServices 之后 Blt 就随固件一起没了。真硬件上罕见(个别虚拟化/特殊驱动才有),检测到就直接拒绝启动、串口报原因,别硬撑着写一个永远画不出的屏
模式选择不用纠结:遍历 max_mode 逐个 QueryMode,找到与面板原生分辨率一致的那个(通常 mode 0 就是)。工控单屏固定模式,选完写死,没有 DPI、没有多屏、没有热插拔。
二、像素与上屏:第一批代码
格式做成一个枚举,所有颜色先 pack 再落地,格式差异被关在这一处:
|
|
pitch 的坑值得单独一节:不少平台 pixels_per_scan_line > horizontal_resolution(扫描硬件要求行首对齐,padding 补在行尾)。按 width 算地址的症状极具迷惑性——屏幕整体"斜"掉、每行往左错开 (pitch−width) 个像素的累积量,第一次见会以为是分辨率设错。纪律只有一条:真帧缓冲按 pitch 步进,后备缓冲按 width 步进,两套行距永远别混用。
上屏因此必须逐行拷,不能一整块 memcpy:
|
|
双缓冲的账:1080p@32bpp 一帧 1920×1080×4 ≈ 7.9MB。直接往真帧缓冲上画,扫描扫到一半的界面会被用户看见;画在 back 上整帧 present,画面有原子性。工控界面以静态+局部刷新为主,配合脏矩形(图形篇展开)只拷变化的行区间,一次状态数字刷新通常 < 64KB——8MB 全刷和 32KB 局刷之间是 250 倍的差距,这套设计的基本盘就有了。
三、这块显存在哪:PCI 视角与"内存映射里查不到"的偷取内存
接管不止是拿到地址,还得知道这块内存的性质,否则后面分配器会替你踩雷。
用 PCIe 枚举 找到核显:Intel 平台上固定在 bus 0 : dev 2 : func 0(vendor 0x8086,class 03)。读它的 BAR:
- BAR0 = GTTMMADR:寄存器窗口 + GTT(图形地址翻译表)所在
- BAR2 = GMADR:GTT 映射窗口,CPU 通过它访问被图形驱动重映射的内存
GOP 给的 FrameBufferBase 在 Intel 核显上通常落在两处之一:stolen memory(固件开机时从物理内存顶部"偷"走一块给显示用,DSM/ISM),或 BAR2 窗口内的 GTT 映射地址。不用去猜是哪种——把 FrameBufferBase 跟两个 BAR 的区间比对一下就知道,这个核对同时也是防呆:万一某天换了独显(BAR 里分配显存),代码路径不用改。
真正要记住的事实是:stolen memory 不会出现在 UEFI 内存映射的可用区里——它不是 conventional memory,有的固件标成保留,有的干脆不提。这就是内存管理器篇坚持"GOP 区间显式排除"的原因:不排除,页帧分配器哪天把这段"没人认领"的内存分给内核堆,屏幕花掉的同时堆数据也废了,两头一起完蛋。
这块内存是真实 RAM(核显共享系统内存),可以回读:开机对前 4KB 做 memset+memcmp 自检,结果进自检报告。它是设备侧路径,默认缓存属性是 UC——慢的根源在下一节。
四、UC 太慢:MTRR 把帧缓冲改成写合并
先把现象摆出来:默认 UC(Uncacheable,强一致)属性下,CPU 对帧缓冲的每一笔写都直捅内存总线,没有缓存吸收、没有写合并。1080p 全屏填充在 UC 下是几十毫秒量级的事——图形界面稍微一动就有肉眼可见的拖影,present 变成主循环里最重的一步。
解法:把帧缓冲的物理区间设成 WC(Write Combining,写合并)——写操作先攒在合并缓冲里,成行地突发到内存。改法是 MTRR(Memory Type Range Registers),一组 MSR:
| MSR | 名称 | 用途 |
|---|---|---|
| 0xFE | IA32_MTRRCAP | bits 7:0 = 可用变量区间数(典型 8~10 对,够用但别浪费) |
| 0x200 / 0x201 | IA32_MTRR_PHYSBASE0 / PHYSMASK0 | 第一对变量区间,之后每对 +2 |
| 0x3FF | IA32_MTRR_DEF_TYPE | bit11 固定区间使能 / bit12 MTRR 总使能 / bits 10:8 默认类型 |
|
|
四个坑,每个都值一次烧机:
- 对齐:size 必须 2 的幂、base 按 size 对齐。帧缓冲通常自带 MB 级对齐(stolen memory 整块划的),拿
size.next_power_of_two()往上取;若扩出来的区间侵入了常规内存,宁可拆两段小幂拼,也别让 WC 盖到内核数据——WC 盖住普通内存的后果是读乱序,比慢可怕得多 - 双核一致性:MTRR 是每核一份 MSR。SMP 唤醒之后两个核都要设相同值(多核篇的 rendezvous 会合点正好干这个)。两核 MTRR 不一致的症状是"一核画得快一核画得慢还偶发花屏",极难查
- 顺序:先读 MTRRCAP 确认有空闲对,写 PHYSBASE/PHYSMASK,最后才动 DEF_TYPE 的使能位;DEF_TYPE 要读-改-写,别的位别碰
- WC 的可见性:写合并缓冲里的数据在读回、上锁、串行化指令前不一定落地——读自己刚写的像素前先
sfence,否则可能读到旧值
改造收益的量级:UC→WC 后全屏填充从几十 ms 掉到几 ms,差一个数量级起步。别信这里的数字,跑通后自己测一遍填色耗时记进日志当基线——这是后面所有图形优化的参照系。
五、撕裂怎么办
GOP 没有 flip/vsync 接口,present 的拷贝和扫描周期不同步:拷贝进行中恰好扫过那段,剧烈动画会有横向撕裂。双缓冲已经把窗口缩小到"上屏拷贝期间",而工控界面以静态显示和低频局部刷新为主,拷贝窗口里通常本来就没有正在变化的内容,撕裂无从谈起;哪天真要做剧烈动画了,再回头补 vsync(自己编程 Intel 显示引擎做页面翻转),基础篇不背这个包袱。
六、GOP 之后干不了的事
- 换分辨率/换模式:ExitBootServices 之后 GOP 随固件消失。要换模式等于自己编程 Intel 的 PLL/pipe/port——EDID 篇讲了怎么读显示器的能力,真正的模式编程是自研显示线路的活,另文再说
- 热插拔检测:DisplayPort 的 HPD 中断要自己接显示引擎的寄存器,GOP 时代不归我们管
- 硬件加速 2D:Intel 有 legacy 2D BLT 引擎(XY_SRC_COPY_BLT 那套命令流),但喂它要建命令流、管 GTT,复杂度换来的收益对工控 UI 是负的——CPU + WC + 脏矩形已经富余,这条路明确不走
- 多屏:专用系统单屏,不做
总结
实战清单:GOP Mode 五元组逐字段核对 + 四种 PixelFormat 处理(BltOnly 拒启);PixelFmt::pack 把格式差异关进一处;pitch 纪律 + 双缓冲 + 逐行 present;PCI 视角核对 BAR/stolen memory、分配器显式排除、回读自检;MTRR 四步(CAP 查空位 → PHYSBASE/PHYSMASK → DEF_TYPE 最后动 → 双核同设 + sfence 纪律)。跑通标志:全屏色条 + 四边走线不斜屏;帧缓冲回读自检通过;WC 前后的填色耗时差一个数量级并记录在案;分配器全量扫描确认帧缓冲区间从未被分出去。
下一篇在这块画布上建图形系统:渲染原语、Unicode 点阵字体、控件和事件分发。
参考链接
- OSDev Wiki: GOP——ModeInfo 字段与模式查询
- Intel SDM Vol.3 —— MTRR 架构(系统编程手册内存类型章节)
- 本系列:UEFI固件层实战(GOP 从哪来)、PCIe 枚举实战(BAR 视角)、内存管理器实战(显式排除)、CPU信息与多核实战(双核同设 MTRR)、显示子系统深入(EDID 与模式边界)
Author 软件开发大郭
LastMod 2026-09-11