Write OS kernel from scratch – BIOS boot to real mode

Time:2021-10-21

Series catalog

Implement boot loader

Nextpreparation, starting from this article, we will enter the preparation of boot loader. Some similar tutorials on the Internet may skip this stage and directly prepare the boot loader for you, so you can directly start writing the kernel, such as the one recommended beforeJamesM’s kernel development tutorialsThat’s it. However, I strongly recommend that you implement the boot loader yourself, especially for beginners. The reasons are as follows:

  • It is not difficult, compared with the kernel behind it;
  • It helps you quickly improve your assembly ability, which is still very important in the later C language kernel writing and debugging;
  • The programming in the boot bare metal running phase helps you establish a correct understanding of the loading and mapping relationship between disk, memory, instructions and data, and prepare for the subsequent kernel, executable program loading and the establishment of virtual memory, especially if you feel vague about this one;
  • In fact, the boot phase will initially build the framework of segment and virtual memory to lay the foundation for subsequent kernel writing;

Boot into BIOS

This is a classic question, that is, what happens after the computer motherboard is powered on and powered on?

First, we need to know the state of CPU and memory after startup. After startup, the initial mode of CPU is real mode, and the address width is 20 bits, that is, the maximum address space is 1MB. The division of 1MB space is fixed. Each block has a specified purpose and is mapped to different devices:

Write OS kernel from scratch - BIOS boot to real mode

BIOS operation

Let’s take a look at what happens after startup:

  1. After power on, the instruction register IP of the CPU is forced to be the address0xFFFF0, this address is mapped to the code on the BIOS firmware, which is the address of the first instruction after the computer is turned on;
  2. The CPU starts to execute the code on the BIOS. This part mainly checks the hardware input and output devices and establishes an initial interrupt vector table. At present, there is no need to go deep into it;
  3. The final stage of the BIOS code is to check the on the boot diskmbrPartition, the so-called MBR partition is the first 512b content on the disk, also known asBoot partition; BIOS will check the 512b: the last two bytes must be two magic numbers:0x55and0xaaOtherwise, it is not a legal boot disk;
  4. After passing the check, the BIOS loads the 512b into memory0x7C00At, until 0x7e00, and then the instruction jumps to 0x7c00 to start execution; At this point, BIOS exits the stage;

Draw the above table into a diagram, remove the interference items, and leave only the parts we care about:

Write OS kernel from scratch - BIOS boot to real mode

  • The yellow part is the MBR loaded into the memory, with the starting address 0x7c00;
  • The white part is the memory space that we can use freely behind us;
  • The shaded part of the slash is BIOS code;

The figure shows the main workflow of BIOS. Starting from the address 0xffff0, after a series of code execution, finally verify and read the first 512b sector of the disk, load it into the yellow part as MBR, and the address is 0x7c00, and then the instruction jumps to enter the execution of MBR;

Work of MBR

So what does MBR need to do? Because the size of MBR is limited to 512b, you can’t put a lot of code and data in it, so its most important work is only one:

  • Load the following loader part from disk to memory, and jump to loader to continue execution;

Memory layout planning

Therefore, we need to plan the memory layout of the whole boot load stage. Here we directly give the overall picture of disk and memory:

Write OS kernel from scratch - BIOS boot to real mode

At present, we focus on the following parts of memory 1MB, and different parts are marked with different colors:

  • Yellow: MBR
  • Blue: Loader
  • White: free to use

After the work of BIOS, now the instruction has come to the MBR part. It needs to load the loader in the blue part from the disk to the memory, and the address is 0x8000. Note that this address can be specified freely, as long as it is in the white area in the figure and there is enough space. Our loader will not be very large. According to the estimation of more spare space, 4KB is enough.

MBR code

First give the code in my project. The path issrc/boot/mbr.S, for your reference.

First focus on the beginning:

SECTION mbr vstart=MBR_BASE_ADDR

MBR_BASE_ADDRDefined inboot.incIn, it is 0x7c00, which means that the contents in the whole MBR are addressed from 0x7c00, including code and data. This is very important because we have known in advance that the BIOS will load the MBR to this location, so the addressing of the contents in the whole MBR must start from here, so that the subsequent access to the code and data in the MBR can be addressed correctly after the BIOS jumps to the first instruction of the MBR.

I marked the entrance of MBR formbr_entry, I have defined several functions later. We might as well annotate them in C language:

void init_segments();

Here, several segment registers are initialized, and the initial values are 0, which indicates the segment memory distribution of flat mode. Now you don’t have to go deep into it. In addition, I also moved the stack to 0x7b00, which is just free play, not necessary at all.

Next, load the loader:

void load_loader_img();
//The assembly code of this function directly uses registers to pass parameters.
void read_disk(short load_mem_addr,
               short load_start_sector,
               short sectors_num);

Here is the main work of MBR. Load the loader from the disk to the memory 0x8000,
read_diskThe three parameters are:

//The loader loading address is 0x8000;
short load_mem_addr = LOADER_BASE_ADDR;
//The loader image starts at the second sector on the disk, immediately after the MBR;
short load_start_sector = 1;
//The size of the loader is 8 sectors, 4KB in total;
short sectors_num = 8;

read_diskThe function involves reading the disk. You need to use a bunch of CPUs to control the port and interrupt function of the disk. You need to consult the documents. It is lengthy and complicated. I copied the content of Chapter 3 of the Book Truth restoration of the operating system. In fact, you don’t have to study deeply. You can use it. You just need to know what it does.

After loading the loader, you can jump to the loader address0x8000Execution:

jmp LOADER_BASE_ADDR

The whole jump process from BIOS – > MBR – > loader is shown in the figure below. The loader part is marked with a light blue shadow because it actually has no valid data at present. We will wait for it to be implemented and loaded into memory later:

Write OS kernel from scratch - BIOS boot to real mode

Finally, there is a key little thing:
After this code, the space used is far from 512b. We fill all the remaining space with 0 (in fact, anything can be filled in, but it can’t be executed anyway). Finally, write it at the end of 512b0x55and0xaaTwo magic numbers:

times 510-($-$$) db 0
db 0x55, 0xaa

So far, the MBR coding is completed, which is very short and simple. Next, we need to compile it, make it into a boot image, load it into Bochs and run it.

Run MBR

First, you need to create a disk image file. Here you use Bochs’s ownbximageThis command line tool:

>> bximage -hd -mode="flat" -size=3 -q scroll.img 1>/dev/null

In fact, it generates a 3MB file filled with 0. The disk with the size of 3MB is enough to accommodate all data such as MBR, boot, kernel and other user programs for our project.bximageThe print log will also tell you what parameters should be set for the disk in the configuration file bochsrc.txt, which is very convenient.

Next, compile MBR. S using NASM:

nasm -o mbr mbr.S

Then you get a 512b MBR file. Next, write it into the disk image file, which is used hereddThis command:

dd if=mbr of=scroll.img bs=512 count=1 seek=0 conv=notrunc

Note that the MBR is written to the first sector of the disk image file (512b).


Now we get a disk image file like this:
Write OS kernel from scratch - BIOS boot to real mode

Then you can load the disk image file into Bochs and run it, as before:

bochs -f bochsrc.txt

But before that, MBR had better make a small change. Because there is no loader in our image at this time, the loaded loaders are all 0, which is not executable code, so the last instruction of MBRjmp LOADER_BASE_ADDRThen the CPU will hang up, so you can add a sentence before this instructionjmp $, this is a dead cyclewhile (true) {}, let the program hover here, and you can pause Bochs and see if it stops at this instruction. If so, it indicates that the MBR has run successfully.

Write OS kernel from scratch - BIOS boot to real mode

summary

MBR is short and concise. There are not many difficulties in it, but it is difficult to start when it is finished. As the beginning of the whole kernel image, we need to start planning the layout of the whole memory in advance. If you are not familiar with the principles of assembly, instructions and memory running on bare metal, MBR is also a very good mobile phone training meeting. It is recommended that you compare the decompiled MBR code and Bochs debugging, which can quickly help you establish relevant cognition.

Recommended Today

Swift advanced (XV) extension

The extension in swift is somewhat similar to the category in OC Extension can beenumeration、structural morphology、class、agreementAdd new features□ you can add methods, calculation attributes, subscripts, (convenient) initializers, nested types, protocols, etc What extensions can’t do:□ original functions cannot be overwritten□ you cannot add storage attributes or add attribute observers to existing attributes□ cannot add parent […]