Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "Obsolete Exec Memory Allocation"
Steven Solie (talk | contribs) |
Steven Solie (talk | contribs) |
||
(3 intermediate revisions by the same user not shown) | |||
Line 14: | Line 14: | ||
|- |
|- |
||
| MEMF_ANY |
| 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. |
+ | | 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 |
| MEMF_CHIP |
||
Line 95: | Line 95: | ||
* physical and virtual address likely will be different |
* physical and virtual address likely will be different |
||
* memory might not be physically contiguous (but not virtually) |
* memory might not be physically contiguous (but not virtually) |
||
+ | |||
+ | = Archaic Memory Functions = |
||
+ | |||
+ | {{Note|title=Warning|text=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: |
||
+ | |||
+ | <syntaxhighlight> |
||
+ | 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 */ |
||
+ | }; |
||
+ | </syntaxhighlight> |
||
+ | |||
+ | ; 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(). |
||
+ | |||
+ | <syntaxhighlight> |
||
+ | /* |
||
+ | * 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; |
||
+ | } |
||
+ | </syntaxhighlight> |
||
+ | |||
+ | {{Note|title=''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: |
||
+ | |||
+ | <syntaxhighlight> |
||
+ | IExec->AddMemList(0x200000, MEMF_FAST, 0, 0xF00000, "FZeroBoard"); |
||
+ | </syntaxhighlight> |
||
+ | |||
+ | 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". |
Latest revision as of 17:22, 4 October 2016
Contents
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".