Linux operating system study notes (two) kernel operation

Preface

  In the above, we analyzed the process from pressing the power button to the completion of the BootLoader loading. After the loading is complete, the Linux kernel must be officially started, and before that, the switch from real mode to protected mode must be completed first. This article mainly analyzes the following parts

  • Alternation of new and old interruptions
  • Open A20
  • Enter the main function
  • Kernel initialization

  In fact, there is still a lot of content in the whole process, such as checking various hardware devices, etc., which are omitted here. Now start diving into the ocean of Linux source code.

Alternation of new and old interruptions

  Obviously, the interrupt in real mode cannot be the same as the interrupt in protected mode, so we need to close the old interrupt (cli) and establish a new interrupt (sti). mainThe interrupt service system whose function can adapt to the protected mode is rebuilt before the interrupt is turned on. At that time, the service program that responds to the interrupt will no longer be the interrupt service program provided by the BIOS, instead the interrupt service program provided by the system itself.

  cli and sti always appear at both ends of a complete operation process, the purpose is to avoid interrupting the intervention during this period. The following code will prepare the operating system to enter protected mode. The handover of the interrupt vector table in real mode and the interrupt descriptor table (IDT) in protected mode will be carried out here . Just imagine, if there is no cli, and an interrupt happens to happen, if the user accidentally touches the keyboard, the interrupt will be cut in. You have to face the embarrassing situation that the real mode interrupt mechanism has been abolished and the protected mode interrupt mechanism has not been completed. The result is a system crash. cli and sti ensure that IDT can be completely created during this process to avoid incomplete IDT creation or mixed use of new and old interrupt mechanisms due to unpredictable interrupt entry.

#boot/setup.s
……
do _move:
mov es,ax!destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax!source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
jmp do_move

  The above code mainly completes a task: copy the kernel program located at 0x10000 to the starting location of the memory address at 0x00000. In the previous section, we analyzed the memory map in real mode, where the interrupt vector table and BIOS data area created by the BIOS were originally stored. This copy action completely covers the BIOS interrupt vector table and the BIOS data area, making them no longer exist. The benefits of this are as follows:

  1. The abolition of the BIOS interrupt vector table is equivalent to the abolition of the interrupt service routine in real mode provided by the BIOS.
  2. Reclaim the memory space occupied by the program that has just ended its service life.
  3. Let the kernel code occupy the first, natural and advantageous position of the physical memory address.

  At this time, important roles are about to appear, they are the interrupt descriptor table IDT and the global descriptor table GDT .

  • GDT (Global Descriptor Table, Global Descriptor Table), the only array in the system to store the contents of the segment register (segment descriptor), cooperate with the program to perform segment addressing in protected mode. It is of great significance in the process switching of the operating system. It can be understood as a total directory table of all processes, in which each task (task) local descriptor table (LDT, Local Descriptor Table) address and task status segment (TSS, Task Structure Segment) address, complete the addressing, field protection and field recovery of each segment in the process. GDTR is the GDT base address register. When the program references a segment descriptor through the segment register, it needs to obtain the GDT entry, and the entry identified by GDTR is this entry. After the GDT is initialized by the operating system, the LGDT (Load GDT) instruction can be used to load the GDT base address to the GDTR.

  • IDT (Interrupt Descriptor Table), saves the entry addresses of all interrupt service routines in protected mode, similar to the interrupt vector table in real mode. IDTR (IDT base address register), save the starting address of IDT.

  The 32-bit interrupt mechanism and the 16-bit interrupt mechanism are quite different in principle. The most obvious is that the 16-bit interrupt mechanism uses the interrupt vector table. The start position of the interrupt vector table is at 0x00000, which is fixed; the 32-bit interrupt mechanism uses the interrupt descriptor table (IDT). It is not fixed, it can be flexibly arranged by the designer of the operating system according to the design requirements, and its position is locked by IDTR. GDT is a data structure that manages segment descriptors in protected mode, and is of great significance to the operation of the operating system itself, as well as management and scheduling processes.

  At this moment, the kernel is not really running and there is no process yet, so the first item of the created GDT is empty, the second item is the kernel code segment descriptor, the third item is the kernel data segment descriptor, and the rest are empty. Although IDT has been set, it is actually an empty table, because the interrupt is currently closed and there is no need to call the interrupt service routine. What is reflected here is the idea of ​​"getting enough data".

The process of creating these two tables can be understood as a two-step process:

  1. When designing the kernel code, the two tables have been written, and the required data has also been written. The data area here is a data area formed in the kernel source code, compiled and directly loaded into the memory. The pointing of the special register is completed by the lidt and lgdt instructions in the program
  2. Point the special registers (IDTR, GDTR) to the table.

A20

  Enable A20 is an iconic action, lzma_decompress.img invoked by the aforementioned real_to_protstart. Turning on A20 means that the CPU can perform 32-bit addressing, and the maximum addressing space is 4 GB. Note the change in the memory range in Figure 1-19: from 5 F to 8 F, that is, 0xFFFFFFFF (4 GB).

  In real mode, when the program address more than 0xFFFFF, CPU will "roll back" to the memory address of the start address (note that the strip only 20 address lines of
the lower member, 0xFFFFF + 1 = 0x00000, the highest overflow) . For example, the maximum allowable address of the system segment register (such as CS) is 0xFFFF, and the maximum allowable offset of the instruction pointer (IP) is also 0xFFFF. The maximum absolute address determined by the two is 0x10FFEF, which means that the program can be The resulting addressing range in real mode is nearly 64 KB more than 1 MB (some programs with special addressing requirements take advantage of this feature). In this way, the enabling of the A20 address line here is equivalent to turning off the "rollback" mechanism of the CPU addressing in real mode. The following is to use this feature to verify whether the A20 address line is indeed open. Note that the code here does not run at this time, but is used in the subsequent head running process to detect whether it is in protected mode.

#boot/head.s
……
xorl %eax,%eax
1:incl%eax#check that A20 really IS enabled
movl %eax,0x000000#loop forever if it isn't
cmpl %eax,0x100000
je 1b
……

  If A20 is not turned on, the computer is in a 20-bit addressing mode, and addressing beyond 0xFFFFF must "roll back". A special case is that 0x100000 will roll back to 0x000000, that is, the value stored at address 0x100000 must be exactly the same as the value stored at address 0x000000. By writing a piece of data in the memory location 0x000000, and then comparing the data here and 1 MB (0x100000, note that it has exceeded the real mode addressing range) is the same, you can check whether the A20 address line has been opened.

Enter the main function

  A piece of hardware knowledge is involved here: In the X86 system, the terminal control chip used is called 8259A. This chip is an interrupt controller that can be controlled by a program. A single 8259A can manage 8-level vector priority interrupts, and can be cascaded into a 64-level vector priority interrupt system at most without adding other circuits. In the protection mode of the CPU, int 0x00~int 0x1F are reserved by Intel as internal (non-maskable) interrupts and abnormal interrupts. If the 8259A is not reprogrammed, the int 0x00~int 0x1F interrupt will be overwritten. For example, IRQ0 (clock interrupt) is the 8th (int 0x08) interrupt, but in the protected mode, this interrupt number is the "Double Fault" reserved by Intel. Therefore, the interrupt numbers corresponding to the original IRQ0x00~IRQ0x0F must be redistributed through 8259A programming, that is, in the protection mode, the interrupt numbers of IRQ0x00~IRQ0x0F are int 0x20~int 0x2F.

  The setup program uses the following code to set the CPU working mode to protection mode. Here involves a CR0 register: No. 0 32-bit control register, put the system control flag. Bit 0 is the PE (Protected Mode Enable) flag . When set to 1, the CPU is working in protected mode, and when set to 0, it is in real mode. Set the 0th bit (PE) of CR0 register to 1, that is, set the working mode of the processor to protection mode. When the CPU working mode is changed to a protected mode, an important feature is to decide where to execute the program according to the GDT. As mentioned above, GDT has already written the data initially, these will be used to complete the jump from the setup program to the head program.

#boot/setup.s
mov ax,#0x0001!protected mode(PE)bit
lmsw ax!This is it!
jmpi 0,8!jmp offset 0 of segment 8(cs)

  The head program is the last step before entering main. The head creates a kernel paging mechanism in the space, that is, creates the page directory table, page table, buffer, GDT, IDT at the location of 0x000000, and overwrites the memory space occupied by the code that has been executed by the head program. This means that the head program will discard itself and the main function will begin to execute. The specific paging mechanism is more complicated, so it is planned to be introduced separately in the subsequent memory management part .

  Head constructs IDT, so that the overall structure of the interrupt mechanism is set up first (the actual interrupt service routine is hooked up in the main function), and all interrupt service routines point to the same segment of the service routine that only displays one line of prompt information and returns. In terms of programming technology, this kind of initialization operation can not only prevent the logic confusion caused by unintentionally overwriting code or data, but also give timely prompts to misoperations in the development process. IDT has 256 entries, of which only a few dozen are actually used. For misuse of unused interrupt descriptors, such prompt messages can remind developers to pay attention to errors.

  In addition, the head program must abolish the existing GDT and recreate the GDT in the new location in the kernel. The original location of GDT is the data set in setup.s when designing the code. In the future, the memory location of this setup module will be overwritten when designing the buffer. If the location is not changed, the contents of the GDT will definitely be overwritten by the buffer in the future, which will affect the operation of the system. In this way, the only safe place in the entire memory in the future is where head.s is now.

  The next steps mainly include

  1. Initialize segment registers and stack
  2. Clear the eflag register and the kernel uninitialized data area
  3. Call to decompress_kernel()decompress the kernel image and jump to 0X00100000.
  4. The segment register is initialized to the final value and the BSS field is filled with 0
  5. Initialize the temporary kernel page table

  After the paging mechanism is finally initialized, the PG (Paging) flag will be set to 1, indicating that the address mapping mode adopts the paging mechanism, and finally jumps to the main function, and the kernel starts to initialize.

Kernel initialization

  Note that so far, we have not opened interrupts, and a series of initializations must be completed through the main function before opening a new interrupt, so that the kernel is officially running. This part mainly includes:

  1. Build kernel mode stack for process 0
  2. Clear the eflags register
  3. Call setup_idt()to fill IDT with empty interrupt handler
  4. Pass the parameters obtained in the BIOS to the first page frame
  5. Fill registers with GDT and IDT tables

  After these are completed, the kernel is officially running, and process 0 is created.

to sum up

  This article introduces the entire switching process from real mode to protected mode, completes the loading of the kernel and begins to formally prepare to create process 0. The follow-up will continue to analyze the entire process of starting the kernel to create processes 0, 1, and 2. During the introduction of this article, a lot of assembly code and some knowledge that is very important but not part of the basic process are ignored. Those who are interested in understanding can do more in-depth study and research based on the links in the article, the source code at the end of the article and reference materials.

Source information

[1] GURB 2

[2] syslinux

Reference

[1] Linux-insides

[2] Deep understanding of Linux kernel source code

[3] The art of Linux kernel design

[4] Geek Time Talks about Linux Operating System

Guess you like

Origin blog.csdn.net/u013354486/article/details/105828471