C++ 写入 VGA text-mode 内存在屏幕上不可见

C++ writes to VGA text-mode memory are not visible on the screen

我正在尝试用 C++ 编写一个内核(一个 OS 内核),但我遇到了一个非常奇怪的问题(我相信我做错的事情会很明显对于其他人,我只是为了我的生活找不到问题)

我正在使用 C++ class 来表示 VGA 控制台(BIOS 应该在地址 0xB8000 加载它),可以在 OS 引导序列的早期访问它调试输出。

(有关更多信息,请阅读:http://wiki.osdev.org/Bare_Bones

这是我内核的主要功能:

#include "Kernel.hpp"

extern "C"
{
    void kernelMain ()
    {
        VGATerminal kernelTerm = VGATerminal ();

        //kernelTerm.print("[");
        //kernelTerm.setTextColour(VGATerminal::LIGHT_GREEN);
        //kernelTerm.print("OK");
        //kernelTerm.setTextColour(VGATerminal::WHITE);
        //kernelTerm.print("]\t\tKernel gained control of the CPU.\n");
        //kernelTerm.print("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123465789\n");
    }
}

(现在输出被注释掉了,但它按预期工作,在正确的地方使用正确的字符和颜色)

这是 VGATerminal 构造函数:

VGATerminal::VGATerminal ()
{
    this->row = 0;
    this->col = 0;
    this->currentColour = generateVGAColour(WHITE, BLACK);
    this->vgaBuffer = (uint16*) VGATerminal::VGA_MEMORY;

    for (uint16 r = 0; r < VGATerminal::HEIGHT; r++)    // Loop over rows
    {
        for (uint16 c = 0; c < 8; c++)  // Loop over columns
        {
            this->vgaBuffer[(r * WIDTH) + c] = generateColouredChar(' ', this->currentColour);
            //this->print(' ');
        }
    }

    //this->row = 0;
    //this->col = 0;
}

每当列迭代超过 7 时,此代码将我的 OS 发送到无限循环(7 很好,8 将其发送到无限循环)。 (7 很好,我的意思是它会产生清除屏幕前 7 列的预期行为(尽管我无法真正验证这一点,因为在我清除屏幕之前,它没有填充文本。显示的文本虽然屏幕被正确擦除)

循环从内核开头的某处开始(当我为我的 OS 生成 ISO 时,它实际上循环回到 GRUB 打开之前,GRUB 打开,我 select 我的 OS 回到开头)。

当我用 this->print(' ') 替换 this->vgaBuffer[...] = ... 部分时,一切正常。 (如果我这样做,我还需要重置 this->rowthis->col,所以这两行在最后被注释掉了)

这是 VGATerminal::print(const char c) 函数:

void VGATerminal::print (const char c)
{
    switch (c)
    {
        case '\t':
            // First insert 1 space, this will force the terminal to insert atleast this
            // (Otherwise, you might get something like this:)
            // CATS\tDOGS Becomes CATSDOGS instead of CATS    DOGS
            // This happens because after writing CATS, the terminal is at a multiple of 4
            // and decides it doesn't need to insert anything anymore
            this->vgaBuffer[(this->row * WIDTH) + this->col] = generateColouredChar(' ', this->currentColour);
            this->col++;

            while ((this->col % 4) != 0)
            {
                this->vgaBuffer[(this->row * WIDTH) + this->col] = generateColouredChar(' ', this->currentColour);
                this->col++;

                if (this->col == WIDTH)
                {
                    this->row++;
                    this->col = 0;
                }
            }

            break;

        case '\n':
            this->row++;
            this->col = 0;
            break;

        case '\r':
            this->col = 0;
            break;

        default:
            this->vgaBuffer[(this->row * WIDTH) + this->col] = generateColouredChar(c, this->currentColour);
            this->col++;

            if (this->col == WIDTH)
            {
                this->row++;
                this->col = 0;
            }

            break;
    }
}

支持回车 returns 和换行可能不完整,我想在测试之前先解决这个错误,尽管换行似乎工作正常。

uint16 generateColouredChar (const char c, uint8 colour) 实际上是一个函数(与方法相对):

uint16 VGATerminal::generateColouredChar (const char c, uint8 colour)
{
    return (uint16) colour << 8 | (uint16) c;
}

uint8, uint16, ... 都是它们各自对应的 (uint8_t, uint16_t, ...) 的别名,在另一个 header 中创建如下:

#include <stdint.h>

using uint8 = uint8_t;
using uint16 = uint16_t;
using uint32 = uint32_t;
using uint64 = uint64_t;

using uchar = uint16;

using int8 = int8_t;
using int16 = int16_t;
using int32 = int32_t;
using int64 = int64_t;

(我这样做是因为它不会降低 IMO 的可读性,但它让我不必输入烦人的“_”)

编辑:

这是完整的 VGATerminal class 以供将来参考:

class VGATerminal
{
    private:
        constexpr static uint16 WIDTH = 80;
        constexpr static uint16 HEIGHT = 25;
        constexpr static int VGA_MEMORY = 0xB8000;

        uint16  row;
        uint16  col;
        uint8   currentColour;
        volatile uint16*    vgaBuffer;

    public:
        /// Colour constants
        constexpr static uint8 BLACK = 0;
        constexpr static uint8 BLUE = 1;
        constexpr static uint8 GREEN = 2;
        constexpr static uint8 CYAN = 3;
        constexpr static uint8 RED = 4;
        constexpr static uint8 MAGENTA = 5;
        constexpr static uint8 BROWN = 6;
        constexpr static uint8 LIGHT_GREY = 7;
        constexpr static uint8 DARK_GREY = 8;
        constexpr static uint8 LIGHT_BLUE = 9;
        constexpr static uint8 LIGHT_GREEN = 10;
        constexpr static uint8 LIGHT_CYAN = 11;
        constexpr static uint8 LIGHT_RED = 12;
        constexpr static uint8 LIGHT_MAGENTA = 13;
        constexpr static uint8 LIGHT_BROWN = 14;
        constexpr static uint8 WHITE = 15;

        VGATerminal ();

        void clear ();

        void setTextColour (uint8 colour);

        void setBackgroundColour (uint8 colour);

        void print (const char c);

        void print (const char* str);
};

没有明显的理由(原来 post 不是)为什么 7/8 应该不同。

你如何定义VGATerminal::vgaBuffer?您是否将其标记为 volatile 以避免编译器优化 "useless" 内存写入?

编译器不理解在​​ 0xB8000 地址处写入值在 C++ 语言定义之外具有外部影响,通过使用 volatile 你给编译器提示,它不是常规的 memory C++ variable write(编译器可以自由地以任何方式实现 C++ 变量,即使没有实际内存写入,只要代码按照 C++ 语言定义工作) , 但实际内存 read/write 应该做,因为它可能会导致外部影响(在写入的情况下),或者该值可能已被外部修改(如不同的线程代码,或通过 OS,或通过也具有内存访问权限的外部设备)。

顺便说一句,作为一名汇编程序员,看到如此正确的嵌套 for 循环只是为了清除屏幕,这让我很生气:

VGATerminal::VGATerminal ()
{
    this->row = 0;
    this->col = 0;
    this->currentColour = generateVGAColour(WHITE, BLACK);
    this->vgaBuffer = (uint16*) VGATerminal::VGA_MEMORY;

    const uint16 whiteSpace = generateColouredChar(' ', this->currentColour);
    for (unsigned i = 0; i < VGATerminal::HEIGHT*VGATerminal::WIDTH; ++i)
        this->vgaBuffer[i] = whiteSpace;
}

虽然我认为一旦你对列使用 VGATerminal::WIDTH 优化器可能已经将嵌套循环修改为单循环,但这只是 asm 程序员编写最少代码并避免乘法和多个计数器的老习惯您正在使用连续的内存块,rows/columns 的逻辑分隔对于当前任务并不重要。