6

我正在尝试使用 C 制作一个简单的内核。一切都加载并正常工作,我可以访问视频内存和显示字符,但是当我尝试实现一个简单的 puts 函数时,由于某种原因它不起作用。我已经尝试过自己的代码和其他代码。此外,当我尝试使用在函数外部声明的变量时,它似乎不起作用。这是我自己的代码:

#define PUTCH(C, X) pos = putc(C, X, pos)
#define PUTSTR(C, X) pos = puts(C, X, pos)

int putc(char c, char color, int spos) {
    volatile char *vidmem = (volatile char*)(0xB8000);
    if (c == '\n') {
        spos += (160-(spos % 160));
    } else {
        vidmem[spos] = c;
        vidmem[spos+1] = color;
        spos += 2;
    }
    return spos;
}
int puts(char* str, char color, int spos) {
    while (*str != '\0') {
        spos = putc(*str, color, spos);
        str++;
    }
    return spos;
}
int kmain(void) {
    int pos = 0;
    PUTSTR("Hello, world!", 6);
    return 0;
}

spos起始位置)的东西是因为我无法制作全局位置变量。putc工作正常,但puts没有。我也试过这个:

unsigned int k_printf(char *message, unsigned int line) // the message and then the line #
{
    char *vidmem = (char *) 0xb8000;
    unsigned int i=0;

    i=(line*80*2);

    while(*message!=0)
    {
        if(*message=='\n') // check for a new line
        {
            line++;
            i=(line*80*2);
            *message++;
        } else {
            vidmem[i]=*message;
            *message++;
            i++;
            vidmem[i]=7;
            i++;
        };
    };

    return(1);
};

int kmain(void) {
    k_printf("Hello, world!", 0);
    return 0;
}

为什么这不起作用?我尝试将我的 puts 实现与我的原生 GCC 一起使用(没有颜色和 spos 数据并使用printf("%c")),它运行良好。

4

2 回答 2

7

由于您通常遇到全局变量问题,因此问题很可能与链接器将“Hello World”字符串文字放在内存中的位置有关。这是因为字符串文字通常由链接器存储在全局内存的只读部分中......您没有详细说明您是如何编译和链接内核的,所以我会尝试类似以下和看看这是否有效:

int kmain(void) 
{
    char array[] = "Hello World\n";
    int pos = 0;
    puts(array, 0, pos);
    return 0;
}

这将在堆栈而不是全局内存上分配字符数组,并避免链接器决定放置全局变量的任何问题。

通常,在创建简单内核时,您希望将其编译并链接为平面二进制文件,而不依赖于外部 OS 库。如果您正在使用像 GRUB 这样的多引导兼容引导加载程序,您可能需要查看多引导规范页面中的基本示例代码。

于 2012-07-03T20:11:42.083 回答
2

由于这得到了 SO 之外的引用,我将添加一个通用答案

互联网上有几个内核示例,其中许多处于各种退化状态 - 例如 Multiboot 示例代码缺少编译指令。如果您正在寻找一个工作的开始,可以在http://wiki.osdev.org/Bare_Bones找到一个已知的好示例

最后要妥善处理三件事:

  1. 引导加载程序需要正确加载内核,因此它们必须就某种格式达成一致。GRUB 定义了一个相当普遍的标准,即 Multiboot,但您可以自行开发。归结起来,您需要选择一种文件格式和位置,在内核代码执行之前,您的内核和相关元数据的所有部分最终都在内存中。通常将 ELF 格式与 multiboot 一起使用,该格式在其标头中包含该信息

  2. 编译器必须能够创建与平台相关的二进制代码。典型的 PC 以 16 位模式启动,之后 BIOS 或引导加载程序可能经常决定更改它。通常,如果您使用 GRUB legacy,Multiboot 标准会根据其合同将您置于 32 位模式。如果你在 64 位 linux 上使用默认编译器设置,你最终会得到错误架构的代码(恰好足够相似,你可能会得到看起来像你想要的结果)。编译器还喜欢重命名部分或包含特定于平台的机制和安全功能,例如堆栈探测或金丝雀。尤其是 Windows 上的编译器倾向于注入特定于主机的代码,这些代码在没有 Windows 的情况下运行时当然会中断。提供的示例故意使用单独的编译器来防止此类问题的各种问题。这些

  3. 链接器必须能够以创建符合引导加载程序合同的输出所需的方式组合代码。链接器具有生成二进制文件的默认方式,通常它根本不是您想要的。在几乎所有情况下,为此任务选择 gnu ld 意味着您需要编写一个链接描述文件,将所有部分放在您想要的位置。省略的部分将导致数据丢失,错误位置的部分可能会使映像无法启动。假设您有 gnu ld,除了您选择的十六进制编辑器之外,您还可以使用捆绑的 nm 和 objdump 工具来告诉您输出二进制文件中出现的位置,并使用它检查您是否一直遵守合同为你设置。

这种基本类型的问题最终会追溯到不遵循上述一个或多个步骤。使用此答案顶部的参考并查找差异。

于 2012-11-27T13:31:27.140 回答