我怎样才能直接写到屏幕上?
Posted
技术标签:
【中文标题】我怎样才能直接写到屏幕上?【英文标题】:How can I write directly to the screen? 【发布时间】:2011-01-04 02:05:10 【问题描述】:我是一个对汇编语言非常感兴趣的青少年。我正在尝试用 Intel x86 汇编程序编写一个小型操作系统,我想知道如何直接写入屏幕,就像不依赖 Bios 或任何其他操作系统一样。我查看了 coreboot、Linux 和 Kolibri 等的源代码,希望能找到并理解执行此操作的一些代码。在这方面我还没有成功,虽然我相信我会再看一下 Linux 源代码,它是我搜索过的源中最容易理解的。
如果有人知道这一点,或者知道我可以查看某些源代码的哪个位置,如果他们告诉我,我将不胜感激。
或者更好的是,如果有人知道如何识别 Intel x86 CPU 上的哪个 I/O 端口连接到哪个硬件,那也将不胜感激。我需要问这个问题的原因是,在英特尔 64 和 IA-32 架构软件开发人员手册第 1 卷:基本架构中的输入/输出章节中,以及第 3 卷中的 IN 或 OUT 指令部分中,我能找到这些信息吗?而且因为在我拥有的资源中搜索相关说明太费力了。
【问题讨论】:
哇,谢谢大家的精彩回复。所提供的所有答案看起来都很有帮助。 user336462 :只是出于好奇:您对这一切有什么看法,差不多 10 年后? :) 【参考方案1】:第 1 部分
对于旧 VGA 模式,有一个固定地址可写入(旧版)显示内存区域。对于文本模式,该区域从 0x000B8000 开始。对于图形模式,它从 0x000A0000 开始。
对于高分辨率视频模式(例如,由 VESA/VBE 接口设置的模式),这不起作用,因为传统显示内存区域的大小限制为 64 KiB,并且大多数高分辨率视频模式需要更多空间(例如 1024 * 768 * 32-bpp = 2.25 MiB)。为了解决这个问题,VBE 支持两种不同的方法。
第一种方法称为“组切换”,在这种方法中,任何时候只有一部分显卡的显存映射到遗留区域(您可以更改映射的部分)。这可能会很麻烦——例如,要绘制一个像素,您可能需要计算该像素在哪个存储库中,然后切换到该存储库,然后计算存储库中的哪个偏移量。更糟糕的是,对于某些视频模式(例如,每个像素有 3 个字节的 24-bpp 视频模式),可能只有像素数据的第一部分在一个存储库中,而同一像素数据的第二部分在不同存储库中.这样做的主要好处是它适用于实模式寻址,因为传统显示内存区域低于 0x00100000。
第二种方法称为“Linear Framebuffer”(或简称为“LFB”),可以访问视频卡的整个显示内存区域,而无需任何混乱的银行切换。您必须询问 VESA/VBE 接口该区域在哪里(通常位于“PCI 孔”中,介于 0xC0000000 和 0xFFF00000 之间)。这意味着您无法在实模式下访问它,需要使用保护模式或长模式或“虚模式”。
要在使用 LFB 模式时找到像素的地址,您需要执行类似“pixel_address = display_memory_address + y * bytes_per_line + x * bytes_per_pixel”的操作。 “bytes_per_line”来自 VESA/VBE 接口(可能与“horizontal_resolution * bytes_per_pixel”不同,因为水平线之间可以有填充)。
对于“银行切换”VBE/VESA 模式,它变得更像:
pixel_offset = y * bytes_per_line + x * bytes_per_pixel;
bank_number = pixel_offset / bank_size;
pixel_starting_address_within_bank = pixel_offset % bank_size;
对于一些旧的 VGA 模式(例如 256 色“模式 0x13”),它与 LFB 非常相似,除了行之间没有填充,您可以执行“pixel_address = display_memory_address + (y * Horizontal_resolution + x) * bytes_per_pixel ”。对于文本模式,它基本上是相同的,除了 2 个字节确定每个字符及其属性 - 例如“char_address = display_memory_address + (y * Horizontal_resolution + x) * 2”。对于其他旧的 VGA 模式(单色/2 色、4 色和 16 色模式),视频卡的内存排列完全不同。它被分成“平面”,其中每个平面包含一位像素,并且(例如)要在 16 色模式下更新一个像素,您需要写入 4 个单独的平面。出于性能原因,VGA 硬件支持不同的写入模式和不同的读取模式,而且它可能会变得很复杂(过于复杂,无法在此处充分描述)。
第 2 部分
对于 I/O 端口(在 80x86 上,“PC 兼容”),有 3 个常规类别。第一个是使用固定 I/O 端口的“事实上的标准”遗留设备。这包括 PIC 芯片、ISA DMA 控制器、PS/2 控制器、PIT 芯片、串行/并行端口等。几乎所有描述如何对这些设备进行编程的内容都会告诉您设备使用哪些 I/O 端口。
下一个类别是传统/ISA 设备,其中设备使用的 I/O 端口由卡本身的跳线决定,并且没有理智的方法可以通过软件确定它们使用哪些 I/O 端口。为了解决这个问题,最终用户必须告诉操作系统每个设备使用哪些 I/O 端口。值得庆幸的是,这些硬壳的东西都已经过时了(尽管这并不一定意味着没有人在使用它)。
第三类是“即插即用”,其中有一些方法可以询问设备它使用哪些 I/O 端口(在大多数情况下,更改设备使用的 I/O 端口)。这方面的一个例子是 PCI,其中有一个“PCI 配置空间”,可以告诉您有关每个 PCI 设备的大量信息。对于这个类别,如果不在运行时进行,任何人都无法确定哪些设备将使用哪些 I/O 端口,并且更改某些 BIOS 设置可能会导致任何/所有这些设备更改 I/O 端口。
另请注意,英特尔 CPU 只是一个 CPU。没有什么可以阻止这些 CPU 用于与“PC 兼容”计算机完全不同的东西。英特尔的 CPU 手册绝不会告诉您任何有关 CPU 本身之外存在的硬件(包括芯片组或设备)的信息。
第三部分
http://osdev.org/(他们的 wiki 和论坛)可能是获取更多信息(面向操作系统开发人员/爱好者)的最佳去处。
【讨论】:
我要补充一点,除非您为操作系统构建 GUI 与 BIOS 配合使用,否则它将节省您花在任务切换和内存管理等“有趣”事情上的时间。附:我认为每个青少年程序员都应该编写一个操作系统;)即使它只是一个引导加载程序。你学到了很多 是的!我也是少年。 Av 已经进入了我的操作系统的 GUI 阶段。 @preciousbetine 如果您可以分享您的代码(无论您认为它有多糟糕),那将会很有趣。在 youtube 上观看了 Ben Eater 的很多关于构建计算机的视频后,这可能是 x86 上 ASM 的一个非常好的介绍...... @JohnHunt,谢谢!我很快就会考虑这样做。 @JohnHunt 你可以查看我的操作系统here。【参考方案2】:要直接写入屏幕,您可能应该写入 VGA 文本模式区域。这是一块内存,是文本模式的缓冲区。
文本模式屏幕由 80x25 个字符组成;每个字符为 16 位宽。如果设置了第一位,则字符将在屏幕上闪烁。接下来的 3 位详细说明背景颜色;第一个字节的最后 4 位是前景(或文本字符)的颜色。接下来的 8 位是字符的值。这通常是代码页 737 或 437,但它可能因系统而异。
Here 是详细介绍此缓冲区的 Wikipedia 页面,here 是到代码页 437 的链接
几乎所有的 BIOS 都会在系统启动之前将模式设置为文本模式,但有些笔记本电脑的 BIOS 不会启动到文本模式。如果您还没有进入文本模式,您可以使用int10h
非常简单地设置它:
xor ah, ah
mov al, 0x03
int 0x10
(上面的代码使用了 BIOS 中断,所以它必须在 Real Mode 下运行。我建议把它放在你的引导扇区中。)
最后,这是我为在保护模式下编写字符串而编写的一组例程。
unsigned int terminalX;
unsigned int terminalY;
uint8_t terminalColor;
volatile uint16_t *terminalBuffer;
unsigned int strlen(const char* str)
int len;
int i = 0;
while(str[i] != '\0')
len++;
i++;
return len;
void initTerminal()
terminalColor = 0x07;
terminalBuffer = (uint16_t *)0xB8000;
terminalX = 0;
terminalY = 0;
for(int y = 0; y < 25; y++)
for(int x = 0; x < 80; x++)
terminalBuffer[y * 80 + x] = (uint16_t)terminalColor << 8 | ' ';
void setTerminalColor(uint8_t color)
terminalColor = color;
void putCharAt(int x, int y, char c)
unsigned int index = y * 80 + x;
if(c == '\r')
terminalX = 0;
else if(c == '\n')
terminalX = 0;
terminalY++;
else if(c == '\t')
terminalX = (terminalX + 8) & ~(7);
else
terminalBuffer[index] = (uint16_t)terminalColor << 8 | c;
terminalX++;
if(terminalX == 80)
terminalX = 0;
terminalY++;
void writeString(const char *data)
for(int i = 0; data[i] != '\0'; i++)
putCharAt(terminalX, terminalY, data[i]);
你可以阅读这个on this page。
【讨论】:
闪烁位不适用于所有硬件/模拟器。一些硬件/仿真器会闪烁,而另一些则认为它是强度位(1=高强度,0=低强度)。当您说第一位时,您实际上指的是最高有效位 (MSB)。通常位从 0 到 n-1 编号(在这种情况下 N 为 16) 是的,如果我没记错的话 QEMU 不会模拟这个位,但 VirtualBox 会。【参考方案3】:有点超出我的范围,但你可能想看看VESA。
【讨论】:
谢谢,那个地方看起来比我一直在寻找的大多数地方都更有帮助。 @user336462 没问题。让我们知道它是如何工作的以及你最终会做什么。 我不知道为什么这被否决了,但最好留言说明原因。 我找不到 VESA 的汇编代码,但我发现有一本书似乎可以回答我的问题,所以看起来我会改为调查它。这本书是 EGA、VGA 和超级 VGA 卡的程序员指南。 @user336462 我有那本书,很难在里面找到正确的信息,但我曾经打电话给作者,他向我保证信息就在里面。【参考方案4】:这不是那么简单。虽然 BIOS 提供 INT 10h 来将文本写入屏幕,但图形会因适配器而异。例如,您可以在此处找到有关 VGA http://www.wagemakers.be/english/doc/vga 的信息。一些古老的 SVGA 适配器http://www.intel-assembler.it/portale/5/assembly-game-programming-encyclopedia/assembly-game-programming-encyclopedia.asp 在这里。
【讨论】:
【参考方案5】:对于一般的 I/O 端口,你必须通过 BIOS,这意味着中断。许多蓝月亮之前,我使用来自Don Stoner 的参考资料帮助编写了一些实模式程序集,但几个月后我精疲力尽,忘记了我所知道的大部分内容。
【讨论】:
【参考方案6】:我找到了一个可以提供有关此事的一些有用信息的地方: http://www.osdever.net/FreeVGA/home.htm 它讨论了一些对编写直接写入屏幕的代码很重要的细节。我是从 Don Stoner 网站的链接中找到的(感谢 SilverbackNet 的链接!)。我仍在寻找更多信息,因此,如果有人有更多想法,我将非常感谢您的帮助。
【讨论】:
【参考方案7】:我发现一本书似乎可以回答我的问题。在 FreeVGA 页面上阅读后,我意识到这样的书可能存在。 这是链接: Programmer's Guide to the EGA, VGA and Super VGA cards
【讨论】:
以上是关于我怎样才能直接写到屏幕上?的主要内容,如果未能解决你的问题,请参考以下文章