home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 15 / CD_ASCQ_15_070894.iso / vrac / sk210f.zip / EXECSWAP.DOC < prev    next >
Text File  |  1994-05-03  |  18KB  |  356 lines

  1.                         More Memory for DOS Exec
  2.                               Kim Kokkonen
  3.  
  4. As many have lamented, the 640K of memory available to DOS programs is
  5. looking smaller every year. With TSR's gobbling up memory on one end,
  6. and our applications growing larger on the other, it is easy to use up
  7. all the space and then some. Of course, necessity is the mother of
  8. invention, so desperate DOS programmers have devised a number of ad hoc
  9. methods to cram more functions into the same space -- by using expanded
  10. and extended memory, overlays, and so on.
  11.  
  12. This article describes another such method. We've enhanced the DOS Exec
  13. function by swapping most of the calling program into expanded memory or
  14. to disk, and giving all that free memory to the child process. When the
  15. subprocess is complete, the calling program is swapped back into place
  16. and continues normally. This technique is especially valuable for menu-
  17. ing environments which must execute other large programs, or modern pro-
  18. gramming editors which are expected to spawn huge compilations at the
  19. touch of a key. In fact, it's useful for any program that must invoke
  20. another.
  21.  
  22. The swapping Exec function is implemented in a Turbo Pascal 5.0 unit
  23. called ExecSwap. The real meat of the code is written in assembly
  24. language, however, and with some changes could be linked into other
  25. languages such as C or Fortran.
  26.  
  27. Turbo Pascal Program Organization
  28. ---------------------------------
  29. To explain how ExecSwap works, we'll need to delve into the organization
  30. of a Turbo Pascal program. Let's examine the program shown in Figure 1.
  31. What this program (named X) does isn't important. We'll just use it to
  32. show the arrangement of memory. X uses two of Turbo's standard units,
  33. Crt and Dos. It also implicitly uses the System unit, as does every
  34. Turbo Pascal program.
  35.  
  36. Figure 2 maps out the various segments. (You can see a similar map of a
  37. real program by having the compiler create a MAP file and inspecting the
  38. segment map at the beginning of that file.) It's important to note that
  39. each Pascal unit has its own code segment (denoted by CS_xxx in Figure
  40. 2), and that the code segments are arranged in what might seem like
  41. reverse order. That is, the unit appearing first in the USES statement
  42. is linked at the highest memory address, while the main program has the
  43. lowest code segment. If the program doesn't need to use the heap, the
  44. memory above the heap base may not be allocated.
  45.  
  46. Figure 1: Example Program
  47.  
  48.       program X;
  49.       uses {System,} Dos, Crt;
  50.       begin
  51.         ClrScr;
  52.         Exec('C:\COMMAND.COM', '');
  53.       end.
  54.  
  55. Figure 2: Memory Map of Example Program
  56.  
  57.       PSP:           program segment prefix            lower addresses
  58.       CS_X:          X code                                   |
  59.       CS_Crt:        Crt code                                 |
  60.       CS_Dos:        Dos code                                 v
  61.       CS_System:     System code                       higher addresses
  62.       DS:            initialized data                         |
  63.                      uninitialized data                       |
  64.       SS:            stack                                    v
  65.       HeapOrg:       heap base
  66.       HeapPtr:       heap high water mark
  67.                      available heap space
  68.       FreePtr:       free list
  69.       FreePtr+1000h: top of program
  70.                      available DOS memory
  71.       xxxx:          top of memory
  72.  
  73. ExecSwap's goal is to copy most of the memory used by the program to
  74. secondary storage and then to deallocate that memory. ExecSwap needs to
  75. leave only enough of itself behind to call DOS Exec and restore the
  76. image when the child process returns.
  77.  
  78. By this criterion, the best place for ExecSwap's code would be in the
  79. main body of the program. In this way, it could start swapping memory at
  80. the lowest possible code segment and free the most memory for the child
  81. process. In Figure 2's terms, it would start swapping at code segment
  82. CS_X and continue to the top of the program. After deallocating memory,
  83. the only overhead would be the program segment prefix (256 bytes) plus
  84. the portion of segment CS_X required to undo the swap. Figure 3 shows
  85. what memory might look like while the child process was active. The rest
  86. of program X would have been stored in EMS memory if available, or in a
  87. disk file if not.
  88.  
  89. Figure 3: Memory Map while Child Process is Active
  90.  
  91.       PSP:           program segment prefix      | ExecSwap
  92.       CS_X:          X code (partial)            | overhead
  93.         .--------------------------------------------------
  94.         |  child program  program segment prefix
  95.         |  ...
  96.         |  xxxx:          top of memory
  97.  
  98. There's another factor to consider, though. ExecSwap should be conven-
  99. ient to use in more than just one program. Hence, we've made it a self-
  100. contained unit which is available just by adding it to the main pro-
  101. gram's USES statement. Considering Figure 2 again, it's clear that when
  102. we USE ExecSwap we want to add it at the very end of the list. In that
  103. case, the memory map will look like Figure 4. The memory that remains
  104. allocated during the Exec is the PSP, the code in the main program X,
  105. and whatever part of ExecSwap must remain resident.
  106.  
  107. Figure 4: Memory Map after using ExecSwap
  108.  
  109.       PSP:           program segment prefix
  110.       CS_X:          X code
  111.       CS_ExecSwap:   ExecSwap code               <-----------
  112.       CS_Crt:        Crt code
  113.       CS_Dos:        Dos code
  114.       CS_System:     System code
  115.       ...
  116.       xxxx:          top of memory
  117.  
  118. The main program's code segment need not be very large, of course. In
  119. the extreme case, the main program would consist of nothing but a USES
  120. statement and a single procedure call to another unit. This reduces the
  121. overhead of the Exec call to essentially just the PSP plus ExecSwap
  122. itself. And that's not much: ExecSwap's resident portion consumes less
  123. than 2000 bytes.
  124.  
  125. Using ExecSwap
  126. --------------
  127. Before we plunge into the mechanics of ExecSwap, we'll describe how it
  128. is used by an application. The unit interfaces three routines, shown in
  129. Figure 5. Before performing an Exec call, the program must call
  130. InitExecSwap. This routine computes how many bytes to swap and allocates
  131. space to store the swapped region.
  132.  
  133. Figure 5: ExecSwap Routines
  134.  
  135.   function InitExecSwap(LastToSave : Pointer;
  136.                         SwapFileName : String) : Boolean;
  137.     {-Initialize for swapping, returning TRUE if successful}
  138.  
  139.   function ExecWithSwap(Path, CmdLine : String) : Word;
  140.     {-DOS Exec supporting swap to EMS or disk}
  141.  
  142.   procedure ShutdownExecSwap;
  143.     {-Deallocate swap area}
  144.  
  145. The swapped region of memory starts just beyond the resident portion of
  146. ExecSwap. The programmer must specify the _end_ of the region with the
  147. parameter LastToSave, since the choice depends on how the program uses
  148. the heap. What we choose for LastToSave affects only the size of the
  149. swap file, or the amount of EMS memory needed, but has no effect on
  150. resident overhead during the Exec call.
  151.  
  152. There are three reasonable values for LastToSave. Passing the System
  153. variable HeapOrg tells ExecSwap not to save any part of the heap; this
  154. is the correct option for programs that make no use of the heap. Passing
  155. HeapPtr causes ExecSwap to save all allocated portions of the heap. Only
  156. the free list is ignored, so this is a good choice for programs that
  157. don't fragment the heap. Passing the expression Ptr(Seg(FreePtr^)+$1000,
  158. 0) tells ExecSwap to save the entire heap, including the free list. This
  159. is the most conservative option, but it may lead to swap files
  160. approaching 640K bytes in size.
  161.  
  162. InitExecSwap's second parameter, SwapFileName, specifies the name and
  163. location of the swap file. If EMS memory is available, this name won't
  164. be used, but otherwise InitExecSwap will create a new file. InitExecSwap
  165. assures that sufficient EMS or disk space exists for the swap, otherwise
  166. it returns FALSE. It's a good idea, of course, to put the swap file on
  167. the fastest drive that will hold it, to minimize swap times. It's also
  168. prudent to avoid a floppy drive, since the user may change disks while
  169. the child process is active. The swap file remains open, using a file
  170. handle, until ShutdownExecSwap is called or the program ends.
  171. InitExecSwap marks the file with the Hidden and System attributes so
  172. that the user of the child process won't be tempted to delete it.
  173.  
  174. ExecWithSwap is analogous to the standard Exec procedure in Turbo's Dos
  175. unit. Its first parameter is the pathname of the program to execute, and
  176. the second is the command line to pass to it. The only difference from
  177. Exec is that ExecWithSwap is a function, returning the status of the
  178. call in a Word. The function returns DOS error codes, with one excep-
  179. tion. Figure 6 lists the most common codes.
  180.  
  181. Figure 6: ExecWithSwap Error Codes
  182.  
  183.   0  Success
  184.   1  Swap error (no swap storage, disk error, EMS error)
  185.   2  File not found
  186.   3  Path not found
  187.   8  Insufficient memory
  188.  
  189. You may never need to call ShutdownExecSwap, since ExecSwap sets up an
  190. exit handler that automatically calls it when the program ends. In some
  191. cases, however, you may want to close and erase the swap file or regain
  192. EMS space before continuing.
  193.  
  194. There's a small conundrum here. We've said ExecSwap should be last in
  195. the USES list, and we also want the main program to do as little as
  196. possible. So where do we place calls to the ExecSwap routines? It's
  197. easiest to call them from the main program, and take the hit in
  198. overhead. Turbo Pascal provides a better key to the puzzle, though.
  199. Version 5 supports procedure variables, and version 4 makes it easy to
  200. fake them. So what we do is this: in the main program, assign the
  201. address of each ExecSwap procedure to a procedure variable declared in a
  202. unit used early in the USES list. Then call ExecSwap's routines in any
  203. later unit by referring to the procedure variables.
  204.  
  205. One caution about using ExecSwap: since most of your program's code
  206. isn't in memory while the child process runs, it's essential that the
  207. program's interrupt handlers be deactivated first. Turbo Pascal 5
  208. provides a handy procedure called SwapVectors that does this for all the
  209. System interrupt handlers. Call SwapVectors just before and after
  210. ExecWithSwap, and treat any of your own handlers in a similar fashion.
  211.  
  212. Listing 1 offers a simple example of using ExecSwap. You can assemble
  213. EXECSWAP.ASM (Listing 3) using MASM 4.0 or later, or any compatible
  214. assembler. Then compile the test program to an EXE file and run it, and
  215. you'll enter a DOS shell. If you have a DOS memory mapping utility,
  216. you'll see that the TEST program is using less than 3K of memory. The
  217. swap file uses about 20K, most of that for the 16K stack which is
  218. Turbo's default. If the swap goes to EMS, the EMS block will be 32K
  219. bytes, since EMS is allocated in 16K chunks. Type Exit to leave the
  220. shell and the test program will regain control.
  221.  
  222. A real program provides more impressive results. We developed ExecSwap
  223. for use in our Turbo Analyst product, which offers an integrated
  224. environment where the programmer can edit source files, then Exec the
  225. compiler, debugger, or any of many other programming utilities. Without
  226. benefit of ExecSwap, the environment keeps about 250K of memory during
  227. the Exec. With ExecSwap, the overhead is only about 4K. That 246K makes
  228. a huge difference!
  229.  
  230. How It's Done
  231. -------------
  232. ExecSwap's Pascal source file, EXECSWAP.PAS, is given in Listing 2. It's
  233. little more than a shell for the assembly language routines in
  234. EXECSWAP.ASM, Listing 3.
  235.  
  236. Looking at InitExecSwap in Listing 2, you'll see that it checks first
  237. for EMS memory (any version of EMS will do). If that is available, it is
  238. used in preference to disk storage. If not, InitExecSwap goes on to
  239. assure that there's enough space on the specified drive to hold the swap
  240. area. In our production version of ExecSwap (trimmed here for the sake
  241. of brevity), we check that the drive doesn't hold removable media.
  242. InitExecSwap also stores several items in global variables where they're
  243. easily accessible by the assembly language routines, and installs an
  244. exit handler to clean up after itself in case the program halts
  245. unexpectedly.
  246.  
  247. The tricky stuff is in EXECSWAP.ASM. The file starts with the standard
  248. boilerplate needed for linking to Turbo Pascal. We declare a number of
  249. temporary variables in the code segment; these are essential because the
  250. entire data segment is gone during critical portions of ExecWithSwap.
  251. One of these variables is a temporary stack. It's a small one, only 128
  252. bytes, but it is required since the normal Turbo Pascal stack is also
  253. swapped out. Macro definitions follow; we've used more than our usual
  254. number of macros to keep the listing to a reasonable length.
  255.  
  256. ExecWithSwap starts by copying a number of variables into the code
  257. segment. Then it checks to see whether swapping will go to EMS or disk.
  258. If neither has been activated, ExecWithSwap exits immediately, returning
  259. error code 1. Otherwise, ExecWithSwap processes one of four similar
  260. loops: one each to swap to or from disk or EMS storage. Let's trace the
  261. "swap to EMS" loop in detail, at label WriteE. The sequence for swapping
  262. to disk is so similar that we won't need to describe it here.
  263.  
  264. We first map EMS memory, making the first 16K page of the EMS swap area
  265. accessible through the page window at FrameSeg:0. (Note that ExecSwap
  266. doesn't save the EMS context; if your application uses EMS for other
  267. storage, be sure to remap EMS after returning from ExecWithSwap.) The
  268. macro SetSwapCount then computes how many bytes to copy into the first
  269. page, returning a full 16K bytes unless it's also the last page. The
  270. first location to save is at label FirstToSave, which immediately
  271. follows the ExecWithSwap routine. The MoveFast macro copies the first
  272. swap block into the EMS window. BX is then incremented to select the
  273. next logical EMS page, and the DS register is adjusted to point to the
  274. next swap block, 16K bytes higher in memory. The loop continues until
  275. all the bytes have been copied to EMS.
  276.  
  277. Next we must modify the DOS memory allocation, so that the space just
  278. swapped out is available to the child process. First we save the current
  279. allocated size so we can restore it later. Then we switch to the small
  280. temporary stack which is safely nestled in the code segment, and finally
  281. call the DOS SetBlock function to shrink our memory to just beyond the
  282. end of the ExecWithSwap routine.
  283.  
  284. The actual DOS Exec call follows. The implementation here is similar to
  285. the one in Borland's Dos unit. It validates and formats the program path
  286. and command line, parses FCB's (file control blocks) from the command
  287. line in case the child expects them, and calls the DOS Exec function.
  288. The error code returned by Exec is stored until the reverse swap is
  289. complete.
  290.  
  291. The reverse swap is just that: it reallocates memory from DOS and copies
  292. the parent program back into place. There is one critical difference
  293. from the first swap, however. Errors that occur during the reverse swap
  294. are fatal. Since the program to return to no longer exists, our only
  295. recourse is to halt. The most likely reason for such an error is the
  296. inability to reallocate the initial memory block. This occurs whenever
  297. the Exec call (or the user) has installed a memory resident program
  298. while in the shell. Be sure to warn your users not to do this! ExecSwap
  299. could write an error message before halting; to save space here, we've
  300. just set the ErrorLevel, which can be checked within a batch file:
  301.  
  302.   0FFh     can't reallocate memory
  303.   0FEh     disk error
  304.   0FDh     EMS error
  305.  
  306. ExecWithSwap is done after it switches back to the original stack,
  307. restores the DS register, and returns the status code.
  308.  
  309. The remainder of EXECSWAP.ASM is a collection of small utility routines,
  310. some of which may find general use in your library.
  311.  
  312. In Summary
  313. ----------
  314. ExecSwap seems quite reliable. It doesn't depend on any newly discovered
  315. undocumented features of DOS, and has been tested by thousands of our
  316. products' users.
  317.  
  318. There are a few additional features it might have. Our production
  319. version writes status messages while swapping, so nervous users don't
  320. think their hard disks are being formatted. It might also support direct
  321. swapping to extended memory -- we haven't done so because experience
  322. indicates that using extended memory in a DOS application is a
  323. compatibility nightmare, and RAM disks seem quite adequate for swapping.
  324. If the remainder of ExecSwap were converted to assembly language, Turbo
  325. Pascal's link order conventions (within a unit) could be circumvented
  326. and another 500 bytes or so of Exec overhead would be saved. With a few
  327. more DOS memory management calls, it would be possible for the parent
  328. and child processes to share a common data area. Finally, an extension
  329. of the ExecSwap concept allows TSR programs to leave just a core of
  330. interrupt handlers in memory, and swap the application code in when they
  331. pop up (SideKick Plus apparently does this).
  332.  
  333. The ExecSwap unit has become a very useful item in our bag of tricks.
  334. With an ExecSwap-based DOS shell in the programming editor we use, we
  335. can achieve the kind of multitasking we need ("interruption-based"
  336. multitasking). ExecSwap should make it easier for you to squeeze more
  337. functionality into that 640K box as well.
  338.  
  339. Acknowledgement
  340. ---------------
  341. Special thanks to Chris Franzen of West Germany, who added disk swapping
  342. capability to our original unit, which supported only EMS.
  343.  
  344. This DOC file is an unedited version of an article that appeared in the
  345. April 1988 issue of Dr. Dobbs Journal.
  346.  
  347. About the Author
  348. ----------------
  349. Kim Kokkonen is the president of TurboPower Software, and the author of
  350. many public domain Turbo Pascal tools. He can be reached at P.O. Box
  351. 66747, Scotts Valley, CA 95066.
  352.  
  353. Listing 1: TEST.PAS
  354. Listing 2: EXECSWAP.PAS
  355. Listing 3: EXECSWAP.ASM
  356.