Protected Mode programming for the intel386tm, intel486tm AND pentiumtm processors
Title:Protected Mode programming for the intel386, intel486 and pentium processors.
Intel Ref. No: [7143.doc] [ ] [mfg_PB]
Faxback No:7143
Prods. covered: 80386, 80486, Pentium
Date: 10/11/93
Related Info:See Faxback documents [7144], [7145][7146]
Keywords:Protected Mode, 386, 486, Pentium, software
FaxBACK* Service:
Quick, Simple, Easy. Available 24 hours a day!
1-800-628-2283 or (916)356-3105 (US or Canada)
+44(0)793-496646 (Europe)
Bulletin Board Services (BBS):
Latest Product Information and Support, 24 hours a day!
(916)356-3600 (US or Canada)
+44(0)793-496340 (Europe)
Download the Latest FaxBACK Catalogs and FaxFind Windows Utility
1-800-897-2536 (US or Canada)
Europe, call BBS # above and look under FaxBACK area in File Locator
Literature Hotline:
For Current Literature Guide Order No. 210620
1-800-548-4725 (US or Canada)
Europe, contact local Intel office
* Other brands and names are the property of their respective owners.
for the
Intel386 ,
Intel486 , and
Pentium Processors September 1993,
Version 1.0
Intel Corporation makes no warranty for the use of its products and assumes no responsibility for
any errors which may appear in this document, nor does it make a commitment to update the information contained
herein.
Intel retains the right to make changes to these specifications at any time, without notice.
Contact your local Intel sales office or your distributor to obtain the latest specifications before placing your
product order.
MDS is an ordering code only and is not used as a product name or trademark of Intel Corporation.
Intel Corporation and Intel's FASTPATH are not affiliated with Kinetics, a division of Excelan, Inc. or its FASTPATH
trademark or products.
*Other brands and names are the property of their respective owners.
© INTEL CORPORATION, 1993
Contents
List of Figures
- Duty Cycle of Sound Waves
List of Tables
- Tools Alternatives
- Protection Structure
- Tested Configurations
List of Examples
- Jump Macros
- Timer Module
- Scheduler
- Exception Handlers
- Sound Device Driver
- Experiment Task
- Kernel Task
- Protected Mode Equates
- Protected Mode Macros
- Startup and Shutdown
Since its introduction in 1985, the Intel386© processor and later processors (the Intel486© processor
and the Intel Pentium© processor) have operated in one of two modes: real-address mode and protected virtual-address
mode. The real-address mode provides compatibility with earlier processors. Protected mode promotes increased performance
and reliability in most software applications that are designed to use that mode. To help you capture the power
of 32-bit protected mode in your own programming, this application note illustrates a number of protected-mode
programming techniques via an example application that you can duplicate on your 32-bit, AT-compatible PC using
MS-DOS* and the Microsoft* assembler MASM.
You will find this application note useful if you:
- Are a programmer of custom operating systems or real-time executives.
- Need to modify or enhance existing protected-mode operating systems, such as UNIX*, Windows NT*, or OS/2*.
- Need to develop DOS memory managers or DOS extenders.
- Just want to understand better how the use of protected mode can enhance your software application.
We assume that you have access to the basic facts about protected mode as presented in an Intel Programmer's
Reference Manual or User's Manual for your processor or in one of the many third-party reference books (refer to
the Bibliography for a list of some possibilities). You will probably want to have one of these books at hand as
you read through this application note.
1. Overview
It is almost as easy to program applications for protected mode as to program them for real mode - even easier
in some cases - however, systems level programming for protected mode can seem a little off-putting until you get
some experience and actually feel the new power at your disposal. So, this note presents a simple application that
allows you to take the processor for a test drive at full speed. In a short trip such as this, it is not possible
to explore all the features of protected mode nor try out all the tools and environments that aid protected-mode
programming. We will concentrate on protection, segmentation, and multitasking. Along the way, however, we will
point out some other territories that you will want to return to later and explore on your own.
1.1. 32-Bit
Protected Mode
The first thing to note about protected mode is that it is the "native mode" of the processor. The
processor starts executing in real-address mode (called just "real mode" by most people) because that
mode is capable of executing unchanged all software designed for the 16-bit 8088 processor that was used in the
original PC models. But most of the architectural enhancements made to processor generations since the 8088 are
available only in protected mode. Following is a list of the most important of these enhancements:
- 32-bit architecture. The 32-bit registers and internal data paths double the speed at which data can be moved
internally.
- Extended addressing. Programs can address up to four gigabytes of physical memory.
- Flexible processor-enforced protection. Two types of memory partitioning (segmentation and paging) create independent
protected address spaces that promote protection and reliability. The two types of partitioning are both under
control of the operating system, allowing it to select from a wide range of protection models.
- Virtual memory. Programs can transparently address more memory than physically available on a given machine.
The 32-bit architecture provides the programming resources required to directly support "large" applications
- those characterized by large integers, large data structures, large programs (or large numbers of programs),
and so on.
1.2. Protected-Mode
Environments
Applications programmers can use 32-bit protected mode easily if they are developing in a protected-mode environment.
Following are the most widely known, commercially available protected-mode operating environments:
- DOS extenders
- Microsoft Windows* 3.x
- UNIX SVR4, OS/2 2.x, and Windows NT
The following sections expand on each of these environments.
1.2.1. DOS
and DOS Extenders
DOS does not and cannot execute in 32-bit, protected mode. However, DOS extenders can act as a mini-operating-system,
and execute application programs in 32-bit protected mode. DOS extenders provide operating system services to programs
in one of three ways:
- By trapping DOS/BIOS calls, translating them, and delivering them to DOS/BIOS in either real mode or virtual
8086 mode.
- By trapping DOS/BIOS calls and emulating them in the extender.
- By providing extended (non-DOS) services via an extended API in the extender itself.
While there are many similarities among the various DOS extender products, there are also many differences.
If you choose to use a DOS extender, you should arrange to use compilers, linkers, and debuggers that are compatible
with the extender and with each other.
1.2.2. Windows
and Win32s
Microsoft Windows 3.1 is not, strictly speaking, a 32-bit environment. When executing in its enhanced mode,
Windows is a 16-bit DOS extender with an extensive API that isolates programs from DOS and from the AT hardware,
and provides a uniform graphical user interface. It also provides non preemptive multitasking services.
Windows is important to 32-bit programming, however, because it weans applications from their dependency on
the DOS API and the AT platform. Windows provides two migration paths for 16-bit applications to future 32-bit
environments:
- The Windows 16-bit API is supported by 32-bit operating systems Windows NT and OS/2 2.1. In both cases, 16-bit
API calls are converted to 32-bit API calls by interface procedures called "thunks," so it is likely
that 16-bit applications will execute less efficiently in the 32-bit environment than they did in their original
16-bit environment.
- The Win32 API, the "native" API of Windows NT, is supported on Windows 3.1 in the form of the Win32s
SDK.
The following excerpts about Win32s are taken from "An Overview of Win32s" in file W32SWH.TXT of the
MSWIN32 forum on Compuserve:
Win32s is an extension of Windows version 3.1 that allows applications using a subset of the Win32 Application
Programming Interface (API) to run unmodified on both Windows version 3.1 and Windows NT. The Win32s subset consists
of Win32 equivalents of Win16 functions (Win16 is the 16-bit API offered in Windows version 3.1) as well as flat
memory management and structured exception-handling features. Win32s programs will take full advantage of the 32-bit
capabilities of the Intel 80x86 microprocessors. Using the Win32s API, developers can compile and run 32-bit code
today that will be supported across Microsoft's 32-bit platforms tomorrow.
Regarding performance, the same source says:
Win32s is a perfect candidate for applications that are memory-intensive or calculation-intensive, such as CAD
packages, paint packages, image manipulation tools, spreadsheet programs, and simulation software. Manipulating
data in 32-bit mode improves the performance of these applications significantly. Win32s applications will also
see performance improvements in Windows version 3.1, despite the slight overhead caused by 32-bit to 16-bit translation
(thunking) that occurs during API calls. On Windows NT, Win32s applications will call Win32 functions directly;
for this reason, a Win32s application will run faster than its Win16 counterpart on both Windows version 3.1 and
on Windows NT.
Refer to this and other files in the MSWIN32 forum for more information on Win32s.
1.2.3. UNIX,
OS/2 2.x, and Windows NT
UNIX is not just one operating system. There are many 32-bit versions of UNIX: UnixWare*, SCO* Open Desktop*,
Dell UNIX, Interactive, NeXTStep*, and Solaris*, to name a few. All these versions of UNIX, as well as OS/2 version
2.x and Windows NT, release the full power of 32-bit protected mode. The 32-bit protected mode is their normal
mode of operation. All provide full 32-bit memory management, preemptive multitasking, and virtual memory. All
utilize a page-based, two-layered protection model with a separate linear address space for each task, and support
the "flat" memory model for application programs.
Multiprocessor capability is another attraction of these operating systems. Many of them (Windows NT, OS/2 2.1,
and several of the UNIX variants) are ready for symmetric multiprocessing (SMP) platforms. Each SMP operating system
is designed so that it can be executed by any of the processors in a multiprocessor system. An SMP operating system
offers multithreading features, so that both operating-system and application processes can be executed by more
than one processor simultaneously. Thus, multithreaded software is as scalable as the hardware platform; increasing
system performance becomes a matter of adding processors.
While the advantages of converting applications to 32-bit protected mode are many, these operating systems have
not neglected 16-bit applications. In addition to their native, 32-bit execution environments, OS/2, Windows NT,
and most of the UNIX variants have execution environments that support 16-bit DOS applications. Typically, this
consists of executing DOS and its application program as a virtual 8086 mode task. Further, OS/2, Windows NT, and
some of the UNIX variants have execution environments that support Windows 3.x applications. Here the variety of
implementations is greater:
- Windows NT executes each Windows 3.1 application as a thread within a single Windows compatibility process.
- OS/2 can either execute each Windows 3.1 application as a separate OS/2 task or execute all Windows 3.1 applications
as a single OS/2 task.
- Some of the UNIX variants execute Windows 3.1 only in its standard mode treating it as any other DOS application.
For a more extensive overview of these operating systems, refer to the group of operating-system articles in
PC Magazine, June 15, 1993.
1.3. Development
Tools for Protected Mode
The tools formerly used for programming 16-bit processors and the 16-bit compatibility mode of the newer processors
are not, in general, adequate for 32-bit, protected-mode programming. Following is a list of tools categories,
each with a brief explanation of the new features needed to support 32-bit, protected-mode programming.
Object Format Needs to accommodate 32-bit addresses, and new protected-mode data structures.
Operating System Needs to manage 32-bit address space, new protected-mode data structures, new exceptions, and
load new object formats.
Compiler Needs to generate new addressing modes and instructions, calls to new operating systems, and new object
formats.
Linker Needs to deal with 32-bit addresses, and new object formats.
Debugger Needs to handle processors' new debugging features, exceptions, disassembly of new instructions, viewing
of new data structures, and observe protection rules.
This example uses the minimum possible set of tools, so that you can run the example on your own PC without
having to invest in additional tools. For real applications, however, you will probably need a more extensive toolbox.
Table 1 lists the tools used by the example, along with the
corresponding tools available for more sophisticated applications.
Table 1. Tools Alternatives
Tool
|
Used by Example
|
Alternatives
|
Assembler/Compiler |
Microsoft MASM |
A variety of 32-bit compilers for languages such as C, Pascal, FORTRAN, COBOL, Lisp, etc. are available from the
vendors listed in the file TVEND1.TXT1. |
Linker |
MASM Linker; some self-binding within the example program. |
32-bit linkers are usually provided by vendors of compilers or operating systems. |
Loader |
COMMAND.COM, the DOS loader, loads the program in real mode |
Loaders for 32-bit object files are customarily built into 32-bit operating systems. DOS extenders are often loaded
by DOS, after which the extender uses its internal loader to load the protected-mode program. |
Operating system |
Uses no run-time operating-system services. |
UNIX, OS/2, Windows NT, DOS Extenders |
Debugger |
Royer Associates' debugger was used for the development of this example. |
Refer to the file TVEND1.TXT1 for a list of debuggers or debugging environments for protected mode. |
NOTE 1. The file TVEND1.TXT contains a list of tool vendors. This
file is located in the libraries of the Intel ACCESS forum (GO INTELACCESS) on Compuserve.
1.4. Tools Standardization
With the growing number of 32-bit operating environments—32-bit extended DOS, Windows, UNIX, and OS/2—comes
the potential for proliferation of object file formats and fragmentation of the development tools that use these
formats, namely compilers, loaders, and debuggers. To control such proliferation and fragmentation, a number of
leading industry vendors have joined together to form the Tool Interface Standards (TIS) committee. The charter
of the TIS committee is "to standardize sets of technical specifications for interfaces visible to development
tools and compilers." Tool standardization offers the following benefits to software developers:
- Improved productivity by standardization of the development environment.
- "Mix and match" tools leading to more choices and improved productivity. The developer can mix a
favorite linker with a favorite debugger without regard to where those tools come from.
- Improved "time to market" for multiplatform products.
- Efficient development. Because there is less need to focus on porting, more R&D dollars can be used to
add value.
The TIS standards developed so far are available from Intel Literature Distribution (refer to the bibliography)
and from the Intel ACCESS forum on Compuserve (GO INTELACCESS).
2. The Example
Application
To illustrate protected-mode programming, we have selected a simple psychoacoustical application. The application
is the implementation of an experiment to answer the question "Do changes in the duty cycle of a sound wave
produce any acoustical effect that can be perceived by the human ear?" Duty cycle refers to the proportion
of time spent at positive air pressure compared to the total length of a sound wave, as Figure 1
shows.
Figure 1.
Duty Cycle of Sound Waves
To carry out the experiment, we will implement a protected-mode, real-time software system that runs on an AT-compatible
system with an Intel386, Intel486, or Pentium processor. This software system has three general parts:
1. Kernel.
The kernel provides low-level, operating-system-like services. It comprises:
- Start-up and shut-down procedures.
- A timer manager that uses the programmable interval timer to maintain a software clock.
- A scheduler that uses the software clock to dispatch tasks at requested intervals.
- Exception handlers.
2. Device driver.
The example has one device driver that moves the diaphragm of the speaker of the AT-compatible system in and out
repetitively at variable frequencies with variable duty cycle. The frequency and duty cycle are system parameters
that can be changed by application tasks. The device driver calls on the scheduler to help the device driver make
changes to the state of the speaker at appropriate time intervals.
3. Application task.
The application program varies the sound parameters to create various frequencies and various duty cycles at each
frequency. The application also calls on the scheduler to help the application make changes to the sound parameters
at specific time intervals.
We will run the experiment and listen to the sounds that the speaker creates.
The example has some of the features of a DOS extender, and therefore can execute in a DOS environment without
the aid of any other software. But it is not a complete DOS extender. It does execute on an AT equipped with DOS.
We use DOS to load the experiment, but otherwise we do not use any of the features of DOS. A real DOS extender,
on the other hand, would help the 32-bit application access many of the services provided by 16-bit DOS. Another
major function of a DOS extender is to allow programs to access extended memory (memory above one megabyte). The
example, by placing the processor in protected mode, does enable its applications to access extended memory; however,
the application programs do not take advantage of that ability, and the kernel provides no memory space management
functions. The subject of extended addressing in protected mode has been well covered by other documentation (refer
to the bibliography); the purpose of the example is to illustrate other features of protected mode: protection
and task switching.
Example 1 shows the macros used throughout the example code to implement intersegment jumps. These macros
provide the correct override prefixes for jumping between the various combinations of 16-bit and 32-bit segments.
Example 1. Jump Macros
;The following macro is used to execute a far call when in protected
;mode. You must supply the desired offset and selector. It also
;assumes 32-bit mode because we want the DD to give a 32-bit offset.
;If using this in 16-bit mode, you must change the DD to DW.
;This can be used for task switches or calling gates.
PCALLF32_32 MACRO SEL1,OFF1
DB 9AH
DD OFF1
DW SEL1
ENDM
;This macro is for making a far call from a 32-bit segment to a
;16-bit segment. The 66H tells the CPU to use a 16-bit operand
;instead of a 32-bit. Assumes OFF1 is defined in a USE16 segment.
PCALLF32_16 MACRO SEL1,OFF1
DB 66H
DB 09AH
DW OFF1
DW SEL1
ENDM
;This macro is for making a far jump from a 32-bit segment to a
;16-bit segment. The 66H tells the CPU to use a 16-bit operand
;instead of a 32-bit. Assumes OFF1 is defined in a USE16 segment.
PJMPF32_16 MACRO SEL1,OFF1
DB 66H
DB 0EAH
DW OFF1
DW SEL1
ENDM
;This macro is for making a far jump from a 16-bit segment to a
;32-bit segment. The 66H tells the CPU to use a 32-bit operand
;instead of a 16-bit. Assumes OFF1 is defined in a USE32 segment.
PJMPF16_32 MACRO SEL1,OFF1
DB 66H
DB 0EAH
DD OFFSET OFF1
DW SEL1
ENDM
;This macro is for making a far jump from a 32-bit segment to a
;32-bit segment. This can be used for task switches. Assumes OFF1 is
;defined in a 32-bit segment.
PJMPF32_32 MACRO SEL1,OFF1
DB 0EAH
DD OFF1
DW SEL1
ENDM
;This macro is for making a far jump from a 16-bit segment to a
;16-bit segment. This can be used for task switches. Assumes OFF1 is
;defined in a 16-bit segment.
PJMPF16_16 MACRO SEL1,OFF1
DB 0EAH
DW OFF1
DW SEL1
ENDM
The following sections explain each module in detail and discuss its protection needs. After all the modules
have been presented, we will show how the descriptors of the segments of those modules are placed in descriptor
tables. The order of presentation is from low-level modules to high-level modules.
3. Timer Module
The example application controls the movement of the cone of the AT's speaker in real time at audible frequencies;
therefore, the application needs a very high-resolution clock. The clock is based upon interrupts from one of the
timers in the AT's programmable interval timer. These interrupts illustrate the use of an interrupt procedure.
3.1. Applying Protection
to Interrupt Procedures
Interrupt handlers can be implemented either as tasks or procedures. The interrupt task provides greater protection,
while the interrupt procedure provides greater efficiency. In this case, we have chosen to make the timer interrupt
handler a procedure because the application demands a very high-frequency clock tick. An interrupt procedure must
execute at privilege level zero (PL0), because use of any other privilege level results in an illegal outward call
when the timer interrupt occurs while the processor is already executing a PL0 procedure. The timer procedure's
descriptors must be in the GDT, so that the timer interrupt can occur while any task is executing. The timer procedure's
data is likewise stored in a global, PL0, data segment.
3.2. Example Code
The timer module contains several timer-related subroutines and the timer interrupt service routine. All are
located in the kernel code segment.
- A compile-time variable TIMER_FREQUENCY that specifies the timer tick interval so that it can be easily
changed. This variable is available to other kernel procedures so that they can convert TIMER_COUNT into
seconds.
- A 32-bit real-time clock variable TIMER_COUNT that holds a count of clock ticks since program start-up.
- A timer interrupt procedure TIMER_ISR. This procedure is very short, so that timer interrupts do not consume
processor cycles excessively. It increments TIMER_COUNT and checks for overflow. Overflow of TIMER_COUNT
causes the example to terminate. TIMER_COUNT is never reset.
- An initialization procedure TIMER_INIT. This sets the programmable interval timer to interrupt at TIMER_FREQUENCY.
- A shutdown procedure TIMER_RESTORE that restores the programmable interval timer to its original state,
the state required for returning control to DOS.
- A subroutine ENABLE_LEVEL_0 that enables the timer interrupt by programming the PIC.
Example 2. Timer Module
;**************** TIMER module ;It uses two segments: kernel code and kernel
stack.
KERNEL_STACK32 SEGMENT
KERNEL_STACK32_BEGIN LABEL WORD
DD 400H DUP(0)
KERNEL_STACK32_END LABEL WORD
KERNEL_STACK32 ENDS
;The _BEGIN and _END labels are used later to calculate
; the segment limit for the segment descriptor.
KERNEL_CODE32 SEGMENT USE32 PUBLIC
ASSUME CS:KERNEL_CODE32,DS:KWAIT_DATA32,SS:KERNEL_STACK32
KERNEL_CODE32_BEGIN:
TIMER_FREQUENCY EQU 20000 ;units, cycles/sec
TIMER_VALUE EQU 1193182/TIMER_FREQUENCY
TIMER_INIT PROC
;This proc reprograms the CT0 on the 8254 Programmable Interval
;Timer, to count with the value, TIMER_VALUE. On the AT platform,
;the input clock of the 8254 is 1.193182 MHz, so a value of 119
;in TIMER_VALUE gives an output frequency of about 10 KHz.
;In general, frequency = 1.193182*10^6/TIMER_VALUE
MOV AL,36H ;Command: select timer 0
OUT 43H,AL ;Write control word
MOV AX,TIMER_VALUE ;Clock divisor
OUT 40H,AL ;Low byte of counter 0
MOV AL,AH
OUT 40H,AL ;High byte of counter 0
RET
TIMER_INIT ENDP
TIMER_RESTORE PROC
;This sets the timer back to the original frequency.
CLI ;Disable interrupts
MOV AL,36H ;Command: select counter 0
OUT 43H,AL ;Write control word
MOV AX,0FFFFH ;Clock divisor used by DOS
OUT 40H,AL ;Low byte of counter 0
MOV AL,AH
OUT 40H,AL ;High byte of counter 0
RET
TIMER_RESTORE ENDP
ENABLE_LEVEL_0 PROC
;Enable the timer interrupt, level 0 on the master 8259.
IN AL,PICA01 ;Get inerrupt mask
AND AL,0FEH ;Clear bit 0
OUT PICA01,AL ;Reprogram mask
RET
ENABLE_LEVEL_0 ENDP
;The timer interrupt routine simply increments TIMER_COUNT,
; setting the OVERFLOW_FLAG if necessary.
TIMER_ISR: ;Interrupts are disabled upon
entry
PUSH EAX ;Save registers
PUSH DS
MOV AX,KWAIT_DATA32_SEL ;Load segment descriptor
MOV DS,AX ; of TIMER_COUNT
ADD TIMER_COUNT,1 ;ADD is necessary because
JNC TIMER_ISR_EXIT ; INC does not set overflow.
INC OVERFLOW_FLAG ;Record overflow condition
TIMER_ISR_EXIT:
MOV AL,EOI ;PIC End-Of-Interrupt command
OUT PICA00,AL ;Send EOI to PIC
POP DS ;Restore registers
POP EAX
IRETD ;Return, restoring interrupt flag
KERNEL_CODE32 ENDS
4. Scheduler Module
A task (also called a process in UNIX and some other operating systems) is an instance of a program loaded into
memory, together with its current state, which includes a pointer to the current instruction in the program. In
the Intel 32-bit architecture, a task is represented by a task state segment (TSS), a processor-recognized memory
structure that stores the state of the task when it is not being executed. The processor switches from the task
that it is currently executing to a new task by loading the processor state from the TSS of the new task and beginning
to execute the instructions of that task at the point indicated by the new instruction pointer. An operating system
triggers a task switch by jumping or calling to the TSS of the new task or to a task gate for that TSS.
This scheduler is designed to cause events to happen at specified time intervals. The timer module gives the
scheduler its sense of time; the scheduler has access to the real-time clock variable TIMER_COUNT. Events are implemented
as tasks.
The mechanism for scheduling tasks for execution is round-robin and nonpreemptive; that is, each task in turn
is given an opportunity to run and each task runs until it gives up the processor. Such a mechanism works as long
as each task always gives up the processor voluntarily and uses little time before doing so.
4.1. Applying Protection
to the Scheduler
The scheduler is an example of a critical OS kernel procedure that needs maximum protection; therefore, the
scheduler is stored in a global, PL0 segment. The scheduler must be callable from any privilege level, so it has
PL3 call gates, which ensure that other programs enter the procedure only at its intended entry points. The scheduler's
data structures are stored in a global, PL0 segment so that they are accessible only by trusted PL0 procedures.
4.2. Example Code
The scheduler consists of :-
- A circular linked list of all tasks. The entry for each task has the following fields:
- A 32-bit WAKE_UP_TIME with the same units as the real-time clock. WAKE_UP_TIME is initially set
to zero.
- A pointer to the task's TSS.
- A link to the next entry in the list.
- A pointer to the current task in the list.
- Two wait procedures WAIT_TICKS(INTERVAL) and WAIT_MSECS(INTERVAL). These are actually two entry
points to the same procedure, and each has a call gate. The two are identical, except for the units of INTERVAL
— either timer ticks, or milliseconds which are converted to timer tick units. The common procedure sets the task's
WAKE_UP_TIME to TIMER_COUNT + INTERVAL. Then it steps through the list of tasks, starting
with the next task in turn, looking for the next ready task by comparing each task's WAKE_UP_TIME to TIMER_COUNT.
A task is ready if its WAKE_UP_TIME is less than or equal to TIMER_COUNT. A task is not ready if
its WAKE_UP_TIME is greater than TIMER_COUNT. If no tasks are ready, the scheduler loops indefinitely
through the task list. Sooner or later some task's waiting period will expire as TIMER_COUNT advances. When
WAIT finds a ready task, it checks whether it is the current task. If so, WAIT just returns. If it
is another task, WAIT jumps to the TSS of that task, causing the processor to perform a task switch.
Example 3. Scheduler
;***************** WAIT (SCHEDULER) module
;The data segment contains the linked list of tasks
;and several other variables.
;The code segment contains the wait function
KWAIT_DATA32 SEGMENT USE32 PUBLIC
KWAIT_DATA32_BEGIN:
TASK_LINK STRUC
WAKE_UP_TIME DD 0 ;Wakeup value
THIS_TASK_OFFSET DD 0 ;Dummy offset placeholder
THIS_TASK_SEL DW 0 ;Selector to this task
NEXT_TASK_LINK DD 0 ;Pointer to next task link
TASK_LINK ENDS
CURRENT_TASK_LINK DD KERNEL_TASK_LINK
OLD_TASK_LINK DD 0
TASK_LIST LABEL FWORD ;List of scheduled tasks
APP_TASK_LINK TASK_LINK <0,0,APP_TSS_SEL,SOUND_TASK_LINK>
SOUND_TASK_LINK TASK_LINK <0,0,SOUND_TSS_SEL,KERNEL_TASK_LINK>
KERNEL_TASK_LINK TASK_LINK <0,0,KERNEL_TSS_SEL,APP_TASK_LINK>
TIMER_COUNT DD 0 ;Count of clock ticks
OVERFLOW_FLAG DB 0 ;Set when TIMER_COUNT overflows
KWAIT_DATA32_END:
KWAIT_DATA32 ENDS
KWAIT_CODE32 SEGMENT USE32 PUBLIC
ASSUME CS:KWAIT_CODE32,DS:KWAIT_DATA32
KWAIT_CODE32_BEGIN:
WAIT_TICKS PROC FAR
;This is called when a task is done for the time being.
;It executes in the context of the calling task.
;EAX = WAIT TIME in clock tick units. If EAX = -1,
;we have reached the end of the SOUND_TABLE
PUSH EAX ;Save regs
PUSH ESI
PUSH EBX
PUSH DS
MOV BX,KWAIT_DATA32_SEL
MOV DS,BX ;Load data segment of KERNEL
MOV ESI,CURRENT_TASK_LINK ;Get current task_link
MOV OLD_TASK_LINK,ESI ;Save task_link to current context
CMP EAX,-1 ;Check for end of sound-list
JNE WAIT1 ;Not reached
INC OVERFLOW_FLAG ;End of sound list
JMP WAIT2
WAIT1:
ADD EAX,TIMER_COUNT ;Form wakeup time
WAIT2:
MOV [ESI].TASK_LINK.WAKE_UP_TIME,EAX ;Save new wakeup time
CHECK_NEXT:
CMP OVERFLOW_FLAG,0 ;Time to shut down?
JE CHECK1 ;No
PJMPF32_32 KERNEL_TSS_SEL, 0 ;Yes, switch to kernel task
CHECK1:
MOV EAX,[ESI].TASK_LINK.NEXT_TASK_LINK ;Get ptr for next
MOV CURRENT_TASK_LINK,EAX ;Store ptr
MOV ESI,CURRENT_TASK_LINK ;Point to next task_link
MOV EAX,[ESI].TASK_LINK.WAKE_UP_TIME ;Get wake up time
CMP TIMER_COUNT,EAX ;Time to wake up?
JB CHECK_NEXT ;No
DISPATCH_THIS_ONE:
CMP ESI,OLD_TASK_LINK ;Are we in the task already?
JE SAME_TASK ;Yes, return to caller
; Switch to new task by jumping to its TSS
JMP FWORD PTR [ESI].TASK_LINK.THIS_TASK_OFFSET
SAME_TASK: ;returns here next time this task is dispatched
POP DS ;pop registers and return
POP EBX
POP ESI
POP EAX
RET
WAIT_TICKS ENDP
MIN_PERIOD EQU 1
DEFAULT_PERIOD EQU 1000
WAIT_MSECS PROC FAR ; Alternate entry point to WAIT_TICKS.
;This wait function is called by the application, it uses application
;units of duration, i.e. milliseconds. It converts to clock ticks,
;then jumps to the normal wait. EAX = duration in milliseconds.
CMP EAX,-1
JE WAIT_TICKS ;If -1, do not convert
MOV EBX,1193 ;Timer input frequency
MUL EBX ;EAX = EAX * EBX
MOV EBX,TIMER_VALUE
DIV EBX ;EAX = EAX / EBX
CMP EAX,MIN_PERIOD
JA CP2
MOV EAX,DEFAULT_PERIOD ;Use default
CP2:
JMP WAIT_TICKS
WAIT_MSECS ENDP
KWAIT_CODE32_END:
KWAIT_CODE32 ENDS
Example
4. Exception Handlers
KERNEL_CODE32 SEGMENT USE32 PUBLIC
ASSUME CS:KERNEL_CODE32,DS:KWAIT_DATA32,SS:KERNEL_STACK32
EXCEPTION_BEEP:
;INPUT--CX = counter value
; --frequency = 1.19*10^6/(counter value)
; BX = number of beeps
MOV AL,0B6H ;Command: select counter 2
OUT 43H,AL ;Write control word
MOV AX,CX ;Get count
OUT 42H,AL ;Low byte of counter 2
MOV AL,AH
OUT 42H,AL ;High byte of counter 2
EXB1:
MOV AL,3 ;Gate sound on
OUT PORT_B,AL
MOV ECX,500000H
LOOP $ ;Delay
MOV AL,0 ;Gate sound off
OUT PORT_B,AL
MOV ECX,500000H
LOOP $ ;Delay
DEC BX
JNZ EXB1 ;Do again!
PJMPF32_16 RET_CODE16_SEL,RET_REAL ;Done
EXCEPTION_HANDLER0:
;All handlers are similar--they generate a series of beeps
;Frequency increases with exception number
;Number of beeps = exception number plus 1
MOV CX,1800H
MOV BX,1
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER1:
MOV CX,1700H
MOV BX,2
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER2:
MOV CX,1600H
MOV BX,3
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER3:
MOV CX,1500H
MOV BX,4
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER4:
MOV CX,1400H
MOV BX,5
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER5:
MOV CX,1300H
MOV BX,6
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER6:
MOV CX,1200H
MOV BX,7
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER7:
MOV CX,1100H
MOV BX,8
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER8:
MOV CX,1000H
MOV BX,9
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER9:
MOV CX,0F00H
MOV BX,10
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER10:
MOV CX,0E00H
MOV BX,11
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER11:
MOV CX,0D00H
MOV BX,12
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER12:
MOV CX,0C00H
MOV BX,13
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER13:
MOV CX,0B00H
MOV BX,14
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER14:
MOV CX,0A00H
MOV BX,15
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER15:
MOV CX,0900H
MOV BX,16
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER16:
MOV CX,0800H
MOV BX,17
JMP EXCEPTION_BEEP
EXCEPTION_HANDLER17:
MOV CX,0700H
MOV BX,18
JMP EXCEPTION_BEEP
KERNEL_CODE32_END:
KERNEL_CODE32 ENDS
6. Sound Module
The example application generates sound by alternately applying current to and withdrawing current from the
voice coil of the PC's speaker. The software controls the voice coil via a port of the PC's programmable peripheral
controller.
6.1. Applying Protection
to a Device Driver
The processor's I/O privilege level (IOPL) defines a level of privilege specifically for the execution of I/O
and related instructions. Device drivers are, generally speaking, not implemented as part of the kernel, but rather
as users of kernel services. They are considered less trusted than the kernel because they are subject to more
frequent change. On the other hand they are critical parts of the system that should not be subject to interference
from application programs. Therefore, IOPL is customarily set to a privilege level 1 or 2. In this case the IOPL
is initialized to 1 in the flags field of each task's TSS.
The code in the device driver task runs at PL1, so that it can execute I/O instructions. Programs executing
at privilege levels greater than 1 are not able to execute I/O. The sound specifications are placed in a global,
PL1, data segment, so that the device driver code can access them, but application code cannot.
6.2. Example Code
The module that controls the sound consists of the following parts:
- The current sound specifications. The specifications consist of the current ONPERIOD and OFFPERIOD.
The full period of a sound cycle is ONPERIOD + OFFPERIOD. ONPERIOD is full period times the
duty cycle; OFFPERIOD is full period times the complement of the duty cycle.
- A procedure CHANGESOUND that changes the sound specifications. The parameters to CHANGESOUND
are in units of milliseconds, which CHANGESOUND converts to timer-ticks. The procedure CHANGESOUND
must be callable by application programs; therefore, it has a global, PL3 call gate.
- The task SOUND_TASK that drives the speaker. Its logic is an infinite loop through the following steps:
- If ONPERIOD is nonzero, toggle speaker on (apply current to voice coil).
- Wait for ONPERIOD.
- If OFFPERIOD is nonzero, toggle speaker off (withdraw current from voice coil).
- Wait for OFFPERIOD.
Note that an ONPERIOD of zero causes a silence for a time interval of OFFPERIOD, and vice versa.
It does not make sense for both ONPERIOD and OFFPERIOD to be zero, but this condition is harmless;
the speaker state does not change, and WAIT is called repeatedly until the sound specifications change.
A period of -1 causes the application to terminate.
- An initialization procedure CLEAR_SPEAKER_GATES which readies the speaker. The initialization procedure
must be at PL0.
Example 5. Sound Device Driver
;************************ SOUND module
;It contains a global code segment (_G), a local code segment (_L),
;a global data segment containing the ONPERIOD, OFFPERIOD variables,
;and two local stack segments.
SOUND_DATA32_G SEGMENT USE32 PUBLIC
SOUND_DATA32_G_BEGIN:
ONPERIOD DD 50 ;Defaults
OFFPERIOD DD 50
SOUND_DATA32_G_END:
SOUND_DATA32_G ENDS
SOUND_CODE32_G SEGMENT USE32 PUBLIC
ASSUME CS:SOUND_CODE32_G,DS:SOUND_DATA32_G,SS:SOUND_STACK32_L
SOUND_CODE32_G_BEGIN:
CONVERT_SOUND_PARMS PROC
;This procedure changes the "user" parameters to clock
;tick units.
;If an error occurs, a default value is supplied.
;We assume EBX is frequency and EDI is duty_cycle
MOV EAX,TIMER_VALUE
CMP EBX,-1 ;Is parm = -1?
JE QUIT_PLAYING
MUL EBX
MOV EBX,EAX ;Save result
MOV EAX,1193182 ;Actual timer input frequency
DIV EBX ;EAX = period in TIMER_COUNTS
CMP EAX,0 ;Error if 0
JNE CP1 ;OK
MOV EAX,50 ;Use a default period
CP1:
MOV ECX,EAX ;Save for later use =PERIOD
MOV EBX,EDI ;Get duty-cycle
CMP EBX,100 ;Is it within range?
JBE CP2 ;Yes
MOV EBX,50 ;No, supply default
CP2:
MUL EBX
MOV EBX,100 ;Form percentage
DIV EBX ;EAX = PERIOD*DUTY_CYCLE =ONPERIOD
SUB ECX,EAX ;ECX = OFFPERIOD
RET
QUIT_PLAYING:
MOV EAX,-1 ;End of list reached
MOV ECX,-1
RET
CONVERT_SOUND_PARMS ENDP
CHANGESOUND PROC FAR
CALL CONVERT_SOUND_PARMS ;Convert to clock tick units
;Two parameters are returned from CONVERT_SOUND_PARMS.
;This proc uses the parameters
;to modify the ONPERIOD and OFFPERIOD variables.
;NOTE, THIS RUNS IN THE CONTEXT OF THE CALLING TASK.
;INPUT EAX=new onperiod
; ECX=new offperiod
PUSH ESI
PUSH DS
MOV ESI,SOUND_DATA32_G_SEL ;Load data segment
MOV DS,SI
MOV ONPERIOD,EAX ;Store new data
MOV OFFPERIOD,ECX
POP DS
POP ESI
RET
CHANGESOUND ENDP
SOUND_CODE32_G_END:
SOUND_CODE32_G ENDS
SOUND_CODE32_L SEGMENT USE32 PUBLIC
ASSUME CS:SOUND_CODE32_L,DS:SOUND_DATA32_G,SS:SOUND_STACK32_L
SOUND_CODE32_L_BEGIN:
SOUND_START32:
SOUND_TASK:
CMP ONPERIOD,0
JE ST1
IN AL,PORT_B
OR AL,00000010B ;Set speaker data bit
OUT PORT_B,AL
ST1:
MOV EAX,ONPERIOD ;Wait time
PCALLF32_32 WAIT_TICKS_GATE_SEL,0
IN AL,PORT_B
AND AL,NOT 00000010B ;Clear speaker data bit
OUT PORT_B,AL
MOV EAX,OFFPERIOD ;Wait time
PCALLF32_32 WAIT_TICKS_GATE_SEL,0
JMP SOUND_TASK
SOUND_CODE32_L_END:
SOUND_CODE32_L ENDS
SOUND_STACK32_L SEGMENT
SOUND_STACK32_L_BEGIN LABEL WORD
DD 400H DUP(0)
SOUND_STACK32_L_END LABEL WORD
SOUND_STACK32_L ENDS
SOUND_STACK32_L_PL0 SEGMENT
SOUND_STACK32_L_PL0_BEGIN LABEL WORD
DD 400H DUP(0)
SOUND_STACK32_L_PL0_END LABEL WORD
SOUND_STACK32_L_PL0 ENDS
7. The Experiment
Task
The application task changes the global sound specification occasionally by calling CHANGESOUND, then waits
by calling WAIT. The application code in this task runs at PL3. In our example application, this task will probably
be revised often, as the experimenter tunes the parameters of the experiment.
7.1. Applying Protection
to Application Programs
Application programs are typically executed as separate tasks. Their code and data segments are PL3 segments
in the LDT of the task. This structure gives maximum protection among application tasks and also protects other
parts of the operating system from the applications.
7.2. Example Code
In the application module, the frequency, duty cycle, and wait period until next change are stored in an assembly-language
table. The code loops through this table repeatedly, calling CHANGESOUND and WAIT_MSECS to satisfy the specifications
of each entry.
Example 6. Experiment Task
;****************** APPLICATION module
;It contains code, data, and three stacks, all defined in APP_LDT
USER_SOUND STRUC
FREQUENCY DD 0
DUTY_CYCLE DD 0
DURATION DD 0
USER_SOUND ENDS
APP_DATA32 SEGMENT USE32 PUBLIC
APP_DATA32_BEGIN:
;The sound table uses the format FREQUENCY, DUTY_CYCLE, DURATION
;FREQUENCY in Hertz
;DUTY_CYCLE in values from 0-100
;DURATION in milliseconds
SOUND_TABLE_BEGIN LABEL DWORD
INCLUDE SOUNDS.ASM
SOUND_TABLE_END LABEL DWORD
SOUND_TABLE_PTR DD SOUND_TABLE_BEGIN
APP_DATA32_END:
APP_DATA32 ENDS
APP_CODE32 SEGMENT USE32 PUBLIC
ASSUME CS:APP_CODE32,DS:APP_DATA32,SS:APP_STACK32
APP_CODE32_BEGIN:
APP_START32:
APP_TASK:
;This task pulls the FREQUENCY values from the SOUND_TABLE
;and loads the sound driver's parameters.
;The SOUND_TABLE_PTR starts at the first table entry and
;is incremented each time until the end of the table. At the
;end, it wraps around to the beginning. If a -1 is found in
;the table, it simply jumps to EXIT. A -1 causes termination
;of the program.
MOV ESI,SOUND_TABLE_PTR ;Get pointer
MOV EBX,[ESI].USER_SOUND.FREQUENCY ;Get next frequency
MOV EDI,[ESI].USER_SOUND.DUTY_CYCLE ;Get next duty cycle
PCALLF32_32 CHANGESOUND_GATE_SEL,0 ;Set new parms
MOV ESI,SOUND_TABLE_PTR
MOV EAX,[ESI].USER_SOUND.DURATION ;Get duration
ADD DWORD PTR SOUND_TABLE_PTR,TYPE USER_SOUND ;Update ptr
CMP SOUND_TABLE_PTR,OFFSET SOUND_TABLE_END ;Too big?
JB APP2 ;No
MOV SOUND_TABLE_PTR,OFFSET SOUND_TABLE_BEGIN ;Yes, reset
APP2:
PCALLF32_32 WAIT_MSECS_GATE_SEL,0 ;Wait duration
JMP APP_TASK
APP_CODE32_END:
APP_CODE32 ENDS
;Define stack segments for all privilege levels
; that can be called by this task.
APP_STACK32 SEGMENT
APP_STACK32_BEGIN LABEL WORD
DD 400H DUP(0)
APP_STACK32_END LABEL WORD
APP_STACK32 ENDS
APP_STACK32_PL1 SEGMENT
APP_STACK32_PL1_BEGIN LABEL WORD
DD 400H DUP(0)
APP_STACK32_PL1_END LABEL WORD
APP_STACK32_PL1 ENDS
APP_STACK32_PL0 SEGMENT
APP_STACK32_PL0_BEGIN LABEL WORD
DD 400H DUP(0)
APP_STACK32_PL0_END LABEL WORD
APP_STACK32_PL0 ENDS
8. Protected
Mode Startup and Termination Module
There is also a task that belongs to the kernel. It serves as the initial task—the first task dispatched by
the initialization procedure. It performs as much of system initialization as can be done in protected mode. It
also serves as a watchdog task that waits for a prespecified time, then shuts the kernel down. This guarantees
termination in spite of possible failures in the primary application task. It performs as much of the kernel shut-down
as can be done in protected mode, then jumps back to the 16-bit segment where shut-down will be completed.
8.1. Applying Protection
to Kernel Tasks
While it is not necessary, it is often convenient for a kernel to have one or more tasks of its own, and this
task is an example of such a situation. We use this task to illustrate two points about tasks in general:
- The code in a task can execute at any privilege level, not just PL3. The code in this task executes at PL0,
because it is part of the kernel. Its code and data segments are at PL0.
- A task does not necessarily have an LDT. All the segments of this task are located in the GDT. This design
decision is arbitrary; the segments could also be in an LDT.
8.2. Example Code
This module consists of:
- A compile-time variable KERNEL_TIMEOUT that specifies how long the example may run.
- A task that gets control as soon as the system is in protected mode. The task calls various global initialization
procedures, then waits KERNEL_TIMEOUT milliseconds, giving the main tasks of the experiment a chance to run. When
it gets control again, it is time to terminate the protected mode example. It simply calls the global termination
procedures, then jumps to the real-mode termination procedure.
Example 7. Kernel Task
;**************** KERNEL TASK module
;It uses two segments: the kernel code and kernel stack.
KERNEL_CODE32 SEGMENT USE32 PUBLIC
ASSUME CS:KERNEL_CODE32,DS:KWAIT_DATA32,SS:KERNEL_STACK32
CLEAR_SPEAKER_GATES PROC
;Clears the timer 2 gate (bit 0) and speaker data (bit 1)
PORT_B EQU 61H ;I/O port B
IN AL,PORT_B
AND AL,11111100B ;Clear speaker bits
OUT PORT_B,AL
RET
CLEAR_SPEAKER_GATES ENDP
KERNEL_TIMEOUT EQU 25000 ;Units = milliseconds
;The kernel terminates after KERNEL_TIMEOUT milliseconds
KERNEL_WAIT_VALUE EQU KERNEL_TIMEOUT*1193/TIMER_VALUE
;The KERNEL_WAIT_VALUE is in units of the clock tick (interrupt period).
;The arithmetic converts from milliseconds to clock tick units.
KERNEL_START32:
CALL TIMER_INIT ;Initialize timer
CALL CLEAR_SPEAKER_GATES ;Clear bit0 and bit1
CALL ENABLE_LEVEL_0 ;Enable timer interrupt
STI ;Dnable INTR interrupts
MOV EAX,KERNEL_WAIT_VALUE ;Get timeout parameter
PCALLF32_32 WAIT_TICKS_GATE_SEL,0 ;Wait
;Here it is time to quit--from one of three sources.
;1 KERNEL timeout
;2 overflow
;3 end of sound table
KERNEL_SHUTDOWN:
CALL TIMER_RESTORE
PJMPF32_16 RET_CODE16_SEL,RET_REAL ;Back to real mode
KERNEL_CODE32 ENDS
9. Real Mode Startup and Termination
Module
At startup, the kernel is running in 16-bit real mode. It has several functions to perform before it can start
running the 32-bit, protected-mode application.
- Set up the necessary protected-mode structures. In our example, the GDT, IDT, LDT, and TSS structures are defined
statically by assembly language declarations. However, because we are using a real-mode loader (COMMAND.COM) that
has no idea how to fix up the segment base addresses in protected-mode descriptor tables, we have to fix-up the
addresses ourselves.
- Enter protected mode.
- Switch to the first task.
This example has an additional step to perform at startup: reassign the DOS external interrupts to vectors that
do not conflict with protected-mode exceptions. This is necessary because the AT platform uses certain interrupt
vectors that are reserved by Intel. These interrupt vectors are assigned to processor exceptions in protected mode.
The shutdown procedure reverses the steps performed by startup procedure.
- Return the processor to real mode.
- Restore the interrupt vectors.
- Return control to DOS.
Table 2 summarizes how the segment and gate descriptors mentioned in previous sections are assigned to
privilege levels and descriptor tables. Normally, the base addresses in segment descriptors are initialized through
the cooperative efforts of the linker and loader. Because we are using a linker and loader that were not designed
for this purpose, the start-up procedure puts the base address for each segment in the GDT or in the appropriate
LDT.
PL
|
IDT
|
GDT
|
Sound Task
LDT
|
Experiment Task LDT
|
0
|
TIMER_ISR_GATE |
KWAIT_DATA32
KWAIT_CODE32
SOUND_CODE32_G
SOUND_DATA32_G
KERNEL_CODE32
KERNEL_STACK32
DUMMY_TSS
KERNEL_TSS
SOUND_TSS
APP_TSS
SOUND_LDT
APP_LDT |
SOUND_STACK32_L_PL0 |
APP_STACK32_PL0 |
1
|
|
|
SOUND_CODE32_L
SOUND_STACK32_L |
APP_STACK32_PL1 |
2
|
|
|
|
|
3
|
|
CHANGE_SOUND_GATE
WAIT_TICKS_GATE
WAIT_MSECS_GATE |
|
APP_CODE32
APP_DATA32
APP_STACK32 |
9.1. Applying Protection
to Startup and Termination
Most of this code runs in real mode. For the brief time that it runs in protected mode, it runs at PL0, because
the processor always initializes the current privilege level to zero after entering protected mode. Once in protected
mode the startup procedure immediately jumps to the initial task, an action that finishes the initialization of
protected mode by loading the TR and LDTR registers with functional values.
9.2. Example Code
Example 8 shows a number of assembly language EQU statements that are useful for defining protected-mode
data structures. These are used by the startup code.
Example 8. Protected Mode Equates
;This file contains equate statements that are helpful
;in creating protected-mode data structures.
IO_GATE_SEL EQU 68H+3
IO_CALL_SEL EQU 28H
;DESCRIPTOR FIELD VALUES
;Masks for present bit
PRESENT EQU 80H
NOT_PRESENT EQU 0
;Masks for descriptor PL field
DPL0 EQU 00H
DPL1 EQU 20H
DPL2 EQU 40H
DPL3 EQU 60H
;Masks for descriptor S bit
SEGMENT_DESC EQU 10H
SYSTEM_DESC EQU 0
;Masks for type field of segment descriptors
EO_SEG EQU 8 ;EXECUTE ONLY
ER_SEG EQU 0AH ;EXECUTE AND READ
EOC_SEG EQU 0CH ;EXECUTE ONLY, CONFORMING
ERC_SEG EQU 0EH ;EXECUTE AND READ, CONFORMING
RO_SEG EQU 0 ;READ ONLY, NORMAL
RW_SEG EQU 2 ;READ/WRITE, NORMAL
ROED_SEG EQU 4 ;READ ONLY, EXPAND-DOWN
RWED_SEG EQU 6 ;READ/WRITE, EXPAND-DOWN
ED EQU 4 ;EXPAND DOWN BIT
;Masks for D,B,and G bits of segment descriptors
D_BIT EQU 40H ;D BIT FOR 32 BIT TYPES
B_BIT EQU 40H ;B BIT FOR 32 BIT TYPE STACKS
G_BIT EQU 80H ;G BIT FOR LARGE GRANULARITY
;Type field masks for 32-bit system segments and gates
CALL_G_386 EQU 0CH
INT_G_386 EQU 0EH
TRAP_G_386 EQU 0FH
TSS_AVAIL_386 EQU 09
TSS_BUSY_386 EQU 0BH
LDT_DESC EQU 2
TASK_G EQU 5
;Layout of segment descriptor
S_DESC_386 STRUC
ELIMIT DW 0
EBASE_L DW 0
EBASE_M DB 0
EACCESS DB 0
E386 DB 0
EBASE_H DB 0
S_DESC_386 ENDS
;Layout of gate descriptor
G_DESC_386 STRUC
OFFSET_LOW DW 0
SEGMENT_SELECTOR DW 0
DWORD_COUNT DB 0
GACCESS DB 0
OFFSET_HIGH DW 0
G_DESC_386 ENDS
;Layout of task state segment
TSS_STRUC STRUC
RBACKLINK DW 0 ;0 --0
RES1 DW 0 ;1 --2
RESP0 DD 0 ;2 --4
RSS0 DW 0 ;3 --8
RES2 DW 0 ;4 --A
RESP1 DD 0 ;5 --C
RSS1 DW 0 ;6 --10
RES3 DW 0 ;7 --12
RESP2 DD 0 ;8 --14
RSS2 DW 0 ;9 --18
RES4 DW 0 ;10--1A
RCR3 DD 0 ;11--1C
REIP DD 0 ;12--20
REFLAGS DD 0 ;13--24
REAX DD 0 ;14--28
RECX DD 0 ;15--2C
REDX DD 0 ;16--30
REBX DD 0 ;17--34
RESP DD 0 ;18--38
REBP DD 0 ;19--3C
RESI DD 0 ;20--40
REDI DD 0 ;21--44
RES DW 0 ;22--48
RES5 DW 0 ;23--4A
RCS DW 0 ;24--4C
RES6 DW 0 ;25--4E
RSS DW 0 ;26--50
RES7 DW 0 ;27--52
RDS DW 0 ;28--54
RES8 DW 0 ;29--56
RFS DW 0 ;30--58
RES9 DW 0 ;31--5A
RGS DW 0 ;32--5C
RES10 DW 0 ;33--5E
RLDTR DW 0 ;34--60
RES11 DW 0 ;35--62
RTBIT DW 0 ;36--64
RIOBASE DW 0 ;37--66
TSS_STRUC ENDS
Example 9 shows a number of assembly language macros that are useful when setting up protected mode data
structures. The first of these, LGDTD, SGDTD, LIDTD, and SIDTD, are just 32-bit versions of the assembler's LGDT,
SGDT, LIDT, and SIDT instructions. The macro PUT_BASE_IN_DESCRIPTOR tricks the assembler and linker into providing
the base address of a segment and moves that address into the split base-address fields of a descriptor. The macro
INIT_GDTIDT_ADDRESS stores a segment address in the special memory format required by the LIDT and LGDT instructions.
Example 9. Protected Mode Macros
LGDTD MACRO PARM1
DB 66H
LGDT PARM1
ENDM
SGDTD MACRO PARM1
DB 66H
SGDT PARM1
ENDM
LIDTD MACRO PARM1
DB 66H
LIDT PARM1
ENDM
SIDTD MACRO PARM1
DB 66H
SIDT PARM1
ENDM
PUT_BASE_IN_DESCRIPTOR MACRO LABEL1,DESCRIPTOR1
;This macro calculates the real-mode linear address of LABEL1
;and stores it in the base address field of DESCRIPTOR1
;We assume the descriptor is in the DS segment at this time.
;Input, AX= real mode segment, ESI points to descriptor.
;EDI = offset to label in real mode segment
MOV EAX,0
MOV AX,SEG LABEL1
SHL EAX,4
MOV ESI,OFFSET DESCRIPTOR1
ADD EAX,OFFSET LABEL1 ;add offset of label
MOV [ESI].S_DESC_386.EBASE_L,AX
SHR EAX,16
MOV [ESI].S_DESC_386.EBASE_M,AL ;NOW BASE IS SET UP
ENDM
INIT_GDTIDT_ADDRESS MACRO LABEL2,BASE_VARIABLE
;The linear address of the GDT/IDT is stored in the appropriate
;variable = BASE_VARIABLE. We assume the BASE_VARIABLE is in
;the DS segment.
MOV EAX,0 ;CLEAR EAX
MOV AX,SEG LABEL2
SHL EAX,4 ;EAX = BASE OF DATA SEGMENT
ADD EAX,OFFSET LABEL2
MOV BASE_VARIABLE,EAX ;SET UP GDT BASE ADDRESS
ENDM
The real-mode startup code in Example 10 makes heavy use of the equates in Example 8 and macros in
Example 9. All the descriptors used in protected mode are partially defined by equates. The entry point is
START. It follows these steps:
- Put base addresses into the statically defined descriptors.
- Load the GDTR and IDTR.
- Ready the PIC for protected mode by calling PROTECTED_8259.
- Change the processor's mode to protected.
- Jump to the first task. The PJMPF16_32 macro is used for this purpose because it uses the correct override
prefixes for jumping from a 16-bit segment to a 32-bit TSS.
The label RET_REAL marks the code that changes back to real mode. It follows these steps:
- Load segment registers with values appropriate for real mode.
- Set the IDTR to point to the real-mode IDT.
- Switch to real mode.
- Flush the instruction prefetch queue so that addresses previously decoded in protected mode can be decoded
again according to real mode rules.
- Prepare the PIC for real mode by calling REAL_8259.
- Tell DOS that the program is done.
Example 10. Startup and Shutdown
; ************** Define the real-mode segments **************
STACK SEGMENT STACK USE16 ;Real-mode stack segment
DD 100H DUP(0)
STACK_END LABEL WORD
STACK ENDS
DATA SEGMENT USE16 ;Real-mode data segment
DATA_BEGIN LABEL WORD
;Define the GDT using equates. Each entry starts with an equate
;that defines the selector for that entry, and ends with a
;structure that puts most of the values in the descriptor. The
;base addresses are not filled in here; they are added at run time.
GDT_BEGIN LABEL WORD
NULL_SELECTOR EQU $-GDT_BEGIN ;SELECTOR 00H
GDT DD 0 ;THIS IS THE BEGINNING OF THE GDT
DD 0 ;NULL DESCRIPTOR
RET_CODE16_SEL EQU $-GDT_BEGIN
B = PRESENT+DPL0+ER_SEG+SEGMENT_DESC
RET_CODE16_DESC S_DESC_386 <0FFFFH,0,0,B,0,0>
RET_DATA16_SEL EQU $-GDT_BEGIN
B = PRESENT+DPL0+RW_SEG+SEGMENT_DESC
RET_DATA16_DESC S_DESC_386 <0FFFFH,0,0,B,0,0>
DUMMY_TSS_SEL EQU $-GDT_BEGIN
B = PRESENT+DPL0+TSS_AVAIL_386+SYSTEM_DESC
DUMMY_TSS_DESC S_DESC_386 <67H,0,0,B,D_BIT,0>
KERNEL_TSS_SEL EQU $-GDT_BEGIN
B = PRESENT+DPL0+TSS_AVAIL_386+SYSTEM_DESC
KERNEL_TSS_DESC S_DESC_386 <67H,0,0,B,D_BIT,0>
SOUND_TSS_SEL EQU $-GDT_BEGIN
B = PRESENT+DPL0+TSS_AVAIL_386+SYSTEM_DESC
SOUND_TSS_DESC S_DESC_386 <67H,0,0,B,0,0>
APP_TSS_SEL EQU $-GDT_BEGIN
B = PRESENT+DPL0+TSS_AVAIL_386+SYSTEM_DESC
APP_TSS_DESC S_DESC_386 <67H,0,0,B,0,0>
SOUND_LDT_SEL EQU $-GDT_BEGIN
A = SOUND_LDT_END-SOUND_LDT_BEGIN-1
B = PRESENT+DPL0+LDT_DESC+SYSTEM_DESC
SOUND_LDT_DESC S_DESC_386 <A,0,0,B,0,0>
APP_LDT_SEL EQU $-GDT_BEGIN
A = APP_LDT_END-APP_LDT_BEGIN-1
B = PRESENT+DPL0+LDT_DESC+SYSTEM_DESC
APP_LDT_DESC S_DESC_386 <A,0,0,B,0,0>
WAIT_TICKS_GATE_SEL EQU $-GDT_BEGIN+3
B = PRESENT+DPL3+CALL_G_386+SYSTEM_DESC
A = OFFSET WAIT_TICKS
WAIT_TICKS_GATE G_DESC_386 <A,KWAIT_CODE32_SEL,0,B,0>
WAIT_MSECS_GATE_SEL EQU $-GDT_BEGIN+3
B = PRESENT+DPL3+CALL_G_386+SYSTEM_DESC
A = OFFSET WAIT_MSECS
WAIT_MSECS_GATE G_DESC_386 <A,KWAIT_CODE32_SEL,0,B,0>
CHANGESOUND_GATE_SEL EQU $-GDT_BEGIN+3
B = PRESENT+DPL3+CALL_G_386+SYSTEM_DESC
A = OFFSET CHANGESOUND
CHANGESOUND_GATE G_DESC_386 <A,SOUND_CODE32_G_SEL,0,B,0>
KERNEL_CODE32_SEL EQU $-GDT_BEGIN
A = KERNEL_CODE32_END-KERNEL_CODE32_BEGIN-1
B = PRESENT+DPL0+ER_SEG+SEGMENT_DESC
KERNEL_CODE32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
KERNEL_STACK32_SEL EQU $-GDT_BEGIN
A = KERNEL_STACK32_END-KERNEL_STACK32_BEGIN-1
B = PRESENT+DPL0+RW_SEG+SEGMENT_DESC
KERNEL_STACK32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
KWAIT_CODE32_SEL EQU $-GDT_BEGIN
A = KWAIT_CODE32_END-KWAIT_CODE32_BEGIN-1
B = PRESENT+DPL0+ER_SEG+SEGMENT_DESC
KWAIT_CODE32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
KWAIT_DATA32_SEL EQU $-GDT_BEGIN
A = KWAIT_DATA32_END-KWAIT_DATA32_BEGIN-1
B = PRESENT+DPL0+RW_SEG+SEGMENT_DESC
KWAIT_DATA32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
SOUND_CODE32_G_SEL EQU $-GDT_BEGIN+1
A = SOUND_CODE32_G_END-SOUND_CODE32_G_BEGIN-1
B = PRESENT+DPL1+ER_SEG+SEGMENT_DESC
SOUND_CODE32_G_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
SOUND_DATA32_G_SEL EQU $-GDT_BEGIN+1
A = SOUND_DATA32_G_END-SOUND_DATA32_G_BEGIN-1
B = PRESENT+DPL1+RW_SEG+SEGMENT_DESC
SOUND_DATA32_G_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
GDT_END LABEL WORD
;Define the LDT for the sound task
SOUND_LDT_BEGIN LABEL WORD
SOUND_CODE32_L_SEL EQU $-SOUND_LDT_BEGIN+5
A = SOUND_CODE32_L_END-SOUND_CODE32_L_BEGIN-1
B = PRESENT+DPL1+ER_SEG+SEGMENT_DESC
SOUND_CODE32_L_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
SOUND_STACK32_L_SEL EQU $-SOUND_LDT_BEGIN+5
A = SOUND_STACK32_L_END-SOUND_STACK32_L_BEGIN-1
B = PRESENT+DPL1+RW_SEG+SEGMENT_DESC
SOUND_STACK32_L_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
SOUND_STACK32_L_PL0_SEL EQU $-SOUND_LDT_BEGIN+4
A = SOUND_STACK32_L_PL0_END-SOUND_STACK32_L_PL0_BEGIN-1
B = PRESENT+DPL0+RW_SEG+SEGMENT_DESC
SOUND_STACK32_L_PL0_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
SOUND_LDT_END LABEL WORD
;Define the LDT for the application task
APP_LDT_BEGIN LABEL WORD
APP_CODE32_SEL EQU $-APP_LDT_BEGIN+7
A = APP_CODE32_END-APP_CODE32_BEGIN-1
B = PRESENT+DPL3+ER_SEG+SEGMENT_DESC
APP_CODE32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
APP_DATA32_SEL EQU $-APP_LDT_BEGIN+7
A = APP_DATA32_END-APP_DATA32_BEGIN-1
B = PRESENT+DPL3+RW_SEG+SEGMENT_DESC
APP_DATA32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
APP_STACK32_SEL EQU $-APP_LDT_BEGIN+7
A = APP_STACK32_END-APP_STACK32_BEGIN-1
B = PRESENT+DPL3+RW_SEG+SEGMENT_DESC
APP_STACK32_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
APP_STACK32_PL1_SEL EQU $-APP_LDT_BEGIN+5
A = APP_STACK32_PL1_END-APP_STACK32_PL1_BEGIN-1
B = PRESENT+DPL1+RW_SEG+SEGMENT_DESC
APP_STACK32_PL1_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
APP_STACK32_PL0_SEL EQU $-APP_LDT_BEGIN+4
A = APP_STACK32_PL0_END-APP_STACK32_PL0_BEGIN-1
B = PRESENT+DPL0+RW_SEG+SEGMENT_DESC
APP_STACK32_PL0_DESC S_DESC_386 <A,0,0,B,D_BIT,0>
APP_LDT_END LABEL WORD
;Define the interrupt descriptor table (IDT)
IDT_BEGIN LABEL WORD
B = PRESENT+DPL0+INT_G_386+SYSTEM_DESC
A = OFFSET EXCEPTION_HANDLER0
IDT0_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER1
IDT1_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER2
IDT2_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER3
IDT3_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER4
IDT4_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER5
IDT5_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER6
IDT6_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER7
IDT7_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER8
IDT8_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER9
IDT9_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER10
IDT10_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER11
IDT11_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER12
IDT12_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER13
IDT13_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER14
IDT14_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER15
IDT15_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER16
IDT16_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
A = OFFSET EXCEPTION_HANDLER17
IDT17_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
DQ (2FH-17) DUP(0)
B = PRESENT+DPL0+INT_G_386+SYSTEM_DESC
A = OFFSET TIMER_ISR
TIMER_ISR_GATE_DESC G_DESC_386 <A,KERNEL_CODE32_SEL,0,B,0>
DQ (37H-30H) DUP(0)
IDT_END LABEL WORD
INITIAL_MASK DB 0 ;Initial interrupt mask storage
OLD_IDT_BASE_LIMIT LABEL FWORD ;Real mode vector table address
OLD_IDT_LIMIT DW 3FFH ;Real mode limit
OLD_IDT_BASE DD 0 ;Real mode base
;Define the data structures used by LGDT and LIDT instructions.
GDT_BASE_LIMIT LABEL FWORD ;fword means 6 bytes
GDT_LIMIT DW (GDT_END-GDT_BEGIN)-1 ;THE GDT LIMIT
GDT_BASE DD ? ;AND BASE ADDRESS OF THE GDT
IDT_BASE_LIMIT LABEL FWORD
IDT_LIMIT DW (IDT_END-IDT_BEGIN)-1 ;THE IDT LIMIT
IDT_BASE DD ? ;AND BASE ADDRESS OF THE IDT
DUMMY_TSS TSS_STRUC <> ;dummy TSS used for 1st task switch
; Define task state segments (TSSs) for all tasks
INIT_FLAGS EQU 1202H ;Initial contents of EFLAGS for
; each task. IOPL = 01, IF = 1
KERNEL_TSS TSS_STRUC <,,,,,,,,,,\
,,OFFSET KERNEL_START32,INIT_FLAGS,,,,,\
OFFSET KERNEL_STACK32_END,,,,,\
,KERNEL_CODE32_SEL,,KERNEL_STACK32_SEL,\
,KWAIT_DATA32_SEL,,,,,,,,,>
SOUND_TSS TSS_STRUC <,,OFFSET SOUND_STACK32_L_PL0_END,\
SOUND_STACK32_L_PL0_SEL,,,,,,,\
,,OFFSET SOUND_START32,INIT_FLAGS,,,,,\
OFFSET SOUND_STACK32_L_END,,,,,\
,SOUND_CODE32_L_SEL,,SOUND_STACK32_L_SEL,\
,SOUND_DATA32_G_SEL,,,,,,SOUND_LDT_SEL\
,,,>
APP_TSS TSS_STRUC <,,OFFSET APP_STACK32_PL0_END,\
APP_STACK32_PL0_SEL,,\
OFFSET APP_STACK32_PL1_END,\
APP_STACK32_PL1_SEL,,,,\
,,OFFSET APP_START32,INIT_FLAGS,,,,,\
OFFSET APP_STACK32_END,,,,,\
,APP_CODE32_SEL,,APP_STACK32_SEL,\
,APP_DATA32_SEL,,,,,,APP_LDT_SEL\
,,,>
DATA_END LABEL WORD
DATA ENDS
CODE SEGMENT USE16
ASSUME CS:CODE,DS:DATA,SS:STACK
CODE_BEGIN:
PICA00 EQU 20H ;master 8259 port 0
PICA01 EQU 21H ;master 8259 port 1
EOI EQU 20H ;End-of-interrupt command
REAL_8259 PROC
;This proc restores the master pic to the original setting.
MOV AL,11H
OUT PICA00,AL
MOV AL,08H ;Base vector = 8
OUT PICA01,AL
MOV AL,4 ;Slave on ir2
OUT PICA01,AL
MOV AL,1
OUT PICA01,AL
MOV AL,INITIAL_MASK ;Restore original mask
OUT PICA01,AL
IN AL,60H ;Must clear keybd interrupt on
;some machines
RET
REAL_8259 ENDP
PROTECTED_8259 PROC
;This proc moves the 8259 to vector 30-37H and mask everything
;but the keyboard , IRQ1
IN AL,PICA01 ;Save current mask
MOV INITIAL_MASK,AL
MOV AL,11H
OUT PICA00,AL
MOV AL,30H ;New type = 30H
OUT PICA01,AL
MOV AL,4 ;Slave on level 2
OUT PICA01,AL
MOV AL,1
OUT PICA01,AL
MOV AL,11111111B ;Mask all for now
OUT PICA01,AL
RET
PROTECTED_8259 ENDP
START:
MOV AX,DATA
MOV DS,AX ;SET UP DATA ADDRESSABILITY
;Put base addresses in all segment descriptors
PUT_BASE_IN_DESCRIPTOR DUMMY_TSS,DUMMY_TSS_DESC
PUT_BASE_IN_DESCRIPTOR KERNEL_TSS,KERNEL_TSS_DESC
PUT_BASE_IN_DESCRIPTOR KERNEL_CODE32_BEGIN,KERNEL_CODE32_DESC
PUT_BASE_IN_DESCRIPTOR KERNEL_STACK32_BEGIN,KERNEL_STACK32_DESC
PUT_BASE_IN_DESCRIPTOR KWAIT_DATA32_BEGIN,KWAIT_DATA32_DESC
PUT_BASE_IN_DESCRIPTOR KWAIT_CODE32_BEGIN,KWAIT_CODE32_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_CODE32_G_BEGIN,SOUND_CODE32_G_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_CODE32_L_BEGIN,SOUND_CODE32_L_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_STACK32_L_BEGIN,SOUND_STACK32_L_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_STACK32_L_PL0_BEGIN,SOUND_STACK32_L_PL0_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_DATA32_G_BEGIN,SOUND_DATA32_G_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_LDT_BEGIN,SOUND_LDT_DESC
PUT_BASE_IN_DESCRIPTOR SOUND_TSS,SOUND_TSS_DESC
PUT_BASE_IN_DESCRIPTOR APP_CODE32_BEGIN,APP_CODE32_DESC
PUT_BASE_IN_DESCRIPTOR APP_STACK32_BEGIN,APP_STACK32_DESC
PUT_BASE_IN_DESCRIPTOR APP_STACK32_PL1_BEGIN,APP_STACK32_PL1_DESC
PUT_BASE_IN_DESCRIPTOR APP_STACK32_PL0_BEGIN,APP_STACK32_PL0_DESC
PUT_BASE_IN_DESCRIPTOR APP_DATA32_BEGIN,APP_DATA32_DESC
PUT_BASE_IN_DESCRIPTOR APP_LDT_BEGIN,APP_LDT_DESC
PUT_BASE_IN_DESCRIPTOR APP_TSS,APP_TSS_DESC
INIT_GDTIDT_ADDRESS GDT_BEGIN,GDT_BASE
INIT_GDTIDT_ADDRESS IDT_BEGIN,IDT_BASE
PUT_BASE_IN_DESCRIPTOR CODE_BEGIN,RET_CODE16_DESC
PUT_BASE_IN_DESCRIPTOR DATA_BEGIN,RET_DATA16_DESC
LGDTD GDT_BASE_LIMIT ;Load GDT register
CLI
LIDTD IDT_BASE_LIMIT ;Load IDT register
CALL PROTECTED_8259 ;Set up 8259
MOV EAX,CR0 ;Fetch control register 0
OR EAX,1 ;Set protection enable (PE) bit
MOV CR0,EAX ;Go to protected mode
JMP S1 ;Flush instruction prefetch queue
S1:
MOV AX,DUMMY_TSS_SEL ;Set up dummy TSS to receive
LTR AX ;Undefined state from 1st switch
; ************** First task switch **************
PJMPF16_32 KERNEL_TSS_SEL,0 ;switch to kernel task
RET_REAL: ;Shutdown, back to real mode
CLI ;Disable interrupts
MOV AX,RET_DATA16_SEL ;Load seg regs to set limit
MOV DS,AX ;to real mode 64K value
MOV ES,AX
MOV SS,AX
MOV FS,AX
MOV GS,AX
LIDTD OLD_IDT_BASE_LIMIT ;Set to vector table address
MOV EAX,CR0 ;Fetch control register 0
AND EAX,07FFFFFFEH ;Clear PE bit
MOV CR0,EAX ;Go back to real mode
JMP FAR PTR S16 ;Far jump to reload CS
S16:
MOV AX,DATA ;Load segment registers
MOV DS,AX ; with real mode values.
MOV AX,STACK
MOV SS,AX
MOV SP,OFFSET STACK_END
CALL REAL_8259 ;Restore 8259 to real mode values
STI ;Enable interrupts
MOV AX,4C00H ;Back to DOS
INT 21H ;DOS function call
CODE_END:
CODE ENDS
AUTONUMLGL
10. Compiling
and Executing the Example
The example runs on any minimally configured AT-compatible PC with a 32-bit processor. No extended memory is required,
nor any peripherals beyond those found on all AT-class machines.
The example runs under DOS. It would be more precise to say that it runs alongside DOS, because, although DOS
loads the example into memory, the example does not call on any DOS services after it is loaded. No other software
is required for execution.
Table 3 shows the configurations on which the example has been tested. Because it is not very demanding
of the assembler, of DOS, or of the hardware system, we expect that it will execute correctly on most other AT-compatible
configurations as well.
Table 3. Tested Configurations
Processor |
DOS Version |
MASM Version
|
33 MHz Intel486 DX Microprocessor |
5.0 |
6.1 |
25 MHz Intel386 DX Microprocessor |
5.0 |
6.1 |
25 MHz Intel486 DX Microprocessor |
5.0 |
6.1 |
50 MHz Intel486 DX2 Microprocessor |
5.0 |
6.1 |
60 MHz Pentium |
5.0 |
6.1 |
33 MHz Intel486 DX Microprocessor |
5.0 |
6.0 |
The source code for this example is available from the Intel ACCESS forum on the Compuserve network (GO INTELACCESS)
as file PMSND1.EXE. A command such as the following will compile and link the example program with MASM 6.0 or
6.1: ML /Fl PMSOUND.ASM
Note that some versions of MASM (version 6.1, in particular) use a DOS extender and require the presence of a memory
manager, such as EMM386. To execute the example, however, no programs may be present that use protected mode; for
example DOS extenders, such as Microsoft Windows, memory managers, such as EMM386 and QEMM-386, and RAM disk programs.
Such a software configuration is perhaps most easily obtained by preparing a bootable floppy disk with minimal
AUTOEXEC.BAT and CONFIG.SYS files. Reboot the system from the floppy disk just before executing the example program.
11. Where to Go From
Here
We hope you enjoyed your test drive in protected mode. The techniques shown in this example are those that any
32-bit, protected-mode operating system needs to use. (The only basic techniques not illustrated are paging and
virtual 8086 mode.) The example could serve as the seed for a real-time or embedded kernel, a full fledged OS,
or a DOS extender. Possible territories for further exploration might include:
Real device drivers—for example, keyboard, video, disk, instrumentation, and appliances.
Expanded kernel functions—for example, dynamic memory allocation; priority-based, preemptive scheduler; intertask
data sharing, message passing, and synchronization.
Exception handlers that provide more information about the location and possible cause of the exception.
Debugger.
Paging.
Dynamic task creation with loader.
Virtual memory with swap file.
Virtual-8086 mode and monitor, to extend DOS or to create multiple V86 tasks.
DOS extender.
Whether you use the example application either as a starting point or as a model for further development, or whether
you choose to take advantage of one of the commercially available, 32-bit, protected-mode environments, we wish
you a good trip.
12. About the Authors
Jeff Royer, Royer Associates; (415) 326-8079, Compuserve 71170,2452
Contact Royer Associates for more information about the debugger used for this example and on-site classes covering
protected mode.
Loren Stafford, Dynalogic, Inc.; (415) 967-6355, Compuserve 72260,3045
For more information on protected-mode programming, refer to the following documents from Intel Corporation and
from other sources. If you can't find a book in your local bookstore, you will be happy to know that many of these
books are available from on-line bookstores on Compuserve. Enter FIND BOOK at any "!" prompt to obtain
a list of on-line book sources.
To order Intel literature or to obtain literature pricing information in the U.S. and Canada, call or write Intel
Literature Sales. In Europe and other international locations, please contact your local sales office or distributor.
A list of sales offices and distributors can be found at the back of most Intel publications.
Intel Literature Sales
P.O. Box 7641
Mt. Prospect, IL 60056-7641
In the U.S. and Canada
call toll free
(800) 548-4725
13. Bibliography
80386 System Software Writer's Guide, Intel order number 231499.
Brey, Barry B. 1993. The Advanced Intel Microprocessors. New York: Macmillan.
Crawford, John H. and Patrick P. Gelsinger. 1987. Programming the 80386. San Francisco: Sybex.
Custer, Helen. 1992. Inside Windows NT. Redmond, WA: Microsoft Press.
Deitel, H.M. and M.S. Kogan. 1992. The Design of OS/2. Reading, MA: Addison-Wesley.
DOS Protected Mode Interface (DPMI) Specification, version 1.0, Intel order number 240977.
Duncan, Ray, ed. 1991. Extending DOS: A Programmer's Guide to Protected-Mode DOS. Reading, MA: Addison-Wesley.
Ezzel, Ben. 1993. The power under the hood—Review: Windows NT. PC Magazine. June 15.
Linthicum, David S. and Steven J. Vaughan-Nichols. 1993. The beast turns beauty—UNIX on Intel. PC Magazine. June
15.
Pappas, Chris H. and William H. Murray III. 1988. 80386 Microprocessor Handbook. Berkeley, CA: Osborne McGraw-Hill.
Pentium™ Processor User's Manual, Vol. 3: Architecture and Programming Manual, Intel order number 241430.
Prosise, Jeff. 1993. How DOS programs can use over 1MB of RAM. PC Magazine. June 29.
Robinson, Phillip, ed. 1988. Dr. Dobb's Toolbook of 80286/80386 Programming. Redwood City, CA: M&T Publishing.
Salemi, Joe. 1993. IBM's 32-bit challenger revisited—Preview: OS/2 2.1. PC Magazine. June 15.
Schulman, Andrew. 1990. Stalking GP faults. Dr. Dobbs Journal. January and February.
The source code for these articles is available on Compuserve in the Dr. Dobbs forum (GO DDJFORUM) as GPFLT2.ZIP.
TIS committee. Portable Formats Specification and Format Specification for Windows, Intel order number 241597.
Williams, Albert A. 1990. Roll your own DOS extender. Dr. Dobbs Journal. October and November.
The source code for these articles is available on Compuserve in the Dr. Dobbs forum (GO DDJFORUM) as PROT2.ASC.
Williams, Albert A. 1993. DOS and Windows Protected Mode: Programming with DOS Extenders in C. Reading, MA: Addison-Wesley.
In addition to the above printed literature, much information can be obtained on-line in the following Compuserve
forums:
|