如何直接写入屏幕?

时间:2011-01-04 02:05:11

标签: graphics io x86 assembly

我是一名对汇编语言非常感兴趣的少年。我正在尝试在Intel x86汇编程序中编写一个小型操作系统,我想知道如何直接写入屏幕,就像不依赖于BIOS或任何其他操作系统一样。我正在查看coreboot,Linux和Kolibri等的来源,希望能够找到并理解一些执行此操作的代码。我还没有在这方面取得成功,虽然我相信我会再看一下Linux源代码,但对于我搜索过的源代码来说,这是最容易理解的。

如果有人知道这一点,或者知道我可以看到哪些源代码,我会很感激,如果他们告诉我的话。

或者更好的是,如果有人知道如何识别Intel x86 CPU上的I / O端口连接到哪个硬件,那也是值得赞赏的。我需要问的是,在英特尔64和IA-32架构软件开发人员手册第1卷:基本架构的输入/输出章节中,以及第3卷中的IN或OUT指令的章节中都没有,我能找到这些信息吗?而且因为在我所拥有的资源中搜索相关指令太费劲了。

7 个答案:

答案 0 :(得分:18)

第1部分

对于旧VGA模式,有一个固定地址可写入(传统)显示存储区。对于文本模式,此区域从0x000B8000开始。对于图形模式,它从0x000A0000开始。

对于高分辨率视频模式(例如由VESA / VBE接口设置的模式),这不起作用,因为传统显示存储区域的大小限制为64 KiB,大多数高分辨率视频模式需要更多空间(例如1024 * 768 * 32-bpp = 2.25 MiB)。为了解决这个问题,VBE支持两种不同的方法。

第一种方法称为“银行切换”,其中只有部分视频卡的显示内存随时映射到传统区域(您可以更改映射的部分)。这可能非常混乱 - 例如,要绘制一个像素,您可能需要计算像素所在的哪个库,然后切换到该库,然后计算库中的哪个偏移量。更糟糕的是,对于某些视频模式(例如每像素有3个字节的24-bpp视频模式),只有像素数据的第一部分可能位于一个存储区中,而同一像素数据的第二部分位于不同的存储区中。这样做的主要好处是它可以与实模式寻址一起使用,因为传统的显示存储区域低于0x00100000。

第二种方法称为“线性帧缓冲”(或称“LFB”),可以访问视频卡的整个显示存储区域,而无需任何杂乱的存储区切换。您必须询问此区域所在的VESA / VBE接口(并且它通常位于0xC0000000和0xFFF00000之间的“PCI孔”中)。这意味着您无法在实模式下访问它,并且需要使用保护模式或长模式或“虚幻模式”。

要在使用LFB模式时查找像素的地址,您需要执行类似“pixel_address = display_memory_address + y * bytes_per_line + x * bytes_per_pixel”的操作。 “bytes_per_line”来自VESA / VBE接口(可能与“horizo​​ntal_resolution * bytes_per_line”不同,因为水平线之间可能有填充)。

对于“银行转换”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 * horizo​​ntal_resolution + x)* bytes_per_pixel ”。对于文本模式,它基本上是相同的,除了2个字节确定每个字符及其属性 - 例如“char_address = display_memory_address +(y * horizo​​ntal_resolution + x)* 2”。对于其他旧的VGA模式(单色/双色,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端口。为了解决这个问题,最终用户必须告诉OS每个设备使用哪个I / O端口。值得庆幸的是,这些硬壳已经过时了(虽然这并不一定意味着没有人使用它)。

第三类是“即插即用”,其中有一些方法可以询问设备使用哪个I / O端口(在大多数情况下,更改设备使用的I / O端口)。这方面的一个例子是PCI,其中有一个“PCI配置空间”,可以告诉您有关每个PCI设备的大量信息。对于这些类别,没有人可以确定哪些设备将在运行时使用哪个I / O端口,并且更改某些BIOS设置可能导致任何/所有这些设备更改I / O端口。 / p>

另请注意,Intel CPU只是一个CPU。没有什么能阻止这些CPU被用于与“PC兼容”计算机完全不同的东西。英特尔的CPU手册绝不会告诉您有关CPU本身以外的硬件(包括芯片组或设备)的任何信息。

第3部分

可能是获取更多信息(适用于操作系统开发人员/业余爱好者)的最佳位置是http://osdev.org/(他们的维基及其论坛)。

答案 1 :(得分:1)

有点超出我的范围,但您可能想要查看VESA

答案 2 :(得分:1)

这不是那么简单。虽然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

答案 3 :(得分:1)

对于通用I / O端口,您必须通过BIOS,这意味着中断。许多蓝色卫星之前,我使用Don Stoner的参考资料来帮助编写一些实模式汇编,但几个月后我就把它烧掉了,忘记了我所知道的大部分内容。

答案 4 :(得分:1)

要直接写入屏幕,您应该写入VGA文本模式区域。这是一个内存块,它是文本模式的缓冲区。

文本模式屏幕由80x25个字符组成;每个字符都是16位宽。如果设置了第一位,则字符将在屏幕上闪烁。然后接下来的3位详细说明背景颜色;第一个字节的最后4位是前景(或文本字符)的颜色。接下来的8位是字符的值。这通常是代码页737或437,但它可能因系统而异。

Here是一个详细说明此缓冲区的维基百科页面,而here是指向代码页437的链接

在系统启动之前,几乎所有BIOS都会将模式设置为文本模式,但某些笔记本电脑的BIOS将无法启动进入文本模式。如果您尚未处于文本模式,则可以非常简单地使用int10h进行设置:

xor ah, ah
mov al, 0x03
int 0x10

(上面的代码使用了BIOS中断,所以它必须在实模式下运行。我建议将它放在你的bootsector中。)

最后,这是我为保护模式编写字符串而编写的一组例程。

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

的信息

答案 5 :(得分:0)

我找到了一个提供有关此事的一些好信息的地方: http://www.osdever.net/FreeVGA/home.htm 它讨论了编写直接写入屏幕的代码的一些重要细节。我从链接到Don Stoner的网站找到了它(感谢您的链接,SilverbackNet!)。我还在寻找更多的信息,所以如果有人有更多的想法,我会非常感谢你的帮助。

答案 6 :(得分:0)

我找到了一本似乎回答我问题的书。我意识到在FreeVGA页面上阅读它后,这本书可能存在。 这是链接: Programmer's Guide to the EGA, VGA and Super VGA cards