I am currently writing a bootloader and have begun to run out of space in my 512B, so I have begun writing more code outside the 512B and intend to use the bootloader to read it into memory. I have added to the end of my code:
stack_start:
resb 4096
stack_end:
So that I can allocate space for the stack at the end of the operating system code. Currently in my bootloader, I allocate 4KiB for the stack after the bootloader using the following which I took from here:
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
However now I need a way to allocate the stack to be at the end of my OS code, which will be an unknown size.
I believe I understand how these need to be set up -- similar to using es
, I am using ss
for extending the addressing space, but I can't find anything that explains it well for my level of knowledge. I am also not sure how to set it correctly for putting my stack at the end. I have used:
mov ax, stack_start
mov ss, ax
mov sp, 4096
And have not encountered an error; however I would like to know if this is correct, or whether I've actually just allocated some space for the stack while filling memory with a higher address.
How does ss
actually work? And how can I use it and sp
to allocate memory for the stack at the end of my code?
This is in i386 using nasm.
EDIT: Some way to validate that the stack is in the right place would be really useful, too, if that is at all possible.
I know this is scary for a beginner but there is no good or wrong way to setup the stack as long as it fulfils the requirements and it is bug-free.
Before you have a fully working memory manager, you (the human) have to be the memory manager.
You must be aware of how you use the memory.
The whole concept of "allocating memory" doesn't exist yet! You don't have a memory manager, you just have a bunch of RW RAM addresses.
Start with requiring a minimum amount of memory that your bootloader can safely assume.
Since the Initial Program Loader (the bootloader) is at 0x7c00 it is reasonable to believe the system has 31,5KiB of memory.
You can also assume that no memory is present and rely on the cache but that's and advanced topic.
Stating assumptions is vital when things go wrong.
Then you must be aware of the reserved and used areas, this is achieved with a standard memory map.
An excerpt:
00000 - 003ff IVT
00400 - 004ff BDA
00500 - 0052f Dangerous Zone (The Petch Zone :) )
00530 - 07bff Free
07c00 - 07dff IPL
The "Petch Zone" is an inside joke and homage to Michael Petch.
Build up your minimal environment by setting up a temporary stack.
In the fragment above the area 00530 - 07bff
is free you can use it as a ~29KiB stack.
Since the stack is full-descending you can put the stack pointer ss:sp
at 07c00
.07c00
is a physical address, convert it to any suitable logical address (0000:7c00
, 0001:7bf0
, 0002:7be0
, 0003:7bd0
, ..., 07c0:0000
, any will do pick up the one you like the most) and set SS:SP
to it in any atomic way w.r.t. interrupts you know/like.
EDIT Using logical address with a small offset part leads to problems when the offset underflow wrapping from 0 to fffe
as Ped7g correctly noted.
While the static behaviour checks (the starting address is the same) the dynamic behaviour fails, so it's better to use 0000:7c00
and surely not anything with segment above 0053
.
Any other area will do, setting the stack pointer to a0000
(The end of the conventional memory) is another choice.
None is better, just be aware of where you put what.
EDIT: As Michael Petch pointed out in the comments, the address a0000
is also dangerous.
A safer address would be 9c000
.
Update the memory map with the blocks that fit your needs.
Write down where your kernel starts and ends, where your dynamic data lies and so on.
For example
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
Until now you could have gone away with using static blocks in the memory map but if you want to use more than 1 MiB of memory you need to query the BIOS to get the memory map of available memory.
This is intrinsically dynamic as each system comes with a different amount of memory.
That map is nothing but a very minimal meta-data for a memory manager, so it is time to...
Within the basic environment you setup up to now, code a simple memory manager that book keeps blocks of memory.
Pitfall: A memory manager needs some "meta-memory" to hold its book of allocated memory, this call for an assumption.
Once you can allocate and free memory you can move the stack into a bigger area, load other data from disk or equivalent and so on.
The idea is that you can now manage memory dynamically like you do in C with malloc
and mfree
relieving you from the burden of mentally dealing with the memory map.
A more advanced memory manager is usually written in an high-level language where data manipulation is easier (especially when dealing with topics like Paging).
Rather than putting your stack at the end of your code, put it at the beginning. This is actually pretty common practice for boot loaders. In other words:
cli
mov ax,0x7c00
mov sp, ax
xor ax, ax
mov ss, ax
sti
You are now growing down from where your boatloader code loads. This also makes things easier since one of the very next things you are going to do is to load a second stage. This allows you to easily create the second stage in the same assembly file, load it in and jump to it, without having to worry about the stack overwriting code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With