【问题标题】:Simple C Kernel char Pointers Aren't Working简单的 C 内核字符指针不起作用
【发布时间】:2012-07-03 12:58:10
【问题描述】:

我正在尝试使用 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")),它运行良好。

【问题讨论】:

  • 问题在哪里?您对您尝试做的某事给出了模糊的指示,但没有提供足够的细节让我们帮助您,也没有提供问题让我们回答。什么是故障模式?你试过什么?
  • PUTCH(strlen("Hi\0")+48, 6); 您似乎正在打印一个字符,其值为字符串 "Hi\0" + 48 的长度,这可能是 '2' 的 ASCII 字母(假设您的内核 strlen功能是标准的)。你确定这是你想要打印的吗?
  • 您发布的代码中没有任何内容实际调用 puts()。请完整示例
  • @ugoren 他使用的是 0xb8000,这是自 1981 年以来 IBM PC 中文本模式屏幕的地址(CGA)。 CUDA 还很遥远……
  • 您应该将函数宏的所有参数都括起来以避免副作用。像这样:#define PUTCH(C, X) (pos = putc((C), (X), (pos)))

标签: c pointers x86 kernel osdev


【解决方案1】:

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

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

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

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

【讨论】:

  • 正是我想写的。我在 osdev.org 上读到了这个问题。
  • char array[] = "Hello, World\n"; 应该给你完全相同的行为。
  • 由于 OP 说全局变量不起作用,这几乎肯定是问题所在。考虑编辑您的答案以指出 C 中的字符串文字(通常)被实现为全局变量。
  • @HophatAbc:你是如何编译和链接你的内核的?
  • 我确实遇到了 OP 的问题。应该使用哪些链接器命令来解决问题?
【解决方案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-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    • 2012-03-29
    • 1970-01-01
    相关资源
    最近更新 更多