Tagged with: [ cybos ] [ operatingsystem ] [ tutorial ]
Welcome to the first part of CybOS. We talk a bit about the bootsector. From part 2 on, everything is “kernel based”, which means we have setup the system and jumped to our main kernel. From there, things get really interesting so I jump a bit fast to the boot code. However, in the end of this post, the source code for the bootsector (and second stage loader) can be found so you can see what’s going on.
First things first. When we are talking about x86 (intel) CPU’s, they will ALWAYS start their execution on address 0x7C00. Your mission, should you accept it, is to put there something useful so your computer will actually do something. The putting things there is easy, since that is done by your computer, but you need to place a piece of software that will allow you to start up your system. And you have to do it in 512 bytes (actually less).
The 512 bytes are being read from the drive that you boot from. This is normally the first sector on your drive. This sector contains the bootsector, or on harddisks, the MBR. The bootsector is 510 bytes of code and is followed by 2 bytes (0xAA55) as a signature. Most BIOS’es nowadays don’t really look for this signature, but some old ones still do so you need it there,.. just doing absolutely nothing except taking away some precious bytes for your bootsector.
On harddisks, it’s a bit different. The bootsector is called an MBR (Master Boot Record). This is also 512 bytes (with the last 2 bytes being 0xAA55). But the 64 bytes before (from position 446 to 510) is the partition table. This piece of information holds room for 4 (primary) partitions. Since it’s a very small block, it’s the reason why there can only be 4 primary partitions on your drives. As you will notice very soon, a lot of limitations nowadays are because of backward compatibility issues. IBM decided that 4 partitions would be enough, so 4 is the maximum (which you can overcome by using extended partitions etc). Anyway.. just for now it’s safe to say that you have 64 bytes less space to boot up from a hard disk than you have from a floppy.
So, what to do in the bootsector?
You can do a lot. But I suggest you only load a secondary boot sector. This boot sector is something you can control yourself, so you can actually make it as big as you want, but normally a sector or 2-4 is more than enough for everything you need to accomplish. After all, the primary task of the boot record is to setup some basic stuff and load and run the kernel ASAP.
In CybOS the bootsector consists of 2 parts:
The boot.S is a 512 byte bootsector and does the following:
- Move out of the way from 0x7C00
The reason we move ourself away from 0x7C00, is that when we boot from a MBR, we might want to load the bootsector from the partition actually boot from (we can say that partition 3 is the active boot partition for instance). Since boot sectors EXCEPT to be started from 0x7C00, we have to make sure it’s loaded there. So it’s not really needed for now, but good practice anyway.
- Write message onto screen
Just tell the outside world we have started our boot process. It’s our first communication so it’s nice to see something.
- Setup stack segment
Make sure pop and push and stuff works correctly and as expected.
- Read a fixed number of sectors from the bootdisk into memory on certain spot (the second stage loader AND the kernel). This is done by using INT 0x13. It’s not very complex, but you need to know a bit about cylinders, heads and sectors.
- Jumps to the second stage loader (loader.S)
After we have loaded the second stage loader onto our desired spot, we can jump to there. From that point on, the boot sector is completely useless and we can overwrite this with other data if we want to.
The hardest part of designing your OS is to figure out where to place everything. You have got a lot of memory but not everything can be used (0xB8000 for instance is for VGA, 0xFF000 is ROM etc). We place the loader at position 0x8000. The stack is on position 0x9FFFF and some setup data will be set from 0x9C000 to 0x9F000.
It’s a bit strange to see that we load both the second stage boot loader AND kernel binary at the same time. A better way would be that the second stage loader actually browses the filesystem (for instance: FAT16 or EXT2 system) and searches a specified file (for instance: kernel.bin). That would be the kernel that it must load. For now, this would be too complex so we just add the kernel binary after the second stage loader.
Sector 000 : bootsector
Sector 001 : second stage loader
Sector 00x: kernel
Second stage loader:
This little thing does a little bit more work:
- Detect correct CPU (at least 386 is needed)
Since we need protected mode, we need at least a 386. A 286 is theoretical possible, but it has some differences in the handling of protected mode stuff. Therefore, a 386 is minimum. The boot loader will halt when no 386 or better is found.
- Detect amount of memory (at least 1MB is needed)
We need at least 1MB of memory. For now the kernel is so small we don’t really need it, but it makes our life much simpler with all the memory and data structures we setup. So just use at least 1MB.
- Setup a nice color palette (just because we can)
It’s just a cooler look to have a different color palette. The gray text on black will be changed into a cyan color on blue.. Very nifty indeed.
- Enable A20 line
We need it to address more memory.
- Setup temporary Global Descriptor Table (GDT)
This table is needed so we know where we are in protected mode. It has a very short lifespan since one of the first things we do in the kernel is setup a decent GDT. We don’t do that here because we don’t have all the information we need.
- Enter protected mode
It’s not that hard once everything is setup correctly.
- Setup paging (1:1 mapping)
The paging system just uses a 1 on 1 mapping for the first megabyte. This means that the virtual address 0x12345 is actually physical address 0x12345. Also temporary.
- Setup stack for kernel
The kernel uses a stack (heavily since it’s C). The stack must be setup before we jump to our C kernel.
- Call kernel entry point
We add some kernel parameters (like the stack segment, and the amount of memory). Other things we could add later on is a environment setting. Things that are entered during boot (for instance: kernel boot flags). We don’t do that now but it’s still an option to implement later on if it becomes necessary.
From this point on, we can leave all our assembly only stuff behind and actually use a compiled C written kernel. Still, there are lot’s of things we actually need assembly, but the majority will be C.
[Code will follow soon]