[Chapter Nineteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chapter Nineteen
- 19.2 - Shared Memory
- 19.2.1 - Static Shared Memory
19.2 Shared Memory
The only problem with running different DOS programs as part of a single
application is interprocess communication. That is, how do all these programs
talk to one other? When a typical DOS application runs, DOS loads in all
code and data segments; there is no provision, other than reading data from
a file or the process termination code, for one process to pass information
to another. Although file I/O will work, it is cumbersome and slow. The
ideal solution would be for one process to leave a copy of various variables
that other processes can share. Your programs can easily do this using shared
memory.
Most modern multitasking operating systems provide for shared memory - memory
that appears in the address space of two or more processes. Furthermore,
such shared memory is often persistent, meaning it continues to hold values
after its creator process terminates. This allows other processes to start
later and use the values left behind by the shared variables' creator.
Unfortunately, MS-DOS is not a modern multitasking operating system and
it does not support shared memory. However, we can easily write a resident
program that provides this capability missing from DOS. The following sections
describe how to create two types of shared memory regions - static and dynamic.
19.2.1 Static Shared Memory
A TSR to implement static shared memory is trivial. It is a passive
TSR that provides three functions - verify presence, remove, and return
segment pointer. The transient portion simply allocates a 64K data segment
and then terminates. Other processes can obtain the address of the 64K shared
memory block by making the "return segment pointer" call. These
processes can place all their shared data into the segment belonging to
the TSR. When one process quits, the shared segment remains in memory as
part of the TSR. When a second process runs and links with the shared segment,
the variables from the shared segment are still intact, so the new process
can access those values. When all processes are done sharing data, the user
can remove the shared memory TSR with the remove function.
As mentioned above, there is almost nothing to the shared memory TSR. The
following code implements it:
; SHARDMEM.ASM
;
; This TSR sets aside a 64K shared memory region for other processes to use.
;
; Usage:
;
; SHARDMEM - Loads resident portion and activates
; shared memory capabilities.
;
; SHARDMEM REMOVE - Removes shared memory TSR from memory.
;
; This TSR checks to make sure there isn't a copy already active in
; memory. When removing itself from memory, it makes sure there are
; no other interrupts chained into INT 2Fh before doing the remove.
;
;
;
; The following segments must appear in this order and before the
; Standard Library includes.
ResidentSeg segment para public 'Resident'
ResidentSeg ends
SharedMemory segment para public 'Shared'
SharedMemory ends
EndResident segment para public 'EndRes'
EndResident ends
.xlist
.286
include stdlib.a
includelib stdlib.lib
.list
; Resident segment that holds the TSR code:
ResidentSeg segment para public 'Resident'
assume cs:ResidentSeg, ds:nothing
; Int 2Fh ID number for this TSR:
MyTSRID byte 0
byte 0 ;Padding so we can print it.
; PSP is the psp address for this program.
PSP word 0
OldInt2F dword ?
; MyInt2F- Provides int 2Fh (multiplex interrupt) support for this
; TSR. The multiplex interrupt recognizes the following
; subfunctions (passed in AL):
;
; 00h- Verify presence. Returns 0FFh in AL and a pointer
; to an ID string in es:di if the
; TSR ID (in AH) matches this
; particular TSR.
;
; 01h- Remove. Removes the TSR from memory.
; Returns 0 in AL if successful,
; 1 in AL if failure.
;
; 10h- Return Seg Adrs. Returns the segment address of the
; shared segment in ES.
MyInt2F proc far
assume ds:nothing
cmp ah, MyTSRID ;Match our TSR identifier?
je YepItsOurs
jmp OldInt2F
; Okay, we know this is our ID, now check for a verify, remove, or
; return segment call.
YepItsOurs: cmp al, 0 ;Verify Call
jne TryRmv
mov al, 0ffh ;Return success.
lesi IDString
iret ;Return back to caller.
IDString byte "Static Shared Memory TSR",0
TryRmv: cmp al, 1 ;Remove call.
jne TryRetSeg
; See if we can remove this TSR:
push es
mov ax, 0
mov es, ax
cmp word ptr es:[2Fh*4], offset MyInt2F
jne TRDone
cmp word ptr es:[2Fh*4 + 2], seg MyInt2F
je CanRemove ;Branch if we can.
TRDone: mov ax, 1 ;Return failure for now.
pop es
iret
; Okay, they want to remove this guy *and* we can remove it from memory.
; Take care of all that here.
assume ds:ResidentSeg
CanRemove: push ds
pusha
cli ;Turn off the interrupts while
mov ax, 0 ; we mess with the interrupt
mov es, ax ; vectors.
mov ax, cs
mov ds, ax
mov ax, word ptr OldInt2F
mov es:[2Fh*4], ax
mov ax, word ptr OldInt2F+2
mov es:[2Fh*4 + 2], ax
; Okay, one last thing before we quit- Let's give the memory allocated
; to this TSR back to DOS.
mov ds, PSP
mov es, ds:[2Ch] ;Ptr to environment block.
mov ah, 49h ;DOS release memory call.
int 21h
mov ax, ds ;Release program code space.
mov es, ax
mov ah, 49h
int 21h
popa
pop ds
pop es
mov ax, 0 ;Return Success.
iret
; See if they want us to return the segment address of our shared segment
; here.
TryRetSeg: cmp al, 10h ;Return Segment Opcode
jne IllegalOp
mov ax, SharedMemory
mov es, ax
mov ax, 0 ;Return success
clc
iret
; They called us with an illegal subfunction value. Try to do as little
; damage as possible.
IllegalOp: mov ax, 0 ;Who knows what they were thinking?
iret
MyInt2F endp
assume ds:nothing
ResidentSeg ends
; Here's the segment that will actually hold the shared data.
SharedMemory segment para public 'Shared'
db 0FFFFh dup (?)
SharedMemory ends
cseg segment para public 'code'
assume cs:cseg, ds:ResidentSeg
; SeeIfPresent- Checks to see if our TSR is already present in memory.
; Sets the zero flag if it is, clears the zero flag if
; it is not.
SeeIfPresent proc near
push es
push ds
push di
mov cx, 0ffh ;Start with ID 0FFh.
IDLoop: mov ah, cl
push cx
mov al, 0 ;Verify presence call.
int 2Fh
pop cx
cmp al, 0 ;Present in memory?
je TryNext
strcmpl
byte "Static Shared Memory TSR",0
je Success
TryNext: dec cl ;Test USER IDs of 80h..FFh
js IDLoop
cmp cx, 0 ;Clear zero flag.
Success: pop di
pop ds
pop es
ret
SeeIfPresent endp
; FindID- Determines the first (well, last actually) TSR ID available
; in the multiplex interrupt chain. Returns this value in
; the CL register.
;
; Returns the zero flag set if it locates an empty slot.
; Returns the zero flag clear if failure.
FindID proc near
push es
push ds
push di
mov cx, 0ffh ;Start with ID 0FFh.
IDLoop: mov ah, cl
push cx
mov al, 0 ;Verify presence call.
int 2Fh
pop cx
cmp al, 0 ;Present in memory?
je Success
dec cl ;Test USER IDs of 80h..FFh
js IDLoop
xor cx, cx
cmp cx, 1 ;Clear zero flag
Success: pop di
pop ds
pop es
ret
FindID endp
Main proc
meminit
mov ax, ResidentSeg
mov ds, ax
mov ah, 62h ;Get this program's PSP
int 21h ; value.
mov PSP, bx
; Before we do anything else, we need to check the command line
; parameters. If there is one, and it is the word "REMOVE", then remove
; the resident copy from memory using the multiplex (2Fh) interrupt.
argc
cmp cx, 1 ;Must have 0 or 1 parms.
jb TstPresent
je DoRemove
Usage: print
byte "Usage:",cr,lf
byte " shardmem",cr,lf
byte "or shardmem REMOVE",cr,lf,0
ExitPgm
; Check for the REMOVE command.
DoRemove: mov ax, 1
argv
stricmpl
byte "REMOVE",0
jne Usage
call SeeIfPresent
je RemoveIt
print
byte "TSR is not present in memory, cannot remove"
byte cr,lf,0
ExitPgm
RemoveIt: mov MyTSRID, cl
printf
byte "Removing TSR (ID #%d) from memory...",0
dword MyTSRID
mov ah, cl
mov al, 1 ;Remove cmd, ah contains ID
int 2Fh
cmp al, 1 ;Succeed?
je RmvFailure
print
byte "removed.",cr,lf,0
ExitPgm
RmvFailure: print
byte cr,lf
byte "Could not remove TSR from memory.",cr,lf
byte "Try removing other TSRs in the reverse order "
byte "you installed them.",cr,lf,0
ExitPgm
; Okay, see if the TSR is already in memory. If so, abort the
; installation process.
TstPresent: call SeeIfPresent
jne GetTSRID
print
byte "TSR is already present in memory.",cr,lf
byte "Aborting installation process",cr,lf,0
ExitPgm
; Get an ID for our TSR and save it away.
GetTSRID: call FindID
je GetFileName
print
byte "Too many resident TSRs, cannot install",cr,lf,0
ExitPgm
; Things look cool so far, so install the interrupts
GetFileName: mov MyTSRID, cl
print
byte "Installing interrupts...",0
; Patch into the INT 2Fh interrupt chain.
cli ;Turn off interrupts!
mov ax, 0
mov es, ax
mov ax, es:[2Fh*4]
mov word ptr OldInt2F, ax
mov ax, es:[2Fh*4 + 2]
mov word ptr OldInt2F+2, ax
mov es:[2Fh*4], offset MyInt2F
mov es:[2Fh*4+2], seg ResidentSeg
sti ;Okay, ints back on.
; We're hooked up, the only thing that remains is to zero out the shared
; memory segment and then terminate and stay resident.
printf
byte "Installed, TSR ID #%d.",cr,lf,0
dword MyTSRID
mov ax, SharedMemory ;Zero out the shared
mov es, ax ; memory segment.
mov cx, 32768 ;32K words = 64K bytes.
xor ax, ax ;Store all zeros,
mov di, ax ; starting at offset zero.
rep stosw
mov dx, EndResident ;Compute size of program.
sub dx, PSP
mov ax, 3100h ;DOS TSR command.
int 21h
Main endp
cseg ends
sseg segment para stack 'stack'
stk db 256 dup (?)
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes db 16 dup (?)
zzzzzzseg ends
end Main
This program simply carves out a chunk of memory (the 64K in the SharedMemory
segment) and returns a pointer to it in es
whenever a program
executes the appropriate int 2Fh
call (ah
= TSR
ID and al
=10h). The only catch is how do we declared shared
variables in the applications that use shared memory? Well, that's fairly
easy if we play a sneaky trick on MASM, the Linker, DOS, and the 80x86.
When DOS loads your program into memory, it generally loads the segments
in the same order they first appear in your source files. The UCR Standard
Library, for example, takes advantage of this by insisting that you include
a segment named zzzzzzseg
at the end of all your assembly language
source files. The UCR Standard Library memory management routines build
the heap starting at zzzzzzseg
, it must be the last segment
(containing valid data) because the memory management routines may overwrite
anything following zzzzzzseg
.
For our shared memory segment, we would like to create a segment something
like the following:
SharedMemory segment para public 'Shared'
; // define all shared variables here//
SharedMemory ends
Applications that share data would define all shared variables in this shared
segment. There are, however, five problems. First, how do we tell the assembler/linker/DOS/80x86
that this is a shared segment, rather than having a separate segment for
each program? Well, this problem is easy to solve; we don't bother telling
MASM, the linker, or DOS anything. The way we make the different applications
all share the same segment in memory is to invoke the shared memory TSR
in the code above with function code 10h. This returns the address of the
TSR's SharedMemory segment in the es
register. In our assembly
language programs we fool MASM into thinking es
points at its
local shared memory segment when, in fact, es
points at the
global segment.
The second problem is minor, but annoying nonetheless. When you create a
segment, MASM, the linker, and DOS set aside storage for that segment. If
you declare a large number of variables in a shared segment, this can waste
memory since the program will actually use the memory space in the global
shared segment. One easy way to reclaim the storage that MASM reserves for
this segment is to define the shared segment after zzzzzzseg
in your shared memory applications. By doing so, the Standard Library will
absorb any memory reserved for the (dummy) shared memory segment into the
heap, since all memory after zzzzzzseg
belongs to the heap
(when you use the standard meminit
call).
The third problem is slightly more difficult to deal with. Since you will
not be use the local segment, you cannot initialize any variables in the
shared memory segment by placing values in the operand field of byte, word,
dword, etc., directives. Doing so will only initialize the local memory
in the heap, the system will not copy this data to the global shared segment.
Generally, this isn't a problem because processes won't normally initialize
shared memory as they load. Instead, there will probably be a single application
you run first that initializes the shared memory area for the rest of the
processes that using the global shared segment.
The fourth problem is that you cannot initialize any variables with the
address of an object in shared memory. For example, if the variable shared_K
is in the shared memory segment, you could not use a statement like the
following:
printf
byte "Value of shared_K is %d\n",0
dword shared_K
The problem with this code is that MASM initializes the double word after
the string above with the address of the shared_K variable in the local
copy of the shared data segment. This will not print out the copy in the
global shared data segment.
The last problem is anything but minor. All programs that use the global
shared memory segment must define their variables at identical offsets within
the shared segment. Given the way MASM assigns offsets to variables within
a segment, if you are one byte off in the declaration of any of your variables,
your program will be accessing its variables at different addresses than
other processes sharing the global shared segment. This will scramble memory
and produce a disaster. The only reasonable way to declare variables for
shared memory programs is to create an include file with all the shared
variable declarations for all concerned programs. Then include this single
file into all the programs that share the variables. Now you can add, remove,
or modify variables without having to worry about maintaining the shared
variable declarations in the other files.
The following two sample programs demonstrate the use of shared memory.
The first application reads a string from the user and stuffs it into shared
memory. The second application reads that string from shared memory and
displays it on the screen.
First, here is the include file containing the single shared variable declaration
used by both applications:
; shmvars.asm
;
; This file contains the shared memory variable declarations used by
; all applications that refer to shared memory.
InputLine byte 128 dup (?)
Here is the first application that reads an input string from the user and
shoves it into shared memory:
; SHMAPP1.ASM
;
; This is a shared memory application that uses the static shared memory
; TSR (SHARDMEM.ASM). This program inputs a string from the user and
; passes that string to SHMAPP2.ASM through the shared memory area.
;
;
.xlist
include stdlib.a
includelib stdlib.lib
.list
dseg segment para public 'data'
ShmID byte 0
dseg ends
cseg segment para public 'code'
assume cs:cseg, ds:dseg, es:SharedMemory
; SeeIfPresent- Checks to see if the shared memory TSR is present in memory.
; Sets the zero flag if it is, clears the zero flag if
; it is not. This routine also returns the TSR ID in CL.
SeeIfPresent proc near
push es
push ds
push di
mov cx, 0ffh ;Start with ID 0FFh.
IDLoop: mov ah, cl
push cx
mov al, 0 ;Verify presence call.
int 2Fh
pop cx
cmp al, 0 ;Present in memory?
je TryNext
strcmpl
byte "Static Shared Memory TSR",0
je Success
TryNext: dec cl ;Test USER IDs of 80h..FFh
js IDLoop
cmp cx, 0 ;Clear zero flag.
Success: pop di
pop ds
pop es
ret
SeeIfPresent endp
; The main program for application #1 links with the shared memory
; TSR and then reads a string from the user (storing the string into
; shared memory) and then terminates.
Main proc
assume cs:cseg, ds:dseg, es:SharedMemory
mov ax, dseg
mov ds, ax
meminit
print
byte "Shared memory application #1",cr,lf,0
; See if the shared memory TSR is around:
call SeeIfPresent
je ItsThere
print
byte "Shared Memory TSR (SHARDMEM) is not loaded.",cr,lf
byte "This program cannot continue execution.",cr,lf,0
ExitPgm
; If the shared memory TSR is present, get the address of the shared segment
; into the ES register:
ItsThere: mov ah, cl ;ID of our TSR.
mov al, 10h ;Get shared segment address.
int 2Fh
; Get the input line from the user:
print
byte "Enter a string: ",0
lea di, InputLine ;ES already points at proper seg.
gets
print
byte "Entered '",0
puts
print
byte "' into shared memory.",cr,lf,0
Quit: ExitPgm ;DOS macro to quit program.
Main endp
cseg ends
sseg segment para stack 'stack'
stk db 1024 dup ("stack ")
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes db 16 dup (?)
zzzzzzseg ends
; The shared memory segment must appear after "zzzzzzseg".
; Note that this isn't the physical storage for the data in the
; shared segment. It's really just a place holder so we can declare
; variables and generate their offsets appropriately. The UCR Standard
; Library will reuse the memory associated with this segment for the
; heap. To access data in the shared segment, this application calls
; the shared memory TSR to obtain the true segment address of the
; shared memory segment. It can then access variables in the shared
; memory segment (where ever it happens to be) off the ES register.
;
; Note that all the variable declarations go into an include file.
; All applications that refer to the shared memory segment include
; this file in the SharedMemory segment. This ensures that all
; shared segments have the exact same variable layout.
SharedMemory segment para public 'Shared'
include shmvars.asm
SharedMemory ends
end Main
The second application is very similar, here it is
; SHMAPP2.ASM
;
; This is a shared memory application that uses the static shared memory
; TSR (SHARDMEM.ASM). This program assumes the user has already run the
; SHMAPP1 program to insert a string into shared memory. This program
; simply prints that string from shared memory.
;
.xlist
include stdlib.a
includelib stdlib.lib
.list
dseg segment para public 'data'
ShmID byte 0
dseg ends
cseg segment para public 'code'
assume cs:cseg, ds:dseg, es:SharedMemory
; SeeIfPresent Checks to see if the shared memory TSR is present in memory.
; Sets the zero flag if it is, clears the zero flag if
; it is not. This routine also returns the TSR ID in CL.
SeeIfPresent proc near
push es
push ds
push di
mov cx, 0ffh ;Start with ID 0FFh.
IDLoop: mov ah, cl
push cx
mov al, 0 ;Verify presence call.
int 2Fh
pop cx
cmp al, 0 ;Present in memory?
je TryNext
strcmpl
byte "Static Shared Memory TSR",0
je Success
TryNext: dec cl ;Test USER IDs of 80h..FFh
js IDLoop
cmp cx, 0 ;Clear zero flag.
Success: pop di
pop ds
pop es
ret
SeeIfPresent endp
; The main program for application #1 links with the shared memory
; TSR and then reads a string from the user (storing the string into
; shared memory) and then terminates.
Main proc
assume cs:cseg, ds:dseg, es:SharedMemory
mov ax, dseg
mov ds, ax
meminit
print
byte "Shared memory application #2",cr,lf,0
; See if the shared memory TSR is around:
call SeeIfPresent
je ItsThere
print
byte "Shared Memory TSR (SHARDMEM) is not loaded.",cr,lf
byte "This program cannot continue execution.",cr,lf,0
ExitPgm
; If the shared memory TSR is present, get the address of the shared segment
; into the ES register:
ItsThere: mov ah, cl ;ID of our TSR.
mov al, 10h ;Get shared segment address.
int 2Fh
; Print the string input in SHMAPP1:
print
byte "String from SHMAPP1 is '",0
lea di, InputLine ;ES already points at proper seg.
puts
print
byte "' from shared memory.",cr,lf,0
Quit: ExitPgm ;DOS macro to quit program.
Main endp
cseg ends
sseg segment para stack 'stack'
stk db 1024 dup ("stack ")
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes db 16 dup (?)
zzzzzzseg ends
; The shared memory segment must appear after "zzzzzzseg".
; Note that this isn't the physical storage for the data in the
; shared segment. It's really just a place holder so we can declare
; variables and generate their offsets appropriately. The UCR Standard
; Library will reuse the memory associated with this segment for the
; heap. To access data in the shared segment, this application calls
; the shared memory TSR to obtain the true segment address of the
; shared memory segment. It can then access variables in the shared
; memory segment (where ever it happens to be) off the ES register.
;
; Note that all the variable declarations go into an include file.
; All applications that refer to the shared memory segment include
; this file in the SharedMemory segment. This ensures that all
; shared segments have the exact same variable layout.
SharedMemory segment para public 'Shared'
include shmvars.asm
SharedMemory ends
end Main
- 19.2 - Shared Memory
- 19.2.1 - Static Shared Memory
Art of Assembly: Chapter Nineteen - 29 SEP 1996
[Chapter Nineteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]