z88dk - The z88 Development Kit - z88 Far Malloc Library


Home
News
Downloads
Documentation
Contact
Links
Introduction

Far memory routines have now been implemented in z88dk for the Z88. This document describes how you can write C programs using these routines, and also gives details of how the scheme is implemented for anyone interested.

The document is divided into the following sections:


Overview & Limitations

The Z88 far memory model used by the Z88 Development Kit uses a 3-byte far pointer size. Near pointers can easily be cast as far pointers, so it is easy to write programs using a mixture of near and far memory. It is obviously not possible to cast far pointers as near pointers, however.

There is no limit to the size of the far memory heap (other than the 4Mb limit imposed by the Z88 architecture!), and allocations of any size can be made, provided your heap is large enough and the host Z88 has sufficient RAM available. Very large heaps will require more of your bad application RAM to be reserved for memory management, however, and full details of this are given in the next section.

The far memory model is only supported by the application target type in Small C; so it is not possible to write programs running under BASIC using far data. Due to the memory overheads required by the allocation routines (see implementation details if you're interested), any application which uses this model will be bad.

Packages may use far pointers as parameters passed to them by applications, but since each application has its own far heap managed through its static workspace, the following limitations apply:

  • the far pointer may only be used while the process to which it belongs is active
  • far pointers may not be passed from one process to another

Using far memory in Z88 applications

Compiler Directives

Using the far memory allocation words in your Z88 applications is quite straightforward. Firstly, you must signal that you will be using the far memory functions with the following line:

  #define FARDATA 1

This line should be before any includes, as the standard library header files use it to determine whether to make far memory versions of the library functions available.

Secondly, specify the size of the far heap your application requires with a line as follows:

  #pragma -farheap=65536

The above line gives a heapsize of 64K. You can specify any size up to a maximum of 4194304 (4Mb). Bear in mind the following points:

  • memory is not actually reserved in the Z88 until it is allocated with the malloc function, so you can specify a large heapsize without worrying that your application is "wasting" large amounts of the Z88s memory
  • the amount of bad application memory (or static workspace) required by the allocation system is equal to (HEAPSIZE/128)+487 bytes, where HEAPSIZE is the maximum heap size in bytes

For example, if you have a maximum heapsize of 65536 bytes, the amount of static workspace used will be 999 bytes. A 4Mb heap would require about 32.5K of static workspace!

Allocating and using far memory

Within your C application, you can now use the following functions:

far *malloc(long size)
Returns a far pointer to a region of memory "size" bytes in size
void free(far *ptr)
Free the previously allocated region of memory at "ptr"
void freeall(void)
Frees all allocated memory (this is done automatically when applications are terminated)

You can use various string functions on far memory pointers; where functions take two parameters, you can also provide near pointers as parameters (remembering to cast them as far!) The following functions are currently available for far pointers: strchr, strrchr, strcpy, strncpy, strcat, strncat, strlen, strlwr, strupr.

For more information, take a look at the example in examples/app/farmall/.


Implementation Details

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.



Last Modified by Dom on 11.10.2003