Protection Mode Overview

Source: Internet
Author: User
Protection Mode Overview

From: http://www.pagoda-ooos.org/

In the 8086/8088 era, there was only one operating mode for the processor. At that time, this mode was not named because there were no other operating modes. 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, 8086/8088 of the schema 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. This mode is recommended for all new applications and operating systems. To ensure PM compatibility, the processor allows RM programs to be executed in a protected, multitasking environment. This feature is called virtual-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. It appears on the 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 an external SMM interrupt pin (SMI #) is activated or an SMI is received from the APIC (Advanced Programming Interrupt Controller), the processor enters the SMM. In SMM, when the context of the currently running program is saved, the processor switches to a separate 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 provides 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 address of a segment, the maximum length of a segment is 64 KB, which is the maximum length that a 16-bit system can represent. Offset is the offset relative to the segment base address. 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 the segment), and the access to a memory address needs to be pointed out: Which segment is used? And the offset relative to the base address of this segment, this offset 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, and the 16-bit offset can never be larger than this limit. In actual programming, we use the 16-bit segment register CS (code segment), DS (Data Segment), and SS (stack segment) to specify the segment, the CPU shifts the value in the block memory to 4-bit on the left and places it on the 20-bit address line to form the 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 to say, the memory management mode of protected 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, it is a segment-page mode; otherwise, it 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 protected mode runs on a 32-bit system, the two factors of segment: base address and limit are also 32-bit. The IA-32 allows the base address of a segment to be set to any value that 32-bit can represent (limit can be set to 32-bit to represent, it refers to anything that is multiples of 2 ^ 12), unlike in real mode, the base address of a segment can only be a multiple of 16 (because its low 4-bit is obtained through the left shift operation, it can only be 0, in this way, the 16-bit segment register is used to indicate 20-bit base address), while the limit of a segment can only be a fixed value of 64 KB. In addition, the protected mode, as its name implies, 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 mode, the description of a segment includes three factors: [base address, limit, access], they are put together in a 64-bit long data structure, called segment descriptors. In this case, if we use a 64-bit segment descriptor to reference a segment, we must use a 64-bit long segment memory to mount the segment descriptor. However, to maintain backward compatibility, Intel still sets the CIDR block to 16-bit (although each CIDR Block actually has a 64-bit invisible part, but for programmers, the block memory is 16-bit. Obviously, we cannot directly reference the 64-bit segment descriptor through the 16-bit length block memory.

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, is to use the content of the 13-bit high in the segment register as an index ). The global array is gdt. In fact, gdt stores not only segment descriptors, but also other descriptors. They are all 64-bit long and will be discussed later.

Gdt can be placed anywhere in the memory. When a programmer uses a segment register to reference a segment descriptor, the CPU must know the gdt entry, that is, where the base address is located, therefore, Intel designers provide a register GDTR to store the gdt entry address. After the programmer sets the gdt to a location in the memory, you can use the lgdt command to load the gdt entry address into the memory generator. From then on, the CPU will access the gdt according to the content in the memory generator as the gdt entry.

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, and each 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 operation of the lgdt command is a 32-bit memory address, which stores a 32-bit gdt entry address, and 16-bit gdt limit. The operand of the lldt command is a 16-bit selector. The main content of this selection is: the index value of the mounted LDT segment descriptor in gdt -- this is the same as the pattern used to Reference segments through the segment memory just discussed.

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 selector. Its 13-bit high is used as the subscript index of the referenced segment descriptor in gdt/LDT, and bit 2 is used to specify whether the referenced segment descriptor is put in gdt or in LDT, bit 0 and bit 1 are the request privilege levels of RPL, which are used for protection purposes. We will not discuss them in detail here.

The segment selector is used as the gdt/LDT index in the mounted segment register discussed earlier. When a memory address needs to be referenced, the segment: offset mode is used. The specific operation is as follows: load the segment selector in the corresponding segment register. According to this segment selector, you can find the corresponding segment descriptor in gdt or LDT. The segment descriptor records the base address of this segment and adds offset, the final memory address is obtained. 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.

The IA-32 provides a segmentation mode called the basic flat model. This mode requires that at least two segment descriptors must be defined in gdt, one for referencing data segment, and the other for referencing 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 less than that, however, 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. After the protected mode is enabled and the OS kernel is started, how do we set gdt for the OS, the memory management mode is set by the kernel itself. Booting only needs to set all linear space for the data segment and code segment of the kernel.

The format of the segment descriptor is shown in:

Specific code segments and data segments are shown in the following format:

The following is the temporary gdt set for entering the Protected Mode in the booting phase. Three segment descriptors are defined here: the first is the empty descriptor specified by the system, the second is the code segment that references the 4 GB linear space, and the third is the data segment that references the 4 GB linear space. This is the lowest gdt setting required by the "Basic flat model", but it is in the booting stage, just to enter the Protected Mode and provide a continuous kernel, the goal of the largest linear space is enough.

# 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 the 32-bit linear base address must be a 32-Bit Absolute physical address, rather than the offset relative to a specific segment. In the booting stage, before entering the protected 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 $4, % 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, interrupt processing uses IVT (interrupt vector table), and in protected mode, interrupt processing uses IDT (Interrupt Descriptor Table). Therefore, we must set idtr before entering protected 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 be set by OS kernel, In the booting stage, we only need to set the IDT base address and size in idtr to 0, then, after entering the protected 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 $ 0xff, % Al # mask all interrupts for now
Outb % Al, $0xa1
Call Delay

Movb $ 0xfb, % Al # mask all IRQ's but irq2 which
Outb % Al, $0x21 # is cascaded

 

# Ensure that all coprocessor operations are correctly Reset

Xorw % ax, % ax
Outb % Al, $0xf0
Call Delay

Outb % Al, $0xf1
Call Delay

 

# Delay is needed after doing I/O

Delay:
Outb % Al, $0x80
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 $1, % 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 $1, % 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 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 also sets the invisible part based on the visible part. For example, after we run "ljmp $0x1234, $ go", the visible part of the CS register is 1234 H. At the same time, the invisible 32-bit base 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, we do not consider other values in the access information part. We only consider its D/B bits. Because the command is in real mode, D/B is set to 0, indicates 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, the segment selector is loaded into the visible part of the CS register, at the same time, the CPU finds the corresponding segment descriptor in the corresponding Descriptor Table (gdt or LDT) based on this selection and loads its content into the invisible part of the CS register. 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 16-bit, this is irrelevant to whether you are using real 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!

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.