Series catalog
- Preface
- Preparation work
- BIOS boot to real mode
- GDT and protected mode
- A preliminary
- 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
kernel disk mirroring
previous article 160fecd9b02e13 virtual memory preliminary exploration , this article will officially load and start the kernel, which is the green part in the figure:
Of course, the kernel image needs to be read and loaded from the disk, so here is an old picture, which is disk
and memory
(physical memory):
By the way, the slash-shaded part with the question mark in the above picture is the kernel page tables
previous chapter, which is the orange part of the first picture, with a total of 256 sheets occupying an area of 1MB.
Write the kernel
kernel
back to 060fecd9b02fd1, which is the green part in the figure, it does not actually exist yet, so first we need to implement and compile a simple demo kernel. If you have no idea what a kernel is, you may ask: What does the kernel look like?
The answer is very simple: the kernel is almost the same as the executable program you usually write in C language, and it also starts with a main function.
Below we will implement our first kernel:
void main() {
while (1) {}
}
It is as simple as that, except for a while
loop, there is nothing else, but it is enough for our demo here.
Compile the kernel
There are many compilation parameters, such as 32-bit encoding, disabling the C standard library, etc. (this is our own customized OS, and it is impossible to be compatible with the C standard library).
gcc -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -no-pie -fno-pic -c main.c -o main.o
Link kernel:
ld -m elf_i386 -Tlink.ld -o kernel main.o
A link configuration file link.ld
will be used here:
ENTRY(main)
SECTIONS
{
.text 0xC0800000:
{
code = .; _code = .; __code = .;
*(.text)
}
.data ALIGN(4096):
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
}
.bss ALIGN(4096):
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}
end = .; _end = .; __end = .;
}
The most important thing here is to define the start address 0xC0800000
text
segment, which is also the start of the entire kernel addressing. If you still remember on , we planned the virtual memory distribution of the kernel space:
0xC0800000
will be the entry address of the kernel, because the text
section will be loaded here, and then data
, bss
and so on. loader
ends, it will jump to this address.
In addition, the entry function of the entire executable file is defined as main
.
The compiled and linked kernel is an ELF format binary, we might as well disassemble it and dump it to see:
objdump -dsx kernel
You can see that the address of the main
0xC080000
, which is the first instruction after entering the kernel.
Make the kernel image
dd if=kernel of=scroll.img bs=512 count=2048 seek=9 conv=notrunc
seek=9
is because the previous mbr
and loader
already occupy the first 9 sectors on the disk. Here the kernel size is 2048 sectors and 1MB in total, which is large enough for our project, and it is completely sufficient.
Now the disk mirroring finally becomes like this:
Read and load the kernel
After the image is prepared, the kernel can be read and loaded. First of all, give the code link init_kernel for your reference.
And before mbr
and loader
of different loading, where the
read and
loading two separate words, because they are two steps:
- original binary of the kernel disk image to a free place in the memory, where the binary is in ELF format;
- Loading: parse the ELF executable binary obtained in the previous step, and
section
to the place where they are by
First look at the first step "read". We chose the 1MB at the top of the virtual memory, that is (0xFFFFFFFF - 1MB) ~0xFFFFFFFF
as the storage address of the binary image. Of course, this is completely a personal choice. I chose here because no one will bother here at the moment; of course, we must allocate the corresponding physical for it. Page frames
, the page table
was established in 060fecd9b036a0, so I also found 1MB free space from the remaining physical memory space and mapped it; then, the kernel image can be read in as before when reading mbr and loader.
Next is the second step "loading". This involves parsing according to the specification of the ELF file format. You need to spend some time to understand the relevant documents. The main thing is to get section
program header table
, as well as the loaded memory address (of course the virtual address), and then the data copy past. The memory address loaded this time is where 0xC0800000
starts. page table
, it is of course necessary to pre-allocate frames for them and establish a memory map in 060fecd9b03701. All this work is done ahead of time in the function allocate_pages_for_kernel
Enter the kernel
Everything is ready, then you can really enter the kernel:
init_kernel:
call allocate_pages_for_kernel
call load_hd_kernel_image
call do_load_kernel
; init floating point unit before entering the kernel
finit
; move stack to 0xF0000000
mov esp, KERNEL_STACK_TOP - 16
mov ebp, esp
; let's jump to kernel entry :)
jmp eax
ret
First initialize the floating-point unit of the CPU to prevent abnormalities from behind.
Then I moved stack
to the higher address 0xF0000000
, which of course is not necessary, it is entirely my personal choice. The current stack location is actually very good (about the 0xC0007B00
below 060fecd9b0387d, where 0x7B00
is mbr , and after opening paging we use 0xC0000000 + 0x7B00
visit, if you remember). It's just that I hope that the stack position after entering the kernel can be moved to a brand new place, so I did so much. The location of the stack is more flexible, as long as it is an idle place that will not be disturbed.
Then very simple, jmp eax
an instruction jump kernel
entrance.
Why is eax
? This is the return value of the above function do_load_kernel
. This function is the function we use to parse the ELF binary that loads the kernel. It will return the entry address of the kernel, which is the address of the main
function. This address is e_entry
field of the ELF Header
in the ELF file. . The entry address of the ELF executable binary is determined during the linking phase. It is actually specified by link.ld
in the ENTRY(main)
If it goes well, the results of the operation are as follows:
The program has successfully entered the kernel and ran to 0xC0800003
, which is the position of the while loop. This will be the real beginning of the kernel journey :)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。