Dec 232017
 
Turbo Pascal Unit with allows EXEC calls with swapping to disk or EMS. Written by the people from Turbo Power Software.
File EXECSW13.ZIP from The Programmer’s Corner in
Category Pascal Source Code
Turbo Pascal Unit with allows EXEC calls with swapping to disk or EMS. Written by the people from Turbo Power Software.
File Name File Size Zip Size Zip Type
EXECSWA2.PAS 4773 1674 deflated
EXECSWAP.ASM 21434 4611 deflated
EXECSWAP.DOC 18104 6844 deflated
EXECSWAP.OBJ 2050 1274 deflated
EXECSWAP.PAS 4180 1435 deflated
EXECSWAP.TPU 4992 2377 deflated
EXECTEST.EXE 6896 4630 deflated
EXECTEST.PAS 545 327 deflated

Download File EXECSW13.ZIP Here

Contents of the EXECSWAP.DOC file


More Memory for DOS Exec
Kim Kokkonen

As many have lamented, the 640K of memory available to DOS programs is looking
smaller every year. With TSR's gobbling up memory on one end, and our
applications growing larger on the other, it is easy to use up all the space
and then some. Of course, necessity is the mother of invention, so desperate
DOS programmers have devised a number of ad hoc methods to cram more functions
into the same space -- by using expanded and extended memory, overlays, and so
on.

This article describes another such method. We've enhanced the DOS Exec
function by swapping most of the calling program into expanded memory or to
disk, and giving all that free memory to the child process. When the
subprocess is complete, the calling program is swapped back into place and
continues normally. This technique is especially valuable for menuing
environments which must execute other large programs, or modern programming
editors which are expected to spawn huge compilations at the touch of a key.
In fact, it's useful for any program that must invoke another.

The swapping Exec function is implemented in a Turbo Pascal 5.0 unit called
ExecSwap. The real meat of the code is written in assembly language, however,
and with some changes could be linked into other languages such as C or
Fortran.

Turbo Pascal Program Organization
---------------------------------
To explain how ExecSwap works, we'll need to delve into the organization of a
Turbo Pascal program. Let's examine the program shown in Figure 1. What this
program (named X) does isn't important. We'll just use it to show the
arrangement of memory. X uses two of Turbo's standard units, Crt and Dos. It
also implicitly uses the System unit, as does every Turbo Pascal program.

Figure 2 maps out the various segments. (You can see a similar map of a real
program by having the compiler create a MAP file and inspecting the segment
map at the beginning of that file.) It's important to note that each Pascal
unit has its own code segment (denoted by CS_xxx in Figure 2), and that the
code segments are arranged in what might seem like reverse order. That is, the
unit appearing first in the USES statement is linked at the highest memory
address, while the main program has the lowest code segment. If the program
doesn't need to use the heap, the memory above the heap base may not be
allocated.

Figure 1: Example Program

program X;
uses {System,} Dos, Crt;
begin
ClrScr;
Exec('C:\COMMAND.COM', '');
end.

Figure 2: Memory Map of Example Program

PSP: program segment prefix lower addresses
CS_X: X code |
CS_Crt: Crt code |
CS_Dos: Dos code v
CS_System: System code higher addresses
DS: initialized data |
uninitialized data |
SS: stack v
HeapOrg: heap base
HeapPtr: heap high water mark
available heap space
FreePtr: free list
FreePtr+1000h: top of program
available DOS memory
xxxx: top of memory

ExecSwap's goal is to copy most of the memory used by the program to secondary
storage and then to deallocate that memory. ExecSwap needs to leave only
enough of itself behind to call DOS Exec and restore the image when the child
process returns.

By this criterion, the best place for ExecSwap's code would be in the main
body of the program. In this way, it could start swapping memory at the lowest
possible code segment and free the most memory for the child process. In
Figure 2's terms, it would start swapping at code segment CS_X and continue to
the top of the program. After deallocating memory, the only overhead would be
the program segment prefix (256 bytes) plus the portion of segment CS_X
required to undo the swap. Figure 3 shows what memory might look like while
the child process was active. The rest of program X would have been stored in
EMS memory if available, or in a disk file if not.

Figure 3: Memory Map while Child Process is Active

PSP: program segment prefix | ExecSwap
CS_X: X code (partial) | overhead
.--------------------------------------------------
| child program program segment prefix
| ...
| xxxx: top of memory

There's another factor to consider, though. ExecSwap should be convenient to
use in more than just one program. Hence, we've made it a self-contained unit
which is available just by adding it to the main program's USES statement.
Considering Figure 2 again, it's clear that when we USE ExecSwap we want to
add it at the very end of the list. In that case, the memory map will look
like Figure 4. The memory that remains allocated during the Exec is the PSP,
the code in the main program X, and whatever part of ExecSwap must remain
resident.

Figure 4: Memory Map after using ExecSwap

PSP: program segment prefix
CS_X: X code
CS_ExecSwap: ExecSwap code <-----------
CS_Crt: Crt code
CS_Dos: Dos code
CS_System: System code
...
xxxx: top of memory

The main program's code segment need not be very large, of course. In the
extreme case, the main program would consist of nothing but a USES statement
and a single procedure call to another unit. This reduces the overhead of
the Exec call to essentially just the PSP plus ExecSwap itself. And that's not
much: ExecSwap's resident portion consumes less than 2000 bytes.

Using ExecSwap
--------------
Before we plunge into the mechanics of ExecSwap, we'll describe how it is
used by an application. The unit interfaces three routines, shown in Figure 5.
Before performing an Exec call, the program must call InitExecSwap. This
routine computes how many bytes to swap and allocates space to store the
swapped region.

Figure 5: ExecSwap Routines

function InitExecSwap(LastToSave : Pointer; SwapFileName : String) : Boolean;
{-Initialize for swapping, returning TRUE if successful}

function ExecWithSwap(Path, CmdLine : String) : Word;
{-DOS Exec supporting swap to EMS or disk}

procedure ShutdownExecSwap;
{-Deallocate swap area}

The swapped region of memory starts just beyond the resident portion of
ExecSwap. The programmer must specify the _end_ of the region with the
parameter LastToSave, since the choice depends on how the program uses the
heap. What we choose for LastToSave affects only the size of the swap file, or
the amount of EMS memory needed, but has no effect on resident overhead during
the Exec call.

There are three reasonable values for LastToSave. Passing the System variable
HeapOrg tells ExecSwap not to save any part of the heap; this is the correct
option for programs that make no use of the heap. Passing HeapPtr causes
ExecSwap to save all allocated portions of the heap. Only the free list is
ignored, so this is a good choice for programs that don't fragment the heap.
Passing the expression Ptr(Seg(FreePtr^)+$1000, 0) tells ExecSwap to save the
entire heap, including the free list. This is the most conservative option,
but it may lead to swap files approaching 640K bytes in size.

InitExecSwap's second parameter, SwapFileName, specifies the name and location
of the swap file. If EMS memory is available, this name won't be used, but
otherwise InitExecSwap will create a new file. InitExecSwap assures that
sufficient EMS or disk space exists for the swap, otherwise it returns FALSE.
It's a good idea, of course, to put the swap file on the fastest drive that
will hold it, to minimize swap times. It's also prudent to avoid a floppy
drive, since the user may change disks while the child process is active. The
swap file remains open, using a file handle, until ShutdownExecSwap is called
or the program ends. InitExecSwap marks the file with the Hidden and System
attributes so that the user of the child process won't be tempted to delete
it.

ExecWithSwap is analogous to the standard Exec procedure in Turbo's Dos unit.
Its first parameter is the pathname of the program to execute, and the second
is the command line to pass to it. The only difference from Exec is that
ExecWithSwap is a function, returning the status of the call in a Word. The
function returns DOS error codes, with one exception. Figure 6 lists the most
common codes.

Figure 6: ExecWithSwap Error Codes

0 Success
1 Swap error (no swap storage, disk error, EMS error)
2 File not found
3 Path not found
8 Insufficient memory

You may never need to call ShutdownExecSwap, since ExecSwap sets up an exit
handler that automatically calls it when the program ends. In some cases,
however, you may want to close and erase the swap file or regain EMS space
before continuing.

There's a small conundrum here. We've said ExecSwap should be last in the USES
list, and we also want the main program to do as little as possible. So where
do we place calls to the ExecSwap routines? It's easiest to call them from the
main program, and take the hit in overhead. Turbo Pascal provides a better key
to the puzzle, though. Version 5 supports procedure variables, and version 4
makes it easy to fake them. So what we do is this: in the main program, assign
the address of each ExecSwap procedure to a procedure variable declared in a
unit used early in the USES list. Then call ExecSwap's routines in any later
unit by referring to the procedure variables.

One caution about using ExecSwap: since most of your program's code isn't in
memory while the child process runs, it's essential that the program's
interrupt handlers be deactivated first. Turbo Pascal 5 provides a handy
procedure called SwapVectors that does this for all the System interrupt
handlers. Call SwapVectors just before and after ExecWithSwap, and treat any
of your own handlers in a similar fashion.

Listing 1 offers a simple example of using ExecSwap. You can assemble
EXECSWAP.ASM (Listing 3) using MASM 4.0 or later, or any compatible assembler.
Then compile the test program to an EXE file and run it, and you'll enter a
DOS shell. If you have a DOS memory mapping utility, you'll see that the TEST
program is using less than 3K of memory. The swap file uses about 20K, most of
that for the 16K stack which is Turbo's default. If the swap goes to EMS, the
EMS block will be 32K bytes, since EMS is allocated in 16K chunks. Type Exit
to leave the shell and the test program will regain control.

A real program provides more impressive results. We developed ExecSwap for use
in our Turbo Analyst product, which offers an integrated environment where the
programmer can edit source files, then Exec the compiler, debugger, or any
of many other programming utilities. Without benefit of ExecSwap, the
environment keeps about 250K of memory during the Exec. With ExecSwap, the
overhead is only about 4K. That 246K makes a huge difference!

How It's Done
-------------
ExecSwap's Pascal source file, EXECSWAP.PAS, is given in Listing 2. It's
little more than a shell for the assembly language routines in EXECSWAP.ASM,
Listing 3.

Looking at InitExecSwap in Listing 2, you'll see that it checks first for EMS
memory (any version of EMS will do). If that is available, it is used in
preference to disk storage. If not, InitExecSwap goes on to assure that
there's enough space on the specified drive to hold the swap area. In our
production version of ExecSwap (trimmed here for the sake of brevity), we
check that the drive doesn't hold removable media. InitExecSwap also stores
several items in global variables where they're easily accessible by the
assembly language routines, and installs an exit handler to clean up after
itself in case the program halts unexpectedly.

The tricky stuff is in EXECSWAP.ASM. The file starts with the standard
boilerplate needed for linking to Turbo Pascal. We declare a number of
temporary variables in the code segment; these are essential because the
entire data segment is gone during critical portions of ExecWithSwap. One of
these variables is a temporary stack. It's a small one, only 128 bytes, but it
is required since the normal Turbo Pascal stack is also swapped out. Macro
definitions follow; we've used more than our usual number of macros to keep
the listing to a reasonable length.

ExecWithSwap starts by copying a number of variables into the code segment.
Then it checks to see whether swapping will go to EMS or disk. If neither has
been activated, ExecWithSwap exits immediately, returning error code 1.
Otherwise, ExecWithSwap processes one of four similar loops: one each to swap
to or from disk or EMS storage. Let's trace the "swap to EMS" loop in detail,
at label WriteE. The sequence for swapping to disk is so similar that we won't
need to describe it here.

We first map EMS memory, making the first 16K page of the EMS swap area
accessible through the page window at FrameSeg:0. (Note that ExecSwap doesn't
save the EMS context; if your application uses EMS for other storage, be sure
to remap EMS after returning from ExecWithSwap.) The macro SetSwapCount then
computes how many bytes to copy into the first page, returning a full 16K
bytes unless it's also the last page. The first location to save is at label
FirstToSave, which immediately follows the ExecWithSwap routine. The MoveFast
macro copies the first swap block into the EMS window. BX is then incremented
to select the next logical EMS page, and the DS register is adjusted to point
to the next swap block, 16K bytes higher in memory. The loop continues until
all the bytes have been copied to EMS.

Next we must modify the DOS memory allocation, so that the space just swapped
out is available to the child process. First we save the current allocated
size so we can restore it later. Then we switch to the small temporary stack
which is safely nestled in the code segment, and finally call the DOS SetBlock
function to shrink our memory to just beyond the end of the ExecWithSwap
routine.

The actual DOS Exec call follows. The implementation here is similar to the
one in Borland's Dos unit. It validates and formats the program path and
command line, parses FCB's (file control blocks) from the command line in case
the child expects them, and calls the DOS Exec function. The error code
returned by Exec is stored until the reverse swap is complete.

The reverse swap is just that: it reallocates memory from DOS and copies the
parent program back into place. There is one critical difference from the
first swap, however. Errors that occur during the reverse swap are fatal.
Since the program to return to no longer exists, our only recourse is to
halt. The most likely reason for such an error is the inability to reallocate
the initial memory block. This occurs whenever the Exec call (or the user) has
installed a memory resident program while in the shell. Be sure to warn your
users not to do this! ExecSwap could write an error message before halting;
to save space here, we've just set the ErrorLevel, which can be checked within
a batch file:

0FFh can't reallocate memory
0FEh disk error
0FDh EMS error

ExecWithSwap is done after it switches back to the original stack, restores
the DS register, and returns the status code.

The remainder of EXECSWAP.ASM is a collection of small utility routines, some
of which may find general use in your library.

In Summary
----------
ExecSwap seems quite reliable. It doesn't depend on any newly discovered
undocumented features of DOS, and has been tested by thousands of our
products' users.

There are a few additional features it might have. Our production version
writes status messages while swapping, so nervous users don't think their hard
disks are being formatted. It might also support direct swapping to extended
memory -- we haven't done so because experience indicates that using extended
memory in a DOS application is a compatibility nightmare, and RAM disks seem
quite adequate for swapping. If the remainder of ExecSwap were converted to
assembly language, Turbo Pascal's link order conventions (within a unit) could
be circumvented and another 500 bytes or so of Exec overhead would be saved.
With a few more DOS memory management calls, it would be possible for the
parent and child processes to share a common data area. Finally, an extension
of the ExecSwap concept allows TSR programs to leave just a core of interrupt
handlers in memory, and swap the application code in when they pop up
(SideKick Plus apparently does this).

The ExecSwap unit has become a very useful item in our bag of tricks.
With an ExecSwap-based DOS shell in the programming editor we use, we can
achieve the kind of multitasking we need ("interruption-based" multitasking).
ExecSwap should make it easier for you to squeeze more functionality into
that 640K box as well.

Acknowledgement
---------------
Special thanks to Chris Franzen of West Germany, who added disk swapping
capability to our original unit, which supported only EMS.

This DOC file is an unedited version of an article that appeared in the April
1988 issue of Dr. Dobbs Journal.

About the Author
----------------
Kim Kokkonen is the president of TurboPower Software, and the author of many
public domain Turbo Pascal tools. He can be reached at P.O. Box 66747, Scotts
Valley, CA 95066.

Listing 1: TEST.PAS
Listing 2: EXECSWAP.PAS
Listing 3: EXECSWAP.ASM


 December 23, 2017  Add comments

Leave a Reply