This documentation was written due to the increase in the number of people in the DJGPP mailing list asking for details regarding what protected mode really is. The documentation contains detailed information about protected mode. Starters who wish to merely touch upon "protected mode programming" should generally read only the introduction, while others who require more information can get into the depths.
If you have other questions, you can post to the PMode list. The instructions to join this list are given at the end of this document.
What is Protected Mode?
The 80386+ provides many new features to overcome the deficiencies of 8086 which has almost no support for memory protection, virtual memory, multitasking, or memory above 640K, and also remains compatible with the 8086 family. In DJGPP, the 386, 486, etc. are considered to be identical. The points made about the 386 apply to all its successors as well.
The 8086 runs in only one mode - the real mode. When Intel engineers designed the 286, they wanted to support extra features that would be incompatible with the 8086. They also wanted to maintain 8086 compatibility. To satisfy these requirements, the 286 uses two modes - Real Mode and Protected Mode. Real mode, the default, makes the chip act like an 8086 with only minor enhancements. There is a huge difference when it comes to protected mode. Almost all programs designed to run on an 8086 won't run under protected mode without major changes. DOS is one of these programs.
The 386 has all the features of the 8086 and 286, with many more enhancements. The default, as in the earlier processors, is real mode. Like the 286, the 386 can operate in protected mode. However, the protected mode on 386 is vastly different internally. Protected mode on the 386 offers the programmer better protection and more memory than on the 286. The 386 also supports a third mode, Virtual 8086 (V86) mode. In V86 mode, the 386 operates in protected mode but allows some programs it is running to use a simulated real-mode environment. That means programs like DOS can run in protected mode without the need for switching between real and protected modes. The V86 mode has several advantages over real mode which are mentioned in the chapters that follow.
All the 386's special features become available in the processor's protected mode. Some of the extra powers of this mode are mentioned here...
Access to 4 gigabytes of memory - This is the most obvious difference between protected mode and real mode. Protected mode programs can use up to 4 GB of memory for data, code and stack space. Using some undocumented features of the 8086 processors, it is possible for real-mode programs to access memory above the 1MB limit for data storage. However, using these techniques for code and stack space is generally impractical. Of course, you probably won't have 4GB of memory installed on your system. That brings us to the next feature.
Virtual memory - The Memory Management Unit (MMU) on the 386 allows virtual memory to be implemented, which makes a program think that it has 4GB of memory when it has less (actually much less). The 386 and special operating system software simulate the extra memory using a mass storage (like a hard disk drive). Of course, you need about 4GB of free disk storage space, but that's another problem.
Address translation - The MMU also allows addresses to be translated, or mapped, before use. For example, you might want to translate all references to a 4KB block at a segment B800H (the CGA text buffer) to a data buffer in your program. Later, your program could copy the buffer to the screen. This is useful when redirecting the output of a program that directly writes to the screen. Translation can also simulate expanded memory without an expanded memory board. But there are also certain functions in the later versions of EMS which can't be emulated by the MMU. But you will hardly require those functions even if your program is advanced.
Programs work with logical addresses. The 386 converts these logical addresses into 32-bit linear (non-segmented addresses). The MMU then converts linear addresses to physical addresses. If the MMU isn't active, linear and physical addresses are equivalent. Applying this terminology to real mode, the address B800:0010 is a logical address. Its equivalent linear address is B8010H. Since real mode doesn't use the MMU, the physical address is the same as the linear address.
Improved segmentation - In real mode, all segments are 64KB long and are in fixed locations. In protected mode, segments can be as short as one byte or as long as 4GB. The function __djgpp_nearptr_enable() uses this feature. Attempting to access memory past the end of a segment will cause an error. If the segment is 4GB long, then the addresses get wrapped if a program tries to access beyond the 4GB limit. Segments may start off at any location. In addition, the programmer determines each segment's intended use, which the 386 enforces. That is, if the program attempts to write data into a segment meant for code, the 386 will force an error. You also can define a segment that covers the entire address range of 4GB and effectively dispense with segments altogether. All memory references are then via 32-bit non-segmented pointers. These flat pointers correspond directly to linear addresses.
Memory protection - The 386 allows memory to be protected. For example, a user's program may not be able to overwrite operating system data. This, combined with the checks on segments, protects programs against bugs that would crash the computer.
Process protection - In a similar fashion to memory protection, different programs (or parts of a program) can be protected from each other. One program might not have access to another program's data, while the operating system might have access to everyone's data. Conversely, user programs may have only limited access to the operating system's data. This is actually implemented using the page protection mechanism provided by the MMU.
32-bit registers - All general-purpose registers on the 386 are 32-bits wide. Except for the E prefix (ex: EAX instead of AX), these registers have the same names as in the 8086. Two new segment registers (FS and GS) are also available; they are accessible from all modes but are most useful in protected mode programs. Real-mode programs can also access these 32-bit registeres, but they won't use them for indexing purposes. And using 32-bit registers in protected mode (32-bit protected mode) will cut down the size of the code generally.
Improved addressing modes - In real mode, programs can only
form addresses with constant values, the BX or BP register, and the SI
or DI register. In protected mode programs, any register can form
addresses. An index can include a scale factor of two, four or
eight. This allows you to write instructions like MOV EBX,
[EDI][EAX*8]+2
.
Multitasking support - The 386 has special provisions to save the current processor state and switch to a new task (known as context switch). A single instruction can switch contexts rapidly. This has important ramifications for operating systems and real-time processing. The 386 also supports nested tasks. A task can return to its original task using a back-link.
Hardware debugging - The 386 has special hardware for implementing single-step code and data breakpoints. This hardware is available in real mode with some special techniques.
In the 8086-type processors, memory is organized into bytes (8 bits=1 byte). When dealing with quantities larger than eight bits, the 8086 stores the least significant byte in the lowest address. While that sounds logical, it's confusing when you're reading listings or memory dumps because the numbers seem backwards. For instance, the computer stores the word B800H as two bytes: 00H followed by B8H.
The Intel family of processors use a memory-addressing technique known as segmentation. A segment is a region of memory. The computer can handle multiple segments. In real mode (the one in which DOS normally runs), each segment is 64K long and there are 65536 possible segments. But these segments overlap so that each starts 16 bytes after the one before it. This is why DOS cannot address more than 1MB directly (65536 * 16 = 1048576 = 1MB). The 8086 and 8088 can only address 1MB anyway. The 286, 386+ can accomodate much more memory, but DOS cannot access it directly.
Segments are numbered from 0000H to FFFFH. Since each segment is 64KB long, we use a value called an offset, to specify the byte we want to address. A complete 8086 address always contains a segment and an offset.
If a segment is 0040H and the offset is 0102H, we write 0040:0102. Because segments overlap every 16 (10H) bytes, address 0000:0010 is identical to address 0001:0000. Likewise 0040:0000 is the same as address 0000:0400, which is the same as 0020:0200. The computer also stores segmented addresses ``backwards''. For instance, 0040:1234 appears like this in the computer's memory (in hex): 34 12 40 00 To verify that all the addresses above are the same, convert them to linear addresses. To convert a segmented address to linear address, multiply the segment value by 16 (10H) and add the offset. So, we have (all in hex) 0040 * 10 + 0000 = 00400 0000 * 0 + 0400 = 00400 0020 * 10 + 0200 = 00400 all of which ultimately point to the same area of memory.
The 80386 has four general-purpose registers, a flags register, six segment registers, two index registers, a stack segment register and pointer, base register and an instruction pointer register. In addition to these, there are other special registers on the 386, namely:
GDTR (Global Descriptor Table Register) IDTR (Interrupt Descriptor Table Register) LDTR (Local Descriptor Table Register) TR (Task Register) CR0-CR3 (Control Registers) DR0-DR7 (Debug Registers)
The figure below shows most of these registers and their significance. As you can see, the 386+ have a variety of special registers. They usually aren't useful under DOS, but we'll see some of them when we look at protected mode extensions to DOS.
The 8086 family of processors can respond to 256 different
interrupts. Starting at location 0000:0000 is a table of addresses for
each interrupt. Each entry in this interrupt vector table is four
bytes long, enough for a segment and an offset. So, we have 1024 bytes
or 1K reserved for the Real Mode Interrupt vector table. If the
processor receives an interrupt 2 (INT 2), for example, it saves the
flags and current values of CS and IP on the stack. It then obtains the
address stored at location 0000:0008 and executes the interrupt service
routine (ISR) at that address. An IRET
instruction in the
ISR signals the end of the interrupt1 and causes the
processor to resume what it was doing before the interrupt.
A hardware interrupt is a special signal from an I/O device to the computer. This signal informs the computer that the I/O device requires attention. The processor will normally stop whatever it is doing to service the interrupt. When the interrupt completes, the processor resumes execution where it left off.
Hardware interrupts can come from various sources. For example,
every time you press or release a key, you generate an interrupt. Other
interrupts originate from the clock, printer, serial port, disk drives
and so on. The processor itself causes some interrupts. INT 0
for
instance, occurs when a program attempts to divide by zero. These are
all hardware interrupts. Hardware interrupt handling is perhaps one of
the most important functions in protected mode. Badly written hardware
interrupt handlers can cause disasters.
The 8259 Programmable Interrupt Controller (PIC) on the motherboard manages all hardware interruptrs. These controllers take interrupt signals from various devices and convert them to specific interrupts for the processor.
Tha table below shows the hardware interrupts in real mode with their corresponding PIC interrupt request inputs (known as IRQ lines). Do not confuse IRQ numbers with interrupt numbers. For example, the keyboard connects to IRQ1, which sends INT 9 to the CPU. The PICs can be reprogrammed to generate different interrupt numbers for each IRQ. This technique is generally used by screen trapping programs and also by DOS Extenders such as go32.
The PICs also control the priority of interrupts. For example, the clock (on IRQ 0) has a higher priority than the keyboard (IRQ 1). If the processor is servicing a clock interrupt, the PIC won't generate an interrupt for the keyboard until the clock's ISR resets the PIC. On the other hand, the clock can interrupt the keyboard's ISR. The PICs can be programmed to use a variety of priority schemes, but this is rarely, if ever, seen in PC programming.
The AT's extra PIC connects to the IRQ 2 of the first PIC. Therefore, the extra PIC's IRQs (8 through 15) have the same priority as IRQ 2. Disabling IRQ 2 disabled all of the second PICs interrupts.
Interrupt | IRQ Number | Description
|
00H | - | Divide by zero or divide overflow
|
02H | - | NMI (Non-maskable Interrupt)
|
04H | - | Overflow (generated by INTO)
|
08H | 0 | System timer
|
09H | 1 | Keyboard
|
0AH | 2 | Interrupt from second PIC
|
0BH | 3 | COM2
|
0CH | 4 | COM1
|
0DH | 5 | LPT2
|
0EH | 6 | Floppy Disk
|
0FH | 7 | LPT1
|
70H | 8 | Real Time Clock
|
71H | 9 | General I/O
|
72H | 10 | General I/O
|
73H | 11 | General I/O
|
74H | 12 | General I/O
|
75H | 13 | Coprocessor
|
76H | 14 | Hard Disk
|
77H | 15 | General I/O
|
You can stop interrupts from disturbing an important section of code in
several ways. The CLI
instruction disables all interrupts except
the nonmaskable interrupt NMI since the NMI, which does not go
through a PIC cannot be disabled this way2. In addition, PICs
can be reprogrammed to turn off specific interrupts.
Normally, we would expect the 80386 to wrap addresses in real
mode. Anticipating this, the AT motherboard designers routed the
A20
line through the keyboard controller. Ordinarily, the
controller blocks A20 from reaching the memory chips. A special command
to the keyboard controller can enable the A20, and another command can
disable it again later. This so-called A20 gate is essential to
accessing extended memory, whether in real or protected
mode3.
Understanding segments is the key to understanding protected mode. Protected mode segments have little in common with real-mode segments. A protected-mode segment register holds a 16-bit segment selector (see the figure below). Unlike in real mode, the selector has nothing to do with the segment's location in memory. Instead, the value in the register is an index into a table of segment descriptors. Each descriptor defines one segment and determines where the segment resides, the segment type, and other important parameters such as the access rights.
The selector contains three fields. The lowest two bits (RPL) pertain to the 386's protection mechanism which is explained shortly. The next bit, TI, determines which table of descriptors defines the segment.
There are three segment descriptor tables:
Segment selectors never refer to the IDT. If TI is zero, the segment's definition is in the GDT. If it is one, the LDT contains the definition.
Each descriptor table can hold upto 8192 descriptors. The INDEX
bits (bits 15 through bit 3) in the selector determine which descriptor
to use.
The GDTR and IDTR registers determine the location of the GDT and IDT respectively. Each contains a 32-bit address and a 16-bit limit. The limit is one less than the length of the table in bytes. The GDTR and IDTR are 48-bit registers. The address is linear rather than a segment-offset pair. Each table can contain upto 64KB or 8192 descriptors.
The GDT, as its name implies, is global; even when the system is multitasking, all tasks share the GDT. This is also true of the IDT - each task uses the same one. If one task changes the GDT or IDT, all tasks are affected.
The LDTR determines the location of the LDT. Unlike the GDT, each task usually has its own LDT. Unlike the GDTR, the LDTR does not contain a 48-bit address and limit. Instead, it holds a segment selector that must point to a special entry in the GDT. This GDT entry points to the LDT. The GDT may contain pointer to several LDTs.
The IDT is analogous to the real-mode interrupt vector table. Each descriptor defines the response to one of the 256 possible interrupts. Even though the IDT can contain upto 8192 descriptors, any more than 256 is a waste.
The figure below show the logical format of a basic descriptor table entry. Also, other special types of descriptors are also shown.
Two fields in the segment descriptor are particularly interesting. The P bit (bit 47) determines whether the segment is present. An operating system can clear this bit to create a virtual segment. When a program tries to use a virtual segment, the 386 generates an error. The operating system can then load the segment from the disk and try again. When P is clear, bits 0 through 39 and 48 through 63 can contain any values. The operating system could store a disk address here, for example. But this is unsuitable is the segment size is large. Fortunately, the 386 provides a better way to use virtual memory.
The other interesting field is the A bit (bit 40). The 386 sets this bit when any program writes to the segment. Our crude virtual memory system might use this bit to decide whether it should write a segment to disk before making it absent and reusing its space.
Another question is how a descriptor can specify a segment from one byte to 4GB long. The limit field given is only 20 bits. This is where the G bit (bit 55) comes into play. If G is zero, the limit field corresponds to one segment's maximum legal offset (one less than its length). If G is one, however, the 386 shifts the limit field 12 places left to make a 32-bit limit. The 386 fills the bottom 12-bits with ones.
For example If a dscriptor has a limit field of one and G is one, the actual limit is 1FFFH. When G is one and limit is zero, the limit is FFFFH.
This scheme allows us to specify a segment with a length of less than 1 Mbyte or a multiple of 4 Kbytes. When G is zero, the segment can range from one byte to 1 Mbyte long. When G is one, the segment can range from 4 Kbytes to 4 Gbytes in length (in 4 Kbytes steps).
It is possible to create two or more descriptors that point to the same area of memory. An operating system, for example, might load a file into a data segment and jump to a code segment that starts in the same place. This process is known as aliasing. The operating system must have some way of loading the GDT and other tables. Therefore, it will usually have a data segment alias to the GDT. Some systems set up a data segment that covers all 4GB of memory. Tables can then be written directly with that data sgement. Operating systems rarely allow user programs to access the GDT, IDT and other system tables directly.
The processor reserves the first slot of the GDT. The selector for this slot is the null selector, and using it always causes an error. The null selector's descriptors should be all zeros. Non-zero values work but you never know ;-).
Protected mode gets its name from the 386's privilege protection. Each program has a privilege level, or PL, from zero to three. Programs at PL0 can execute any instruction and access any data. Programs at PL3 can't execute certain instructions; they also can't access data that belong to more privileged programs. Each segment descriptor has a Descriptor Privilege Level (DPL) that the 386 uses for protection. The 386 also controls which programs can execute I/O instructions.
A privilege hierarchy is important for supporting modern operating systems. In a typical operating system, the main kernel runs at PL0. Other parts of the operating system might run at PL1. Device drivers can run at PL2; they need to do direct device I/O. User programs in this system would run at PL3. This scheme has many advantages. In particular, a malicious program can't damage the operating system or other user programs.
PL0 programs alone can execute the following instructions: •HLT •CLTS •LGDT •LIDT •LLDT •LTR •LMSW •MOV (to/from control/debug/test registers) On 486 systems •INVD •WBINVD •INVLPG On Pentium and above •RDMSR •WRMSR •RDTSC
The IOPL field in the EFLAGS register allows the operating system to control who can do I/O. These two bits determine the minimum privilege level a program must have to execute I/O instructions (CLI, STI, IN, INS, OUT and OUTS). If IOPL is zero, only PL0 programs can do I/O. If IOPL is 3, all programs can execute I/O instructions. Only a PL0 program can modify the IOPL flags. When other programs modify the flags, IOPL doesn't change! Leaving IOPL=3 makes life easy for DOS Extenders, but it can cause major problems since real-mode programs mostly use direct I/O.
A program's privilege level is equal to the RPL field of the selector in the CS register. This is the current privilege level or CPL. You can't directly modify the CS register so that it has a different RPL. The same holds true for SS as well.
Programs can't load a sgement register with just any selector. When a data segment register (DS, ES, FS or GS) is loaded, the 386 checks the DPL against the program's CPL and the selector's RPL. The 386 first compares the CPL to the RPL. The largest one becomes the effective privilege level (EPL). If the DPL is greater than ot equal to the EPL, the 386 loads the sgment register; otherwise an error occurs.
The SS register must be loaded with a segment whose DPL and CPL are equal. The 386 also checks to make sure a stack segment is readable, writeable and present.
The 386 provides a special stack segment type. You can also use a plain data segment for a stack, if you wish. A stack segment's limit field indicates the lowest legal offset in the segment.
It is always valid to load a null selector (0 through 3) into a segment register. However, any attempt to access memory via the selector will cause an error as expected.
The 386, as mentioned earlier, has support for multitasking, i.e. running several processes concurrently. In reality, however, they do not run concurrently. It only appears to the user as though they were all running at the same time.
The 386 uses the Task State Segments (TSSs) to support multitasking. The TSS descriptor points to a buffer which must be at least 104 bytes long. In addition to multitasking, TSSs can also be used for hardware interrupt handling (using task gates in the IDT). The TSS selector is neither readable nor writeable. Generally, a TSS alias is created, which is nothing but a "data type" segment pointing to the TSS buffer. TSS selectors always appear in the GDT, never in LDT or IDT. However, as mentioned above, task gates may appear in the IDT. The processor uses the TSS selector internally.
Since a multitasking switch requires the processor state to be saved, the buffer mostly contains the contents of the hardware registers. When a task switch occurs, the processor saves various details in the TSS buffer automatically. This process is very quick and hence not many CPU cycles are wasted in switching to a new task. Before a task is initially started, the operating system has to fill in certain entries in the TSS buffer.
Notice the slots for stack segments and pointers for PL0, PL1 and PL2 near the top of the TSS. A program must maintain separate tasks for each privilege ring it may use. In particular the PL0 stack segments and pointers are a must. Otherwise, the processor may shut down due to a double fault in case of an interrupt.
A task switch may occur during a "far jump" or a "far call". The offset of the call or jump is simply ignored. The values in the TSS are loaded into the registers and the new task beings to execute. The selector referred by the jump or call must be a TSS selector or a task gate. The task gate contains the TSS selector. But unlike the TSS selector, the task gate may occur in the GDT, IDT or LDT. The EPL must always be less than or equal to the TSS's DPL as in case of the normal segment selectors.
The 386 also supports nested tasks. It handles nested tasks using the NT bit in the EFLAGS register. Whenever a task "calls" another task, the 386 stores the old task's TSS selector in the "back-link" field of the new TSS. Also, the NT bit in the EFLAGS register is set. When the new task wishes to return to the old one, it issues an IRET instruction4.
The TSS aren't reentrant. Whenever a task is running, the 386 sets the BUSY bit in the TSS selector to indicate this. This is done to prevent recurive calling of tasks.
As mentioned earlier, the TSS must be at least 104 bytes long. The size of the TSS may extend to any size. The 386 contains a pointer to an I/O bitmap in the last field. The size of this bitmap is usually 8K, but may be lesser. The I/O bitmap is optional. If the size of the I/O bitmap is lesser, entries past the end of this bitmap are considered as "1s". Each bit in the I/O bitmap corresponds to one I/O port. If a task tries to do I/O, the 386 checks the task's CPL against the IOPL. If the CPL is less than or equal to the IOPL, access is granted, otherwise the 386 checks the I/O bitmap. If the bit corresponding to the I/O port used is zero, the 386 allows access, otherwise it denies access. A bitmap should always end with 0FFh.
The 386 has support for 256 interrupts (or exceptions) just as in the 8086. The Interrupt Descriptor table (IDT) contains the definition for each of these interrupts. The IDT can have 8192 entries. But if more than 256 of these entries are present, they simply remain unused. The IDT may contain trap gates, interrupt gates or task gates. A trap gate is one that does not clear interrupts before entering the exception handler. However, an interrupt gates disables all interrupts before entering the handler. Details about task gates have been given elsewhere in this document. See Multitasking.
Interrupts may be caused due to external hardware (which comes through the PIC. See Hardware Interrupts.), may be caused by the processor itself, or may be a software interrupt. Carefully handling processor generated and hardware interrupts is one of the major tasks of an operating system. Slightest errors in these interrupt handlers may result in disasters and blue screens ;-).
The processor generated 386 exceptions can be classified into faults, traps and aborts.
A fault is a correctable error. Most invalid operations result in faults. When a fault occurs, the CS:EIP points to the instruction that caused the fault. Faults are considered to be the least serious exceptions. The most common of these faults is the "General Protection Fault (GPF)".
Traps accur whenever a software interrupt occurs. Software interrupts are caused due to the execution of INT and INTO instructions. When a trap occurs, the CS:EIP points to the instruction following the actual instruction that caused the trap.5 The system cannot restart traps.
Aborts are serious errors that indicate that there may be a severe problem with the operating system itself. The Double Fault and FPU Overrun are considered as aborts.
The table below shows the various exceptions with their classifications:
Exception (#num) Type
|
Divide Error (0) Fault
|
Debug (1) F/Trap
|
Breakpoint (3) Trap
|
Overflow (4) Trap
|
Bounds Check (5) Fault
|
Bad Opcode (6) Fault
|
No Coprocessor (7) Fault
|
Double Fault (8) Abort
|
Coprocessor Overrun (9) Abort
|
Invalid TSS Segment (0A) Fault
|
Segment not Present (0B) Fault
|
Stack Fault (0C) Fault
|
General Protection Fault (0D) Fault
|
Page Fault (0E) Fault
|
Coprocessor Error (10) Fault
|
The 386 has at least two facilities to support memory management. They are:
Among the two types, use of the segmentation mechanism is a must, while paging is optional to the user. Paging provides a mechanism to implement virtual memory used by almost all operating systems today. CWSDPMI, the official DJGPP DPMI server, uses this too. The advantage of using the paging mechanism is that you can restrict access to specific portions of the memory, while that would be hard to do with a segmentation scheme. You can enable paging using the CR0 register. Segments are used to keep track of data, code and the stack a certain program is using. However, an operating system might also use a single selector that covers the entire 4GB of addressable space and described earlier in this document. See Advantages.
Enabling paging, however isn't as simple as setting bit 31 of CR0. You need to set up the Page Directory Entries and the Page Table Entries, the PDEs and PTEs. The PDEs and PTEs are actually tables which are used to convert the logical address into the physical address. See Advantages.
As with most other functionality, only PL0 programs can activate the MMU. User programs never know that an MMU exists! The pages are classified as User/System. PL0 programs can access any page. PL3 programs can access a "User" page, but not a system page. Since 4GB of memory is often not available on most systems, the page tables for these aren't usually setup. The corresponsding entries in the PDEs are marked as "not present".
The MMU uses two types of tables to translate addresses - the Page Directory Entry and the Page table Entries. Each PDE corresponds to 4 Megabytes of contiguous memory. The PDE contains a pointer to the starting of the coressponding pgae tables. These PTEs can be used to set permissions for 4K blocks within the PDEs 4M area. The PTEs also contain the actual physical address. Note that the PDE contains the Physical Address of the page tables. To translate a linear address into a physical address, the MMU looks at bits 31-22 of the linear address. This is used to select the PDE in concern. Bits 21-12 are used to select one of the 1024 PTEs in the page table selected. The lower 12 bits form the lower 12-bits of the physical address.
Now, the question is how the MMU finds where the Page Directories are present. The CR3 register holds the base address of the PDEs. Hence, it is also known as the PDBR (Page Directory Base Register)6
As mentioned earlier in this document, the 80386 has a third mode called the V86 mode. In this mode, the 80386 behaves exactly like an 8086 with a few minor exceptions. In V86 mode, the CPU may not issue any of the privileged instructions such as LGDT, LIDT, LLDT, SLDT, ARPL, LAR, LSL, LTR, VERR, VERW, INVD, WBINVD, WRMSR, RDMSR, etc. However, unlike the real mode, the MMU is active in the V86 mode. Therefore, the operating system still has control over a V86 task, although it runs just like in real mode. Since privileged instructions cannot be executed, the OS can prevent system crashes or malicious programs from destroying any data.
A task is said to be a V86 task when the VM bit in the EFLAGS register is set. Only a PL0 program can set this bit. The bit can also be set in the TSS before the task is executed. The advantage of a having a V86 task rather than a real mode task is obvious - it's due to security reasons. The other reason is that the time taken by the CPU to switch from protected mode to real mode is considerable and greatly slows down the program. By running the program in V86 mode, the program is actually executed faster.
A V86 task, just like a real mode program, uses segments and offsets rather than a selector:offset pair. It can access 1MB of memory. A V86 task always runs at PL3. They can do I/O only if the IOPL is 3 or if the TSS bitmap allows them to do so. If IOPL is less than 3, then the CPU generates a General Protection Fault whenever the V86 task attempts to execute the following instructions - CLI, STI, INT, IRET, LOCK, PUSHF and POPF.
Interrupt Handling is one of the most important tasks of any operating system. In particular, without handling the hardware interrupts correctly, anything could happen. The system could crash or cause unusual problems. This section gives an overview of interrupt handling in Protected Mode and V86 Mode.
Interrupt handling in Protected Mode has been mentioned previously in this document. See Exceptions.
Interrupt handling in V86 mode is slighly more complex. Although the processor is in V86 mode, the interrupt handling still occurs in protected mode. Since the segment registers will most probably contain garbage values for the selectors, the 386 zeros all segment registers before calling the appropriate interrupt handler. Keep in mind that we're talking of hardware interrupts here, not the ones generated by the INT instruction. The SS will contain the selector for the PL0 stack in the Task State Segment. The structure of the VM86 stack frame which is pushed into the PL0 stack is as shown in the figure below.
When the General Protection Fault
occurs, it can be either due to
the fact that a V86 task was interrupted or it could be due to an
interrupt in a true protected mode program. To differentiate between the
two, the handler can look at the EFLAGS register to check if a V86 task
was running. If a V86 task generated the interrupt, the interrupt
handler can look at the offending instruction. Once the cause of
interrupt is known, the operating system can choose to emulate the
instruction (instructions like CLI, STI, etc.). or simply terminate the
program or take any other action.
Working in Protected Mode requires that you first switch to protected
mode. To switch to protected mode and do some real
work, you need
to follow these steps:
Setting up the GDT and IDT should be easy. Create the descriptors and then load GDTR with the address and length of the table. The programs by Alexei A Frounze describes all this.
Setting up the TSS can be easy, too, if you follow a neat procedure. Finally, do a far jump to the TSS and that should do it.
An excellent set of programs have been written by Alexei A Founze and can be downloaded from his website or get that from here.
To run these set of programs, you'll need a system with the following configuration.
The following is a brief description of each of the programs:
This document is written and maintained by Prashant TR. If you have comments, questions or any suggestions, please send E-Mail to Prashant TR or to the PMode list. Make sure you're referring to the latest version of this document on http://www.midpec.com/djgpp/protmode/. If you have problems understanding this, or want to raise a question, consider posting/joining the PMode list. The instructions to join this list are on http://www.midpec.com/. Even otherwise, you are free to send any comments there.
The following people have provided valuable comments and information and made this document a successful one:
In reality, this hardly
happens. Most BIOSes and even DOS, sometimes, use instructions like
RETF 2
, making this INT-IRET sequence unreliable enough for
simulating interrupts in V86 mode. A work-around is to use a special
VM86 stack-frame which is takes care of all this.
By definition the 8086 can't mask the NMI. On the PC, the motherboard, however, does have the ciruitary to prevent an NMI from occurring. You will rarely need to disable the NMI. On the ATs, the motherboard controls it with port 70H. Clearing bit 7 of this port will cut off NMI.
Many programs get access to memory beyond the 1 MB limit by simply enabling the A20. With the A20 enabled, the addresses won't wrap around, so they can access around 64K of memory above the 1MB limit without getting to real mode. But then, this can cause several problems with memory managers, so it must be carefully done
The IRET instruction in the 386's protected mode has a much different meaning than what it has in real mode. To return from an interrupt handler in protected mode, IRETD is used.
Traps have nothing to do with trap gates. A trap can be handled using an interrupt gate or a trap gate.
Note that the PDE and all PTEs must lie on the beginning of a 4K block.