Skip to content

第一百四十八章:帧缓冲

合道期

涉及内核源码:

林小源穿过存储区域,眼前的景象豁然开朗——一面巨大的光幕矗立在面前,光幕上闪烁着密密麻麻的彩色光点。每个光点都是一个像素,整面光幕由 1920 乘以 1080 个光点组成。

光幕底部站着一个年轻人,手里拿着一根细长的画笔,正一笔一笔地往光幕上涂抹颜色。他的动作很慢——每画一个光点,就要计算它的内存偏移地址,然后小心翼翼地点上去。

"你在做什么?"林小源问。

年轻人头也不抬:"写像素。我是 framebuffer——最原始的显示驱动。用户空间的程序打开 /dev/fb0,mmap 映射显存,然后就能直接往光幕上写颜色了。"

他画完一个红色的光点,退后一步看了看:"简单吧?buf[y * line_length + x * 4] = color。一行 7680 字节,1080 行,总共 8MB 显存。全部映射到内存地址空间里。"

林小源看着那面光幕,注意到年轻人的画笔没有任何特效——没有硬件加速,没有图层叠加,没有抗锯齿。每一笔都是最原始的像素写入。

"这也太慢了吧?"林小源皱眉,"如果要画一个复杂的 3D 场景呢?"

framebuffer 年轻人苦笑着放下画笔:"那你就得自己算每一个像素的颜色。我没有 GPU 加速的能力——我只是把显存暴露出来,让你直接写。简单,但代价就是没有硬件帮忙。"

林小源忽然意识到,显示竟然也是一种设备——跟键盘、磁盘一样,都有自己的驱动。


c
/*
 * framebuffer 的概念:
 *
 *   显存 — 存储像素数据的内存
 *   帧缓冲 — 显存的抽象
 *   /dev/fb0 — framebuffer 设备
 *
 * framebuffer 的操作:
 *   open("/dev/fb0", O_RDWR)
 *   mmap — 映射显存
 *   直接写入像素数据
 *
 * framebuffer 的信息:
 *   分辨率: 1920x1080
 *   色深: 32 位
 *   行长度: 1920 * 4 = 7680 字节
 *
 * DRM (Direct Rendering Manager):
 *   现代的显示框架
 *   替代 framebuffer
 *   支持 GPU 加速
 */

printf("=== framebuffer — 显示设备的抽象 ===\n\n");

printf("framebuffer 的概念:\n");
printf("  显存 — 存储像素数据的内存\n");
printf("  帧缓冲 — 显存的抽象\n");
printf("  /dev/fb0 — framebuffer 设备\n\n");

printf("--- 使用 framebuffer ---\n");
printf("1. 打开设备:\n");
printf("   int fd = open(\"/dev/fb0\", O_RDWR);\n\n");
printf("2. 获取信息:\n");
printf("   ioctl(fd, FBIOGET_VSCREENINFO, &var);\n");
printf("   分辨率: %dx%d\n", 1920, 1080);
printf("   色深: 32 位\n\n");
printf("3. 映射显存:\n");
printf("   buf = mmap(NULL, size, PROT_WRITE,\n");
printf("              MAP_SHARED, fd, 0);\n\n");
printf("4. 写入像素:\n");
printf("   buf[y * line_length + x * 4] = color;\n\n");

printf("--- framebuffer 的信息 ---\n");
printf("分辨率: 1920x1080\n");
printf("色深: 32 位\n");
printf("行长度: 7680 字节\n");
printf("显存大小: 7680 * 1080 = 8MB\n\n");

printf("--- DRM ---\n");
printf("现代的显示框架:\n");
printf("  替代 framebuffer\n");
printf("  支持 GPU 加速\n");
printf("  支持多显示器\n");
printf("  /dev/dri/card0\n\n");

printf("--- 终端与 framebuffer ---\n");
printf("Linux 控制台:\n");
printf("  framebuffer 绘制文本\n");
printf("  字符映射到像素\n");
printf("  fbcon 驱动\n");

林小源在光幕旁坐下,仔细观察 framebuffer 的工作方式。他注意到,当你 mmap 映射了显存之后,写入操作和写普通内存没有任何区别——CPU 直接往那段地址写数据,数据就出现在光幕上。

"这就是直接映射的威力,"framebuffer 说,"没有中间层,没有缓冲区拷贝。你写什么,屏幕上就显示什么。"

林小源伸手试了试。他在脑海中想象一个蓝色的光点,然后在地址 y * 7680 + x * 4 的位置写入了一个颜色值。光幕上立刻亮起了一个蓝色的点。

"感觉就像在一张巨大的画布上直接画画,"林小源说。

"对,但问题也在这里。"framebuffer 指了指光幕的角落,"如果你要移动一个窗口,你得自己算出所有像素的新位置,然后一个个写过去。没有 GPU 帮你做 BitBLT,没有硬件图层——全靠 CPU 硬算。"

林小源试了试移动那个蓝色光点。他需要先把原来位置的颜色清除,再在新位置写入。如果是一个 100x100 的窗口,那就是一万次像素操作。他感到一阵疲惫。

"简单是有代价的,"framebuffer 说,"我的设计哲学就是:能用就行。嵌入式设备、系统启动阶段、简单显示需求——这些场景不需要 GPU 加速,我够用了。"

他算是明白了,简单从来不是免费的——省掉了复杂性,也就省掉了性能。

林小源告别 framebuffer,沿着光幕边缘走了很远,最终来到一座宏伟的建筑前。建筑的正门上挂着一块巨大的牌匾:"DRM——Direct Rendering Manager"。

建筑内部灯火通明,到处是并行工作的流水线。一个穿着黑色铠甲的年轻人站在大厅中央,他的铠甲上嵌满了 GPU 核心,每个核心都在独立处理着不同的渲染任务。

"我是 DRM。"年轻人的声音带着金属质感,"framebuffer 太慢了——他只有一面光幕和一支画笔。我有 GPU,有多显示器支持,有视频解码加速,有图层合成。"

他随手一挥,面前出现了三个光幕——每个光幕上显示着不同的内容,GPU 核心在后台并行渲染,画面流畅得像是在流动。

"framebuffer 的 /dev/fb0 已经是上个时代的东西了,"DRM 说,"现代 Linux 用 /dev/dri/card0。GEM 管理显存分配,KMS 管理显示输出,PRIME 做 GPU 间数据传输——这些都是 framebuffer 做不到的。"

林小源回头望了一眼远处那面朴素的光幕和那个一笔一笔画像素的年轻人。framebuffer 依然在那里安静地工作着,速度虽慢,但稳定可靠。

"他会被淘汰吗?"林小源问。

DRM 沉默了一会儿:"嵌入式设备还在用他。系统启动早期、内核 panic 的蓝屏、简单的文本终端——这些场景不需要我的复杂性。简单的东西,往往最可靠。"

DRM 的功能确实丰富得多,但 framebuffer 的朴素也有它存在的道理。


道藏笔记

内核启示

framebuffer 是最朴素的显示方案——把显存映射到内存,程序直接往里写像素,屏幕上就亮了。通过 /dev/fb0 打开设备,mmap 映射显存,然后 buf[y * line_length + x * 4] = color 就能画一个点。没有 GPU 加速、没有图层合成、没有抗锯齿,全靠 CPU 硬算。但正因为简单,它在嵌入式设备、系统启动早期、内核 panic 蓝屏这些场景里反而最可靠。现代 Linux 用 DRM 替代了 framebuffer,支持 GPU 加速、多显示器、视频解码——功能强大得多,但也复杂得多。简单和复杂各有各的用武之地。


破关试炼

帧缓冲之试

本章把屏幕像素当作可写内存区域来操作,这种显示抽象叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道