【问题标题】:Where to place the stack and load the kernel在哪里放置堆栈并加载内核
【发布时间】:2016-11-06 19:51:52
【问题描述】:

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

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

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

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

我正在0x1000:0x0000 加载我的内核。当我在我的print 函数中PUSH 和后来POP 易失性寄存器时,我的内核只是在我执行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

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

Kernel successfully loaded!
_

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

【问题讨论】:

  • 您未对齐堆栈。您应该将 SP 设置为 0。这会将第一个压入堆栈的单词放在 0000:FFFE。但这不是你的问题,这可能在于你没有显示的代码。您的代码应该与初始化为 0000:FFFF 的 SS:SP 和 1000:0000 的代码一起使用。
  • 你确定你只需要一个堆栈吗?一旦您开始研究多任务处理,您将需要“更多”。
  • @Ross Ridge 哦。单词被压入堆栈?不是字节?我觉得这是我应该知道的,但感谢您让我意识到这一点。
  • @tofro 我现在正在制作一个单任务内核。
  • 在 16 位模式下,推送和弹出是字大小的(即 16 位)。基本上,推送旨在推送寄存器的内容,因此推送/弹出与寄存器具有相同的大小。背景信息在这里:stackoverflow.com/a/15855444/366904

标签: assembly stack x86-16 osdev real-mode


【解决方案1】:

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

我没有遵循你代码中的逻辑,但这很明显:

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 可以是您选择的任何有效标签。


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

注意:将 SS:SP 设置为 0x1000:0x0000 将导致堆栈从 0x1000:0xFFFF 运行到 0x1000:0x0000。推送的第一个 16 位值将位于 0x1000:0xFFFE。


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

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

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

【讨论】:

  • 是的,我不知道为什么我没有注意到那个真正明显的错误。嗯,谢谢你的回答,这很有帮助。 :)
  • 等一下,如果设置为 0x1000:0x0000,我的堆栈是否会从 0x1000:0xFFFF 运行?这似乎有点奇怪。
  • @DrCereal:是的。这是 16 位代码中段偏移体系结构的一个特点。当您在 16 位中执行 PUSH 时,偏移量 (SP) 在数据放入堆栈之前减 2。无论如何,该段都没有改变。如果您取 0x0000 并减去 2,您将得到 0xFFFE。由于该段未更改,因此您最终会得到 0x1000:0xFFFE。如果你想要一个从物理地址 0x00000 到 0x0FFFF 的堆栈,你可以将 SS:SP 设置为 0x0000:0x0000 。在这种情况下,第一次推动将使 SP 减少 2 并将值放在那里。那将是内存地址 0x0000:0xfffe 。
猜你喜欢
  • 2011-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-15
  • 2016-11-29
  • 2010-11-15
  • 1970-01-01
相关资源
最近更新 更多