Robert J. Moore is a senior project engineer with Hughes Aircraft Radar Systems' F-14 Program in El Segundo, California. He can be reached at 2126 Prosser Ave., Los Angeles, CA 90025.
The concept of a memory control block (MCB) was introduced in MS-DOS, Version 2.0, as the operating system's basic method of tracking memory allocation for application programs and installable device drivers. In this article, I'll discuss how DOS uses memory control blocks and will present a Turbo C program named MCB.C that prints out DOS' current state of memory allocation. First, it might be useful to review how the PC uses memory.
Figure 1, next page, shows the major aspects of the PC memory map as documented by Microsoft and IBM. Addresses are shown in hexadecimal segment:offset form. Numeric addresses give absolute memory locations for those items that are always invariant in a PC running under MS-DOS, Versions 2.0, and later Symbolic addresses vary according to the version of DOS you are running and the number and types of applications presently in memory. More than one program will be in memory if one or more terminate-and-stay-resident (TSR) program has been installed.
Figure 1: The PC memory map
Address (Hex) Memory Usage 0000:0000 Interupt vector table 0040:0000 ROM BIOS data area 0050:0000 DOS parameter area 0070:0000 IBMBIO.COM / IO.SYS * mmmm:mmmm BMDOS.COM / MSDOS.SYS * mmmm:mmmm CONFIG.SYS - specified information (device drivers and internal buffers mmmm:mmmm Resident COMMAND.COM mmmm:mmmm Master environment mmmm:mmmm Environment block #1 mmmm:mmmm Application program #1 . . . . . . mmmm.mmmm Environment block #n mmmm:mmmm Application #n xxxx:xxxx Transient COMMAND.COM A000:0000 Video buffers and ROM FFFF:000F Top of 8086 / 88 address space * PC-DOS uses IBMBIO.COM and IBMDOS.COM: MS-DOS uses IO.SYS and MSDOS.SYS instead
The program MCB.C in Listing One computes and prints out specific addresses shown as mmmm: mmmm in Figure 1, which includes most of the unknowns in the DOS memory map. The only address it cannot compute is the starting address of the transient portion of COMMAND. COM (denoted by address xxxx:xxxx). MCB.C also provides insight as to how the DOS memory management functions do their job and can show you exactly where ail your memory has gone the next time you run out of it on a machine overloaded with TSRs. Finally, MCB.C should give you ideas about how to reallocate memory appropriately should it become necessary. For now, I'll discuss DOS memory control blocks and describe where they fit it into all this.
Memory Control Blocks
Microsoft and IBM document the fact that they use MCBs, but neither documents specific details of how they use them. The contents of an MCB, also known as an arena header, are illustrated in Figure 2, page 57. An MCB contains three fields called the chain id, process ID (PID), and block size. Together these fields occupy the first 5 bytes of the MCB; the remaining 11 bytes are unused. An MCB always begins on a paragraph boundary and occupies exactly one paragraph (16 bytes) of memory. The chain id byte indicates whether this is the last MCB (value Z) or if another follows (value M). The memory block controlled by a given MCB always follows immediately, starting at the next paragraph, and has a size equal to the number of paragraphs specified in the block-size field of the MCB. I'll refer to these blocks of memory as memory blocks (MBs) to distinguish them from the MCBs. Subsequent MCB/MB pairs always start just after the previous pair. MCBs can be thought of as a forward-linked-list data structure, with the MBs they control neatly sandwiched in between. In normal operation, there are no gaps in this structure. If DOS detects any corruption in the MCB chain, it prints out an error message and halts the system. (I have had occasion to break the MCB chain deliberately during my investigations; DOS doesn't always notice.)
Figure 2: MCB Fields
MCB Start Memory Control Blaock Fields Address chain PID blk unused yyy0:000 id size Offset------ 0 1-2 3-4 5-15 where chain id = MCB chain-identification byte. Its value is Z for the last MCB in DOS MCB chain and M otherwise PID = Process ID, or the program segment prefix of the program that "owns" the MCB and the memory it controls. blk size = Size of the contiguous block of memeory controlled by the MCB in units of paragraphs. It does not include the MCB itself.
The first MCB/MB pair allocated by DOS is always owned by IBMDOS.COM/MSDOS.SYS and is the fifth PC memory region, as shown in Figure 1. The number of remaining MCBs depends upon whether or not any TSRs have been installed. Typically, any program (transient or TSR) in memory owns at least two MCBs--one for its copy of the environment and the other for its code and data. If the program has allocated additional space (for example as internal buffer space), it may own more MCBs. If a TSR has freed its environment block (as some do), it may own only one MCB.
DOS Memory-Management Functions
Starting with Version 2.0, all versions of DOS contain functions to allocate memory (function 48H), free allocated memory (function 49H), and modify allocated memory (function 4AH). None of these functions deal directly with MCBs; instead they deal with the associated MBs. Changes caused by use of these functions, however, are reflected in the MCBs, as can be seen upon inspection (using my program) after use of any of these functions. In addition, DOS 3.x provides function 58H(get/change memory allocation strategy), which controls how memory is allocated: first fit (the default in DOS 3.x and that used in DOS 2.x); last fit; and best fit.
Note that DOS (actually COMMAND.COM) uses these functions to allocate two memory blocks automatically when it loads and runs a transient program (using the DOS EXEC function 4BH) and frees them upon termination. Any other memory allocated by an application using these functions must subsequently be freed or it will remain in memory. None of the memory allocated for a TSR is released automatically, of course.
A more detailed discussion of these memory-management functions would warrant a separate article. After using my program, all of them should make more sense (the official documentation for them is a little cryptic). Also, with the understanding of DOS memory allocation provided by my program and this article, DOS could be bypassed altogether for memory management, although that is not my intention.
Locating MCBs in Memory
Two of a program's MCBs can be found easily: one MCB is one paragraph above its PSP; the other is one paragraph above the environment block. The segment address of the copy of the environment is contained in the word at offset 2CH in the PSP. (This is not true for the master copy of COMMAND.COM. Under DOS 2.0 - 3.2, the environment pointer for the master copy of COMMAND.COM has a dummy value of 0000. DOS 3.3 corrects this oversight.) This doesn't help you find the MCBs lower in memory, though, because the MCBs are linked only in the forward direction. The trick is to locate the first MCB in memory--once you have done that, it is a simple matter to visit them all.
So how do you find the first MCB? There is no documented way. Any number of brute-force methods have occurred to me, none of them very satisfactory. Fortunately, there is a better method that involves the undocumented DOS Invars function 52H When this function call is complete, ES:[BX-2] points to a word that contains the segment address of the first MCB. Because all MCBs start on a paragraph boundary, that's all you need to locate the first one.
Obtaining Additional Information
Each MCB/MB pair is owned by a PID which is nothing more than the segment address of the PSP of the program that owns the memory. From the PSP address, you can derive other useful information. First, you can find the location of the program's copy of the environment, as mentioned earlier. Another (undocumented) piece of information you can discover is the PID of the parent process, which is located at the word PSP:16H In DOS 3.x only, you can also find the name of the owner program.
The PID owner name consists of the drive, path, and filename of the program associated with the PID. To find it, first locate the copy of the environment for any program other than the operating system files IBMDOS.COM and COMMAND.COM (these must be treated separately) using the word at PID:2CH. Each string in the environment is in ASCIIZ form (which ends in a NULL, the ASCII character with value 0). Search for the first double NULL (sometimes there is more than one).
If a word count of 0001 immediately follows the double NULL (indicating that only one more ASCIIZ string is left in the environment), then the owner of the program is given by the ASCIIZ string immediately following. If the end of the environment is reached and no such pattern is found, the owner is unknown. (This will always be the case for DOS 2.x.) Note that if a TSR has freed its environment block, the owner name thus found is likely to be inaccurate because the TSR no longer owns it and it may have been claimed by a different program (typically, the next TSR or transient program will claim it).
The MCB program
The program MCB.C in Listing One is heavily commented, so I'll make only a few general comments here.
I used the small-memory model of Turbo C, Version 1.5, to compile and link MCB.C into MCB.EXE. The complications of doing so are manifested in the explicit declarations of some far and huge pointers, as explained in the comments.
I used no Turbo C library calls specific to either Turbo C itself or to the IBM PC, so other C compilers should have no problem compiling the program, and it should run on any MS-DOS machine, even one not compatible with the IBM PC. (I ran it without any difficulty on a DEC Rainbow under MS-DOS 2.11.)
The program itself is a straightforward implementation using the material discussed earlier in this article. The first MCB in memory is located using DOS function 52H The program then visits each one and computes and prints out information on each MCB/MB. See the large comment block at the end of function prn_header in Listing One for a detailed description of the fields printed for each MCB.
Examples 1-6, pages 57-63, contain the screen output of several illustrative examples. The conditions under which each was run are summarized in the captions. The effects of other DOS commands run are also shown when they are relevant to the discussion.
Example 1 illustrates the minimum number of MCB/MB pairs because no extra device drivers or TSRs have been installed. The curious free embedded MCB 03 seems to be a leftover from the DOS boot process--it seems strange that DOS should leave it there. Also note the use of memory by MCB.EXE and the final large free block. Many application programs claim all of the remaining memory for themselves when they run, and I was curious as to why MCB.EXE didn't. Because it was an .EXE file, it was possible that its .EXE header specified a smaller amount of memory. I checked the header and found it was set to claim as much free memory as was available. I finally traced the return of memory to DOS by examining the Turbo C start-up code that was executed prior to giving main() control. Sure enough, this code called DOS function 4AH. The parent address (022BH) of IBMDOS.COM in this example is also interesting.
Example 1: System booted with no CONFIG.SYS and no AUTOEXEC.BAT. (I was in the directory C:\TURBOC\DEV where MCB.EXE resides on my sysytem, for this and all subsequent examples.)
C > MCB ========================================================================== MCB MCB ID PID MB PAR- ENV OWNER NO. SEG. SIZE ID BLK? ========================================================================== 01 0973 M 0008 8208 022B N IBMDOS.COM/MSDOS.SYS 02 0B75 M 0B76 3376 0B76 N COMMAND.COM COPY #1 03 0C49 M 0000 48 F000 N FREE MEMORY CONTROL BLOCK 04 0C4D M 0B76 160 0B76 Y COMMAND.COM COPY #1 05 0C58 M 0C5E 64 0B76 Y C:\TURBO\DEV\MCB.EXE 06 0C5D M 0C5E 71232 0B76 N C:\TURBO\DEV\MCB.EXE 07 1DC2 Z 0000 530384 F000 N FREE MEMORY CONTROL BLOCK ============================================================================
In Example 2, the size of the MB controlled by MCB 01 and owned by IBMDOS.COM shows an increase of 8,144 bytes. The extra space was allocated during the boot process in response to the statements in CONFIG.SYS. These statements caused extra storage to be allocated for the installation of the device driver ANSI.SYS as well as that needed to carry out the FILES=50 and BUFFERS=20 commands. This block grows much larger when VDISK.SYS is installed. Try it.
Example 2: The PC rebooted with the CONFIG.SYS shown and no AUTOEXEC.BAT
C> TYPE \CONFIG.SYS device=c: \dos\ansi. files=50 buffers=20 lastdrive=z C > MCB ========================================================================== MCB MCB ID PID MB PAR- ENV OWNER NO. SEG. SIZE ID BLK? ========================================================================== 01 0973 M 0008 16352 022B N IBMDOS.COM/MSDOS.SYS 02 0D72 M 0D73 3376 0D73 N COMMAND.COM COPY #1 03 0E46 M 0000 48 F000 N FREE MEMORY CONTROL BLOCK 04 0E4A M 0D73 160 0D73 Y COMMAND.COM COPY #1 05 0E55 M 0E5B 64 0D73 Y C:\TURBOC\DEV\MCB_EXE 06 0E5A M 0E5B 71232 0D73 N C:\TURBOC\DEV\MCB.EXE 07 1FBF Z 0000 22240 F000 N FREE MEMORY CONTROL BLOCK
In Example 3, the DOS 3.3 command FASTOPEN is seen to be a memory-resident program that owns two blocks of memory--one for a copy of the environment and one for the resident code itself, which is the typical case.
Example 3: The system rebooted with the CONFIG.SYS file in Example 2 and an AUTOEXEC.BAT file with the contents shown
C>TYPE \AUTOEXEC.BAT path c:\dos;c:\util;c:\turboc;c:\util\norton;c:\masm;c:\pe prompt $p$g mgc astclock subst u: c:\util fastopen c: cls C > MCB ========================================================================== MCB MCB ID PID MB PAR- ENV OWNER NO. SEG. SIZE ID BLK? ========================================================================== 01 0973 M 0008 16352 022B N IBMDOS.COM/MSDOS.SYS 02 0D72 M 0D73 3376 0D73 N COMMAND.COM COPY #1 03 0E46 M 0000 48 F000 N FREE MEMORY CONTROL BLOCK 04 0E4A M 0D73 160 0D73 Y COMMAND.COM COPY #1 05 0E55 M 0E5F 128 0D73 Y C:\DOS\FASTOPEN.EXE 06 0E5E M 0E5F 2896 0D73 N C:\DOS\FASTOPEN.EXE 07 0F14 M 0F1E 128 0D73 Y C:\TURBOC\DEV\MCB.EXE 08 0F1D M 0F1E 71232 0D73 N C:\TURBOC\DEV\MCB.EXE 09 2082 Z 0000 519120 F000 N FREE MEMORY CONTROL BLOCK ===========================================================================
Example 4 shows that the DOS GRAPHICS command is also a TSR and that it owns two more blocks of memory. Some TSR programs free their copy of the environment before executing the DOS TSR function. Such programs will not only have no environment block but also the owner of the remaining block holding the resident code may be incorrect because the owner string resides in the freed environment block. A TSR may have also allocated additional memory before exiting to DOS, in which case it would own additional memory blocks, which MCB.EXE would report.
Example 4: Running the DOS command GRAPHICS
C> GRAPHICS C > MCB ========================================================================== MCB MCB ID PID MB PAR- ENV OWNER NO. SEG. SIZE ID BLK? ========================================================================== 01 0973 M 0008 16352 022B N IBMDOS.COM/MSDOS.SYS 02 0D72 M 0D73 3376 0D73 N COMMAND.COM COPY #1 03 0E46 M 0000 48 F000 N FREE MEMORY CONTROL BLOCK 04 0E4A M 0D73 160 0D73 Y COMMAND.COM COPY #1 05 0E55 M 0E5F 128 0D73 Y C:\DOS\FASTOPEN.EXE 06 0E5E M 0E5F 2896 0D73 N C:\DOS\FASTOPEN.EXE 07 0F14 M 0F1E 128 0D73 Y C:\DOS\GRAPHICS.COM 08 0F1D M 0F1E 2144 0D73 N C:\DOS\GRAPHICS.COM 09 0FA4 M 0FAE 128 0D73 Y C:\TURBOC\DEV\MCB.EXE 10 0FAD M 0FAE 71232 0D73 N C:\TURBOC\DEV\MCB.EXE 11 2112 Z 0000 516816 0F14 N FREE MEMORY CONTROL BLOCK ===========================================================================
In Example 5, the secondary copy of COMMAND.COM owns three blocks of memory for some reason. I don't know why, but it's interesting.
Example 5: Installing a secondary copy of the command processor COMMAND.COM
C> COMMAND The IBM Personal Computer DOS Version 3.30 (C) Copyright International Business Machine Corp 1981, 1987 (C) Copyright Microsoft Corp. 1981, 1986 C > MCB ========================================================================== MCB MCB ID PID MB PAR- ENV OWNER NO. SEG. SIZE ID BLK? ========================================================================== 01 0973 M 0008 16352 022B N IBMDOS.COM/MSDOS.SYS 02 0D72 M 0D73 3376 0D73 N COMMAND.COM COPY #1 03 0E46 M 0000 48 F000 N FREE MEMORY CONTROL BLOCK 04 0E4A M 0D73 160 0D73 Y COMMAND.COM COPY #1 05 0E55 M 0E5F 128 0D73 Y C:\DOS\FASTOPEN.EXE 06 0E5E M 0E5F 2896 0D73 N C:\DOS\FASTOPEN.EXE 07 0F14 M 0F1E 128 0D73 Y C:\DOS\GRAPHICS.COM 08 0F1D M 0F1E 2144 0D73 N C:\DOS\GRAPHICS.COM 09 0FA4 M 0FAD 112 0FAD N COMMAND.COM COPY #2 10 0FAC M 0FAD 3376 0FAD N COMMAND.COM COPY #2 11 1080 M 0FAD 160 0FAD Y COMMAND.COM COPY #2 12 108B M 1095 128 0FAD Y C:\TURBOC\DEV\MCB.EXE 13 1094 M 1095 71232 0FAD N C:\TURBOC\DEV\MCB.EXE 14 2112 Z 0000 516816 0F14 N FREE MEMORY CONTROL BLOCK ===========================================================================
Example 6 shows that the space formerly occupied by the secondary COMMAND.COM has been freed upon execution of the EXITcommand. You can install more than one secondary COMMAND.COM if desired, and you can remove each one in reverse order with an EXITcommand.
Example 6: The secondary COMMAND.COM remains in memory until a DOS EXIT command is issued
C> EXIT C > MCB ========================================================================== MCB MCB ID PID MB PAR- ENV OWNER NO. SEG. SIZE ID BLK? ========================================================================== 01 0973 M 0008 16352 022B N IBMDOS.COM/MSDOS.SYS 02 0D72 M 0D73 3376 0D73 N COMMAND.COM COPY #1 03 0E46 M 0000 48 0F14 N FREE MEMORY CONTROL BLOCK 04 0E4A M 0D73 160 0D73 Y COMMAND.COM COPY #1 05 0E55 M 0E5F 128 0D73 Y C:\DOS\FASTOPEN.EXE 06 0E5E M 0E5F 2896 0D73 N C:\DOS\FASTOPEN.EXE 07 0F14 M 0F1E 128 0D73 Y C:\DOS\GRAPHICS.COM 08 0F1D M 0F1E 2144 0D73 N C:\DOS\GRAPHICS.COM 09 0FA4 M 0FAE 128 0D73 Y C:\TURBOC\DEV\MCB.EXE 10 0FAD M 0FAE 71232 0D73 N C:\TURBOC\DEV\MCB.EXE 11 2112 Z 0000 516816 0F14 N FREE MEMORY CONTROL BLOCK ===========================================================================
In Example 7, CHKDSK reports 588,064 bytes free, whereas in Example 6 MCB shows 516,816 bytes free in the MB controlled by MCB 11. Let's reconcile the differences. The free memory reported by CHKDSK is equal to 516,816 (MCB 11) plus 16 plus 71,232 (MCB 10).
Example 7: Running the DOS command CHKDSK to see how much free memory it reports and see how it compares to that reported by MCB.C
C>CHKDSK 21309440 bytes total disk space 53248 bytes in 2 hidden files 59392 bytes in 28 directories 9635840 bytes in 580 user files 11560960 bytes available on disk 652288 bytes total memory 588064 bytes free
This makes sense. When CHKDSK runs, it has memory allocated to it (in fact all the remaining memory). When it reports the amount of free memory, it subtracts the memory needed to run itself but does not discount that needed for its copy of the environment, apparently reasoning that any program run will need to have such an environment block allocated. The extra 16 bytes in the equation above are needed to account for the paragraph of memory needed for the last MCB (11 here) itself. Also note that CHKDSK does not include the memory in any free memory embedded in the body of the allocation chain (such as MCB 03), even though such memory is available for use under the right conditions.
You'll also notice that the PC on which I ran this program seems to have only 652,288 bytes of total memory, which is 3,072 bytes less than the full 640K (655,360 bytes) of memory that is actually installed. This is an example of memory being hidden from DOS. My computer was an IBM PC XT equipped with a Paradise graphics card (which provides for monochrome text and CGA graphics on an IBM monochrome monitor). This video card also needs a memory-resident program (called MGC.COM, see the contents of the AUTOEXEC.BAT file in Example 3) installed in order for it to function. So why doesn't MGC.COM show up in one of the MBs?
It occupies the top 3,072 bytes of memory. After installing itself it modifies DOS' record of memory stored in word 40H:13H and does a warm reboot, which reinitializes the computer without doing a memory check. The reason for this procedure is that MGC.COM must be in memory even when DOS is not being used (for example, for some games that need to be booted from a floppy). The MCB/MB scheme is used only by DOS and wouldn't be sufficient in such cases.
Conclusion
The possible uses for this program are many. You could, for example, examine the effect of all of the DOS memory-allocation functions detail. You could possibly devise a scheme to remove any TSR from memory, not necessarily in reverse of the order installed, without creating a hole in memory. With the location of the master environment known, those who need to change the environment frequently (for example the DOS path) and find the use of SET tedious, could write a full-screen version of the DOS SET command. No doubt you will think of other uses.
MAPPING DOS MEMORY ALLOCATION_ by Robert J. Moore
[LISTING ONE]<a name="01fd_0014">
/*
==================== MCB.C =======================================
This program chains through all the DOS memory control
blocks and computes and prints out information related to
each one.
R.J. Moore (C) 06 June 1988 Version 1.2. May be used freely
for non-commercial purposes only.
Compiled under Turbo C, Version 1.5, using the small memory
model, which itated the explicit declaration of some huge and
far pointers. Tested on IBM PC, IBM XT, IBM AT, TP/286 AT
clone, under PC-DOS 2.1, 3.1, 3.2, and 3.3. Also run under
MS-DOS 2.11 on a DEC Rainbow. PC-DOS 3.3 required some
adjustments as discussed in later comments.
==================================================================
*/
#include <stdio.h>
#include <dos.h>
/*-------Global declarations----------------------------------------*/
struct MCB /*template for a one paragraph MS-DOS MCB */
{
char chain; /* 'Z' for last MCB, 'M' for all others */
unsigned pid; /* PSP segment for process owning the MCB */
unsigned psize; /* Paragraphs of memory in the MB following */
char unused [11]; /* Last 11 bytes of MCB (currently unused) */
};
typedef struct MCB huge *PTRMCB; /*PTRMCB is a type declared
to be a huge pointer to MCB */
/*-------Function prototypes----------------------------------------*/
void main (void);
void far *ffmcb (void); /* Returns far pointer to first MCB. */
void prn_header (void); /* Prints output table header. */
void prn_mcb (PTRMCB pm); /* Prints out MCB and related information.*/
/* Prints out owner name string for pid. */
void prn_pid_own (unsigned pid,unsigned parent);
/*------main()------------------------------------------------------*/
/* Executive to control finding a pointer to each MCB and directing
the printing out of information for each until the end of the
MCB chain is reached. */
void main()
{
PTRMCB ptrmcb; /* ptrmcb is a huge pointer to an MCB */
/* Get pointer to first MCB. Note that ffmcb() returns a far
pointer, which is then cast to a huge pointer. A far pointer
is good enough to find the first MCB since a far pointer can
start at any memory location. However, the use of this pointer
in ptrmcb must be huge because MCBs range over more than 64K,
which is all that a far pointer can handle since the segment
portion of a far pointer never changes. I, of course, found
this out the hard way. Such special declarations can be
avoided if this program is compiled under the huge memory
model, but I think the method I used is more instructive. */
ptrmcb = (PTRMCB) ffmcb(); /* Get far pointer to first MCB and
cast to huge pointer via (PTRMCB). */
prn_header (); /* Print out table header to stdout. */
prn_mcb(ptrmcb); /* Print out information for first MCB.*/
/* Print out MCB information for each of the remaining MCBs */
do
{
ptrmcb += ptrmcb->psize + 1; /* Get pointer to next MCB */
/* Each unit increment of ptrmcb corresponds to one paragraph;
adding ptrmcb->psize thus increments through entire allocated
memory block following the MCB. Since this doesn't include
space occupied by the MCB itself, must increment through one
more paragraph (+ 1) to point to the next MCB. */
prn_mcb(ptrmcb); /* Print out information for MCB */
} while (ptrmcb->chain == 'M'); /* as long as not at end of chain*/
/*Print out final decoration. */
printf ("========================================================");
puts ("===========");
}
/*------ffmcb()-----------------------------------------------------*/
/* Returns a far pointer to the first MCB in memory. Explict
declaration of far needed since small model was used to compile,
as noted in a comment in main. */
void far *ffmcb(void)
{
union REGS regs; /* REGS and SREGS defined in dos.h. */
struct SREGS sregs;
unsigned far *segmptr; /* Far pointer to segment address of MCB.*/
regs.h.ah=0x52; /* Undocumented MS-DOS function 52H. */
intdosx(®s, ®s, &sregs); /* ES:BX-2 points to segment
address of first MCB on return
and is copied to segmptr below.*/
segmptr=MK_FP(sregs.es,regs.x.bx-2);
return MK_FP(*segmptr,0); /* Return pointer to MCB itself. */
} /* Segment pointed to by *segmptr.*/
/* Offset is zero (on paragraph). */
/*-----------prn_header()-------------------------------------------*/
/* Prints out header for the output variables describing the
information for each MCB which will be subsequently printed
out by the function prn_mcb().
*/
void prn_header (void)
{
printf ("===================================================");
puts ("================");
puts ("MCB MCB ID PID MB PAR- ENV OWNER");
puts ("NO. SEG SIZE ENT BLK?");
printf ("===================================================");
puts ("================");
/* MCB NO. = ordinal number of MCB being processed (1,2,...).
MCB SEG = segment address (hex) of memory control block.
ID = chain id, 'Z' if last MCB, 'M' otherwise.
PID = process id, the PSP segment address (hex) of owner of
the MCB. (PSP always starts on paragraph boundary.)
MB SIZE = size of the allocated memory block controlled by
the MCB (the MB immediately follows its associated
MCB at the next paragraph in memory (decimal bytes).
PARENT = segment address (hex) of parent process's PID.
ENV BLK?= 'Y' if the MCB controls an environment block,
'N' otherwise.
OWNER = string that prints out program associated with
the PID.
*/
}
/*------prn_mcb()---------------------------------------------------*/
/* Prints out the information associated with the MCB pointer passed
to it in its argument list
*/
void prn_mcb (PTRMCB pm)
{
static cnt = 0; /* Count of number of times parent has
been equal to the pid. */
static mcbnum = 1; /* Ordinal # of MCB being printed out. */
unsigned parid; /* Parent id (segment address of parent
process). */
unsigned mcbseg; /* Segment address of MCB (offset is
always zero since paragraph aligned).*/
char envf; /*Set to 'Y'/'N' if MB is/is not an
environment block. */
unsigned envseg; /*Segment address of pid's environment
block. */
/* Get parent id located at pid:16H */
parid = * (unsigned far *) MK_FP (pm->pid,0x16);
mcbseg = FP_SEG (pm); /* Segment address of the MCB */
envseg = * (unsigned far *) MK_FP(pm->pid,0x2C); /* segment */
/* Address of pid's environment */
/* located at pid:2CH. */
/* If the MCB segment value plus one equals the environment
segment address, then the MCB controls the environment
block (set envf = 'Y'); otherwise set envf = 'N' */
envf = mcbseg+1 == envseg ? 'Y' : 'N';
/* Count the number of times parent and pid have been equal
(when this is true, memory blocks are owned by COMMAND.COM */
if (parid == pm->pid) cnt++;
/* The above determination of whether an MB is an environment
block isn't complete for DOS versions 2.0 thru 3.2. The
above logic will not identify the master environment block
owned by the master copy of COMMAND.COM since the value at
pid:2CH contains zero, not the segment address of the master
environment. The logic below uses the fact that the master
environment follows the master COMMAND.COM in memory. (The
environment copies for other programs are in memory BEFORE
the pid they are associated with.) Starting with DOS 3.3
pid:2CH always points to the environment, even for the
master COMMAND.COM, so the following is not needed (but it
doesn't do any harm). */
if (!envseg && cnt == 2) envf = 'Y';
/* Print out MCB information except for owner name in the
following call to printf(). */
printf("%2.2u%06.4X%2.1c%06.4X%7lu%5.4X %-5.1c",
mcbnum++,mcbseg,pm->chain,pm->pid,(long) pm->psize*16,parid,envf);
/* Call prn_pid_own() to find and print out owner string */
prn_pid_own(pm->pid,parid);
}
/*------prn_pid_own()-----------------------------------------------*/
/* Prints out owner name string associated with the pid, which is an
input parameter. Also needs the parent address as an input to
identify cases where COMMAND.COM is the owner (true when
pid=parent). This function also uses the fact that the following
pid values are special:
pid = 0 means that MCB is a free block of memory
pid = 8 means that the MCB is owned by IBMDOS.COM/MSDOS.SYS
pid = parent means that the MCB is owned by COMMAND.COM (the only
program that is its own parent.)
In these cases I assign appropriate owner string names instead of
getting them from the environment since they are not available
there. Owner names consisting of a string with the drive, path,
and file name of the program that owns the memory are only
available in DOS 3.x. Note that DOS 3.3 does not provide an owner
string for the master copy of COMMAND.COM for some reason. This
is of no consequence in the method used here.
*/
void prn_pid_own (unsigned pid,unsigned parent)
{
unsigned far *envsegptr; /* Pointer to seg address of environment*/
char far *envptr; /* Pointer to pid's environment */
unsigned far *envsizeptr; /* Pointer to envsize word below */
unsigned envsize; /* Size of pid's environment */
/* Ordinal # of copy of COMMAND.COM in memory (ccnum=1 for master
copy, 2 for first secondary copy (if any), etc. */
static unsigned char ccnum = 0;
/* Pid value saved from previous call to this function.
Initialized to an impossible value (no PSP could start at FFFF:0) */
static prev_pid = 0xFFFF;
switch (pid)
{
/* Assign owner names for two special cases */
case 0 : puts ("FREE MEMORY CONTROL BLOCK");return;
case 8 : puts ("IBMDOS.COM/MSDOS.SYS");return;
}
/* pid:2CH contains ptr to segment address of pid's environment */
envsegptr = (unsigned far *) MK_FP (pid,0x2C);
/* Get pointer to the environment block itself */
envptr = (char far *) MK_FP (*envsegptr,0);
/* Define a pointer that contains the size of the environment
block. Must point back one paragraph (where the environment's
MCB resides) plus three bytes forward (where the MCB block
size field is). */
envsizeptr = (unsigned far *) MK_FP(*envsegptr-1,0x3);
/* Get the size of the environment using the above pointer in
units of bytes (1 paragraph = 16 decimal bytes). */
envsize = *envsizeptr*16;
/* If next stmt is satisfied, owner is a copy of COMMAND.COM */
if (pid == parent)
{
/* If previous pid is different from current pid, have found a
new secondary copy of COMMAND - ccnum keeps track records the
copy number. */
if (prev_pid != pid) ccnum++;
printf ("COMMAND.COM COPY #%-2u\n",ccnum);
prev_pid = pid; /* Save current pid - will be previous */
return; /* in the next call to this function */
}
/* Loop at most until the end of the environment */
while (envsize)
{
/* Decrement counter (envsize) indicating # of bytes left in
environment and advance pointer thru environment block until
either end of environment or a NULL is located
*/
while (--envsize && *envptr++);
/* The next stmt will be true if another NULL immediately
follows the first one located and a word count of 0001 then
follows that. */
if (!*envptr && *(unsigned far *) (envptr+1) == 0x1)
{
envptr +=3; /* Correct pattern found (00 00 01 00) */
break; /* so point envptr to owner string */
}
}
if (envsize)
{
/* If an owner string was found before the end of the
environment so print out the owner name. Note that can't
use puts() or printf() to print out the results since I
used the small memory model.
*/
while(*envptr) putchar(*envptr++);
putchar('\n');
}
else
/* If reached the end of the environment without finding
an owner string (should only occur for DOS 2.x) */
puts ("UNKNOWN OWNER");
}