Protection Mode
(Protected mode, or sometimes pmode) is a 80286 series and later x86 compatible CPU
Operation Mode. The protection mode has some new features and is designed to enhance the stability of multiple workers and systems, such as memory protection, paging systems, and virtual memory supported by hardware. Most of today's x86
Operating systems are all running in protected mode, including Linux, FreeBSD, and Microsoft Windows 2.0 and later versions.
Another 286 and later CPU operation mode is
Real mode: A forward-compatible mode that disables these features. Designed to enable new chips to execute old software. According to the designed specifications, all x86 CPUs
They are all started in real mode to ensure the forward compatibility of traditional operating systems. Before any protection mode features available, they must be manually switched from some programs to the protection mode. In today's computers
It is usually the first task that must be completed by the operating system at startup. It may also use virtual 86 mode when the CPU runs in protected mode.
To execute the Code designed for the real mode.
Although software may also be used in a system in the real mode, the memory protection feature in the protection mode can prevent problematic programs from damaging other work or operating systems.
Memory of the core. In the protection mode, hardware support for program interruption is also provided. Execution content can be handed over to other jobs to implement preemptible multi-jobs.
Most of the CPUs that can use the protection mode also have the characteristics of 32-Bit Memory (such as 80386
Series and any chips later), imported into the integrated protection mode to become the concept of 32-bit processing. Although the 80286 chip supports the protection mode, it still has only 16
Bit metadata. Protection Mode enhancements in Windows 2.0 and later versions are called "386 enhancement mode" because they require 32
And cannot be executed on 286 (even if 286 supports protection mode ).
Even if the protection mode has been enabled on the 32-bit chip, the memory above 1 MB cannot be accessed, it is a memory wrap-around und (memory continuity) factor modeled on the features of the ibm xt system design. This restriction can be avoided by opening A20 line.
In protection mode, the first 32 interrupts are reserved for CPU exception handling. For example, the interrupt 0d (decimal 13) is a normal protection mode error and the interrupt 00 is divided by zero.
In the 8086/8088 era, the processor only has one operating mode (Operation
Mode), because no other operation mode exists, this mode is not named. Since 80286 to 80386, the processor has added two more operating modes-protection mode pm
(Protected Mode) and system management mode SMM (System Management
Mode). Therefore, the 8086/8088 mode is named real-address mode RM (Real-address mode ).
PM is the native mode of the processor. In this mode, the processor supports all commands and all architecture features to provide the highest performance and compatibility. For all new applications
And the operating system, we recommend that you use this mode. To ensure PM compatibility, the processor allows RM programs to be executed in a protected, multitasking environment. This feature is called virtual 8086 mode.
(Virtualization-8086
Mode), although it is not a real processor mode. The Virtual-8086 mode is actually a PM attribute, which can be used by any task.
Rm provides an Intel 8086 processor programming environment and some extensions (such as the ability to switch to PM or SMM ). When the host is powered-up or reset, the processor is in RM.
SMM is a standard architecture feature for all Intel processors. Appears on intel386
SL chip. This mode provides a transparent mechanism for the OS to implement the functions specified by the platform (such as power management or system security. When the external SMM interrupt
Pin (SMI #) is activated or
Controller) receives an SMI, and the processor enters the SMM. In SMM, when the context of the currently running program is saved, the processor switches
The address space. Then the code specified by SMM may be transparently executed. When returned from SMM, the processor returns to the status before being interrupted by system management.
Since the processor is in the RM state after power-up or reset, only PM can play the biggest role for Intel 80386 and later chips. Therefore, we are faced with the problem of switching from RM to PM.
This article does not discuss SMM. This section focuses on how to switch from RM to PM in the booting stage. Here we will not discuss too much details about PM, because intel
Architecture Software Developer's Manual Volume 3: System
Programming has a very detailed and accurate introduction.
1. What is gdt
In protected mode, an important and essential data structure is gdt (Global Descriptor Table ).
Why does gdt exist? Let's first consider the programming model in real mode:
In real mode, we use the segment: Offset method to access a memory address. The segment is the base of a segment.
Address. the maximum length of a segment is 64 KB, which is the maximum length that a 16-bit system can represent. Offset is relative to this segment
Base Address offset. Base Address + offset is an absolute memory address. From this, we can see that a segment has two factors: Base
Address and limit (the maximum length of segments), and the access to a memory address needs to be pointed out: Which segment is used? And
Address offset, which should be less than the limit of this segment. Of course, for a 16-bit system, do not specify the limit. The default value is the maximum length of 64 KB.
The 16-bit offset cannot be greater than the limit. In actual programming, we use the 16-bit register CS (Code
Segment), DS (Data Segment), SS (Stack
Segment) to specify the segment. The CPU shifts the value in the segment memory to 4-bit to the left and places it on the 20-bit address line to form a 20-bit base.
Address.
In the protected mode, the memory management mode is divided into two types: Segment mode and page mode. In this mode, the page mode is also based on the segment mode. That is, protected
The memory management mode of mode is actually pure segment mode and segment page mode. Further, the segment mode is indispensable, while the page mode is optional-if the page mode is used, the segment mode; otherwise, this is a pure segment mode.
.
In this case, we will not consider the page mode. For the segment mode, the segment: Offset method is still used to access a memory address, which is natural. Because
When protected mode runs on a 32-bit system, the two factors of segment are base.
Both address and limit are 32-bit. The IA-32 allows the base of a segment
Address is set to any value that can be expressed by 32-bit (limit can be set to any value that can be expressed by 32-bit in multiples of 2 ^ 12), unlike real
In mode, the base of a segment
Address can only be a multiple of 16 (because it is 4-bit low, it is obtained through the left shift operation, and can only be 0, so as to use 16-bit segment register to represent 20-bit Base
Address), and the limit of a segment can only be a fixed value of 64 KB. In addition, protected
Mode, as its name implies, also provides a protection mechanism for the segment mode, that is, the descriptor of a segment needs to specify its own access permissions ). Therefore, in protected
In mode, the description of a segment includes three factors: [base address, limit,
They are put together in a 64-bit long data structure, called segment descriptor. In this case, if we reference
You must use a 64-bit long block memory to mount this segment descriptor. However, to maintain backward compatibility, Intel still sets the block storage device as 16-bit (although each block storage
In fact, there is a 64-bit long invisible part, but for programmers, the segment memory is 16-bit), so obviously, we cannot directly use the 16-bit length block memory.
References the 64-bit segment descriptor.
What should I do? The solution is to put these 64-bit segment descriptors into an array, and indirectly reference the values in the segment register as subscript indexes (in fact, the segment register
Medium height 13
-Bit content as an index ). The global array is gdt. In fact, in gdt, we store not only segment descriptors, but also other descriptors which are 64-bit long.
Discussion.
Gdt can be placed anywhere in the memory. When a programmer references a segment descriptor through a segment register, the CPU must know the gdt entry, that is, where the base address is, so
Intel's designers provide a register GDTR for storing the gdt entry address. After the programmer sets the gdt to a certain location in the memory, the gdt entry can be obtained through the lgdt command.
The address is loaded into the memory generator. From then on, the CPU will access the gdt Based on the content in the memory.
Gdt is the data structure required by protected mode and is unique-no, and there cannot be multiple. In addition, as shown in its Global Descriptor Table, It is globally visible, which is true for any task.
In addition to gdt, IA-32 also allows programmers to build data structures similar to gdt, known as LDT (Local Descriptor
Table), but unlike gdt, LDT can exist in multiple systems, and it can be known from the LDT name that LDT is not globally visible, they are only visible to the tasks that reference them.
A task can have up to one LDT. In addition, each LDT itself acts as a segment and its segment descriptor is placed in gdt.
The IA-32 also provides a register ldtr for the LDT entry address, because only one task can be running at any time, so the LDT register also needs only one global. If
A task has its own LDT. When it needs to reference its own LDT, it needs to load its LDT segment descriptor into this register through lldt. When the lldt command is different from the lgdt command,
The operand of the lgdt command is a 32-bit memory address, which stores a 32-bit gdt entry address and a 16-bit gdt
Limit. The operand of the lldt command is a 16-bit selector. The primary content of this selector is the index value of the mounted LDT segment descriptor in the gdt.
The pattern for referencing a segment through the segment consortium is discussed in the same way.
LDT is an optional data structure. You can skip it. Using it may bring convenience, but it also brings complexity. If you want to keep your OS Kernel concise and portable, you 'd better not use it.
Reference the segments described by the segment descriptors in gdt and LDT is implemented through a 16-bit data structure, which is called the segment
Selector -- segment selection child. Its 13-bit height serves as the subscript index of the referenced segment descriptor in gdt/LDT, bit
2 is used to specify whether the referenced segment descriptor is put in gdt or in LDT. Bit 0 and bit 1 are RPL-request privilege levels, which are used for protection purposes, we will not discuss it in detail here.
In the loading segment register discussed earlier, the segment is used as the gdt/LDT index.
Selector: When a memory address needs to be referenced, the segment: offset mode is used. The specific operation is to mount the segment in the corresponding segment register.
Selector. According to this segment selector, you can find the corresponding segment in gdt or LDT.
Descriptor. This segment descriptor records the base of this segment.
Address, and then add offset to get the final memory address. As shown in:
2. Setup gdt
According to the discussion in the previous section, gdt is the data structure required by protected mode. Before entering protected mode, we must set gdt and load it into the corresponding registers through lgdt.
Although gdt can be placed anywhere in the memory, because the elements in gdt-Descriptor-are all 64-bit long, that is, they are all 8 bytes, so in order to make the CPU access speed to gdt reach the fastest speed, we should put the gdt entry address in 8 bytes alignment, that is, the address location is a multiple of 8.
In gdt, the first descriptor must be an empty descriptor, that is, its content should all be 0. If this descriptor is referenced for memory access, a general protection exception occurs.
If an OS does not use virtual memory, the segment mode is a good choice. However, modern OS does not use virtual memory, but the convenient and effective memory management method for implementing virtual memory is page-based.
Management. But on the IA-32 if we want to use page-based management, we can only use segment-based-there is no way to completely disable segment-based mode. However, we can try our best to minimize the effect of segments.
IA-32 provides
The segmentation mode of model can achieve this effect. This mode requires that at least two segment descriptors must be defined in gdt, and one is used to reference data
Segment, which is used to reference the code segment. The two segments contain the entire linear space, that is, the segment Limit = 4
GB, even if the actual physical memory is far from that, this space is defined to implement virtual memory by page management in the future.
Here, we are only in the booting stage, so we only need to set up gdt first, and then start the OS
After the kernel, the specific OS intends to set the gdt and memory management mode, which is set by the kernel itself. Booting only needs to provide the data segment and code segment of the kernel.
Set all linear spaces.
The format of the segment descriptor is shown in:
Specific code segments and data segments are shown in the following format:
In the booting stage
Mode. Three segment descriptors are defined here: the first is the empty descriptor specified by the system, the second is the code segment that references 4 GB linear space, and the third is the reference 4
The data segment of the GB linear space. This is the lowest gdt setting required by the "Basic flat model", but it is only in the booting stage to enter protected.
Mode, and provide a continuous, maximum linear space for the kernel, which is sufficient.
# Descriptor tables
Gdt:
. Word 0, 0, 0, 0 # dummy
. Word 0 xFFFF #4 GB-(0x100000*0x1000 = 4 GB)
. Word 0 # base address = 0
. Word 0x9a00 # Code read/exec
. Word 0x00cf # granularity = 4096,386
# (+ 5th nibble of limit)
. Word 0 xFFFF #4 GB-(0x100000*0x1000 = 4 GB)
. Word 0 # base address = 0
. Word 0x9200 # data read/write
. Word 0x00cf # granularity = 4096,386
# (+ 5th nibble of limit)
3. Load gdt
After setting gdt, we need to use the lgdt command to load the set gdt entry address and gdt table size into the GDTR register.
The GDTR Register consists of a 32-bit linear base address and a 16-bit gdt size (in bytes ). Note that for a 32-bit linear Base
The address must be a 32-Bit Absolute physical address, rather than the offset relative to a specific segment. In the booting stage
Before mode, we may not set CS and DS to 0, so we must calculate the absolute physical address of gdt.
To execute the lgdt command, you need to place the two parts in a certain location of the memory, and then pass the memory address at this location as the operand to the lgdt command. The lgdt command then automatically loads the two scores saved in this position into the GDTR register.
# This is the location for storing the two parts required by GDTR.
Gdt_48:
. Word 0x8000 # gdt Limit = 2048,
#256 gdt entries
. Word 0, 0 # gdt base (filled in later)
# The following code is used to calculate the 32-bit linear address of gdt and load it into the GDTR register.
Xorl % eax, % eax # compute gdt_base
Movw % ds, % ax # (convert % DS: gdt to a linear PTR)
Shll, % eax
Addl $ gdt, % eax
Movl % eax, (gdt_48 + 2)
Lgdt gdt_48 # Load gdt with whatever is appropriate
4. Other preparing stuff
Before entering protected mode, in addition to setting and loading gdt, you also need to do the following:
Shield all blocked interruptions;
Load idtr;
All coprocessors are correctly reset.
Because the interrupt processing mechanisms in real mode and protected mode are different, you must disable all blocked interruptions before entering protected mode. either of the following two methods can be used:
Use CLI commands;
Program the 8259a Programmable Interrupt Controller to shield all interruptions.
Even when we enter the protected mode, we cannot immediately open the interrupt, because we must initialize the required data structure for the related Protected Mode Interrupt Processing in the OS kernel, to enable the interrupt. Otherwise, a processor exception occurs.
In real mode, IVT (interrupt vector table) is used for interrupt processing.
In mode, the Interrupt Processing uses IDT (Interrupt Descriptor Table). Therefore, we must
Set idtr before mode.
The format of idtr is the same as that of GDTR, and the loading method of idtr is the same as that of GDTR. Because the interrupt processing program in IDT needs to make the OS
In the booting stage, we only need to set the IDT base address and size in idtr to 0, and then enter protected.
After the mode, the OS Kernel will actually set it.
For details about the interrupt mechanism and handling, refer to interrupt & exception.
#
# This is the location where the idtr content is stored.
#
Idt_48:
. Word 0 # IDT Limit = 0
. Word 0, 0 # IDT base = 0l
# For idtr processing, you only need this command.
LIDT idt_48 # Load IDT with 0, 0
#
# Block all blocked interruptions by setting the 8259a PIC
#
Movb xFF, % Al # mask all interrupts for now
Outb % Al, Xa1
Call Delay
Movb xfb, % Al # mask all IRQ's but irq2 which
Outb % Al, X21 # is cascaded
# Ensure that all coprocessor operations are correctly Reset
Xorw % ax, % ax
Outb % Al, xf0
Call Delay
Outb % Al, xf1
Call Delay
# Delay is needed after doing I/O
Delay:
Outb % Al, X80
RET
5. Let's go
All right, everything is ready, fire! :)
Enter protected mode, or enter real mode, fully controlled by the PE flag of the Cr0 register: If PE = 1, the CPU is switched to PM; otherwise, RM is entered.
There are two ways to set the CR0-PE bit:
The first is the lmsw command used by 80286. Later, the lmsw command was reserved for the CPU of 80386 and later versions to maintain backward compatibility. This command can only affect the lowest 4 bit, namely, PE, MP, em, and Ts, but does not affect others.
#
# Use the lmsw command to enter Protected Mode
#
Movw, % ax # protected mode (PE) Bit
Lmsw % ax # This is it!
The second is Intel's recommended pm entry method on CPU after 80386, that is, the mov command. The mov command can be used to set the values of all fields in the Cr0 register.
#
# Use the mov command to enter Protected Mode
#
Movl % Cr0, % eax
Xorb, % Al # Set Pe = 1
Movl % eax, % Cr0 # Go !!
OK. Now the protected mode is enabled.
Very simple, right? But it's not over yet!
6. Start Kernel
We have switched from real mode to protected mode. Now we are about to start the OS kernel.
The OS Kernel runs in the 32-bit segment mode, but we are still in the 16-bit segment mode. What's going on? In order to understand this problem, we need to carefully discuss the implementation method of the segment mode of IA-32.
The IA-32 provides a total of 6 16-bit segment registers: CS, DS, SS, es, FS, Gs. But in fact, the 16-bit is only visible to the programmer, but each register still contains the 64-bit invisible part.
The visible part is for the programmer to load the segment register, but once the loading is complete, the CPU is actually used only the invisible part, and the visible part is completely useless.
What is invisible content? I have not seen the relevant information about the specific format, but it can be determined that the content of the hidden part is consistent with the content of the segment descriptor (see the format of the segment description ), however, the format may be different. But the format is not important for us to understand this, because it is impossible for programmers to directly operate on it.
We use the CS register as an example. The same is true for other registers:
In real
In mode, when we execute a command to load the CS register (JMP, call, RET, etc.), the relevant values will be loaded into the visible part of the CS register, however, the CPU
See the content in the section to set the invisible part. For example, Run "ljmp x1234, $ go
"Then, the visible part of the CS register is 1234 h, and the invisible part of the 32-bit Base
The address domain is set to 00001234 h, and the 20-bit limit domain is set to a fixed value of 10000 h, that is, 64 KB, access
We do not consider other values in the information part, but only the D/B bit.
Mode mode. Therefore, D/B is set to 0, indicating that this segment is a 16-bit segment. After the visible and invisible parts of the CS register are set, the loading of the CS register is complete.
. Then, when the CPU needs to perform address calculation through the CS content, only the invisible part is referenced.
In protected mode, when we execute a command to load the CS register, segment Selection Sub (segment
Selector) is loaded into the visible part of the CS register, and the CPU finds the corresponding segment descriptor in the corresponding Descriptor Table (gdt or LDT) based on this selection, and loads its content into CS for sending
Invisible part of the memory. Then, when the CPU needs to perform address calculation through the CS content, it only references the invisible part.
From the above description, we can see that the real mode and protected mode are consistent when the CPU calculates the address based on the content of the reference segment register. In addition, we also understand why the segment register we set in real mode still References 16-bit segments in protected mode.
So how can we set CS to reference a 32-bit segment? The method is as discussed earlier. Using the JMP or call command, reference a segment Selection Sub and load a segment descriptor that references a 32-bit segment in gdt.
Note that if the CS Register indicates that the current 16-bit segment is used, the current address mode is also the 16-bit address mode.
Mode or protected
Mode. However, we must use the 32-bit address mode for the JMP or call commands that load 32-bit segments. The current Boot Code is 16-bit.
So we must add the address translation Prefix code 66 h before the JMP/call command.
The following example uses the JMP command to load 32-bit segments. The meaning of the jmpi command is inter-segment jump. Its opcode is eah, and its format is jmpi offset and segment selector.
# Because the current code is a 16-bit code, we need to execute the 32-bit address mode command, before the command
# The address mode needs to switch the prefix of 66 h. If we directly write the JMP command, the compiler will generate the code.
# This is not possible, so we can directly write the relevant data.
. Byte 0x66, 0xea # prefix + jmpi-opcode
. Long 0x1000 # offset
. Word _ kernel_cs # CS segment Selector
The above code is equivalent to a 32-bit command:
Jmpi 0x1000 ,__ kernel_cs
If the _ kernel_cs segment is set to the linear Address [1000 GB] for the segment descriptor referenced by the child, and the OS kernel is placed on the physical address H, then the jmpi command jumps to the entrance of the OS kernel and starts executing it.
At this point, the booting stage is over and the OS is officially started!