|Z88 Developers' Notes|
6. Memory Management
Most operating systems provide calls to allocate chunks of memory and
free them after use. The Z88 is no exception; however, the largest chunk
of memory which can be allocated at any one time, and thus the largest
chunk which can be guaranteed contiguous, is 256 bytes. The only way of
ensuring a larger contiguous chunk is to set up the application as a so-called
'bad application'. This is wasteful of space and time as the OZ is forced
to reshuffle memory, hence the pejorative term attached. A well-written
application should only use the small chunks of memory, which for most
programs should not be difficult, and indeed should provide a useful discipline
as it tends to lead to little wasted memory in comparison with, say, reserving
a 100K chunk and probably leaving much of it unused.
Reserved chunks of memory are allocated from larger 'pools'. To reserve memory, the application must first allocate a memory pool, which is done by the call OS_Mop. This allocates a memory pool, set initially to 256 bytes in size, and a handle to refer to it. The only parameter for this call is the memory mask. The value of the mask effects the segment to which the logical address of allocated memory will refer. The allocation routine will find a free page within a bank and place its 14 bit address in register HL. The upper two bits are provided by the upper two bits of the memory mask. The memory mask must be one of MM_S0 to MM_S3 which correspond to segment 0, 1, 2 and 3 respectively.
Each memory pool is associated with a particular bank of RAM and so all allocations from that pool are guaranteed to be in the same bank. This is usefull to know as it can reduce the amount of bank switching that has to be done. When a pool is exhausted a 'No room' error is returned - this does not mean that the Z88 has run out of memory, but that there is no more room in the bank associated with the pool to give to the application. The application should now attempt to open another memory pool, which will be associated with another bank, and allocate from there. If this attempt is thwarted by a 'No room' error then the Z88 has no more memory it can allocate to the application.
Once a pool is openeded actual allocation can begin using the OS_Mal system call. It is called with a pool handle, returned by the previous OS_Mop system call, and memory is allocated from the stock allocated to the pool. If future calls of OS_Mal exhaust the 256 byte initially allocated to the pool, the pool will expand to include more pages, one at a time. A 'No room' indicates that the memory pool is exhausted, ie. it can find no more free pages in its bank. If more memory is required, a new pool will need to be opened, although it may be that there is no memory left.
OS_Mal returns a bank number, the segment number implied by the pool's memory mask and a logical address. The logical address consists of a 14 bit offset within a bank with the upper two bits defined by the memory mask. Note that the allocated memory is not automatically paged in, so before it can be used, the application must ensure that the relevant bank is bound to the segment in which it is to be used. This will usually be the same segment as specified in the memory mask, if not then the address returned by OS_Mal will have to be altered (the upper two bits). The binding calls are detailed below.
The complement of OS_Mal is OS_Mfr, which will free a chunk of memory.
This may be a subset of a previously allocated chunk, although since a
partially allocated page cannot be used by other applications, it is only
really effective to free whole pages at a time. Finally, the complement
to OS_Mop is OS_Mcl. This call will de-allocate all the memory associated
with the pool, meaning that each chunk does no have to be separately de-allocated,
and close the pool down. Pools must be closed off, even if all the memory
has been freed, otherwise the system will loose a handle and some memory.
Now we have seen how memory is allocated we must show how allocated memory can be paged into the logical address space. This involves binding banks to specific segments. Two calls are provided for this purpose: the first, OS_Mgb, return the current binding for a particular segment ie. which bank is associated with the specific segment, but also returns the old binding. The calls are not particularly slow, but if a lot of bank switching is likely to occur then a faster mechanism can be used which makes use of the Z88's 'fast code' facility detailed in "Miscellaneous useful routines".
The registers used for the memory management routines are carefully
chosen for programmer convenience. Once a pool is opened, register IX holds
the pool handle which OS_Mal, OS_Mfr and OS_Mcl require. The binding calls
expect a segment specifier (MS_S0 to MS_S3) in register C and a bank number
in register B, so OS_Mal exits with these three registers appropriately
set. It establishes the segment number by reference to the memory mask
associated with the pool. OS_Mpb returns the old binding of the same segment
in register B, so providing BC is preserved the old binding, can be restored
simply by calling OS_Mpb again.
This routine demonstrates how 256 bytes can be allocated and paged in ready for use. Note the attention to errors, which is vital in the Z88 system because each application has to share with others.
include "#memory.def" ; memory allocation definitions include "#director.def" ; director call definitions include "#errors.def" ; error code definitions include "#stdio.def" ; standard input/output definition calls .example ld a, MM_S1 ; use segment 1 for memory allocations ld bc, 0 ; as per call spec call_oz(OS_Mop) ; open a memory pool for segment 1 jr c, open_err ; check for error... ; IX = pool handle ld bc, 256 ; 256 bytes = full page xor a ; A = 0 call_oz(OS_Mal) ; allocate page jr c, alloc_err ; check for error call_oz(OS_Mpb) ; effect new binding ; application specific code goes here ; HL points to a page of memory that ; we are free to use... call_oz(OS_Mcl) ; assume IX is still intact xor a ; no error message on exit call_oz(OS_Bye) ; exit the application; routine to handle "No room" when attempting to open a memory pool
.open_err ld a, RC_ROOM ; error code... call_oz(OS_Bye) ; exit with error message; this routine called when OS_Mal fails. A = error code.
.alloc_err call_oz(GN_Esp) ; ext. address of error message call_oz(GN_Soe) ; write message to standard output call_oz(GN_Nln) ; followed by <CR><LF> ; application specific code to handle memory goes here.
|Error handling and related issues||Memory Management||Output and the screen driver|