我对计算机的兴趣与日俱增,这让我开始思考一些更深层次的问题,而这些问题似乎已经不再需要问了。据我所知,我们的计算机在启动时处于文本模式,其中可以使用软件中断显示字符0x10
什么时候AH=0x0e
我们都见过著名的启动字体,无论启动什么计算机,其外观总是一样的。
那么,计算机究竟如何在最低级别(比如说,在操作系统之下)输出图形?而且,图形肯定不是使用软件中断一次输出一个像素的,因为听起来很慢?
是否有定义顶点、多边形、字体等基本输出的标准(例如,OpenGL 下,OpenGL 可能会使用哪些)?让我不禁要问,为什么操作系统在没有安装官方驱动程序的情况下通常可以正常运行;他们是如何做到的?
如果我的假设不正确,请见谅。如果能详细阐述这些主题,我将不胜感激!
答案1
这(部分)是 BIOS 的作用。
尽管实际计算机之间存在这样的差异,但计算机的基本输入输出系统负责为操作系统提供通用接口。
话虽如此,具体到图形,有多种方式在屏幕上绘制。你可以向 BIOS 发送 TTY 命令,但这仅限于实模式。如果你想在保护模式下绘制任何东西,你需要使用 VGA 来绘制东西。我无法比 OSDev 更好地解释它,所以看看点击此处了解更多信息——但基本上,您可以从地址开始写入内存(视频内存是内存映射的)0xB8000
以在屏幕上绘制东西。
如果您需要比 VGA 更高的分辨率,则需要使用 VESA BIOS 扩展;我不熟悉它,但请尝试查看 GRUB 源代码以获取更多信息。
一些有用的参考资料:
-
grub-core/video/i386/pc/vbe.c grub-core/video/i386/pc/vga.c
如果您碰巧熟悉 D——不久前我编写了一个小型引导加载程序,它能够写入屏幕(仅限文本)。如果您感兴趣,以下是代码:
align(2) struct Cell { char ch; ubyte flags = 0x07; }
@property Cell[] vram()
{ return (cast(Cell*)0xB8000)[0 .. CONSOLE_WIDTH * CONSOLE_HEIGHT]; }
void putc(char c)
{
if (isBochs) { _outp(0xE9, c); } // Output to the Bochs terminal!
bool isNewline = c == '\n';
while (cursorPos + (isNewline ? 0 : 1) > vram.length)
{
for (short column = CONSOLE_WIDTH - 1; column >= 0; column--)
{
foreach (row; 0 .. CONSOLE_HEIGHT - 1)
{
uint cell = column + cast(uint)row * CONSOLE_WIDTH;
vram[cell] = vram[cell + CONSOLE_WIDTH];
}
vram[column + (CONSOLE_HEIGHT - 1) * CONSOLE_WIDTH].ch = ' ';
}
cursorPos = cast(ushort)(cursorPos - CONSOLE_WIDTH);
}
if (isNewline)
cursorPos = cast(ushort)
((1 + cursorPos / CONSOLE_WIDTH) * CONSOLE_WIDTH);
else vram[cursorPos++].ch = c;
}
void putc(char c, ubyte attrib) { vram[cursorPos] = Cell(c, attrib); }
void memdump(void* pMem, size_t length)
{
foreach (i; 0 .. length)
putc((cast(char*)pMem)[i]);
}
void clear(char clear_to = '\0', ubyte attrib = DEFAULT_ATTRIBUTES)
{
foreach (pos; 0 .. vram.length)
vram[pos] = Cell(clear_to, attrib);
cursorPos = 0;
}
@property ushort cursorPos()
{
ushort result = 0;
_outp(0x3D4, 14);
result += _inp(0x3D5) << 8;
_outp(0x3D4, 15);
result += _inp(0x3D5);
return result;
}
@property void cursorPos(ushort position)
{
_outp(0x3D4, 14);
_outp(0x3D5, (position >> 8) & 0xFF);
_outp(0x3D4, 15);
_outp(0x3D5, position & 0xFF);
}
答案2
从 IBM PC 及其克隆机的早期开始,显示适配器硬件就非常简单:一小块内存专用于字符单元网格(标准模式下为 80x25 个字符),每个单元占用两个字节的内存。一个字节选择字符,另一个字节选择其“属性” - 彩色适配器的前景色和背景色以及闪烁控制;单色适配器的粗体、下划线、闪烁或反向视频。视频输出硬件根据字符内存的内容从字符形状的 ROM 表中查找像素。
为了提供一定程度的硬件独立性,BIOS 字符映射接口需要执行软件中断才能在屏幕上设置单个字符单元。这很慢而且效率低下。但是,字符内存也可以由 CPU 直接寻址,因此如果您知道存在什么硬件,则可以直接写入内存。无论哪种方式,一旦设置,字符就会一直显示在屏幕上,直到更改为止,并且您需要使用的总字符内存为 4000 字节 - 大约相当于当今单个 32x32 全彩纹理的大小!
在图形模式中,情况类似;屏幕上的每个像素都与内存中的特定位置相关联,并且有一个 BIOS 设置像素接口,但高性能工作需要直接写入内存。后来的标准(如 VESA)允许系统执行一些基于 BIOS 的慢速查询来了解硬件的内存布局,然后直接使用内存。这就是操作系统无需专门的驱动程序即可显示图形的方式,尽管现代操作系统也包含每个主要 GPU 制造商硬件的基本驱动程序。即使是最新的 NVidia 卡也将支持几种不同的向后兼容模式,可能一直追溯到 IBM CGA。
3D 图形与 2D 图形之间的一个重要区别是,在 2D 中,您通常不需要每帧重新绘制整个屏幕。在 3D 中,如果相机移动哪怕一点点,屏幕上的每个像素都可能发生变化;在 2D 中,如果您不滚动,则屏幕的大部分将逐帧保持不变,即使您正在滚动,您通常也可以进行快速的内存到内存复制,而不是重新组合整个场景。因此,这与每帧必须为每个像素执行 INT 10h 完全不同。
来源:我真的老了
答案3
在系统启动过程中BIOS查找视频适配器。具体来说,它会查找视频适配器的内置 BIOS 程序并运行它。此 BIOS 通常位于内存中的 C000h 位置。系统 BIOS 执行视频 BIOS,初始化视频适配器。
在没有操作系统或驱动程序的情况下,BIOS 可以本地显示哪些级别或模式的视频/图形,主要取决于视频 BIOS 本身。
来源/更多信息在这里- “系统启动顺序”
答案4
据我所知,我们的计算机在启动时处于文本模式,当 AH=0x0e 时,可以使用软件中断 0x10 显示字符
你谈论的是传统 BIOS 功能。实际上你根本不需要使用这类功能。你可以直接将它们写入视频内存。
计算机究竟如何在最低级别(例如,在操作系统之下)输出图形?
这与操作系统的运行方式密切相关。无论如何,硬件层面的运行方式是相同的:视频卡有一个视频 RAM,用于存储(简化)下一个要在屏幕上绘制的图像。你可以认为每个地址都是一个代表像素的字节(实际上,通常每个像素需要多个字节)。然后视频控制器负责将其转换为显示器可以理解的信号。
是否有一个标准来定义顶点、多边形、字体等的基本输出(例如,OpenGL 下面,OpenGL 可能会使用)?
据我所知,没有。没有关于逻辑图形表示的标准。
我不禁要问,为什么没有安装官方驱动程序,操作系统通常也可以正常运行;他们是怎么做到的?
因为它的驱动程序已经与操作系统捆绑在一起。