[Next] [Art of Assembly][Randall Hyde] [WEBster Home Page]


Art of Assembly Language: Chapter Nineteen


Art of Assembly/Win32 Edition is now available. Let me read that version.


PLEASE: Before emailing me asking how to get a hard copy of this text, read this.


PDF version of text. The Best Way to read "The Art of Assembly Language Programming"
Support Software for "Art of Assembly"


Important Notice: As you have probably discovered by now, I am no longer updating this document. The reason is quite simple: I'm working on a Windows version of "The Art of Assembly Language Programming". In the past I have encouraged individuals to send me corrections to this text. However, as I am no longer updating this material, don't expect those correctioins to appear in a future release. I am collecting errata that I will post to Webster someday, so feel free to continue sending corrections to AoA/DOS (16-bit) to rhyde@cs.ucr.edu. If you're more interested in leading edge material, please see the information about the Win/32 edition, above.


The Legal Stuff (Copyrights, etc.)


Chapter 19 - Processes, Coroutines, and Concurrency
19.1 - DOS Processes
19.1.1 - Child Processes in DOS
19.1.1.1 - Load and Execute
19.1.1.2 - Load Program
19.1.1.3 - Loading Overlays
19.1.1.4 - Terminating a Process
19.1.1.5 - Obtaining the Child Process Return Code
19.1.2 - Exception Handling in DOS: The Break Handler
19.1.3 - Exception Handling in DOS: The Critical Error Handler
19.1.4 - Exception Handling in DOS: Traps
19.1.5 - Redirection of I/O for Child Processes
19.2 - Shared Memory
19.2.1 - Static Shared Memory
19.2.2 - Dynamic Shared Memory
19.3 - Coroutines
19.3.1 - AMAZE.ASM
19.3.2 - 32-bit Coroutines
19.4 - Multitasking
19.4.1 - Lightweight and HeavyWeight Processes
19.4.2 - The UCR Standard Library Processes Package
19.4.3 - Problems with Multitasking
19.4.4 - A Sample Program with Threads
19.5 - Synchronization
19.5.1 - Atomic Operations, Test & Set, and Busy-Waiting
19.5.2 - Semaphores
19.5.3 - The UCR Standard Library Semaphore Support
19.5.4 - Using Semaphores to Protect Critical Regions
19.5.5 - Using Semaphores for Barrier Synchronization
19.6 - Deadlock



Chapter 19 Processes, Coroutines, and Concurrency


When most people speak of multitasking, they usually mean the ability to run several different application programs concurrently on one machine. Given the structure of the original 80x86 chips and MS-DOS' software design, this is very difficult to achieve when running DOS. Look at how long it's taken Microsoft to get Windows to multitask as well as it does.

Given the problems large companies like Microsoft have had trying to get multitasking to work, you might thing that it is a very difficult thing to manage. However, this isn't true. Microsoft has problems trying to make different applications that are unaware of one another work harmoniously together. Quite frankly, they have not succeeded in getting existing DOS applications to multitask well. Instead, they've been working on developers to write new programs that work well under Windows.

Multitasking is not trivial, but it is not that difficult when you write an application with multitasking specifically in mind. You can even write programs that multitask under DOS if you only take a few precautions. In this chapter, we will discuss the concept of a DOS process, a coroutine, and a general process.


19.1 DOS Processes


Although MS-DOS is a single tasking operating system, this does not mean there can only be one program at a time in memory. Indeed, the whole purpose of the previous chapter was to describe how to get two or more programs operating in memory at one time. However, even if we ignore TSRs for the time being, you can still load several programs into memory at one time under DOS. The only catch is, DOS only provides the ability for them to run one at a time in a very specific fashion. Unless the processes are cooperating, their execution profile follows a very strict pattern.


19.1.1 Child Processes in DOS


When a DOS application is running, it can load and executing some other program using the DOS EXEC function. Under normal circumstances, when an application (the parent) runs a second program (the child), the child process executes to completion and then returns to the parent. This is very much like a procedure call, except it is a little more difficult to pass parameters between the two.

MS-DOS provides several functions you can use to load and execute program code, terminate processes, and obtain the exit status for a process. The following table lists many of these operations.

DOS Character Oriented Functions
Function #

(AH)
Input

Parameters
Output

Parameters
Description
4Bh al- 0

ds:dx- pointer to program name.

es:bx- pointer to LOADEXEC structure.
ax- error code if carry set. Load and execute program
4Bh al- 1

ds:dx- pointer to program name.

es:bx- pointer to LOAD structure.
ax- error code if carry set. Load program
4Bh al- 3

ds:dx- pointer to program name.

es:bx- pointer to OVERLAY structure.
ax- error code if carry set. Load overlay
4Ch al- process return code Terminate execution
4Dh al- return value

ah- termination method.
Get child process return value



19.1.1.1 Load and Execute


The "load and execute" call requires two parameters. The first, in ds:dx, is a pointer to a zero terminated string containing the pathname of the program to execute. This must be a ".COM" or ".EXE" file and the string must contain the program name's extension. The second parameter, in es:bx, is a pointer to a LOADEXEC data structure. This data structure takes the following form:














LOADEXEC        struct
EnvPtr          word    ?       ;Pointer to environment area
CmdLinePtr      dword   ?       ;Pointer to command line
FCB1            dword   ?       ;Pointer to default FCB1
FCB2            dword   ?       ;Pointer to default FCB2
LOADEXEC        ends

Envptr is the segment address of the DOS environment block created for the new application. If this field contains a zero, DOS creates a copy of the current process' environment block for the child process. If the program you are running does not access the environment block, you can save several hundred bytes to a few kilobytes by pointing the environment pointer field to a string of four zeros.

The CmdLinePtr field contains the address of the command line to supply to the program. DOS will copy this command line to offset 80h in the new PSP it creates for the child process. A valid command line consists of a byte containing a character count, a least one space, any character belonging to the command line, and a terminating carriage return character (0Dh). The first byte should contain the length of the ASCII characters in the command line, not including the carriage return. If this byte contains zero, then the second byte of the command line should be the carriage return, not a space. Example:














MyCmdLine       byte    12, " file1 file2",cr

The FCB1 and FCB2 fields need to point at the two default file control blocks for this program. FCBs became obsolete with DOS 2.0, but Microsoft has kept FCBs around for compatibility anyway. For most programs you can point both of these fields at the following string of bytes:














DfltFCB         byte    3," ",0,0,0,0,0

The load and execute call will fail if there is insufficient memory to load the child process. When you create an ".EXE" file using MASM, it creates an executable file that grabs all available memory, by default. Therefore, there will be no memory available for the child process and DOS will always return an error. Therefore, you must readjust the memory allocation for the parent process before attempting to run the child process.

There are other possible errors as well. For example, DOS might not be able to locate the program name you specify with the zero terminated string. Or, perhaps, there are too many open files and DOS doesn't have a free buffer available for the file I/O. If an error occurs, DOS returns with the carry flag set and an appropriate error code in the ax register. The following example program executes the "COMMAND.COM" program, allowing a user to execute DOS commands from inside your application. When the user types "exit" at the DOS command line, DOS returns control to your program.














; RUNDOS.ASM -  Demonstrates how to invoke a copy of the COMMAND.COM
;               DOS command line interpreter from your programs.

                include stdlib.a
                includelib      stdlib.lib

dseg            segment para public 'data'

; MS-DOS EXEC structure.

ExecStruct      word    0               ;Use parent's Environment blk.
                dword   CmdLine         ;For the cmd ln parms.
                dword   DfltFCB
                dword   DfltFCB

DfltFCB         byte    3," ",0,0,0,0,0
CmdLine         byte    0, 0dh          ;Cmd line for program.
PgmName         dword   filename        ;Points at pgm name.

filename        byte    "c:\command.com",0

dseg            ends

cseg            segment para public 'code'
                assume  cs:cseg, ds:dseg

Main            proc
                mov     ax, dseg        ;Get ptr to vars segment
                mov     ds, ax

                MemInit                 ;Start the memory mgr.

; Okay, we've built the MS-DOS execute structure and the necessary
; command line, now let's see about running the program.
; The first step is to free up all the memory that this program
; isn't using. That would be everything from zzzzzzseg on.
;
; Note: unlike some previous examples in other chapters, it is okay
; to call Standard Library routines in this program after freeing
; up memory. The difference here is that the Standard Library
; routines are loaded early in memory and we haven't free up the
; storage they are sitting in.

                mov     ah, 62h         ;Get our PSP value
                int     21h
                mov     es, bx
                mov     ax, zzzzzzseg   ;Compute size of
                sub     ax, bx          ; resident run code.
                mov     bx, ax
                mov     ah, 4ah         ;Release unused memory.
                int     21h


; Tell the user what is going on:

                print
                byte    cr,lf
                byte    "RUNDOS- Executing a copy of command.com",cr,lf
                byte    "Type 'EXIT' to return control to RUN.ASM",cr,lf
                byte    0

; Warning! No Standard Library calls after this point. We've just
; released the memory that they're sitting in. So the program load
; we're about to do will wipe out the Standard Library code.

                mov     bx, seg ExecStruct
                mov     es, bx
                mov     bx, offset ExecStruct   ;Ptr to program record.
                lds     dx, PgmName
                mov     ax, 4b00h               ;Exec pgm
                int     21h

; In MS-DOS 6.0 the following code isn't required. But in various older
; versions of MS-DOS, the stack is messed up at this point. Just to be
; safe, let's reset the stack pointer to a decent place in memory.
;
; Note that this code preserves the carry flag and the value in the
; AX register so we can test for a DOS error condition when we are done
; fixing the stack.

                mov     bx, sseg
                mov     ss, ax
                mov     sp, offset EndStk
                mov     bx, seg dseg
                mov     ds, bx

; Test for a DOS error:

                jnc     GoodCommand
                print
                byte    "DOS error #",0
                puti
                print
                byte    " while attempting to run COMMAND.COM",cr,lf
                byte    0
                jmp     Quit

; Print a welcome back message.

GoodCommand:    print
                byte    "Welcome back to RUNDOS. Hope you had fun.",cr,lf
                byte    "Now returning to MS-DOS' version of COMMAND.COM."
                byte    cr,lf,lf,0

; Return control to MS-DOS

Quit:           ExitPgm
Main            endp
cseg            ends

sseg            segment para stack 'stack'
                dw      128 dup (0)
sseg            ends

zzzzzzseg       segment para public 'zzzzzzseg'
Heap            db      200h dup (?)
zzzzzzseg       ends
                end     Main


19.1.1.2 Load Program


The load and execute function gives the parent process very little control over the child process. Unless the child communicates with the parent process via a trap or interrupt, DOS suspends the parent process until the child terminates. In many cases the parent program may want to load the application code and then execute some additional operations before the child process takes over. Semiresident programs, appearing in the previous chapter, provide a good example. The DOS "load program" function provides this capability; it will load a program from the disk and return control back to the parent process. The parent process can do whatever it feels is appropriate before passing control to the child process.

The load program call requires parameters that are very similar to the load and execute call. Indeed, the only difference is the use of the LOAD structure rather than the LOADEXEC structure, and even these structures are very similar to one another. The LOAD data structure includes two extra fields not present in the LOADEXE structure:














LOAD            struct
EnvPtr          word    ?       ;Pointer to environment area.
CmdLinePtr      dword   ?       ;Pointer to command line.
FCB1            dword   ?       ;Pointer to default FCB1.
FCB2            dword   ?       ;Pointer to default FCB2.
SSSP            dword   ?       ;SS:SP value for child process.
CSIP            dword   ?       ;Initial program starting point.
LOAD            ends

The LOAD command is useful for many purposes. Of course, this function provides the primary vehicle for creating semiresident programs; however, it is also quite useful for providing extra error recovery, redirecting application I/O, and loading several executable processes into memory for concurrent execution.

After you load a program using the DOS load command, you can obtain the PSP address for that program by issuing the DOS get PSP address call. This would allow the parent process to modify any values appearing in the child process' PSP prior to its execution. DOS stores the termination address for a procedure in the PSP. This termination address normally appears in the double word at offset 10h in the PSP. If you do not change this location, the program will return to the first instruction beyond the int 21h instruction for the load function. Therefore, before actually transferring control to the user application, you should change this termination address.


19.1.1.3 Loading Overlays


Many programs contain blocks of code that are independent of one other; that is, while routines in one block of code execute, the program will not call routines in the other independent blocks of code. For example, a modern game may contain some initialization code, a "staging area" where the user chooses certain options, an "action area" where the user plays the game, and a "debriefing area" that goes over the player's actions. When running in a 640K MS-DOS machine, all this code may not fit into available memory at the same time. To overcome this memory limitation, most large programs use overlays. An overlay is a portion of the program code that shares memory for its code with other code modules. The DOS load overlay function provides support for large programs that need to use overlays.

Like the load and load/execute functions, the load overlay expects a pointer to the code file's pathname in the ds:dx register pair and the address of a data structure in the es:bx register pair. This overlay data structure has the following format:














overlay         struct
StartSeg        word    ?
RelocFactor     word    0
overlay         ends

The StartSeg field contains the segment address where you want DOS to load the program. The RelocFactor field contains a relocation factor. This value should be zero unless you want the starting offset of the segment to be something other than zero.


19.1.1.4 Terminating a Process


The process termination function is nothing new to you by now, you've used this function over and over again already if you written any assembly language programs and run them under DOS (the Standard Library ExitPgm macro executes this command). In this section we'll look at exactly what the terminate process function call does.

First of all, the terminate process function gives you the ability to pass a single byte termination code back to the parent process. Whatever value you pass in al to the terminate call becomes the return, or termination code. The parent process can test this value using the Get Child Process Return Value call (see the next section). You can also test this return value in a DOS batch file using the "if errorlevel" statement.

The terminate process command does the following:

Unless you really know what you're doing, you should not change the values at offsets 0Ah, 0Eh, or 12h in the PSP. By doing so you could produce an inconsistent system when your program terminates.


19.1.1.5 Obtaining the Child Process Return Code


A parent process can obtain the return code from a child process by making the DOS Get Child Process Return Code function call. This call returns the value in the al register at the point of termination plus information that tells you how the child process terminated.

This call (ah=4Dh) returns the termination code in the al register. It also returns the cause of termination in the ah register. The ah register will contain one of the following values:

Termination Cause
Value in AH Reason for Termination
0 Normal termination (int 21h, ah=4Ch)
1 Terminated by ctrl-C
2 Terminated by critical error
3 TSR termination (int 21h, ah=31h)


The termination code appearing in al is valid only for normal and TSR terminations.

Note that you can only call this routine once after a child process terminates. MS-DOS returns meaningless values in AX after the first such call. Likewise, if you use this function without running a child process, the results you obtain will be meaningless. DOS does not return if you do this.

19.1 - DOS Processes
19.1.1 - Child Processes in DOS
19.1.1.1 - Load and Execute
19.1.1.2 - Load Program
19.1.1.3 - Loading Overlays
19.1.1.4 - Terminating a Process
19.1.1.5 - Obtaining the Child Process Return Code
19.1.2 - Exception Handling in DOS: The Break Handler
19.1.3 - Exception Handling in DOS: The Critical Error Handler
19.1.4 - Exception Handling in DOS: Traps
19.1.5 - Redirection of I/O for Child Processes
19.2 - Shared Memory
19.2.1 - Static Shared Memory
19.2.2 - Dynamic Shared Memory
19.3 - Coroutines
19.3.1 - AMAZE.ASM
19.3.2 - 32-bit Coroutines
19.4 - Multitasking
19.4.1 - Lightweight and HeavyWeight Processes
19.4.2 - The UCR Standard Library Processes Package
19.4.3 - Problems with Multitasking
19.4.4 - A Sample Program with Threads
19.5 - Synchronization
19.5.1 - Atomic Operations, Test & Set, and Busy-Waiting
19.5.2 - Semaphores
19.5.3 - The UCR Standard Library Semaphore Support
19.5.4 - Using Semaphores to Protect Critical Regions
19.5.5 - Using Semaphores for Barrier Synchronization
19.6 - Deadlock


Art of Assembly: Chapter Nineteen - 29 SEP 1996

[Next] [Art of Assembly][Randall Hyde]



Number of Web Site Hits since Jan 1, 2000: