简单的 C 内核字符指针不起作用



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

#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 != '') {
        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;
}

为什么这不起作用?我尝试将我的 put 实现与我的本机 GCC 一起使用(没有颜色和 spos 数据并使用 printf("%c") ),它工作正常。

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

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

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

通常,在创建简单内核时,您希望将其编译并链接为平面二进制文件,而不依赖于外部操作系统库。 如果您使用的是符合多重引导的引导加载程序(如 GRUB),则可能需要查看多重引导规范页面中的基本示例代码。

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

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

最后,有三件事应该妥善处理:

  1. 引导加载程序需要正确加载内核,因此它们必须就某种格式达成一致。GRUB 定义了相当常见的标准,即多重引导,但你可以自己动手。归根结底,您需要选择一种文件格式和位置,在内核代码执行之前,内核的所有部分和相关元数据最终都会在内存中。通常使用带有多重引导的ELF格式,该格式在其标头中包含该信息

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

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

这种基本类型的问题最终可以追溯到不遵循上述一个或多个步骤。使用此答案顶部的参考资料,然后找到差异。

最新更新