Chapter 1 - Multitasker Structure and Functions
Contents of this chapter
Real Time Monitor Module
Domain Manager Module
High Level Modules
Virtual Machine Module
This manual describes the programming interface for the multitasking
80386 kernel. This kernel creates multiple virtual DOS machines at the
request of the user who controls it through the Task Manager. The user
may run a standard DOS application in each of these virtual DOS machines,
but may also run applications that are aware of, and take advantage of,
the multitasking. This manual tells you how to write such applications,
and also multitasking-aware device drivers.
The Task Manager can also run on a single-tasking kernel as a task
switcher. It has its own API that allows applications and device drivers
to control and respond to task switches. If it finds that it is not running
on the multitasking kernel, an additional switcher-specific API can be
called by applications. The Task Manager API is described in the DR-DOS
System and Programmer's Guide.
Some of the multitasking kernel's features relate directly to those of
the 80386 processor. This is particularly the case with the low level
functions intended only for the use of programmers of device drivers and
system extensions. We use Intel's terminology and direct you to Intel*
Corporation's 386 DX Microprocessor Programmer's Reference Guide
for descriptions of these features, including memory paging, exception
handling, Task State Segments (TSSs) and I/O bitmaps.
Multitasking applications need to call some of the kernel's functions,
but many of them are intended only for the system's internal use. To
simplify the life of those of you who are writing applications that
do not perform low-level processor-specific and operating system-specific
operations, we have flagged each function as an application program
function (named X_function) or system program function (named Z_function).
If you are writing applications you can probably ignore all functions whose
names start with Z_.
The major facilities that applications can use include
The terminology used to describe multitasking can be confusing. There are
multiple virtual DOS machines, each running an application as a task. A
virtual machine and the task in it are called a domain. Each task has one
or more threads of execution or processes, each of which has a state
represented by its current set of processor registers. Each process
must have its own stack, but may share code and static data with other
Each domain has some memory that is private to it, but a domain can also
access some shared or global memory. The private memory initially has the
same contents as that of the parent domain, but execution of the task can
change this without affecting the parent. This private memory is called
instance memory and the data in it instance data since each instance of a
domain has its own copy.
At any time only one process of one task is actually executing. The system
keeps an image in memory of the current registers of all other processes,
which it uses to load the processor's registers when it dispatches the
The kernel consists of a number of cooperating modules. Each module
provides a complete and distinct set of services, and we therefore
describe the kernel module by module.
Figure 1-1 shows the internal structure of
the core of the multitasking kernel, and
Figure 1-5 on page 1-20 shows how the other
modules relate to it.
The core consists of six modules.
The modules which have functions you can call are
The Supervisor module (SUP) performs a variety of jobs on behalf of the
other modules. The functions that applications can call are listed in
Table 2-2 on page 2-2. The most important
is the multitasker installation check, which you must call before you can
assume that any of the other calls are supported.
Real Time Monitor Module
The Real Time Monitor module (RTM) includes the dispatcher, and maintains
all lists of processes and their states. RTM also includes the queue and
mutual-exclusion zone (mutex) functions. Queues are used to communicate
between processes and to synchronize activities. Mutual-exclusion zones
give exclusive use of a shared resource to one process at a time. System
extensions may also use the RTM flag functions.
Processes, or threads, are the active objects that execute code.
Table 2-3 on page 2-3 lists the functions
you call to create and manage processes. Initially, there is just one
process running. When the user tells the Task Manager to create a new task,
or when a process calls X_PCreate, a new process is created. The new
process is said to be the child of the process that called X_PCreate.
Each process has a set of attributes.
- Name. An eight character name by which the user may identify processes.
- Handle. A number by which the process is identified to the system.
- Status. Ready to run or waiting for some event or resource.
- Flags. Process type, including System and Keep flags.
- Parent. The handle of the process that created this one.
Processes can be ready to run, suspended, waiting on a queue, or waiting
for a specified time. Just one process is actually running at a time.
The list of processes that are ready to run is ordered by their priority.
When a process is created, it is given a priority. Every clock tick, the
dispatcher runs the highest priority ready process for one tick, unless it
is in the foreground domain when it is given more - typically five ticks.
You can call X_TickGet to find out the clock tick period (typically one
eighteenth second) and Z_TicksSet to set the number of ticks given to
tasks in the foreground domain. If several processes have the same
priority and are all ready, they share processor time in a round-robin
manner. The Domain Manager and Virtual Machine manager ensure that the
running process sees the correct environment.
Applications can call RTM functions to wait for some number of clock
ticks or to force the dispatcher to run (just as though there had been a
clock tick). They can also ask RTM to change their own priority, but this
may upset the round robin scheduling, which relies on all applications
having the same priority. Changing priority should be done only if it is
found to be essential.
If you must perform some tasks within a specific time, or do something
that makes the computer's state temporarily unstable, you can call
X_CritEnter to force the dispatcher not to run while the application
is in the critical region. Although interrupts are not disabled, no other
process can run while your process is in a critical region, so please use
this facility only if you cannot achieve the same results through
interrupts and mutexes.
Processes have eight character names. These are not used by the system,
but can be displayed to the user to help identify tasks. Internally,
processes are identified by their 32-bit handles. These handles are
global, so they may be passed to processes in any domain. For the
duration of a process, its handle is unique, but a handle may be reused
if a process terminates and another is created. Be careful to ensure that
a handle still refers to the process you wish to specify.
The handle should not be used as a pointer, but only as a parameter to
system calls. There are calls to get the name, priority, status and
domain of a process, given its handle. Calls that require a process
handle allow the value of 0 to imply the calling process.
When a process is created, its parent is told the child's handle, and a
process can get its own handle or a list of all current process handles.
You can obtain the name of any process given its handle, but you cannot
obtain the handle of a process given its name. There is no guarantee or
requirement that process names are unique.
Further details of the possible values of priority, flags and status are
described under the system calls you use to get and/or set them; these
calls are listed in Table 2-3 on page 2-3.
A queue is a fixed length buffer in global memory that can contain a
certain number of messages. Each message is of a fixed length. You
specify the message length and queue capacity when you create the queue.
You also specify the queue's name, which must be unique.
Table 2-4 on page 2-3 lists the functions
you call to create and use queues.
Any process that knows a queue's name can open it and then send messages
to or read messages from it, but the most common use of queues involves
just one process reading from any one queue. The content of a message is
entirely a matter for the applications reading and writing it.
Although each queue's buffer will hold a fixed number of messages,
processes that attempt to read an empty queue or write to a full one
are suspended, so the buffer capacity and sequence of events is hidden
from them. To writers, the queue appears to have an unlimited capacity.
To readers it seems always to have a message. If a process does not want
to be suspended, it can make the read or write conditional on immediate
success. Also, you can read a message without removing it from the queue.
This is called a non-destructive read.
Figure 1-2 shows the various states a queue
may be in and the transitions between them. When you create a queue it is
empty. Any number of processes may wait to read from an empty queue. When
a process at last writes a message to the queue, the process which has
been waiting longest is woken and reads the message.
A similar sequence of events takes place if processes try to write to a full queue. The writing processes are suspended. When a process reads from the queue, there will be space for one more message in the queue buffer. The message from one waiting process is immediately written into this space, and the writing process becomes ready to run again.
Queues are thus a very convenient means to synchronize processes, as well as to pass information between processes.
Mutual Exclusion Zones (Mutexes)
Mutual exclusion zones ensure that only one process at a time is performing some activity. This is one of the most common requirements when multiple processes may be running asynchronously with respect to one another.
A mutual exclusion zone is not necessarily a physical entity such as a section of code. It can be any activity that consists of two or more actions that must be completed as a unit because they require exclusive access to a shared resource. Examples of such activities and resources include shared non-reentrant code, data base updating and printing.
Mutual exclusion is achieved through the use of mutexes, a special type of queue. They have several features that enhance their usefulness.
- Automatic naming. When an application creates a mutex, it is given a unique name and a handle (a number). Other applications that wish to share the resource protected by the mutex must know the handle number.
- Queuing of processes waiting to use the resource. As with normal RTM queues, any number of processes may wait to enter a mutex zone.
- Multiple entry. The process that is in the mutex zone may reenter it. If it does so, a count of the number of times it has entered is incremented and the zone is not relinquished to another process until the owning process has exited the same number of times it entered. This allows, for instance, recursive calls to code that enters a mutex zone.
- Protection against termination. When a process terminates, the system automatically makes it exit from any mutex zones it may be in. This ensures that the zone will not be locked forever.
Figure 1-3 shows the states a mutex may be in and the transitions between them. For simplicity it does not cover the case of the owning process reentering a mutex.
Table 2-6 on page 2-5 lists the functions you call to create and use mutexes.
A mutex comes into existence when an application calls X_MXCreate. The mutex is automatically given a unique name and a handle that is used to identify it in all subsequent calls.
Applications call X_MXEnter to gain exclusive use of the zone. If the zone is currently Free, its state is changed to InUse and the application continues. If another process is in the zone, the second process goes into the queue. If the process calling X_MXEnter is already in the zone, its count is incremented.
When ready to release the resource, the owning application calls X_MXExit. If another process is waiting to enter the zone, it becomes ready to run and is dispatched immediately if its priority is high enough.
Note that for each call to X_MXEnter you must call X_MXExit once.
Being in a mutex guarantees that no other process can enter the same mutex, but it gives no assurance that you will get the CPU time you may need. You can call the RTM Critical Region functions X_CritEnter and X_CritExit to do this.
Flags have been provided to allow Interrupt Service Routines (ISRs) to signal asynchronous events to applications. The quickest way for an ISR to wake up a synchronous process is to call Z_FlagSet.
Table 2-6 on page 2-5 lists the functions you call to create and use flags.
Only one process can wait on any given flag.
Before you can use a flag you must ask RTM to allocate one to you. There are a limited number of flags in the system, so use them sparingly. Each flag is identified only by its number.
The process that is waiting for the event should call Z_FlagWait. If the flag is in its Off state, the process will sleep until the ISR calls Z_FlagSet or until an optional timeout expires. If the flag has already been set, the process continues immediately.
If several interrupts occur before the process calls Z_FlagWait, the flag remains in its On state with a count of the number of "missed" interrupts. The flag stays in the On state until the corresponding number of Z_FlagWait calls has been made. An error code is returned both to the ISR and to the process that calls Z_FlagWait, to show that overrun has occurred.
Memory Manager Module
The Memory Manager module (MEM) and Domain Manager module (DOM) work together to allocate and map memory and descriptors so that each application can have the resources it needs. Many of the functions in MEM are needed only by other parts of the operating system. Table 2-7 on page 2-6 lists the functions in MEM.
Applications will not normally need to call MEM directly. If they need to allocate memory or descriptors, they should do so through higher level portable interfaces, such as XMS, VCPI and DPMI, which in turn call MEM.
Domain Manager Module
A major capability of the operating system is its ability to run multiple DOS applications simultaneously. Each application sees a real mode 8086-like environment of its own, complete with an operating system, device drivers, keyboard, mouse, screen and so on. This environment is called a virtual machine, because it does not exist physically. The combination of a virtual machine and the application running in it is called a domain. Domains are created and managed by the Domain Manager (DOM). Table 2-8 on page 2-8 lists the functions you call to create and control domains.
When the kernel starts up, only one domain is created. It is normally the Task Manager which, in response to hot-key combinations pressed by the user, instructs DOM to create domains, delete them and switch selected domains to the foreground (that is, attach them to the keyboard, mouse and screen).
DOM actually performs only the lower level functions needed to create virtual 8086 and virtual DOS machines. Higher level functions such as virtualizing a VGA are carried out by the VM module.
A virtual machine is achieved by mapping the required physical memory into the first four megabytes of linear address space, setting traps to intercept I/O, then putting the processor into virtual 8086 mode. Virtual 8086 mode is practically indistinguishable from real mode as far as DOS applications are concerned. The required physical memory is a combination of unshared instance memory and shared global memory. The virtualization includes very sophisticated exception handling that can distinguish between the many sources of exceptions and interrupts. By trapping, for example, attempts to read from the screen memory, it allows applications to continue to run in the background. Each application "sees" a full screen. Only the foreground application's screen is visible to the user, but an application can force urgent messages to be displayed immediately, even if its domain is in the background, by calling the Virtual Machine module (VM); see page 1-21 for a description of VM.
Each domain has its own I/O bitmap. The bitmap has a bit for each I/O port address. The bits corresponding to virtualized ports are set. This causes a General Exception whenever an application tries to read from or write to the port. The system recognizes that the exception was caused by an I/O operation, so it invokes the Virtual Machine module's I/O exception handlers, which make sure the port ownership rules are obeyed; see "Virtual Machine Module" on page 1-21 for a description of the rules.
If you are writing system software that similarly needs to trap accesses to other I/O ports, you can call DOM functions to read, set and reset bits in the I/O bitmap of any domain. These functions' names all start Z_IOBitmap...
See the description of Z_HandlerGenEx for a full explanation of exception and interrupt handling, including the use of installable handlers.
Before the Task Manager is loaded, only one domain exists. In its
initialization, Task Manager must determine which memory areas should
be instanced. It uses several methods to do this, including making an
INT 2FH with AX=4B05H to ask global programs to identify their instance
data. Programs that need to instance their data in a multitasking
environment must hook INT 2FH and process it appropriately. Any memory
owned by device drivers and other resident programs that they do not
identify as instanced is assumed to be global. Programs loaded after
the Task Manager is running will be loaded into local memory, that is,
instance memory within one domain only.
The Task Manager informs DOM which addresses are to be instanced when it
has found them all.
Memory instancing uses 80386 page tables for large areas and
software-controlled swapping for small ones. Each domain has its own page
table for the first 4 MB of memory. These page tables point to globally
shared pages and unshared instanced pages. A context switch that includes
a domain switch involves four steps to map memory.
As well as mapping memory, a domain switch loads the I/O bitmap and
exception handlers for the incoming domain, because these may vary from domain to domain.
- Saving the instanced portions of global pages into a data area owned
by the outgoing domain
- Switching to the new domain's base page table
- Copying the global portions of instance pages from the
previous domain's copy
- Loading the instanced portions of global pages from a
data area owned by the incoming domain
Domain Screen Switching
Applications do not normally need to be aware of which domain owns the physical screen, and which domains are running in the background. Unless you are writing system software, you can skip this section.
Global programs that need to be notified of domain screen switches should hook INT 2FH and be alert for the function Build Notification Chain (AX=4B01H), which will be the first indication they have that the Task Manager is active. This informs the kernel of the program's callback function's address, and informs the program of the kernel's service functions' address.
While the Task Manager is active, programs can call Detect Switcher (INT 2FH / AX=4B02H) and then Hook Notification Chain to achieve the same thing.
Programs can call the service functions to perform the following:
High Level Modules
On top of the core modules, the multitasker has several higher level modules, shown in Figure 1-5 and described in the rest of this chapter.
High Level Modules
Of the modules shown in Figure 1-5 only the Virtual Machine (VM) has an API that is unique to the multitasker, though the Task Manager's API is reduced when running on the multitasker. Other modules have no API, or behave the same whether running on the multitasker or raw single tasking DOS, with or without the Task Manager. Their descriptions are therefore not included in this manual.
Virtual Machine Module
The Virtual Machine module (VM) completes the virtualization of a PC from the application's perspective. Its major tasks are
Pop Up Messages
A TSR running in a domain can call VM to ask it to prepare for, and optionally display, a pop-up message on the TSR domain's virtual console. Similarly, an application whose domain is currently in the background may display a message over the foreground screen. In both cases VM saves the previous screen contents and restores them after the message has been shown.
The screen may have been in a graphic mode, so VM clears it and puts it into the default text mode, allowing you to display text by INT 10 or direct screen writes.
Domain Message Mode
Use Domain Messages if your program is a TSR or thread that does not own its domain's screen, and therefore needs to save the screen mode and contents before displaying a message. VM has two related calls: X_MsgDomEnter and X_MsgDomDisplay. They both save the call parameters, put the domain into Domain Message Mode and return immediately to the caller. The message has not been displayed, and nor is the screen in text mode at this point, unless the domain is in the foreground.
If the domain is in the background, its application can continue to write to the virtual screen as normal. When the domain comes to the foreground, VM saves the screen mode and contents, then either displays a message string you passed it (if you called X_MsgDomDisplay) or calls a function you specified (if you called X_MsgDomEnter).
The domain is in the foreground when your message display function is called, and can therefore use the keyboard to take the user's instructions. When the user has responded and you want to remove the message, call X_MsgDomExit. VM restores the original screen mode and contents, and turns off Domain Message Mode.
While in Domain Message Mode, the user can switch a different domain to the foreground. If so, your message will not be seen again until its domain is again in the foreground. X_MsgDomEnter calls your message display function each time the domain comes to the foreground.
Global Message Mode
Use Global Message Mode if your application needs urgently to display a message when its domain is, or may be, in the background. Again, VM has two related calls: X_MsgGlobalEnter and X_MsgGlobalDisplay. They immediately bring the domain to the foreground, save the screen mode and contents, and put the screen into its default text mode. At this point, X_MsgGlobalEnter returns to the caller, whereas X_MsgDomDisplay first displays a message string you passed it.
Because the domain is now in the foreground, you can read the keyboard and output to the screen (though with some restrictions described on page 2-121). When the user has responded and you want to remove the message and return to the background, call X_MsgGlobalExit.
While in Global Message Mode the Task Manager is disabled, so the user cannot avoid seeing the message.
Serial and Parallel Ports
The standard ports known to the ROM BIOS are initially set up to use their default port
numbers and IRQs. You can call VM to change these and to tell it the port numbers and
IRQs of COM and LPT ports (up to COM4 and LPT3), which the BIOS does not know about.
These values are physical and therefore global.
While the Task Manager is running, VM virtualizes the serial and parallel ports. The
method it uses to decide which domain can use the physical ports is based on
timeouts and traps, as follows.
Timeout values (t) are initially set to 5 seconds for a COM port, and 15
seconds for a printer port, though you may call VM to change the timeout on
any port. The operation of timeouts is:
The specified timeout is therefore the maximum time which can elapse between the last access by the owner and the point at which the port is freed. The minimum time is t/2. The timeout value should be set to at least twice the maximum interval expected between port accesses while the port is being used.
- Initially, VM traps all accesses of all virtualized ports (those that have a
non-zero base address and timeout).
- The first domain to access a given COM or PRN port becomes the "owner" of that port, thus other domains cannot use the port. VM stops trapping the port for a time of t/2.
- When that timeout expires, VM sets its trap again, and restarts another timeout of t/2. If the owner accesses the port within that time, VM immediately clears its trap and goes back to Step 2, otherwise Step 4 is performed.
- The port is freed, and will be given to the next domain to access it.
A timeout value of 0 disables virtualization of the port. Any domain may access it freely at any time.
A timeout value of -1 sets an "infinite" timeout; thus, once the port is given to a domain, it never becomes free again, unless the domain is deleted.
The Task Manager controls switching between multiple DOS applications by giving the VM and DOM modules the appropriate sequences of commands. It accepts instructions from the user via keyboard hot-key combinations. It can also be controlled by software, using its native API (INT 2FH / AH=27H), though only the functions listed in Table 2-10 on page 2-11 are available when the Task Manager is running on the multitasking kernel.
The Task Manager also supports the industry-standard INT 2FH / AH=4BH API for task switchers with its corresponding service and notification functions. This interface is provided to support global programs written to run on other DOS task switchers. Although these functions are not described in this manual, they are listed in Table 2-10 on page 2-11 to Table 2-13 on page 2-13.
When the Task Manager is not present, the kernel does not respond if an application or device driver tries to detect the presence of a switcher (INT 2FH / AX=4B02H) or asks for a switcher ID for itself (INT 2FH / AX=4B03H). When the Task Manager is present, the kernel does respond to these requests. However, the Task Manager does not itself call INT 2FH / AX=4B02H, so it cannot be run if a task switcher is already present.
Copyright © 1994, 1997, Caldera, Inc. All rights