8

Series catalog

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 :)


navi
612 声望191 粉丝

naive