Series catalog
- Preface
- Preparation work
- BIOS boot into real mode
- GDT and protected mode
- Virtual Memory
- load and enter the kernel
- display and print
- Global Descriptor Table GDT
- Interrupt handling
- virtual memory perfect
- implements heap and malloc
- first kernel thread
- multi-thread switching
- lock and multi-thread synchronization
- enter user mode
- process
- system call
- simple file system
- Load executable program
- keyboard driver
- Run shell
Implement Boot Loader
preparatory work 160fecd75c948b, from this article we will enter the preparation of the boot loader. There are some similar tutorials on the Internet that may skip this stage and directly prepare the boot loader for you, so that you can directly start writing the kernel. For example, the previously recommended JamesM's kernel development tutorials is like this. But I still strongly recommend to implement the boot loader by myself, especially for beginners, for the following reasons:
- It is not difficult, compared to the kernel behind;
- It helps you to quickly improve the assembly ability, which is still very important in the later C language kernel writing and debugging;
- The programming of the boot bare metal running stage helps you establish a correct understanding of the loading and mapping relationships between disks, memory, instructions and data, and is good for the loading of the kernel, executable programs, and the establishment of virtual memory. Be prepared, especially if you feel vague about this one;
- In the boot phase, the framework of segment and virtual memory will be initially built to lay the foundation for subsequent kernel writing;
Boot into the BIOS
This is a classic question. What happens after the computer motherboard is turned on and powered on?
First of all, we need to know the state of the CPU and memory after booting. After booting, the initial mode of the CPU is real mode, and the address width is 20 bits, that is, the maximum address space is 1MB. The division of this 1MB space is fixed, and each block has a specified purpose and is mapped to different devices:
The work of the BIOS
Let's take a look at what happens after booting:
- After booting, the instruction register ip of the CPU is forcibly set to address
0xFFFF0
, this address is mapped to the code on the BIOS firmware, which is the address of the first instruction after the computer is booted; - The CPU starts to execute the code on the BIOS. This part is mainly related to the inspection of the hardware input and output devices and the establishment of an initial interrupt vector table. There is no need to go into it at present;
- The last stage of the BIOS code is to check the
mbr
partition on the boot disk. The so-called mbr partition is the first 512B content on the disk, also called theboot partition; the BIOS will check the 512B: its last 2 words The section must be two magic numbers:
0x55
and0xaa
, otherwise it is not a legal boot disk; - After the check is passed, the BIOS loads the 512B into the memory
0x7C00
, until 0x7E00, and then the instruction jumps to 0x7C00 to start execution; at this point, the BIOS exits the stage;
Draw the above table as a picture, remove the interference items, and leave only the parts we care about:
- The yellow part is the mbr loaded into the memory, the starting address is 0x7C00;
- The white part is the memory space that we can freely use behind;
- The slash shaded part is the BIOS code;
The figure shows the main workflow of the BIOS, starting from address 0xFFFF0, after a series of code execution, and finally verifying and reading the first 512B sector of the disk, loaded into the yellow part is the mbr, the address is 0x7C00, and then the instruction jumps Turn over and enter the execution of mbr;
mbr's work
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 job is only one:
- Load the following loader part from the disk to the memory, and jump to the loader to continue execution;
Memory layout planning
So we need to plan the memory layout of the entire boot load stage. Here we directly give the overall view of the disk and memory:
We are currently focusing on the content of the memory below 1MB, and different parts are marked with different colors:
- Yellow: mbr
- Blue: loader
- White: free to use
After the work of the BIOS, the instruction has now come to the mbr part. It needs to load the blue part of the loader from the disk to the memory. The address is set to 0x8000. Note that this address can be freely specified, as long as it is in the white area in the figure. And enough space is enough. Our loader part will not be very large either. According to a more marginal estimate, 4KB is enough.
mbr code
First give the code in my project, the path is src/boot/mbr.S for your reference.
First focus on the beginning part:
SECTION mbr vstart=MBR_BASE_ADDR
MBR_BASE_ADDR
defined in boot.inc
as 0x7C00, which means that the content in the entire mbr is addressed starting from 0x7C00, including code and data. This is very important, because we already know that the BIOS will load the mbr to this location, so the addressing of the content in the entire mbr must start from here, so that after the BIOS jumps to the first instruction of the mbr, the follow-up The access to code and data in mbr can be addressed correctly.
I marked the entrance of mbr as mbr_entry
. Later, I defined several functions. We might as well comment it in C language for the following explanation:
void init_segments();
Several segment registers are initialized here, and the initial values are all 0, which means the segment memory distribution method of flat mode. Now you don't need to go into it. In addition, I also moved the stack to the position of 0x7B00. This is just for free play and not necessary at all.
Next load the loader:
void load_loader_img();
// 这个函数的汇编代码直接使用寄存器传参。
void read_disk(short load_mem_addr,
short load_start_sector,
short sectors_num);
Here is the main task of mbr, loading the loader from the disk to the location of memory 0x8000,read_disk
three parameters of 060fecd75c9bbd are as follows:
// loader 加载地址为 0x8000;
short load_mem_addr = LOADER_BASE_ADDR;
// loader 镜像在磁盘上起始位置为第 2 个sector,紧接着 mbr 之后;
short load_start_sector = 1;
// loader 大小为 8 个 sectors,共 4KB;
short sectors_num = 8;
read_disk The function involves reading the disk. It needs a bunch of CPU to control the disk port and interrupt function. You need to consult the document to use, which is tedious and complicated. I copied the content of Chapter 3 of the book "Operating System Truth Recovery" . In fact, you don’t have to delve into it, just use it, you just need to know what it does.
After loading the loader, you can jump to the loader address 0x8000
execute:
jmp LOADER_BASE_ADDR
The whole jump flow from BIOS -> mbr -> loader instruction operation is shown in the figure below. The loader part is marked with light blue shading, because it actually has no valid data at present, waiting for us to implement it and load it into memory later:
Finally, there is a key little thing:
After this pass code, the space used is far from 512B. We fill the remaining space with 0 (in fact, you can fill in whatever you want, it can't be executed anyway), and finally write 0x55
and 0xaa
magic number:
times 510-($-$$) db 0
db 0x55, 0xaa
The mbr encoding is now complete, which is very short and simple. Next we need to compile it and make it into a boot image, load it into Bochs and run it.
Run mbr
First of all, you need to make a disk image file, here again the command line tool bximage
>> bximage -hd -mode="flat" -size=3 -q scroll.img 1>/dev/null
It actually produces a 3MB file full of 0s. The 3MB disk is enough for our project to hold all the data such as mbr, boot, kernel and other user programs. bximage
will also tell you what parameters should be set for the disk in the configuration file bochsrc.txt, which is very convenient.
Next use nasm to compile mbr.S:
nasm -o mbr mbr.S
Then you get a 512B size mbr file. Next, write it into the disk image file, where the command dd
dd if=mbr of=scroll.img bs=512 count=1 seek=0 conv=notrunc
Note that the mbr is written to the first sector (512B) of the disk image file.
Now we get a disk image file like this:
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 first. Because there is no loader content in our mirror at this time, the loaded loader is actually all 0, which is not executable code, so the jmp LOADER_BASE_ADDR
, so you can precede this instruction Add a sentence of jmp $
, which is equivalent to an endless loop while (true) {}
. Let the program hover here, you can pause Bochs and see if it stops at this instruction, if it is, it means that the mbr operation has been successful.
Summarize
mbr is short and sturdy. It doesn't have much difficulty in it, but it is difficult to finish it at the beginning. As the beginning of the entire kernel mirroring, we need to start planning the layout of the entire memory in advance. If you are not familiar with the principles of assembly, instructions, memory, etc. running on bare metal, mbr is also a very good mobile phone training session. It is recommended that you check the decompiled mbr code and Bochs debugging more quickly. Help you build relevant awareness.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。