;------------------------------------------------------------------------------- ; ; SPAWN_S2.ASM - The 'swapper' for the spawn_swap() routine. ; ; ACKNOWLEDGMENTS: ; ; The code for '_spawn_swap' originated from: ; Thomas Wagner ; Ferrari Electronic GmbH ; Beusselstrasse 27 ; D-1000 Berlin 21 ; West Germany ; Version 2.2, released to public domain on 90-08-16. ; ; And was featured in an article: ; "Spawn Programs from Within Your DOS Application Using ; Almost No Additional Memory", ; by Marc Adler, ; in Microsoft Systems Journal, September 1990. ; ; On 11/06/90, KDF obtained a copy of the original code via ; Larry Wagner via MSJ's bulletin board. Subsequently, the ; code was cleaned up and adapted to local conventions. ; Many changes were made to naming conventions and all support ; of turbo pascal was dropped. ; ; Continued local support and enhancement of this software is provided by: ; ; ICADA Technologies, Inc. ; 1508 Humboldt ; Manhattan, KS 66502 ; (913) 537-2864 ; Kent D. Funk ; ; Model Directive, compile with '/DMODL=LARGE', '/DMODL=MEDIUM', etc. IFDEF MODL %.MODEL MODL,C ELSE .MODEL SMALL,C ENDIF ; External References. EXTRN _psp: WORD ; Segment of caller's PSP. ; Swapper Control Flag Constants. SWAPPING EQU 01h ; Must swap memory image out & in. USE_EMS EQU 02h ; Use EMS to swap if possible. CREAT_TEMP EQU 04h ; Use DOS 'create_temp' call. EXEC_ONLY EQU 80h ; Do an 'exec' else 'spawn'. ; EMS and FILE IO Constants. IO_BLKSIZE EQU (16*1024) ; IO block size (bytes). IO_BLKPARAS EQU (IO_BLKSIZE/16) ; IO block size (paragraphs). IO_BLKPSHIFT EQU 10 ; Shift for block paras. IO_BLKBMASK EQU (IO_BLKPARAS-1) ; Mask for partial block bytes. IO_BLKBSHIFT EQU 4 ; Shift for partial block bytes. ; Auxillary MCB chaining structure used to string all MCB's owned by the caller. AUX_MCBS STRUC NXT_ADDR DW ? ; Segment of next MCB owned by ; caller, 0 if end of owner chain. NXT_PARAS DW ? ; PARAS of next MCB owned. NXT_BLKP DW ? ; IO blocks of next MCB owned, ; BLKP= ((PARAS+1)>>IO_BLKPSHIFT). NXT_BLKB DW ? ; IO bytes of next MCB owned, ; BLKB= (((PARAS+1) ; & IO_BLKBMASK) ; << IO_BLKBSHIFT). AUX_MCBS ENDS ; Modified DOS Memory Control Block (MCB) structure. The standard DOS MCB ; includes only the first three members followed by 11 bytes of reserved data. ; !!NOTICE!! - Use of DOS MCB's is undocumented and may not be supported in ; future versions. (Hackers live free or die!) DOS_MCBS STRUC ID DB ? ; 'M' if member of chain, or 'Z' ; if last block in chain. OWNER DW ? ; Segment of PSP which allocated ; this block or 0 if unallocated. PARAS DW ? ; # of paragraphs controlled by ; by this block. DB 3 DUP(?) ; Unused - forced alignment. AUX_MCB DB TYPE AUX_MCBS DUP(?) ; Auxillary MCB structure. DOS_MCBS ENDS ; Standard DOS EXEC Parameter Block. DOS_EXEBLKS STRUC ENVSEG DW ? ; Segment of environment block. CMDTOFF DW ? ; Offset of command-line tail. CMDTSEG DW ? ; Segment of command-line tail. FCB1OFF DW ? ; Offset of 1st FCB. FCB1SEG DW ? ; Segment of 1st FCB. FCB2OFF DW ? ; Offset of 2nd FCB. FCB2SEG DW ? ; Segment of 2nd FCB. DOS_EXEBLKS ENDS ; Memory template (preamble) for the 'core' process. This represents only ; the preliminary version which will be finalized after the size of the ; 'core' code has been determined. PREAMBLE STRUC PARBEG DB 5Ch DUP(?) ; Starting offset from PSP. PAGE_FRAME DW ? ; EMS page frame (0 if no EMS). BASE_PARAS DW ? ; Paragraphs in base MCB. BASE_BLKP DW ? ; IO blocks in base MCB. BASE_BLKB DW ? ; IO bytes in base MCB. SAVE_SS DW ? ; Caller's saved SS. SAVE_SP DW ? ; Caller's saved SP. CORE_SP DW ? ; Core's saved SP. CHAIN DB TYPE AUX_MCBS DUP(?) ; Chain of MCB's owned by caller. EXECBLK DB TYPE DOS_EXEBLKS DUP(?) ; Exec-parameter-block. PSP_ALIGN DW ? ; Caller's command tail alignment. METHOD DB ? ; Method parameter. HANDLE DW ? ; Handle for swap file or EMS. PREAMBLE ENDS .CODE ;--------------------------- CORE CODE BEGINS -------------------------------- ; ; CORE: The following 'core' code is moved into the final memory template ; at address 'CORECODE' and executed there. Note that the ; final memory template can not be completed until this code ; is compiled, thus it is defined immediately after this section. ; ; On Entry: ; BX = low core paragraphs to keep. ; CX = length of environment to copy or zero. ; DS:SI = environment source (ignored if cx=0). ; ES:DI = environment destination (ignored if cx=0). ; (ES = our low core code segment) ; On Exit: ; AX = return code. ; Registers: ; All but CS, SS, ES. ; CORE: jcxz CORE_MEM ; Skip environment copy if CX==0. rep movsb ; Copy environment from DS:SI->ES:DI ; Free all unrequired memory leaving only a minimal core. CORE_MEM: push es ; Set DS= ES= Low core memory segment. pop ds cmp ds:HANDLE,0 ; Is an EMS or File handle in use? je CORE_EXEC ; No, don't free memory. mov ah,04Ah ; DOS function - Modify memory block. int 21h ; Shrink low core memory to minimum core. mov bx,ds:CHAIN.NXT_ADDR ; Set BX to next address in MCB chain. CORE_MEM1: or bx,bx ; Anymore blocks in the MCB chain. jz CORE_EXEC ; No, we are done. mov es,bx ; Load ES with addr of current MCB block. mov bx,es:AUX_MCB.NXT_ADDR ; Set BX to next address in MCB chain. mov ax,es ; Increment ES to start of memory block. inc ax mov es,ax mov ah,049h ; DOS function - Release memory block. int 21h ; Release the current block. jmp CORE_MEM1 ; Keep going until all MCBs are free. ; Execute the requested child process. CORE_EXEC: push ds ; Set ES= DS= Low core memory segment. pop es mov bx,OFFSET EXECBLK ; Set BX to offset of parameter block. mov dx,OFFSET FILENAME ; Set DX to offset of program specification. mov ax,04B00h ; DOS function - Load and execute program. int 21h ; Execute the child process. cli ; Protect from interrupts. mov bx,cs ; Reload segment registers from CS: mov es,bx ; ES, mov ds,bx ; DS, mov ss,bx ; SS, mov sp,ds:CORE_SP ; and restore saved SP. sti ; Let interrupts through. cld ; Restore direction bit. push ax ; Preserve 'exec' return status on stack. pushf test ds:METHOD,EXEC_ONLY ; Is this a 'spawn' or an 'exec'? jz CORE_EXEC1 ; No, this is a 'spawn'. jmp CORE_TERM ; Yes, this is an 'exec' so terminate. CORE_EXEC1: test ds:METHOD,SWAPPING ; Have we swapped memory? jnz CORE_SWAP ; Yes, swap memory back in. jmp CORE_RET ; No, do a simple return. ; Swap memory back in before returning to calling process. CORE_SWAP: mov ah,4Ah ; DOS function - Modify memory block. mov bx,ds:BASE_PARAS ; Load paragraphs in base MCB. int 21h ; Expand low core memory to base MCB. jnc CORE_SWAP1 ; Continue in no error occured. jmp CORE_TERM ; Terminate if an error occured. CORE_SWAP1: cmp ds:PAGE_FRAME,0 ; Are we swapped to EMS or FILE. jne CORE_SWEMS ; Yes, swap memory back in from EMS. jmp CORE_SWFILE ; No, swap memory back in from FILE. ; Swap memory back in from EMS. CORE_SWEMS: mov ax,cs ; Set AX to our base MCB segment. dec ax push ax ; Push current MCB segment. push ds ; Save segment of next MCB structure. mov ax,OFFSET CHAIN ; Save offset of next MCB structure. push ax mov dx,ds:HANDLE ; Set DX to EMS handle. mov cx,ds:BASE_BLKP ; Set CX to number of full blocks to read. push ds:BASE_BLKB ; Push bytes in last partial block. mov ds,ds:PAGE_FRAME ; Set DS to EMS page frame. xor bx,bx ; Set BX to zero. mov di,SWAP_BEG ; Set DI to the start of swap area. CORE_SWEMS1: jcxz CORE_SWEMS3 ; No full blocks, go do the partial bytes. CORE_SWEMS2: push cx ; Save full block count. mov ax,4400h ; EMS funtion - Map memory: int 67h ; BX= Logical page, AL= 0= Phys. page. or ah,ah ; AH= Status= 00h= success. jnz CORE_TERM ; Terminate on error. mov cx,IO_BLKSIZE ; Load CX with block size (16Kbytes). mov si,0 ; Src == DS:SI == EMS_FRAME_SEG:0. push di ; Dst == ES:DI. rep movsb ; Copy 'src' to 'dst'. pop di ; Restore DI. mov ax,es ; Set ES= ES+IO_BLKPARAS. add ax,IO_BLKPARAS mov es,ax pop cx ; Restore full block count. inc bx ; Increment logical page counter. loop CORE_SWEMS2 ; Decrement block counter, test, and loop. CORE_SWEMS3: pop cx ; Set CX to bytes in last partial block. jcxz CORE_SWEMS4 ; Continue to next MCB if no partial block. mov ax,4400h ; EMS function - Map memory: int 67h ; BX= Logical page, AL= 0= Phys. page. or ah,ah ; AH= Status= 00h= success. jnz CORE_TERM ; Terminate on error. mov si,0 ; Src == DS:SI == EMS_FRAME_SEG:0. ; Dst == ES:DI. rep movsb ; Copy 'src' to 'dst'. inc bx ; Increment logical page counter. CORE_SWEMS4: pop si ; Restore offset of next MCB structure. pop ds ; Restore segment of next MCB structure. pop es ; Restore current MCB data segment. mov ax,[si].NXT_ADDR ; Load next MCB data segment. or ax,ax ; Are we done? jz CORE_SWEMS5 ; Yes, go clean up EMS, were done. push ax ; Save next MCB data segment, push ax ; also segment of next MCB structure. mov cx,OFFSET AUX_MCB ; Load and push cx ; save offset of next MCB structure. mov ax,[si].NXT_BLKB ; Load and push ax ; save bytes in last partial block. push bx ; Save logical page counter, push dx ; and EMS handle. call CORESUB ; Allocate next MCB, ES = next data segment. pop dx ; Restore logical page counter, pop bx ; and EMS handle. mov cx,[si].NXT_BLKP ; Set CX to number of full blocks to read. mov ds,cs:PAGE_FRAME ; Set DS to EMS page frame. mov di,0 ; Zero DI to start of next data segment. jmp CORE_SWEMS1 ; Go do the next MCB. CORE_SWEMS5: mov ah,45h ; EMS function - Release handle and memory. int 67h ; DX= handle to release. mov ax,cs ; Reload segment registers: mov es,ax ; ES, mov ds,ax ; and DS back to core segment. jmp SHORT CORE_RET ; Now we can return. ; Terminate the core process and return to DOS. CORE_TERM: cmp cs:PAGE_FRAME,0 ; Are we using an EMS handle. je CORE_TERM1 ; No, go directly to termination. mov ah,45h ; EMS function - Release handle and memory. mov dx,cs:HANDLE ; DX= handle to release. int 67h CORE_TERM1: mov ax,4C00h ; DOS function - Terminate with int 21h ; return code = 00h. ; Swap memory back in from FILE. CORE_SWFILE: mov ax,cs ; Set AX to our base MCB segment. dec ax push ax ; Push current MCB segment. push ds ; Save segment of next MCB structure. mov ax,OFFSET CHAIN ; Save offset of next MCB structure. push ax push ds:BASE_BLKB ; Push bytes in last partial block. mov cx,IO_BLKSIZE ; Set CX to IO block size (16Kbytes). mov bx,ds:HANDLE ; Set BX to file handle for rest of swap. mov dx,SWAP_BEG ; Set DTA offset to start of swap area. mov si,ds:BASE_BLKP ; Set SI to number of full blocks to read. CORE_SWFILE1: mov ah,3Fh ; DOS function - Read file. cmp si,0 ; Any more blocks? je CORE_SWFILE2 ; No, do the partial block. dec si ; Decrement full block count. int 21h ; Read a block from file. jc CORE_TERM ; Read error? mov ax,ds ; Set DS= DS+IO_BLKPARAS. add ax,IO_BLKPARAS mov ds,ax jmp CORE_SWFILE1 ; Go do the next block. CORE_SWFILE2: pop cx ; Set CX to bytes in last partial block. jcxz CORE_SWFILE3 ; Continue to next MCB if no partial block. int 21h ; Read partial block from file. jc CORE_TERM ; Read error? CORE_SWFILE3: pop si ; Restore offset of next MCB structure. pop ds ; Restore segment of next MCB structure. pop es ; Restore segment of current MCB. mov ax,[si].NXT_ADDR ; Load next MCB data segment. or ax,ax ; Are we done? jz CORE_SWFILE4 ; Yes, go clean up FILE, were done. push ax ; Save next MCB data segment, push ax ; also segment of next MCB structure. mov cx,OFFSET AUX_MCB ; Load and push cx ; and save offset of next MCB structure. mov ax,[si].NXT_BLKB ; Load and push ax ; and save bytes in last partial block. push bx ; Save the file handle. call CORESUB ; Allocate next MCB, ES = next data segment. pop bx ; Restore the file handle. mov si,[si].NXT_BLKP ; Set SI to number of full blocks to read. mov ax,es ; Set DS= ES, mov ds,ax ; as the DTA segment of next MCB. mov dx,0 ; Set DTA offset DX to 0. mov cx,IO_BLKSIZE ; Set CX to IO block size (16Kbytes). jmp CORE_SWFILE1 ; Go do the next MCB. CORE_SWFILE4: mov ah,3Eh ; DOS function - Close file. int 21h ; BX= file handle. mov ax,cs ; Reload segment registers: mov es,ax ; ES, mov ds,ax ; and DS back to core segment. ; Return from the core process back to the calling process. CORE_RET: popf ; Restore 'exec' return status. pop ax jc CORE_RET1 ; Return 'exec' error code if carry set. mov ah,4Dh ; Else get child process return code. int 21h db 0CBh ; Opcode for RETF. CORE_RET1: mov ah,3 ; Return 'exec' error as '03xx'. db 0CBh ; Opcode for RETF. ; ; CORESUB : Allocates a block of memory by modifying the MCB chain. ; ; On Entry: ; ES:0 - A pointer to the current MCB. ; DS:SI - A pointer to the next mcbs descriptor (requested MCB). ; On Exit: ; ES:0 - A pointer to the requested MCB. ; Registers: ; AX, BX, and CX. ; CORESUB PROC NEAR cmp es:ID,4Dh ; ID= 4d= 'M' means normal MCB. jnz CORESUB_ABORT ; Terminate if no next MCB. mov ax,es ; Set AX to current MCB. add ax,es:PARAS ; Add # of paras in the current MCB. inc ax ; Increment for the MCB itself. mov es,ax ; We now have the segment of the next MCB. cmp ax,[si].NXT_ADDR ; Compare next MCB with requested MCB. ja CORESUB_ABORT ; Terminate if next MCB > requested. je CORESUB_FOUND ; Process found MCB if same addr as request. add ax,es:PARAS ; Add # of paras in the next MCB. cmp ax,[si].NXT_ADDR ; Compare next->next MCB with requested MCB. jb CORESUB ; Try again if next->next < requested. cmp es:OWNER,0 ; Compare owner's _psp with 0. jne CORESUB_ABORT ; Terminate if not free. ; The wanted MCB starts within the current MCB. We now have to ; create a new MCB at the wanted position, which is initially ; free, and shorten the current MCB to reflect the reduced size. mov bx,es ; Current MCB. inc bx ; Increment (header doesn't count). mov ax,[si].NXT_ADDR sub ax,bx ; Paragraphs between MCB and requested. mov bx,es:PARAS ; Paragraphs in current MCB. sub bx,ax ; Remaining paras. dec bx ; Decrement for header. mov es:PARAS,ax ; Set new size for current. mov cl,es:ID ; Save old id. mov es:ID,4Dh ; Set ID= 'M', there is a next. mov ax,[si].NXT_ADDR ; Point to new requested MCB, mov es,ax mov es:ID,cl ; and initialize to free. mov es:OWNER,0 mov es:PARAS,bx ; We have found a MCB at the right address. If it's not free, abort. ; Else check the size. If the size is ok, we're done (more or less). CORESUB_FOUND: mov es,ax ; Point to found MCB. cmp es:OWNER,0 ; Compare owner's _psp with 0. je CORESUB_FOUND1 ; Continue if found MCB free. CORESUB_ABORT: ; Abort code stuck here so everyone can jmp CORE_TERM ; get to it via relative jumps. CORESUB_FOUND1: mov ax,es:PARAS ; Size of found MCB. cmp ax,[si].NXT_PARAS ; Size of requested MCB. jae CORESUB_FOUND2 ; Continue if at least enough space found. ; If there's not enough room in found MCB, see if next MCB is free, too. ; If so, coalesce next MCB into found MCB and try again, Else terminate. cmp es:ID,4Dh ; Check if normal MCB, ID= 'M'= 4Dh. jnz CORESUB_ABORT ; Terminate if no next MCB. push es ; Save current MCB. mov bx,es ; Next MCB= current+size+1. add ax,bx inc ax mov es,ax ; Point to next MCB. cmp es:OWNER,0 ; Is next free? jne CORESUB_ABORT ; No, terminate. mov ax,es:PARAS ; Yes, load size. inc ax ; Increment for header. mov cl,es:ID ; Load next ID. pop es ; Point back to found MCB. add es:PARAS,ax ; Increase size of found MCB. mov es:ID,cl ; Store next ID into found MCB. jmp CORESUB_FOUND1 ; Go try again. ; The found MCB is free and at least large enough. If it's larger than ; the requested size, create another free MCB after the requested MCB. CORESUB_FOUND2: mov bx,es:PARAS ; Load size of found MCB. sub bx,[si].NXT_PARAS ; Size= Found Size-Request Size. jz CORESUB_FOUND3 ; Exact match, no next to create. push es ; Save found MCB. dec bx ; Size of next MCB (header doesn't count). mov ax,es ; Next MCB= Found MCB + Request Size +1. add ax,[si].NXT_PARAS inc ax mov cl,es:ID ; Save ID of found MCB. mov es,ax ; Point to next MCB. mov es:ID,cl ; Store saved ID as next ID. mov es:PARAS,bx ; Store next size. mov es:OWNER,0 ; Mark next as free. pop es ; Point to found MCB. mov es:ID,4Dh ; Store found ID= 'M', there is a next. mov ax,[si].NXT_PARAS ; Store found size as requested size. mov es:PARAS,ax CORESUB_FOUND3: mov es:OWNER,cs ; Store found owner as our PSP (core cs). ret ; All done. (ES points to requested MCB). CORESUB ENDP IRETI: iret ; ;----------------------------- CORE CODE ENDS -------------------------------- ; CORESIZE = $-CORE ; Bytes in 'core' code. STACKLEN EQU 160 ; 80 word 'core' stack. FCBLEN EQU 16 ; Length of an FCB. ; Memory template (final) for 'core' process. This is the completed ; version, which contains the 'preamble', 'core', 'data', and 'stack' areas. ; Note that all variable length data blocks (i.e. the environment table ; and shared memory) will appear above the core stack. When these data ; blocks are used, the size of the core segment is adjusted at runtime ; near the end of the INIT section. In this manner, the 'core' also ; has a dynamic heap. COREPROCS STRUC DB TYPE PREAMBLE DUP(?) ; Preamble. CORECODE DB CORESIZE DUP(?) ; 'Core' code area. DIV0_OFF DW ? ; Old divide by zero vector. DIV0_SEG DW ? FCB1 DB FCBLEN DUP(?) ; Child default FCB1. FCB2 DB FCBLEN DUP(?) ; Child default FCB2. FILENAME DB 66 DUP(?) ; Child exec filename. CMDTAIL DB 128 DUP(?) ; Child command tail. DB STACKLEN DUP(?) ; 'Core' stack space. COREPROCS ENDS CORE_END EQU (CMDTAIL+128+STACKLEN) ; End of core segment, ; and top of core stack. CORE_LEN EQU (OFFSET CORE_END-OFFSET PARBEG) ; Length of core segment. CORE_PARAS EQU ((CORE_LEN + 15) SHR 4) ; Paras of core to keep. SWAP_BEG = (CORE_PARAS SHL 4) ; Start of the swap area. SAVE_SPACE = (SWAP_BEG - 5Ch) ; Length of overwrite area. IRET_OFF EQU OFFSET (CORECODE+(OFFSET IRETI-OFFSET CORE)) ; Offset of 'iret' opcode. .DATA SAVE_DATA DB SAVE_SPACE dup(?) ; Space for saving the part ; of the memory image ; below the swap area ; that is overwritten ; to set up the 'core'. .CODE EMM_NAME DB 'EMMXXXX0',0 ; ; _SPAWN_SWAP : Spawn a child process with memory swap (low level). ; ; SYNTAX: ; ; int _spawn_swap(method, swapfname, execfname, cmdargs, envlen, envp) ; unsigned char method; ; char *swapfname; ; char *execfname; ; char *cmdargs; ; unsigned int envlen; ; char *envp; ; ; DESCRIPTION: ; _spawn_swap creates and executes a child process and optionally ; swaps out the memory image of the calling process to a temporary ; file or to EMS memory if available. This is the low level version ; which does not do 'path=' searches, 'tmp' filename creation, etc. ; ; _spawn_swap shrinks memory requirements down to a minimal core. ; This allows chaining memory intensive programs, and eases ; building DOS menu-systems. The memory used by the core is less ; than 1K, plus the memory needed for a copy of the environment. ; ; EMS (LIM 3.0 or above) is used automatically if there is enough ; space, otherwise a temporary file is created. ; ; ARGUMENTS: ; method Determines the swap/spawn/exec function as: ; 00 = Spawn, don't swap. ; 01 = Spawn, swap. ; 80 = Exec, don't swap. ; If swapping (01), the following flags can be ORed ; into the method argument. ; 02 = Use EMS if possible ; 04 = Use 'create temporary file' DOS call ; where 'swapfname' is treated as the ; path only. Normally 'swapfname' is ; the complete pathname of the swap file. ; (NOTE: DOS ver 3.00 and above). ; ; swapfname A pointer to a NUL terminated string containing ; the pathname of the file to be used for swapping. ; If 'CREAT_TEMP' flag is set, then the string ; should be a path only. ; ; execfname A pointer to a NUL terminated string containing ; the pathname of the file to executed. The first ; byte must contain the length of the string. ; ; cmdargs A pointer to a NUL terminated string containing ; the command line args for the child process. ; The first byte must contain the length of the ; string. ; ; envlen The length of the optional environment block. ; If 'envlen' is zero, a copy of the callers ; environment will be used. ; ; envp A pointer to a standard DOS environment block. ; PUBLIC _spawn_swap _spawn_swap PROC USES SI DI,\ PMETHOD: BYTE,\ SWAPFNAME: PTR BYTE,\ EXECFNAME: PTR BYTE,\ CMDARGS: PTR BYTE,\ ENVLEN: WORD,\ ENVP: PTR BYTE INIT: cld push ds ; Save a copy of DS for later. mov ax,ds ; Force ES= DS= DGROUP. mov es,ax mov ax,_psp ; Set DS= _psp= our PSP. mov ds,ax ; Copy memory below swap space which will be overwritten during the ; construction of the 'core' process to DGROUP prior to the swap. ; The swap will then write the data out and be restored by the 'core'. ; Finally, the 'post' section copies the data back down from DGROUP ; to the overwritten area. INIT_MEM: mov si,5Ch ; Src= DS:SI= PSP:005C. mov di,OFFSET SAVE_DATA ; Dst= ES:DI= DGROUP:SAVE_DATA. mov cx,SAVE_SPACE ; Siz= SAVE_SPACE. rep movsb ; Copy 'src' to 'dst'. ; Walk the chain of DOS MCB's, creating an auxillary chain linking ; all MCB's which belong to the current process. Four words of the ; MCB 'reserved' area are used to store the address, total size, ; IO blocks, and IO partial block bytes of the next block. ; Note: the auxillary information required for the initial 'base' ; MCB is recorded directly to the 'core' data area. This auxillary ; information is used by the swap routines to swap the current memory ; image out of and into memory. INIT_WALK: mov es,ax ; Set ES to PSP, push es ; and save it for next code section. dec ax ; Set DS to PSP-1 = Base MCB. mov ds,ax mov bx,ds:PARAS ; Paras in the base MCB, mov es:BASE_PARAS,bx ; are stored in core data area. mov bx,ds:OWNER ; Set BX to current process PSP. mov di,OFFSET CHAIN ; Set DI as pointer to next mcb structure. INIT_WALK1: cmp ds:ID,4Dh ; ID= 4D= 'M' means normal MCB. jne INIT_WALK2 ; Break if at last DOS MCB. mov ax,ds ; Next MCB= Current MCB + PARAS + 1. add ax,ds:PARAS inc ax mov ds,ax ; Set DS to next MCB. cmp bx,ds:OWNER ; Is next MCB owned by our process? jne INIT_WALK1 ; No, loop to next MCB. mov es:[di].NXT_ADDR,ds ; Yes, copy next MCB addr to next mcb struc. mov ax,ds:PARAS ; Copy next MCB size to next mcb struc. mov es:[di].NXT_PARAS,ax inc ax ; Increment next size for MCB header. push ax ; Save a copy of SIZE+1. mov cl,IO_BLKPSHIFT ; Convert SIZE+1 to IO blocks. shr ax,cl mov es:[di].NXT_BLKP,ax ; Copy next MCB blocks to next mcb struc. pop ax ; Restore SIZE+1 value. and ax,IO_BLKBMASK ; Convert SIZE+1 to partial IO blocks. mov cl,IO_BLKBSHIFT shl ax,cl ; Convert partial IO blocks into bytes. mov es:[di].NXT_BLKB,ax ; Copy next MCB partial to next mcb struc. mov ax,ds ; Advance ES to next MCB. mov es,ax mov di,OFFSET AUX_MCB ; Set DI as pointer to next mcb structure. jmp INIT_WALK1 ; Process the next MCB. INIT_WALK2: ; We are at the last MCB in the chain. mov es:[di].NXT_ADDR,0 ; Copy 0 to next MCB addr in next mcb struc. ; Swap memory out before entering 'core' process. INIT_SWAP: pop es ; Restore ES= PSP. mov es:PAGE_FRAME,0 ; Mark EMS as not in use. mov al,PMETHOD ; Copy 'method' argument to core data space. mov es:METHOD,al test al,SWAPPING ; Need to swap? jnz INIT_SWAP1 ; Yes, continue. jmp INIT_EXEC ; No, set up the 'core' process. ; We need to adjust the record keeping for the base MCB since not all ; of it will be swapped out. All we need to update are the values ; of BASE_BLKP and BASE_BLKB in the 'core' data area. INIT_SWAP1: mov bx,es:BASE_PARAS ; Load base MCB paras. sub bx,CORE_PARAS ; Subtract 'core' paras. mov ax,bx ; Copy result to AX. mov cl,IO_BLKPSHIFT ; Convert result to IO blocks. shr ax,cl mov es:BASE_BLKP,ax ; Overwrite BASE_BLKP in core data space. xchg ax,bx ; Swap AX<->BX for new copy of result. and ax,IO_BLKBMASK ; Convert result to partial IO blocks. mov cl,IO_BLKBSHIFT shl ax,cl ; Convert partial IO blocks into bytes. mov es:BASE_BLKB,ax ; Overwrite BASE_BLKB in core data space. ; Determine requested swapping method. INIT_SWAP2: test PMETHOD,USE_EMS ; Use EMS ? jnz INIT_SWEMS ; Yes, swap to EMS. jmp INIT_SWFILE ; No, swap to FILE. ; Swap memory out to EMS. Note: we need to determine the total number ; of IO blocks for all MCB's in the current process chain. On entry to ; this section of code, BX= # of full IO blocks and AX= # of IO partial ; bytes in the base MCB. INIT_SWEMS: or ax,ax ; Are there any partial bytes? jz INIT_SWEMS1 ; No, continue. inc bx ; Yes, increment IO block count. INIT_SWEMS1: mov si,OFFSET CHAIN ; Load DS:SI as pointer to next mcb struc. push es ; ES remains pointing to PSP. pop ds INIT_SWEMS2: mov cx,[si].NXT_ADDR ; Get address of next MCB. or cx,cx ; Is it zero? jz INIT_SWEMS3 ; Yes, we are done computing total blocks. add bx,[si].NXT_BLKP ; Add next MCB blocks to IO block count. mov ax,[si].NXT_BLKB ; Load next MCB partial IO bytes. mov ds,cx ; Load DS:SI as pointer to next mcb struc. mov si,OFFSET AUX_MCB or ax,ax ; Are there any partial bytes? jz INIT_SWEMS2 ; No, do the next MCB. inc bx ; Yes, increment IO block count. jmp INIT_SWEMS2 ; Do the next MCB. ; We now have the total # of required EMS blocks. Next determine ; if EMS is available with enough resources. If not, resort to ; swapping via FILE. INIT_SWEMS3: push bx ; Save required IO blocks. push es ; Save PSP. mov ah,35h ; DOS function - Get interrupt vector. mov al,67h ; Get EMM interrupt vector int 21h ; into ES:BX. mov di,10 ; Load ES:DI as pointer to driver name. mov ax,SEG EMM_NAME ; Load DS:SI as pointer to 'EMMXXXX0' const. mov ds,ax mov si,OFFSET EMM_NAME mov cx,8 ; CX= length of name field. repz cmpsb ; Compare strings. pop es ; Restore PSP. pop bx ; Restore required IO blocks. jz INIT_SWEMS4 ; Do strings match? jmp INIT_SWFILE ; No, swap to FILE. INIT_SWEMS4: ; Yes, continue. mov ah,40h ; EMS function - Get manager status. int 67h or ah,ah ; AH= Status= 00h= success. jz INIT_SWEMS5 ; Success? jmp INIT_SWFILE ; No, swap to FILE. INIT_SWEMS5: ; Yes, continue. mov ah,46h ; EMS function - Get EMM version. int 67h or ah,ah ; AH= Status= 00h= success. jz INIT_SWEMS6 ; Success? jmp INIT_SWFILE ; No, swap to FILE. INIT_SWEMS6: ; Yes, continue. cmp al,30h ; AL= version, BCD encoded, major|minor. jae INIT_SWEMS7 ; Is version >= 3.0? jmp INIT_SWFILE ; No, swap to FILE. INIT_SWEMS7: ; Yes, continue. push bx ; Save required IO block count. mov ah,41h ; EMS function - Get page frame segment. int 67h ; returns BX= segment of the page frame. mov es:PAGE_FRAME,bx ; Store EMS page frame to 'core' data area. pop bx ; Restore required IO block count. or ah,ah ; AH= Status= 00h= success. jz INIT_SWEMS8 ; Success? jmp INIT_SWFILE ; No, swap to FILE. INIT_SWEMS8: ; Yes, continue. mov ah,43h ; EMS function - Get handle and int 67h ; allocate 'BX' logical pages. or ah,ah ; AH= Status= 00h= success. jz INIT_SWEMS9 ; Success? jmp INIT_SWFILE ; No, swap to FILE. INIT_SWEMS9: ; Yes, continue. ; At this point we know that the driver is active and sufficient resources ; are available to swap the current memory image into EMS. DX contains ; an EMS handle returned by the last EMS call. From this point on, ; if an error occurs during the swap, we must release the EMS resources ; prior to attempting a FILE swap. mov es:HANDLE,dx ; Store EMS handle in 'core' data space. push es ; Save the PSP. mov ax,es ; Set DS to current MCB data area. mov ds,ax push ds ; Save segment of current MCB data area. mov ax,OFFSET CHAIN ; Save pointer to next MCB structure. push ax push es:BASE_BLKB ; Push bytes in last partial block. mov cx,es:BASE_BLKP ; Set CX to number of full blocks to write. mov es,es:PAGE_FRAME ; Set ES to EMS page frame. xor bx,bx ; Set BX to zero. mov si,SWAP_BEG ; Set SI to the start of swap area. INIT_SWEMS10: jcxz INIT_SWEMS12 ; No full blocks, go do the partial bytes. INIT_SWEMS11: push cx ; Save full block count. mov ax,4400h ; EMS function - Map memory: int 67h ; BX= Logical page, AL= 0= Phys. page. or ah,ah ; AH= Status= 00h= success. jnz INIT_SWEMS15 ; Cleanup errors before swap to FILE. mov cx,IO_BLKSIZE ; Load CX with block size (16Kbytes). mov di,0 ; Dst == ES:DI= EMS_FRAME_SEG:0 push si ; Src == DS:SI. rep movsb ; Copy 'src' to 'dst'. pop si ; Restore SI. mov ax,ds ; Set DS= DS+IO_BLKPARAS. add ax,IO_BLKPARAS mov ds,ax pop cx ; Restore full block count. inc bx ; Increment logical page counter. loop INIT_SWEMS11 ; Decrement block counter, test, and loop. INIT_SWEMS12: pop cx ; Set CX to bytes in last partial block. push cx ; Pad the stack (-4) for error handler. push cx jcxz INIT_SWEMS13 ; Continue to next MCB if no partial block. mov ax,4400h ; EMS function - Map memory: int 67h ; BX= Logical page, AL= 0= Phys. page. or ah,ah ; AH= Status= 00h= success. jnz INIT_SWEMS15 ; Cleanup errors before swap to FILE. mov di,0 ; Dst == ES:DI= EMS_FRAME_SEG:0 ; Src == DS:SI. rep movsb ; Copy 'src' to 'dst'. inc bx ; Increment logical page counter. INIT_SWEMS13: add sp,4 ; Stack correction (+4). pop si ; Restore offset of next MCB structure. pop ds ; Restore segment of next MCB struture. mov ax,[si].NXT_ADDR ; Load next MCB data segment. or ax,ax ; Are we done? jz INIT_SWEMS14 ; Yes, go cleanup, were done swapping. push ax ; Save segment of next MCB structure. mov cx,OFFSET AUX_MCB ; Save offset of next MCB structure. push cx mov cx,[si].NXT_BLKP ; Set CX to number of full blocks to read. mov si,[si].NXT_BLKB ; Push bytes in last partial block. push si mov si,0 ; Set source offset SI to 0. mov ds,ax ; Set source segment DS to next MCB. jmp INIT_SWEMS10 ; Go do the next MCB. ; If all goes well, we end up here after the current memory image ; has been swapped out to EMS. INIT_SWEMS14: pop es ; Restore PSP. jmp INIT_EXEC ; Go setup the 'core' process. ; If an IO error occurs during EMS swapping, we end up here. INIT_SWEMS15: add sp,8 ; Stack correction (+8). pop es ; Restore PSP. mov ah,45h ; EMS function - Release handle and memory. int 67h ; DX= handle to release. ; Swap memory out to FILE. We need to create a temporary ; file prior to swapping. If an error occurs any time during ; the file swap we have no recourse but to give up and return. INIT_SWFILE: mov es:PAGE_FRAME,0 ; Mark EMS as not in use. IF @datasize lds dx,SWAPFNAME ; Load DS:DX as far pointer to swapfname. ELSE pop ds ; Restore DGROUP segment. push ds ; Save it again for later. mov dx,SWAPFNAME ; Load DS:DX as near pointer to swapfname. ENDIF xor cx,cx ; CX == file creation mode= 00h= normal. mov ah,3ch ; DOS function - Create/truncate file. test PMETHOD,CREAT_TEMP ; Create temporary file? jz INIT_SWFILE1 ; No, continue. mov ah,5ah ; Yes, DOS function - Create tmp file. INIT_SWFILE1: int 21h ; Create the file. jc INIT_SWFILE6 ; Create error? mov es:HANDLE,ax ; Copy file handle to 'core' data space. ; The file is now open. Loop through all the MCB's in the owner's chain ; writing each one out to the file in sequential order. mov bx,ax ; Set BX to file handle for rest of swap. mov ax,es ; Set DS to current MCB data area. mov ds,ax push ds ; Save segment of current MCB data area. mov si,OFFSET CHAIN ; Set SI as pointer to next MCB structure. mov cx,es:BASE_BLKP ; Set CX to number of full blocks to write. mov di,es:BASE_BLKB ; Set DI to bytes in last partial block. mov dx,SWAP_BEG ; Set DX to the start of swap area. INIT_SWFILE2: jcxz INIT_SWFILE4 ; No full blocks, go do the partial bytes. INIT_SWFILE3: push cx ; Save full block count. mov cx,IO_BLKSIZE ; Load CX with block size (16Kbytes). mov ah,40h ; DOS function - write to file. int 21h ; DS:DX=DTA, CX=byte count, BX=handle. pop cx ; Restore full block count. jc INIT_SWFILE6 ; Write error? mov ax,ds ; Set DS= DS+IO_BLKPARAS. add ax,IO_BLKPARAS mov ds,ax loop INIT_SWFILE3 ; Decrement block counter, test, and loop. INIT_SWFILE4: mov cx,di ; Set CX to bytes in last partial block. jcxz INIT_SWFILE5 ; Continue to next MCB if no partial block. mov ah,40h ; DOS function - write to file. int 21h ; DS:DX=DTA, CX=byte count, BX=handle. jc INIT_SWFILE6 ; Write error? INIT_SWFILE5: pop ds ; Restore segment of next MCB structure. mov ax,[si].NXT_ADDR ; Load next MCB data segment. or ax,ax ; Are we done? jz INIT_SWFILE7 ; Yes, go cleanup, were done swapping. mov cx,[si].NXT_BLKP ; Set CX to number of full blocks to write. mov di,[si].NXT_BLKB ; Set DI to bytes in last partial block. push ax ; Save segment of next MCB structure. mov si,OFFSET AUX_MCB ; Set SI as pointer to next MCB structure. mov dx,0 ; Set DTA offset DX to 0. mov ds,ax ; Set DTA segment DS to next MCB. jmp INIT_SWFILE2 ; Go do the next MCB. ; If an IO error occurs during FILE swapping, we end up here. INIT_SWFILE6: add sp,2 ; Stack correction (+2). pop ds ; Restore DGROUP. mov ax,0100h ; Set return status 0x0100. ret ; Return to caller. ; If all goes well, we end up here after the current memory image ; has been swapped out to FILE. INIT_SWFILE7: mov ax,4200h ; DOS function - Move file pointer (abs). xor cx,cx ; CX= hi word of offset. mov dx,cx ; DX= lo word of offset. int 21h ; Rewind the file. ; Setup the 'core' execution process. At this point all necessary ; swapping has been preformed. Now we need to prepare various data ; structures, move the core code into low memory, set up an auxillary ; stack and then transfer control to the 'core'. There are also ; some bits and pieces of garbage to contend with. ; ; First a bit of trivia! ; The 'psp_align' word is carefully aligned at 80h in the caller's PSP, ; Normally, this is the length of the command line with offset 81h ; being the command line. So as not to interfere with anyone who ; might inspect the 'core' process we will set the length to 0 ; followed by an empty command line. INIT_EXEC: mov es:PSP_ALIGN,0d00h ; 00h,0dh = empty command line ; The next several steps initialize the DOS EXEC parameter block. mov ax,es ; Set all segments to the PSP. mov es:EXECBLK.FCB1SEG,ax mov es:EXECBLK.FCB2SEG,ax mov es:EXECBLK.CMDTSEG,ax mov es:EXECBLK.ENVSEG,0 ; Initially 0, more on the environment ; later on. ; Parse the 'cmdargs' into the default FCB's in 'core' data space. IF @datasize lds si,CMDARGS ; Set DS:SI as far pointer to 'cmdargs'. ELSE pop ds ; Restore DGROUP segment. push ds ; Save it again for later. mov si,CMDARGS ; Set DS:SI as near pointer to 'cmdargs'. ENDIF push si ; Save offset of the cmdargs. mov di,OFFSET FCB1 ; Set exec parameter block fcb1 offset mov es:EXECBLK.FCB1OFF,di ; to the fcb1 data space. push di ; Save the offset of the fcb1 data space. mov cx,FCBLEN ; CX= # bytes in 1 fcb. xor ax,ax ; AX= 0. rep stosw ; Init both fcb's to 0. pop di ; Restore offset of fcb1 data space. mov ax,2901h ; DOS function - Parse filename. int 21h mov di,OFFSET FCB2 ; Set exec parameter block fcb2 offset mov es:EXECBLK.FCB2OFF,di ; to the fcb2 data space. mov ax,2901h ; DOS function - Parse filename. int 21h pop si ; Restore offset of the cmdargs. ; Move 'cmdargs' into 'core' data space. mov cl,BYTE PTR [si] ; 1st byte of 'cmdargs' is length. xor ch,ch mov di,OFFSET CMDTAIL ; Set exec parameter block cmdtail offset mov es:EXECBLK.CMDTOFF,di ; to the cmdtoff data space. inc cx ; Increment length for trailing '\r'. rep movsb ; Copy 'src' DS:SI to 'dst' ES:DI. mov al,0Dh ; Set trailing character, stosb ; and store it. ; Move 'execfname' into 'core' data space. IF @datasize lds si,EXECFNAME ; Set DS:SI as far pointer to 'execfname'. ELSE mov si,EXECFNAME ; Set DS:SI as near pointer to 'execfname'. ENDIF lodsb ; 1st byte of 'execfname' is length. mov cl,al xor ch,ch mov di,OFFSET FILENAME ; Set ES:DI as pointer to filename. rep movsb ; Copy 'src' DS:SI to 'dst' ES:DI. xor al,al ; AL= 0, stosb ; and store it. ; Setup environment copy. The actual copy takes place in the ; 'core' code after we copy it down. This is to avoid the ; demise which would occur if the environment were large ; enough that it would overwrite any part of the code in ; this file. mov bx,CORE_PARAS ; Load paragraphs to keep. mov cx,ENVLEN ; Load the environment size. jcxz INIT_EXEC1 ; Continue if no environment. mov ax,cx ; Convert size into paragraphs, add ax,15 ; PARAS= ((size + 15) >> 4). shr ax,1 shr ax,1 shr ax,1 shr ax,1 add bx,ax ; Adjust keep_paras up by env_paras. IF @datasize lds si,ENVP ; Set DS:SI as far pointer to 'env'. ELSE mov si,ENVP ; Set DS:SI as near pointer to 'env'. ENDIF mov ax,es ; Set environment segment in exec add ax,CORE_PARAS ; parameter block to, mov es:EXECBLK.ENVSEG,ax ; ENV_SEG= PSP+CORE_PARAS+ENV_PARAS. ; Now do the context switch into the 'core' process. ; First, swap stacks. INIT_EXEC1: cli ; Protect from interrupts. mov es:SAVE_SS,ss ; Save caller's stack, mov es:SAVE_SP,sp ; (the one we are using). mov ax,es ; Swap to the 'core' stack PSP:CORE_END mov ss,ax mov sp,OFFSET CORE_END sti ; Let interrupts through. push cx ; Save environment length. push si ; Save environment 'src' offset. push ds ; Save environment 'src' segment. ; Second, move 'core' code down. mov di,OFFSET CORECODE ; DI= offset of destination. call NEAR PTR INIT_MARKER ; Determine where we are in the current CS? INIT_MARKER: pop si ; SI= absolute offset of 'INIT_MARKER'. push si ; Save a copy of 'ABS:INIT_MARKER'. add si,CORE-INIT_MARKER ; Convert ABS:INIT_MARKER to ABS:CORE. mov cx,CORESIZE ; CX= # of bytes to copy. push cs ; Set src DS to the current CS. pop ds rep movsb ; Copy 'src' DS:SI to 'dst' ES:DI. ; Third, save and patch INT0 (div. by zero) vector to 'iret' in 'core'. xor ax,ax ; Point DS to vector table. mov ds,ax cli ; Protect from interrupts. mov ax,WORD PTR ds:0 ; Save the old vector offset. mov es:DIV0_OFF,ax mov ax,WORD PTR ds:2 ; Save the old vector segment. mov es:DIV0_SEG,ax mov WORD PTR ds:0,IRET_OFF ; Install new offset. mov WORD PTR ds:2,es ; Install new segment. sti ; Let interrupts through. ; Fourth, cleanup the 'core' stack. pop ax ; Restore 'ABS:INIT_MARKER'. add ax,POST-INIT_MARKER ; Convert 'ABS:INIT_MARKER' to 'ABS:POST'. pop ds ; Restore environment 'src' segment pop si ; Restore environment 'src' offset pop cx ; Restore environment length mov di,SWAP_BEG ; Set env 'dst' offset to beginning of ; swap space so it gets overwritten ; on swap in. ; Fifth, push on the far address of 'core' return address. push cs ; Segment. push ax ; Offset. mov es:CORE_SP,sp ; Copy stack pointer to 'core' data space. ; Sixth, simulated far call into 'core' code. push es ; Segment. mov ax,OFFSET CORECODE push ax ; Offset. db 0CBh ; Opcode for RETF ; The 'core' process returns here after swapping code back into memory. POST: cli ; Protect from interrupts. mov ss,es:SAVE_SS ; Restore caller's stack. mov sp,es:SAVE_SP sti ; Let interrupts through. ; Restore INT0 (div. by zero) vector. xor cx,cx ; Point DS to vector table. mov ds,cx cli ; Protect from interrupts. mov cx,es:DIV0_OFF ; Restore old offset. mov WORD PTR ds:0,cx mov cx,es:DIV0_SEG ; Restore old segment. mov WORD PTR ds:2,cx sti ; Let interrupts through. ; Restore overwritten part of program pop ds ; Restore original DGROUP. mov bx,es:PAGE_FRAME ; Store EMS status in BX while overloading. mov si,OFFSET SAVE_DATA ; DS:SI points to 'src'. mov di,5Ch ; ES:DI points to 'dst'. mov cx,SAVE_SPACE ; CX= bytes to copy. rep movsb ; Copy 'src' to 'dst'. ; Delete the temporary swap file, if created. or bx,bx ; EMS swap? jnz POST1 ; Yes, we are out of here. push ds ; Save DGROUP. push ax ; Save our return code. IF @datasize lds dx,SWAPFNAME ; Load DS:DX as far pointer to 'swapfname'. ELSE mov dx,SWAPFNAME ; Load DS:DX as near pointer to 'swapfname'. ENDIF mov ah,41h ; DOS function - Delete file. int 21h ; DS:DX= filename. pop ax ; Restore our return code. pop ds ; Restore DGROUP. POST1: ret ; Bye, bye. _spawn_swap ENDP END ;-------------------------------------------------------------------------------