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

  1. Duty Cycle of Sound Waves

List of Tables

  1. Tools Alternatives
  2. Protection Structure
  3. Tested Configurations

List of Examples

  1. Jump Macros
  2. Timer Module
  3. Scheduler
  4. Exception Handlers
  5. Sound Device Driver
  6. Experiment Task
  7. Kernel Task
  8. Protected Mode Equates
  9. Protected Mode Macros
  10. 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:

  1. 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.
  2. 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:
    1. A 32-bit WAKE_UP_TIME with the same units as the real-time clock. WAKE_UP_TIME is initially set to zero.
    2. A pointer to the task's TSS.
    3. 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:
    1. If ONPERIOD is nonzero, toggle speaker on (apply current to voice coil).
    2. Wait for ONPERIOD.
    3. If OFFPERIOD is nonzero, toggle speaker off (withdraw current from voice coil).
    4. 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.

  1. 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.
  2. Enter protected mode.
  3. 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.

  1. Return the processor to real mode.
  2. Restore the interrupt vectors.
  3. 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.

    Table 2. Protection Structure

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:

  1. Put base addresses into the statically defined descriptors.
  2. Load the GDTR and IDTR.
  3. Ready the PIC for protected mode by calling PROTECTED_8259.
  4. Change the processor's mode to protected.
  5. 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:

  1. Load segment registers with values appropriate for real mode.
  2. Set the IDTR to point to the real-mode IDT.
  3. Switch to real mode.
  4. Flush the instruction prefetch queue so that addresses previously decoded in protected mode can be decoded again according to real mode rules.
  5. Prepare the PIC for real mode by calling REAL_8259.
  6. 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&trade; 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: