如何在i386中正确设置ss和sp寄存器

时间:2017-07-24 14:58:20

标签: assembly operating-system nasm bootloader i386

我目前正在编写一个bootloader并且已经开始耗尽我的512B空间,所以我开始在512B之外编写更多代码并打算使用bootloader将其读入内存。我添加到我的代码的末尾:

stack_start:
    resb 4096
stack_end:

这样我就可以在操作系统代码的末尾为堆栈分配空间。目前在我的引导程序中,我使用以下here中的以下内容在引导加载程序之后为堆栈分配4KiB:

mov ax, 07c0h           ; 4K stack space after the bootloader -- code is running at 0x07c0
add ax, 288             ; (4096 + 512)/16 bytes per paragraph (288 paragraphs)
mov ss, ax
mov sp, 4096            ; moves the stack pointer

但是现在我需要一种方法来将堆栈分配到我的操作系统代码的末尾,这将是一个未知的大小。

我相信我理解这些是如何设置的 - 类似于使用es,我使用ss来扩展寻址空间,但我找不到任何可以解释它的东西因为我的知识水平。我也不确定如何正确设置它以使我的堆栈结束。我用过:

mov ax, stack_start
mov ss, ax
mov sp, 4096

并没有遇到错误;但是我想知道这是否正确,或者我是否真的只为堆栈分配了一些空间,同时用更高的地址填充内存。

ss如何实际运作?我如何使用它和sp在我的代码末尾为堆栈分配内存?

这是在使用nasm的i386中。

编辑:如果可能的话,验证堆栈位于正确位置的某些方法也非常有用。

3 个答案:

答案 0 :(得分:3)

我知道这对初学者来说是可怕的,但是只要满足要求并且没有错误,就没有好的或错误的方法来设置堆栈。
在拥有一个完全正常工作的内存管理器之前,你(人)必须是内存管理器 你必须知道你如何使用记忆 "分配内存的整个概念"还不存在!你没有内存管理器,你只有一堆RW RAM地址。

记录您的假设

首先要求引导程序可以安全地假设的最小内存量 由于初始程序加载器(引导加载程序)位于0x7c00,因此可以认为系统具有31.5KiB的内存。 您还可以假设没有内存,并依赖缓存,但这是高级主题。

当出现问题时,陈述假设至关重要。

记住差距

然后您必须了解保留和使用区域,这是通过标准memory map实现的。

摘录:

00000 - 003ff   IVT
00400 - 004ff   BDA
00500 - 0052f   Dangerous Zone (The Petch Zone :) )
00530 - 07bff   Free
07c00 - 07dff   IPL

" Petch Zone" is an inside joke并向Michael Petch致敬。

做出完全有意识的决定

通过设置临时堆栈来建立您的最小环境 在上面的片段中,区域00530 - 07bff是免费的,您可以将它用作~29KiB堆栈 由于堆栈是完全降序的,因此您可以将堆栈指针ss:sp放在07c00 07c00物理地址,将其转换为任何合适的逻辑地址(0000:7c000001:7bf00002:7be00003:7bd0 ,. ..,07c0:0000,任何人都会选择你最喜欢的那个)并以任何原子方式将SS:SP设置为它中断你知道/喜欢。

编辑如果正确注意到Ped7g的偏移下溢从0到fffe,则使用带有小偏移部分的逻辑地址会导致问题。
虽然静态行为检查(起始地址相同),但动态行为失败,因此最好使用0000:7c00,而且肯定不会使用0053以上的段。

任何其他区域都可以,将堆栈指针设置为a0000(传统内存的末尾)是另一种选择。
没有比这更好的了,只是意识到你把它放在哪里。

编辑:评论中的Michael Petch pointed outa0000,地址9c000也很危险。
更安全的地址是00000 - 003ff IVT 00400 - 004ff BDA 00500 - 0052f Dangerous Zone (The Petch Zone :) ) 00530 - 07bff Stack 07c00 - 07dff IPL 07e00 - 08fff Kernel 09000 - 10000 Other kernel stuff

更新内存映射

使用符合您需要的块更新内存映射 记下内核开始和结束的位置,动态数据所在的位置等等。

例如

malloc

到目前为止,您可能已经在内存映射中使用了静态块,但如果您想使用超过1 MiB的内存,则需要query the BIOS来获取可用内存的内存映射。
这本质上是动态的,因为每个系统都有不同的内存量。

该映射只是内存管理器的一个非常小的元数据,所以现在是时候......

实现简单的内存管理器

在您设置到目前为止的基本环境中,编写一个简单内存管理器,该书可以保存内存块。

陷阱:内存管理器需要一些"元内存"保留其已分配内存的书,这需要假设

一旦你可以分配和释放内存,你可以将堆栈移动到更大的区域,从磁盘或等效的方式加载其他数据等等。
我们的想法是,您现在可以像使用mfreeString?一样动态管理内存,从而减轻您在心理上处理内存映射的负担。

更高级的内存管理器

更高级的内存管理器通常用高级语言编写,数据操作更容易(特别是在处理Paging等主题时)。

答案 1 :(得分:1)

不要将堆栈放在代码的 end ,而是将其放在开头。这实际上是启动加载器的常见做法。换句话说:

 cli
 mov ax,0x7c00
 mov sp, ax
 xor ax, ax
 mov ss, ax
 sti

您现在正在从您的装载程序代码加载的位置开始增长。这也使事情变得更容易,因为您要做的下一件事之一就是加载第二阶段。这使您可以轻松地在同一个程序集文件中创建第二个阶段,加载并跳转到它,而不必担心堆栈覆盖代码。

答案 2 :(得分:1)

我在启动代码中做的一件事就是尽可能将堆栈放在常规内存中。

    Select t2.Name, t2.HRS_TOT, t2.APPROVE_DATE, t1.Date, t1.HRS 
   FROM t1 
   INNER JOIN t2 On t1.SumOfHRS = t2.HRS_TOT 
        t1 = SELECT Name, Date, Sum(HRS) As SumOfHRS, 
   FROM tbl1 GROUP BY date ORDER BY ID 
   SYS_MEM    equ 12H
   StkSize    equ 44  ; # of 1024 byte blocks to reserve for stack

这里有一些我的要求特有的东西,但基本上我在内存顶部抓住了44k,这就是堆栈的存在,直到我进入64位。

然后,在我的情况下,读取内核的代码被移动到540H并且内核( 1181扇区)被读入1000H到94600H。我发现这在实际和受保护模式下非常实用。