Knowledge of the way far memory is implemented on
the Z88 may be helpful to
anyone wishing to write a similar library for another machine, or for
those
hoping to port the method to their own (non-C) environment.
Also, if you are planning to rewrite the Z88
operating system (!) or replace
any of the memory-allocation calls using the pkg_ozcr call, you should
take
careful note of the assumptions I have made here, and try not to break
them ;-)
Note that all the assumptions made below are true
for all versions of OZ up to
and including v4.0
Memory overheads
Whenever far memory support is required in an
application, a certain amount of
bad application workspace is automatically reserved. This consists of:
- pool_table (224 bytes)
- Used to hold memory pool handles allocated by OZ
- copybuff (258 bytes)
- Used by some library routines as a temporary
buffer for OZ calls that use
strings (this is not used internally by memory allocation routines, and
will not
be considered further)
- malloc_table (2 bytes for every 256 bytes
required in the far heap)
- The most important structure, holding details
of the bank and address of
each 256-byte allocation in the heap
The pool table
This structure, as noted above, is used to hold
the pool handles that have
been opened by the application. Initially, it is filled with nulls,
indicating
that no pools are open.
This is a table of 1-byte entries, each
corresponding to a bank in the Z88's
memory map. As banks 0-31 are always ROM (internal), the first entry
corresponds
to bank 32, with the final entry corresponding to bank 255. Each entry
holds
either 0 (indicating no pool open for that bank), or a value which
relates
directly to the pool handle returned by OZ for that bank.
Three important assumptions are made when
constructing and using this table:
- each OZ pool handle always gives allocations
from the same bank; when the bank is
exhausted, no further allocations are possible with the handle
- as a consequence, for each process, only one
pool handle is ever associated with
a particular bank
- the values of OZ pool handles are always of the
form $0XY0, where $XY!=$00
The first two assumptions mean that we only need
224 table entries (one for each
bank) no matter how much memory we allocate. The last assumption means
that each
entry only needs to be one byte in size (we shift the handle value 4
bits to the right),
and a zero value can indicate no pool.
The malloc table
Each entry in the malloc table consists of two
bytes, and corresponds to an allocation
of 256 bytes of far memory.
The first byte contains the bank number containing
the allocation (0 indicates no
allocation has been made for this entry), and the second byte contains
the high byte
of the address of the memory allocation (always in segment 1).
The low byte of the address is not required, since
for allocations of 256 bytes, OZ
always returns an address with a low byte of zero; thus this is always
assumed.
Allocation method
The first stage in memory allocation is to locate
a section of the malloc table
which has not yet been allocated (ie contains nulls) and which is large
enough for the
required allocation. An extra 2 bytes are always added to the amount of
memory required,
to hold the number of pages (256-byte allocations) in the allocated
block.
When a suitable region has been located (malloc
always uses the smallest suitable
region), we start making allocations and filling the malloc table. This
is done as
follows:
- first, existing pools in the pool table are
checked, and allocations made from these
- if all existing pools are exhausted, new pools
are opened and allocations made from these
If all pools are exhausted and no further ones can
be opened before all memory is allocated,
the memory allocated so far is freed, and malloc returns an error.
Once the allocations are completed, the number of
256-byte pages used is stored in the first
two bytes, and a far pointer following this value is returned.
Deallocation method
Deallocating is much more straightforward. The
pointer is decremented twice, and the value
fetched is thus the number of 256-byte pages that were allocated.
A loop is entered which frees each 256-byte
allocation, and zeros the corresponding entry
in the malloc table.
Note that no pool handles are freed during this
process, but they become usable again when
further mallocs are performed. All handles in the pool table
are freed by the freeall
function, which is automatically called on exit from the application.
|