.ALPHA .MODEL SMALL .DATA? ; ; Memory management system ; Some Assembly Required ; Richard Grehan -- BYTE Magazine ; NOTE: If you need to see how the interface works, locate ; a copy of the MEMTEST.C file. ; ; Local storage. ; ; List pointers NUPTRLST DW ? ;Head of in use pointer list FPTRLST DW ? ;Head of free pointer node list FREE_START DW ? ;Segment of free memory start (pointer nodes) HEAP_START DW ? ;Segment of start of heap area HEAP_LEFT DW ? ;Space remaining in heap data area ; ; Used during compaction. ; (CUBASE and CULEN define the current "bubble" of unused memory that ; we want percolated to the top of memory. CUBASE DW ? ;Current base CULEN DW ? ;Current length SAVEBLK DW ? ;Saved block number DRTYFLG DB ? ;Dirty flag .CODE ; ; >>>Turbo C interface routines follow<<< ; PUBLIC _initmem ;_initmem ;This routine initializes the memory management package. ;Call it with the number of pointer blocks you want to allocate. ;The routine returns with the number of available paragraphs of ;memory. ; unsigned int intimem(numptrs) ; unsigned int numptrs; _initmem: PUSH BP MOV BP,SP PUSH SI PUSH DI ;We need to find out how much memory is available to us. ;Do this by asking for FFFFH paragraphs. The call will fail, but ;the system will tell us the largest available amount. MOV AH,48H MOV BX,0FFFFH ;Grab more than you could possibly g INT 21H ;We will assume failure here. PUSH BX ;Save # of paragraphs available MOV AH,48H INT 21H ;Do it again POP BX JNC _ini1 ;Better not fail! XOR AX,AX ;Show failure POP DI POP SI POP BP RET _ini1: XCHG AX,BX ;Exchange the pointers MOV CX,[BP+4] ;Get number of nodes he wants CALL INIT_HEAP ;Initialize the heap area MOV AX,HEAP_LEFT ;Return amout available for memory POP DI POP SI POP BP RET PUBLIC _gethandle ;_gethandle ; Call this function with the number of paragraphs you want to ; allocate. It returns the handle to that memory. ; A zero return means the request failed. ; unsigned int gethandle(numparas) ; unsigned int numparas _gethandle: PUSH BP MOV BP,SP PUSH SI PUSH DI MOV AX,FREE_START MOV ES,AX ;Set up ES register MOV DX,[BP+4] ;Get number of paragraphs CALL ALLOC_BLK MOV AX,DI POP DI POP SI POP BP RET PUBLIC _freehandle ;_freehandle ; Call this function with the handle to a node pointer you want ; to return to the heap. ; unsigned int freehandle(hnum) ; unsigned int hnum; _freehandle: PUSH BP MOV BP,SP PUSH SI PUSH DI MOV AX,FREE_START MOV ES,AX ;Set up ES register MOV DI,[BP+4] CALL FREE_BLK POP DI POP SI POP BP RET PUBLIC _lockhandle ;_lockhandle ; Call this function with the handle to a node pointer you want to lock. ; unsigned int lockhandle(hnum) ; unsigned int hnum _lockhandle: PUSH BP MOV BP,SP PUSH DI MOV AX,FREE_START MOV ES,AX MOV DI,[BP+4] OR WORD PTR ES:[DI+2],8000H POP DI POP BP RET PUBLIC _unlockhandle ;_unlockhandle ; The reverse of lockhandle. ; unsigned int unlockhandle(hnum) ; unsigned int hnum; _unlockhandle: PUSH BP MOV BP,SP PUSH DI MOV AX,FREE_START MOV ES,AX MOV DI,[BP+4] AND WORD PTR ES:[DI+2],7FFFH POP DI POP BP RET PUBLIC _handtoseg ;_handtoseg ; Call this routine to get the base segment of a block of memory ; pointed to by a handle. ; handtosegoff(hnum,seg) ; unsigned int hnum; ; unsigned int *seg; _handtoseg: PUSH BP MOV BP,SP PUSH DI MOV AX,FREE_START MOV ES,AX MOV DI,[BP+4] MOV AX,ES:[DI-4] MOV DI,[BP+6] MOV [DI],AX POP DI POP BP RET ; ;INIT_HEAP ;entry: AX=# of paragraphs in free memory ; BX=starting segment of free memory ; CX=number of pointer nodes requested ; INIT_HEAP: PUSH ES ;Save PUSH DI MOV FREE_START,BX MOV ES,BX ;First, calculate number of bytes in the pointer ;area. MOV DX,CX SHL DX,1 ;# of pointers * 8 SHL DX,1 SHL DX,1 ;Calculate the starting segment of the heap data ;area. ADD DX,15 SHR DX,1 SHR DX,1 SHR DX,1 SHR DX,1 ;Calculate remaining paragraphs SUB AX,DX MOV HEAP_LEFT,AX ADD DX,BX MOV HEAP_START,DX ;Set up initial block MOV WORD PTR NUPTRLST,2 XOR DI,DI ;First pointer XCHG DX,AX STOSW ;Base MOV AX,DX STOSW ;Length XOR AX,AX STOSW ;No next pointer STOSW ;No previous pointer ;Build linked list of free pointers. MOV WORD PTR FPTRLST,6 DEC CX ;Don't use first pointer ADD DI,4 ;Bump to second pointer ILP1: MOV AX,DI ADD AX,8 SHR AX,1 ;Steal bit STOSW ADD DI,6 LOOP ILP1 SUB DI,8 ;Put terminator on tail XOR AX,AX STOSW ;Restore registers and go home POP DI POP ES RET ; ;ALLOC_BLK ; On entry: DX holds number of paragraphs you want to allocate. ; ES holds segment of pointer region. ; On exit: DI holds byte offset to pointer node referencing allocated ; block. If the routine was unable to fulfill the request, DI ; holds 0. ;Note: this routine uses a first-fit algorithm. ALLOC_BLK: XOR CH,CH ;Borrow CH as a loop flag ALL1: MOV DI,NUPTRLST ;Get head of list SHL DI,1 ;Make it a byte offset ;See if the memory block is in use ALL2: TEST WORD PTR ES:[DI],8000H JNZ ALL3 ;The block is free...does it reference enough space? CMP DX,ES:[DI-2] JA ALL3 ;Great! I'll take this one! JMP CLAIM_BLK ;Exit through CLAIM-BLK ;The block is either in use, or is too small. ;Try the next block. ALL3: MOV DI,ES:[DI] SHL DI,1 OR DI,DI ;Are we at the end of the list? JNZ ALL2 ;We've hit the end of the list... ;Try to compact things ALL4: PUSH DX CALL COMPACT POP DX CMP CX,DX ;Did we free up enough memory? JAE ALL1 ;Yes, go git it. XOR DI,DI ;No, return in shame. RET ; ;FREE_BLK ; DI is the BYTE offset to the pointer node of the memory block to make ; free. ES is assumed to hold the segment of the pointer area. ; FREE_BLK: PUSH BX PUSH CX ;Get the length of the current block and save it MOV CX,ES:[DI-2] ;Get length of current block ;See if there's a following block. MOV BX,ES:[DI] ;Get next pointer SHL BX,1 ;Make it a byte pointer OR BX,BX ;See if it exists JZ DEL1 ;There is a following block. See if it is in use. TEST ES:[BX],8000H JNZ FRE1 ;Following block is not in use. Add its length to current. ;Then delete the following node pointer. DEL1: ADD CX,ES:[BX-2] PUSH DI MOV DI,BX CALL RELEASE_PTR POP DI ;See if there's a previous block FRE1: MOV BX,ES:[DI+2] ;Get previous pointer SHL BX,1 ;Make it a byte pointer OR BX,BX ;And see if it exists JZ FRE2 ;Previous block exists, see if it is in use. TEST WORD PTR ES:[BX],8000H JS FRE2 ;The previous block is not in use -- add length of current ;block to it. Then delete current block. ADD WORD PTR ES:[BX-2],CX CALL RELEASE_PTR POP CX POP BX RET ;Previous block exists or is in use. Set current block as ;being freed. FRE2: MOV ES:[DI-2],CX AND WORD PTR ES:[DI],7FFFH POP CX POP BX RET ; ;UNLOCK_BLOCK ; On entry: DI holds BYTE offset of pointer node referencing block ; to unlock. ES holds segment of pointer area. UNLOCK_BLOCK: AND WORD PTR ES:[DI+2],7FFFH RET ; ;RELEASE_PTR ; On entry: DI holds BYTE offset to a pointer to return to free list. ; ES is assumed to hold segment of pointer area ; Note that this routine does NOTHING with any heap memory that ; the pointer may be referencing. RELEASE_PTR: PUSH AX ;Save PUSH BX PUSH CX ;First remove block from list MOV AX,ES:[DI] ;AX=word ptr to next node AND AH,7FH MOV CX,ES:[DI+2] ;CX=word ptr to prev. node AND CH,7FH MOV BX,AX ADD BX,BX ;Next node exist? JZ RLS1 ;Next node exists-his previous node is our old previous node AND WORD PTR ES:[BX+2],8000H ;Clear lo 15 bits ADD ES:[BX+2],CX ;Attach chain ;See if previous node exists RLS1: MOV BX,CX ADD BX,BX JZ RLS2 ;Previous node exists-his next node is our old next node AND WORD PTR ES:[BX],8000H ADD ES:[BX],AX ;Now put us on the free pointer list RLS2: MOV AX,FPTRLST ;We now point to head... MOV ES:[DI],AX ;...of old free-pointer list. MOV AX,DI SHR AX,1 ;Store word pointer. MOV FPTRLST,AX POP CX POP BX POP AX RET ; ;GET_PTR ; Returns in DI byte offset to a pointer block from the free list. ; Returns 0 if list empty. The pointer block returned is available ; for use. ; GET_PTR: PUSH AX MOV DI,FPTRLST ADD DI,DI ;Make byte offset JZ GP1 MOV AX,ES:[DI] MOV FPTRLST,AX GP1: POP AX RET ; ;CLAIM_BLK ; DI holds byte offset to pointer block. ; DX holds # of paragraphs to claim from this block. ; Assumes ES holds segment to pointer region. ; This routine automatically sets the inuse bit on the block. ; Returns DI=0 if fail, else DI is the pointer to the claimed block. CLAIM_BLK: PUSH AX PUSH BX MOV AX,ES:[DI-2] ;Get length OR WORD PTR ES:[DI],8000H ;Show block as in use. SUB AX,DX ;Grab what you can JNZ CBLK1 ;Something left? ;If nothing left, just go home CBLK0: POP BX POP AX RET ;If something left, request a new pointer node and tie it in ;with the current pointer node. First, though, adjust the ;length of the current block. CBLK1: MOV ES:[DI-2],DX ;Adj. len. ADD DX,ES:[DI-4] ;Get new base. MOV BX,DI ;Save pointer in BX CALL GET_PTR ;Get a new pointer node OR DI,DI JZ CBLK0 ;Augh! MOV ES:[DI-2],AX ;New length MOV ES:[DI-4],DX ;New base ;New pointer node's next pointer is current ;pointer node's next pointer MOV DX,ES:[BX] AND DH,7FH ;Clear the in-use bit. MOV ES:[DI],DX ;New pointer node's prev. pointer is current pointer node. MOV AX,BX SHR AX,1 MOV ES:[DI+2],AX ;Current pointer node's next pointer is new node. MOV AX,DI SHR AX,1 AND WORD PTR ES:[BX],8000H ADD ES:[BX],AX ;Successor node's (if successor exists) previous pointer is new ;node SHL DX,1 ;Successor exists? OR DX,DX JZ CBLK2 MOV DI,DX AND WORD PTR ES:[DI+2],8000H ADD ES:[DI+2],AX ;All linking done...go home CBLK2: MOV DI,BX POP BX POP AX RET ; ;COMPACT ; Performs a general compaction on the heap. ; Starts at low address, and works to top of heap. ; Locked blocks may cause this routine to fail to ; coalesce all fragments. ; COMPACT returns in CX the largest available free memory block. ; If no pointers are available, COMPACT returns with CX=0. ; COMPACT: XOR AX,AX MOV CUBASE,AX ;Clear base MOV CULEN,AX ;Clear length MOV SAVEBLK,AX ;Clear saved block number MOV DRTYFLG,AL ;Clear dirty flag MOV CX,AX ;Clear largest free block storage ;Start at lowest block MOV DI,NUPTRLST SHL DI,1 ;Look for next block CMPCT1: OR DI,DI JNZ CMPCT1A ;End of list? JMP CMPCT8 CMPCT1A: TEST WORD PTR ES:[DI],8000H JNZ CMPCT1B JMP CMPCT4 ;Block is in use -- see if it is movable CMPCT1B: TEST WORD PTR ES:[DI+2],8000H JNZ CMPCT3 ;Block is not locked -- see if we need to move it. MOV AX,CUBASE ;Anything in base/length? OR AX,CULEN JZ CMPCT2 ;Move this block. MOV AX,ES:[DI-4] ;Source MOV BX,CUBASE ;Destination MOV DX,ES:[DI-2] ;Length PUSH DX ;Save stuff PUSH DS PUSH ES MOV DS,AX MOV ES,BX CALL copy_down ;Do the move POP ES POP DS ;Adjust the base address in the block MOV ES:[DI-4],BX ;New base is old CUBASE POP DX ADD CUBASE,DX ;Adjust saved base ;Set the dirty bit. MOV BYTE PTR DRTYFLG,1 ;Move to the next pointer node. CMPCT2: MOV BX,DI ;Save so we know previous pointer MOV DI,ES:[DI] SHL DI,1 JMP CMPCT1 ;Block is locked. See if the dirty bit is set. If so, ;we need to release the pointer node in SAVEBLK, then create ;a new pointer node to hold the contents of CUBASE and CULEN. CMPCT3: MOV AL,DRTYFLG OR AL,AL JZ CMPCT2 ;No dirty bit...skip everything. PUSH DI ;Save current pointer MOV DI,SAVEBLK CALL RELEASE_PTR CALL GET_PTR OR DI,DI JNZ CMPCT3AA JMP CMPCTZ ;Yikes!! No pointers! CMPCT3AA: MOV AX,CUBASE MOV ES:[DI-4],AX MOV AX,CULEN MOV ES:[DI-2],AX POP BX ;Retrieve current pointer. ;Tie pointer node in just before current pointer node. MOV AX,BX SHR AX,1 MOV ES:[DI],AX ;Set new node's next pointer MOV AX,ES:[BX+2] AND AH,07FH MOV ES:[DI+2],AX ;Set new block's prev pointer ;Fix next node's previous pointer MOV DX,DI SHR DX,1 AND WORD PTR ES:[BX+2],8000H ADD ES:[BX+2],DX ;Fix previous node's (if any) next ptr. SHR DI,1 SHL AX,1 OR AX,AX JNZ CMPCT3A ;This node is first in line. MOV NUPTRLST,DI MOV DI,BX JMP CMPCT3B ;This node is not first in line...go ahead and fix the previous ;node's pointers. CMPCT3A: MOV BX,AX AND WORD PTR ES:[BX+2],8000H ADD ES:[BX],DI POP DI CMPCT3B: MOV BYTE PTR DRTYFLG,0 JMP CMPCT2 ;Go to next block ;Block is not in use. See if something is in base/len, and ;if so...delete the old copy of SAVEBLK. This pointer node ;then becomes the new SAVEBLK. CMPCT4: MOV AX,CUBASE OR AX,CULEN JZ CMPCT6 MOV BX,DI ;Save DI MOV DI,SAVEBLK CALL RELEASE_PTR ;Delete the pointer node MOV DI,BX MOV AX,ES:[DI-2] ;Accumulate length ADD CULEN,AX CMP CX,CULEN ;Is this new length greater than before? JAE CMPCT5 MOV CX,CULEN CMPCT5: MOV AX,ES:[DI-4] ;New base MOV CUBASE,AX MOV SAVEBLK,DI JMP CMPCT3B CMPCT6: MOV AX,ES:[DI-2] MOV CULEN,AX CMP CX,AX ;New length greater than before? JAE CMPCT7 MOV CX,AX CMPCT7: JMP CMPCT5 ;We've hit the end of the list...is CUBASE/CULEN set? CMPCT8: MOV AX,CUBASE OR AX,CULEN JZ CMPCTX ;BASE/LEN set...see if dirty bit is set. CMP BYTE PTR DRTYFLG,0 JZ CMPCTX ;Release saved pointer node. Create a new pointer node to hold ;CUBASE/CULEN and attach this node to the end of the list. MOV DI,SAVEBLK CALL RELEASE_PTR CALL GET_PTR OR DI,DI JZ CMPCTZ ;No nodes?! We're sunk! MOV AX,CUBASE MOV ES:[DI-4],AX MOV AX,CULEN MOV ES:[DI-2],AX MOV WORD PTR ES:[DI],0 ;End of list. SHR BX,1 MOV ES:[DI+2],BX SHL BX,1 SHR DI,1 MOV ES:[BX],DI ;Show safe return CMPCTX: RET ;Error return CMPCTZ: XOR CX,CX RET ; ; ;copy_down ;entry: DS = Source segment base ; ES = destination segment base ; DX = # of paragraphs to move ;This routine moves DX paragraphs from source to destination. ;It presumes than DS>ES. copy_down: PUSH AX PUSH BX PUSH CX PUSH SI PUSH DI MOV BX,1000H ;Used a lot CDLP1: CMP DX,BX ;More than 64K left? JB CDLP2 MOV CX,32768 ;32K words XOR SI,SI ;>>>May not be necessary on... MOV DI,SI ;...later loops REP MOVSW ;Shift everyone by 64K MOV AX,DS ADD AX,BX MOV DS,AX MOV AX,ES ADD AX,BX MOV ES,AX SUB DX,BX JMP CDLP1 CDLP2: SHR DX,1 ;Calc words remaining SHR DX,1 SHR DX,1 MOV CX,DX XOR SI,SI MOV DI,SI REP MOVSW POP DI POP SI POP CX POP BX POP AX RET ; ;copy_up ;entry: DS = Source paragraph ; ES = Destination paragraph ; DX = Number of paragraphs to move ; copy_up: PUSH AX PUSH BX PUSH CX PUSH SI PUSH DI MOV AX,DS MOV BX,ES ;Used a lot ;Set DS/ES to top of window ADD AX,DX MOV DS,AX ADD BX,DX MOV ES,BX STD ;Autodecrement ;See if number of bytes to move is <64K CUP1: CMP DX,1000H JB CUP2 ;Shift segment registers down 64K SUB AH,10H MOV DS,AX SUB BH,10H MOV ES,BX ;Set up SI/DI, CX, and do the move MOV SI,0FFFEH MOV DI,SI MOV CX,32768 REP MOVSW ;Subtract 64K from DX SUB DH,10H JMP CUP1 ;Move remainder less than 64K CUP2: SUB AX,DX MOV DS,AX SUB BX,DX MOV ES,BX SHL DX,1 SHL DX,1 SHL DX,1 ;Number of words left to move MOV CX,DX SHL DX,1 DEC DX DEC DX ;Byte offset to start MOV SI,DX MOV DI,DX REP MOVSW POP DI POP SI POP CX POP BX POP AX RET ; ;make_phys ;Converts segment/offset address to 32-bit physical address. ;entry: AX=segment, BX=offset ;exit: AX=hi 16-bits of phys addr ; BX=lo 16-bits of phys addr make_phys: PUSH CX ;Save registers PUSH DX MOV CX,4 ;Bits to shift ROL AX,CL ;Align segment MOV DX,AX ;Save in DX AND DX,0FFF0H ;Clear lo nibble in DX AND AX,0FH ;Save lo nibble in AX ADD BX,DX ;Lo 16-bits in BX ADC AX,0 ;Get carry POP DX ;Restore POP CX RET ;make_segoff ;Converts 32-bit physical address in AX/BX to segment/offset. ;Note that the resulting segment/offset is automatically normalized. ;entry: AX=hi 16-bits of phys addr ; BX=lo 16-bits of phys addr ;exit: AX=segment, BX=offset make_segoff: PUSH DX ;Save register MOV DX,BX AND DX,0FH ;Save lo nibble SHR AX,1 ;Shift high part back into segment RCR BX,1 SHR AX,1 RCR BX,1 SHR AX,1 RCR BX,1 SHR AX,1 RCR BX,1 MOV AX,BX MOV BX,DX POP DX RET ;cmp_segoff ;Compare two segment/offset addresses and return comparison based on ;location in physical memory. ;entry: stack contains: ; segment <========2nd entry ; offset <======| ; segment <========1st entry ; offset <======| ; return (1 word) ;exit: entries undisturbed on stack ; AX is 0 if equal ; 1 if 1>2 ; -1 if 1<2 cmp_segoff: PUSH BP ;Save MOV BP,SP PUSH BX PUSH DX PUSH CX MOV BX,[BP+4] ;1st entry offset MOV AX,[BP+6] ;1st entry segment CALL norm_segoff MOV CX,AX ;Save result MOV DX,BX MOV BX,[BP+8] ;2nd entry offset MOV AX,[BP+10] ;2nd entry segment CALL norm_segoff CMP CX,AX ;Compare segments POP CX ;Done w/CX- restore JA ONEG JB TWOG CMP DX,BX ;Compare offsets JA ONEG JB TWOG XOR AX,AX POP DX POP BX RET ONEG: MOV AX,1 POP DX POP BX RET TWOG: MOV AX,-1 POP DX POP BX RET ;norm_segoff ;Normalize a segment offset. ;entry: AX=segment, BX=offset ;exit: AX=segment*, BX=offset* ;Physical address pointer to by segment*/offset* is equal to address ;pointed to by original segment/offset. Only, 0<=offset*<=15. norm_segoff: PUSH DX ;Save DX MOV DX,BX AND BX,0FH ;Clear low nibble SHR DX,1 ;Retrieve segment portion of old offset SHR DX,1 SHR DX,1 SHR DX,1 ADD AX,DX POP DX RET END