在哪里放置堆栈和加载内核

Where to place the stack and load the kernel

我是操作系统开发的新手,我很好奇我在开发自己的引导加载程序时 运行 遇到的一个问题。我的操作系统将用汇编语言编写,并且将 运行 在 16 位实模式下。

我知道堆栈是什么,我的印象是它向下增长到内存中。如果我错了,请纠正我。我知道如何将基本内核从软盘加载到内存中,但我认为这不是问题所在。

我遇到的问题 运行 是我不确定在哪里放置堆栈并将我的内核加载到内存中。我试过像这样创建我的堆栈,但我 运行 遇到了问题:

mov ax, 0x0000
mov ss, ax
mov sp, 0xFFFF

我正在 0x1000:0x0000 加载我的内核。当我 PUSH 和后来 POP 我的 print 函数中的易失性寄存器时,我的内核只是挂起 second 我做的时候 call print。这是我的 print 函数:

print:
    push ax
    push bx
    push cx

    mov al, [si]
    cmp al, 0
    je p_Done

    cmp al, 9
    je p_Tab

    mov ah, 0xE
    int 0x10

    cmp al, 10
    je p_NewLine
   p_Return:
    inc si
    jmp print
  p_Tab:
    xor cx, cx
   p_Tab_Repeat:
    cmp cx, 8
    je p_Return
    mov ah, 0xE
    mov al, " "
    int 0x10
    inc cx
    jmp p_Tab_Repeat
  p_NewLine:
    xor bx, bx
    mov ah, 0x3
    int 0x10

    mov dl, 0x00
    mov ah, 0x2
    int 0x10
    jmp p_Return
  p_Done:
    pop cx
    pop bx
    pop ax
    ret

这些是我要显示的行:

db "Kernel successfully loaded!", 10, 0
db 9, "Lmao, just a tab test!", 10, 0

这是我的内核 运行s(_ 是光标)时得到的输出:

Kernel successfully loaded!
_

它成功打印了第一行,但在打印第二行时挂起。如果我删除 PUSHPOP 语句,它就可以正常工作。当我尝试在我的 print 函数中保存和恢复寄存器时,为什么我的内核会挂起?我应该在哪里放置我的堆栈以及我应该在哪里加载我的内核?

这不是 Minimal Complete Verifiable Example 也无济于事,但您的问题表明可能需要寻找的东西。通常如果代码通过删除函数序言和结尾中的 PUSHes 和 POPs 来工作,这通常意味着堆栈在执行过程中变得不平衡函数体。不平衡的堆栈将导致 RET 指令 return 到达堆栈顶部的任何半随机位置。这可能会导致明显的挂起 and/or 重新启动。行为将是未定义的。

我没有遵循您代码中的逻辑,但这很突出:

print:
    push ax
    push bx
    push cx

    ... snip out code for brevity  

    jmp print

在某些时候,您的 print 功能可能会在所有推送之前的某个时间点重新启动。这将导致更多的 PUSHes 进入堆栈,而最后没有相应的 POPs。我认为您可能一直在尝试获得这样的行为:

print:
    push ax
    push bx
    push cx

.prloop:
    ... snip out code for brevity  

    jmp .prloop

.prloop 标签出现在函数的顶部,但在推送之后。这可以防止将多余的值放在堆栈上。 .prloop 可以是您选择的任何有效标签。


堆栈可以放置在内存中未被系统使用且不会干扰您的引导加载程序 and/or 内核代码的任何位置。正如@RossRidge 指出的那样,使用 0xFFFF 的 SP 会使堆栈不对齐,因为它是一个奇数地址 (0xFFFF=-1)。 x86 不会抱怨(缺少对齐检查标志),但它会损害某些 x86 架构上的堆栈性能。

注意:设置SS:SP到0x1000:0x0000会导致堆栈从[=运行 58=] 下降到 0x1000:0x0000。推送的第一个 16 位值将位于 0x1000:0xFFFE.


您的内核和堆栈通常在物理地址 0x00520 和 0x90000 之间的任何位置都是安全的,只要它们不相互冲突即可。在某些系统上,0x90000 和 0xA0000 之间的内存区域的上半部分可能不可用。如果你想使用这个内存区域,我会避开 0x9C000 和 0xA0000 之间的区域。此区域可由 BIOS 作为 Extended BIOS Data Area (EBDA) 的一部分使用。

可以通过调用ROM-BIOS 的中断12h 服务,或直接读取0x00413 处的字来获知可用低内存区(LMA) 的确切数量space。无论哪种情况,结果都是 KiB 的可用内存量。如果实际内存小于640 KiB,and/or LMA顶部的部分内存被EBDA或其他软件使用,那么结果将低于640(即0x0280)。从技术上讲,结果也可以高于 640。通过乘以或左移以KiB为单位的数量,可以计算出以段落或字节为单位的等效数量。

不应使用 0x00000 和 0x00520 之间的区域,因为它包含实模式中断向量 table、BIOS Data Area (BDA) 和被视为保留的 32 字节内存。