; 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