Copyright (c) Hyperion Entertainment and contributors.

Obsolete Exec Memory Allocation

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Introduction

Prior to AmigaOS 4.0, Exec's memory system was implemented as a simple linked list. Many older applications may have used the obsolete techniques described in this section to handle memory resources.

The purpose of this section is to describe the obsolete techniques in detail so that it may be easier to port those applications to Exec's modern memory system.

Memory Attributes

When asking the system for memory, an application can ask for memory with certain attributes. The currently supported flags are listed below.

Attribute Flag Meaning
MEMF_ANY This indicates that there is no requirement for the memory. Exec will always try to allocate faster memory before slower memory (if available). This is the default and should be used as often as possible. MEMF_ANY memory may be paged out. A typical implementation of malloc() would use MEMF_ANY as the memory flag. For compatibility reasons, MEMF_ANY maps to MEMF_SHARED.
MEMF_CHIP Deprecated. This indicates the application wants a block of chip memory, meaning it wants memory addressable by the classic Amiga custom chips. Chip memory is required for any data that will be accessed by custom chip DMA. This includes screen memory, images that will be blitted, sprite data, copper lists and audio data. Only useful on classic machines and should be avoided.
MEMF_FAST Deprecated. This indicates a memory block outside of the range that the classic Amiga special purpose chips can access. "FAST" means that the special-purpose chips do not have access to the memory and thus cannot cause processor bus contention, therefore processor access will likely be faster. Since the flag specifies memory that the custom chips cannot access, this flag is mutually exclusive with the MEMF_CHIP flag. Only useful on classic machines, and should be avoided
MEMF_VIRTUAL Use virtual memory in any case. This will fail if only public memory is available. Usually, you should not use this flag but use MEMF_ANY instead.
MEMF_EXECUTABLE Allocate memory that can hold executable code. Executable code must be placed in a specifically marked segment in memory to prevent buffer overflow exploits and for the automatic code type determination (PowerPC native vs. emulated 68k) to work. Failure to do so will result in an ISI exception.
MEMF_PUBLIC Deprecated. This indicates that the memory should be accessible to other tasks. MEMF_PUBLIC memory can never be paged out. See the discussion below why you should avoid it.
MEMF_SHARED Memory of this type is sharable between tasks. See the discussion below for details.
MEMF_PRIVATE Memory allocated with this flag is private for this task only. No other task can access it and any attempt to do so will result in a DSI exception.

Several flags are available that modifies or augments the memory allocation in a specific way. They can usually be used together with other flags listed above.

Modifier Flag Meaning
MEMF_CLEAR This indicates that the memory should be initialized with zeros.
MEMF_LOCAL Deprecated. This indicates memory which is located on the motherboard which is not initialized on reset.
MEMF_24BITDMA Deprecated. This indicates that the memory should be allocated within the 24 bit address space, so that the memory can be used in Zorro-II expansion device DMA transactions. This bit is for use by Zorro-II DMA devices only. It is not for general use by applications.
MEMF_REVERSE Deprecated. Indicates that the memory list should be searched backwards for the highest address memory chunk which can be used for the memory allocation.
MEMF_NO_EXPUNGE Prevents expunging from happening if set. Expunge is the process of freeing unused system resources.
MEMF_HWALIGNED Causes memory to be aligned to the hardware's physical page size. This flag is useful if you want to use the MMU to, for example, write protect memory.
MEMF_DELAYED Delay actual allocation of physical pages until they are accessed. Not currently implemented.

MEMF_PUBLIC and MEMF_SHARED

MEMF_PUBLIC is about one of the most misused features of AmigaOS. MEMF_PUBLIC was more or less described as "memory that cannot be swapped out, moved, or otherwise made unavailable." Unfortunately, this more or less applied to any memory in the classic AmigaOS. Therefore, many people just added the MEMF_PUBLIC flag to just about any allocation.

Starting with AmigaOS 4.0 a new MEMF_SHARED flag was introduced. Part of the problem of MEMF_PUBLIC is that classic AmigaOS device drivers assume that the physical and virtual addresses of a block of memory are the same. This assumption was carried over into AmigaOS 4.x. However, the cost of this is that less virtualized memory is available. Public memory is therefore limited in AmigaOS 4.x and only a fixed amount of it is made available during system startup. MEMF_PUBLIC is almost always completely unnecessary and can be replaced by MEMF_SHARED.

What does MEMF_PUBLIC mean?

MEMF_PUBLIC assumes that a memory block is allocated that cannot be physically moved around, is contiguous and will not be swapped. If you look at it this way, these requirements are almost always unnecessary. A normal application does not need to care about the physical address of a memory block nor will it have to think about the block not being contiguous as long as the virtual addresses are. The only requirement an application may have is to pin a block of memory, preventing it from being swapped out, for performance reasons. Swapped out memory might take a much longer time to be made available which can be an issue depending on the application.

MEMF_PUBLIC, due to its design, implies that the block of memory is available to all tasks and entities in the system. This is important for sending messages, for example. At the moment, there is nothing in the system that actually prevents access to another task's memory but semantics dictate that messages should be globally shared and a future memory system will enforce these semantics.

In summary, MEMF_PUBLIC means:

  • cannot be swapped
  • cannot be moved
  • accessible to all tasks
  • physical address equals virtual address
  • memory is contiguous

What does MEMF_SHARED mean?

A memory block that was allocated with MEMF_SHARED is accessible to all tasks and entities on the system. It is not protected and thus can be written to and read from by any task. Usually, its attributes can be changed as well. Other than that, no restriction is placed on the memory, which means it can be swapped (if not locked) and it can move in physical address space (not in virtual address space) and it might be not physically contiguous.

In summary, MEMF_SHARED means

  • can be swapped
  • can be moved
  • accessible to all tasks
  • physical and virtual address likely will be different
  • memory might not be physically contiguous (but not virtually)

Archaic Memory Functions

Warning
The following functions are extremely slow and should never be used in any new project. Even using a global memory will be faster than using these functions.

Allocate() and Deallocate() use a memory region header, called MemHeader, as part of the calling sequence. You can build your own local header to manage memory locally.

This structure takes the form:

struct MemHeader {
    struct Node       mh_Node;
    UWORD             mh_Attributes;  /* characteristics of this region */
    struct  MemChunk *mh_First;       /* first free region              */
    APTR              mh_Lower;       /* lower memory bound             */
    APTR              mh_Upper;       /* upper memory bound + 1         */
    ULONG             mh_Free;        /* total number of free bytes     */
};
mh_Attributes
is ignored by Allocate() and Deallocate().
mh_First
is the pointer to the first MemChunk structure.
mh_Lower
is the lowest address within the memory block. This must be a multiple of eight bytes.
mh_Upper
is the highest address within the memory block + 1. The highest address will itself be a multiple of eight if the block was allocated to you by AllocMem().
mh_Free
is the total free space.

This structure is included in the include file <exec/memory.h>.

The following sample code fragment shows the correct initialization of a MemHeader structure. It assumes that you wish to allocate a block of memory from the global pool and thereafter manage it yourself using Allocate() and Deallocate().

/*
 * allocate.c - example of allocating and using a private memory pool.
 */
#include <exec/types.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/dos.h>
 
#define BLOCKSIZE 4000     /* or whatever you need */
 
int main()
{
    /* Get the MemHeader needed to keep track of our new block. */
    struct MemHeader *mh = (struct MemHeader *)IExec->AllocVecTags(sizeof(struct MemHeader),
      AVT_ClearWithValue, 0,
      TAG_END);
    if (!mh) return(RETURN_FAIL);
 
    /* Get the actual block the above MemHeader will manage. */
    struct MemChunk  *mc;
    if ( !(mc = (struct MemChunk *)IExec->AllocVecTags(BLOCKSIZE, TAG_END)) );
    {
        IExec->FreeVec(mh);
        return(RETURN_FAIL);
    }
    mh->mh_Node.ln_Type = NT_MEMORY;
    mh->mh_First        = mc;
    mh->mh_Lower        = (APTR)mc;
    mh->mh_Upper        = (APTR)(BLOCKSIZE + (ULONG)mc);
    mh->mh_Free         = BLOCKSIZE;
 
    mc->mc_Next  = NULL;                     /* Set up first chunk in the freelist */
    mc->mc_Bytes = BLOCKSIZE;
 
    APTR block1 = (APTR)IExec->Allocate(mh,20);
    APTR block2 = (APTR)IExec->Allocate(mh, 314);
 
    IDOS->Printf("Our MemHeader struct at $%lx. Our block of memory at $%lx\n", mh, mc);
    IDOS->Printf("Allocated from our pool: block1 at $%lx, block2 at $%lx\n", block1, block2);
 
    IExec->FreeVec(mh);
    IExec->FreeVec(mc);
 
    return 0;
}
How Memory Is Tagged.
Only free memory is "tagged" using a MemChunk linked list. Once memory is allocated, the system has no way of determining which task now has control of that memory.

If you allocate memory from the system, be sure to deallocate it when your task exits. You can accomplish this with matched deallocations, or by adding a MemList to your task's tc_MemEntry, or you can deallocate the memory in the finalPC routine (which can be specified if you perform AddTask() yourself).

Adding Memory to the System Pool

When non-Autoconfig memory needs to be added to the system free pool, the AddMemList() function can be used. This function takes the size of the memoryblock, its type, the priority for the memory list, the base address and the name of the memory block. A MemHeader structure will be placed at the start of the memory block, the remainder of the memory block will be made available for allocation. For example:

IExec->AddMemList(0x200000, MEMF_FAST, 0, 0xF00000, "FZeroBoard");

will add a two megabyte memory block, starting at $F00000 to the system free pool as Fast memory. The memory list entry is identified with "FZeroBoard".