Graphics 如何直接写入屏幕?

Graphics 如何直接写入屏幕?,graphics,io,x86,assembly,Graphics,Io,X86,Assembly,我是一个对汇编语言非常感兴趣的青少年。我正在尝试用英特尔x86汇编程序编写一个小型操作系统,我想知道如何直接写入屏幕,就像在中一样,而不依赖BIOS或任何其他操作系统。我浏览了coreboot、Linux和Kolibri等的源代码,希望找到并理解一些实现这一点的代码。在这方面我还没有成功,不过我相信我会再看一看Linux源代码,它是我搜索过的源代码中最容易理解的 如果有人知道这一点,或者知道我可以看源代码的哪一部分,如果他们告诉我,我将不胜感激 或者更好的是,如果有人知道如何识别Intel x8

我是一个对汇编语言非常感兴趣的青少年。我正在尝试用英特尔x86汇编程序编写一个小型操作系统,我想知道如何直接写入屏幕,就像在中一样,而不依赖BIOS或任何其他操作系统。我浏览了coreboot、Linux和Kolibri等的源代码,希望找到并理解一些实现这一点的代码。在这方面我还没有成功,不过我相信我会再看一看Linux源代码,它是我搜索过的源代码中最容易理解的

如果有人知道这一点,或者知道我可以看源代码的哪一部分,如果他们告诉我,我将不胜感激


或者更好的是,如果有人知道如何识别Intel x86 CPU上的哪个I/O端口连接到哪个硬件,那也会很感激。我之所以要问这个问题,是因为无论是在《英特尔64和IA-32体系结构软件开发人员手册》第1卷:基本体系结构中关于输入/输出的章节,还是在第3卷中关于输入或输出指令的章节中,我都找不到这些信息。因为在我掌握的资料中查找相关说明太难了。

有点超出了我的范围,但你可能想了解一下。

这并不是那么简单。虽然BIOS提供INT 10h将文本写入屏幕,但不同适配器的图形不同。例如,您可以在此处找到有关VGA的信息。这里有一些古老的SVGA适配器。

对于一般I/O端口,您必须通过BIOS,这意味着中断。很多年前,我使用中的引用来帮助编写一些实模式程序集,但几个月后我就对它精疲力竭了,忘记了我知道的大部分内容。

第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模式时查找像素的地址,可以执行类似“像素地址=显示内存地址+每行y*字节+每像素x*字节”的操作。“每行字节数”来自VESA/VBE接口(可能与“水平分辨率*每像素字节数”不同,因为水平行之间可能存在填充)

对于“银行切换”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非常相似,只是行之间没有填充,您可以执行“像素\地址=显示\内存\地址+(y*水平\分辨率+x)*字节\每像素”。对于文本模式,它基本上是相同的,除了2个字节决定每个字符及其属性-例如,“char\u address=display\u memory\u address+(y*水平分辨率+x)*2”。对于其他旧的VGA模式(单色/2色、4色和16色模式),显卡的内存安排完全不同。它被分为“平面”,每个平面包含一个像素位,并且(例如)要在16色模式下更新一个像素,您需要写入4个单独的平面。出于性能原因,VGA硬件支持不同的写入模式和不同的读取模式,并且可能会变得复杂(太复杂,此处无法充分描述)

第二部分

对于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设备的大量信息。对于这一类别,t
xor ah, ah
mov al, 0x03
int 0x10
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]);       
    }           
}