

<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.amigaos.net/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ryan+Dixon</id>
	<title>AmigaOS Documentation Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.amigaos.net/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ryan+Dixon"/>
	<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/wiki/Special:Contributions/Ryan_Dixon"/>
	<updated>2026-05-09T14:42:23Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12397</id>
		<title>Exec Memory Allocation</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12397"/>
		<updated>2024-12-08T02:54:28Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: /* Program Address Space */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Exec Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
Exec manages all of the free memory currently available in the system. Using a slab allocation system, Exec keeps track of memory and provides the functions to allocate and access it.&lt;br /&gt;
&lt;br /&gt;
When an application needs some memory, it can either declare the memory statically within the program or it can ask Exec for some memory. When Exec receives a request for memory, it searches its free memory regions to find a suitably sized block that matches the size and attributes requested.&lt;br /&gt;
&lt;br /&gt;
Prior to AmigaOS 4.0, the OS did not make use of the CPU&#039;s memory management unit and used memory &amp;quot;as-is&amp;quot;. That is, if you have different memory expansions plugged into your system, the memory will be seen as chunks located somewhere in the 4 gigabyte address space. Since version 4.0, the MMU will be used to &amp;quot;map&amp;quot; memory pages from their physical location to a virtual address. There are multiple reasons why this is better than using the verbatim physical addresses - among other things it reduces the effect of &amp;quot;memory fragmentation&amp;quot; and simplifies the possibility to swap currently unused memory pages to persistent storage such as a hard disk.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;downside&amp;quot; is that the virtual address of a memory block is almost never identical to the physical address. This isn&#039;t much of a downside, since an application will never really need to care about it. If an application allocates a block of memory of n bytes, it will get a pointer back that points to at least n continuous addresses as expected. The pages that &amp;quot;fill&amp;quot; this memory block may come from different physical locations scattered throughout the physical memory but a program will never noticed that. For all intent and purpose, the application sees a single continuous block of memory.&lt;br /&gt;
&lt;br /&gt;
=== Program Address Space ===&lt;br /&gt;
&lt;br /&gt;
It is important to remember that, just like in classic AmigaOS, a single address space is used for all programs. Sometimes the mention of an MMU can lead people to assume that each process on the Amiga will have its own personal, partitioned address space. The following two programs demonstrate that, even though they are separate processes, it is possible to read and write another&#039;s memory. The memory locations are the same virtual address and that virtual address maps onto the same physical address.&lt;br /&gt;
&lt;br /&gt;
This program has a global variable. It prints out the virtual address of the global, the value at that address (which can be optionally specified), waits for a keypress and, finally, prints out the value at that same address again in case it has been externally updated (which is done by the subsequent program listing):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
volatile char x = 127;&lt;br /&gt;
&lt;br /&gt;
/* Usage: a [VAL] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(argc==2)&lt;br /&gt;
        x = (char)atoi(argv[1]);&lt;br /&gt;
&lt;br /&gt;
    printf(&amp;quot;Virtual Address    of `x&#039;: %p\n&amp;quot;, (void*)&amp;amp;x);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    (void)getchar();&lt;br /&gt;
    printf(&amp;quot;Final Value        of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This program reads in an address as an argument and prints the value at that address even though it does not &amp;quot;own&amp;quot; the memory. By adding an additional argument, this program can also write to that &amp;quot;foreign&amp;quot; address. After this program is complete, you can press a key on the previous program and see that the value changed:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* Usage: b ADDR [VAL_TO_WRITE] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(!(argc==2 || argc==3))&lt;br /&gt;
        return 10;&lt;br /&gt;
&lt;br /&gt;
    volatile char *byte = (volatile char*)strtol(argv[1],NULL,16);&lt;br /&gt;
    printf(&amp;quot;Selected Virtual Address    : %p\n&amp;quot;,(void*)byte);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of Address: %d\n&amp;quot;,*byte);&lt;br /&gt;
    if(argc==3)&lt;br /&gt;
    {&lt;br /&gt;
        printf(&amp;quot;Writing Value `%d&#039; to Address: %p\n&amp;quot;,atoi(argv[2]),(void*)byte);&lt;br /&gt;
        *byte=(char)atoi(argv[2]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The same applies for stack and heap allocated memory.&lt;br /&gt;
&lt;br /&gt;
=== Slab allocation ===&lt;br /&gt;
&lt;br /&gt;
[[File:SlabDiagram.jpg|right]]&lt;br /&gt;
&lt;br /&gt;
The AmigaOS memory architecture is based on the &amp;quot;slab allocator&amp;quot; system or &amp;quot;object cache&amp;quot;. In essence, the slab allocator only allocates objects of a single size, allocating these in larger batches (&amp;quot;slabs&amp;quot;) from the low-level page allocator. These slabs are then divided up into buffers of the required size, and kept within a list in the slab allocator.&lt;br /&gt;
&lt;br /&gt;
Allocating an object with the slab allocator becomes a process of simple node removal: the first node in the first slab containing free nodes is removed and returned for use. Since the slab allocator keeps free slabs or partially free slabs in a separate list from the full slabs, this operation can be carried out in constant time. Freeing memory is accomplished by returning the buffer to its cache, and adding it to its original slab&#039;s free list. Slabs that are completely free can be returned to the system&#039;s page pool (this operation is actually driven by demand, and timestamps are used to avoid unnecessary loading of data, or &amp;quot;thrashing&amp;quot;). External fragmentation is minimal, and internal fragmentation is controlled and guaranteed not to exceed a certain amount.&lt;br /&gt;
&lt;br /&gt;
=== Object caching ===&lt;br /&gt;
&lt;br /&gt;
The slab allocator can also be used to cache objects. In the real world a lot of memory allocation operations will be used to allocate the same object. The system has a number of data structures which are allocated frequently (semaphores, message ports and the like). Every time such structures are allocated, they must be initialised, and when they are deleted again, they must be cleaned up. It&#039;s likely, however, that such a structure will be needed again in the future, so that it can be kept in its initialised state and re-used later. This further reduces the load on the allocator routines, and thus improves system performance.&lt;br /&gt;
&lt;br /&gt;
The object caches work on memory that has already been mapped into the virtual memory space.&lt;br /&gt;
&lt;br /&gt;
=== More Advantages ===&lt;br /&gt;
&lt;br /&gt;
Another advantage is the possibility to improve CPU cache usage. Usually, most objects have &amp;quot;hot spots&amp;quot;, i.e. they have a few fields that are used often. Since most of the time a little memory is left unused in a slab (the object size might not be a multiple of the slab size), this additional memory can be used to &amp;quot;shift&amp;quot; the hot spots by a few bytes to optimise the memory structure, leading to better cache usage.&lt;br /&gt;
&lt;br /&gt;
Finally, the system can be expanded to multiple CPUs with next to no overhead. On multi-CPU system, these expanded slab allocators scale almost linearly with the number of CPUs employed, making it the ideal choice for such systems.&lt;br /&gt;
&lt;br /&gt;
The combination of object caching and keeping caches for different memory blocks (for AllocVec/FreeVec emulation) makes the memory management more efficient, faster, and generally more future-proof than the old free list approach used in AmigaOS 3.x and earlier.&lt;br /&gt;
&lt;br /&gt;
See Wikipedia for more information on [http://en.wikipedia.org/wiki/Slab_allocation slab allocator systems].&lt;br /&gt;
&lt;br /&gt;
=== Physical page allocation ===&lt;br /&gt;
&lt;br /&gt;
Every memory location in a computer system has its own, unique address. That is, there is a byte location at address x where you can store and retrieve a single byte. This address is fixed; there is no way to change it without physically changing the hardware. Therefore, this address is called the “physical” address.&lt;br /&gt;
&lt;br /&gt;
The physical address of a memory page is most often completely irrelevant to the application. The CPU will typically only see the virtual address. AmigaOS will take care of assigning virtual addresses to memory paging. This is often called “mapping” a page. Only in very special cases will the physical address be relevant. For example, device drivers that want to pass memory to a hardware device via DMA. Since the MMU is part of the CPU, any external hardware like an IDE controller will not see the virtual but only physical addresses.&lt;br /&gt;
&lt;br /&gt;
A common operation in memory allocation is the assignment of virtual addresses to physical memory locations. Allocation of physical memory is usually done differently from virtual allocations, since it&#039;s necessary to free up only part of the allocation (when for example the pager kicks in).&lt;br /&gt;
&lt;br /&gt;
The de-facto standard in allocation of physical pages is a method invented by Knuth, called [http://en.wikipedia.org/wiki/Buddy_memory_allocation the &amp;quot;buddy system&amp;quot;]. Basically every modern operating system uses it and AmigaOS is no exception.&lt;br /&gt;
&lt;br /&gt;
Buddy systems are in essence size-segregated free lists. To allocate, the system searches for a free block of at least the size of the allocation. Then, if the block is too large, it&#039;s split into two even-sized blocks. These blocks are called &amp;quot;buddies&amp;quot;. One block is returned to it&#039;s appropriate free list, and the other is considered further, maybe splitting it further until it&#039;s size matches that of the allocation.&lt;br /&gt;
&lt;br /&gt;
In a buddy system, it&#039;s easy to determine whether the &amp;quot;buddy&amp;quot; is free or not, because it&#039;s address can simply be decided based on the address of the block to be freed.&lt;br /&gt;
&lt;br /&gt;
=== Virtual address space allocation ===&lt;br /&gt;
&lt;br /&gt;
Most CPUs come with a special unit that is called a &amp;quot;memory management unit&amp;quot; or &amp;quot;MMU&amp;quot; for short. The MMU&#039;s primary job is to rearrange the physical memory within the 4 gigabytes of address space in a way that is convenient for the operating system and/or applications. To do that effectively, it divides the memory into blocks (called &amp;quot;pages&amp;quot;). For every page the MMU has an entry in a table that specifies where the CPU should &amp;quot;see&amp;quot; this page, and what special attributes the page has. The address where the CPU &amp;quot;sees&amp;quot; this page is a 32 bit address as well, but since the memory is not really located there, we call that a &amp;quot;virtual&amp;quot; address.&lt;br /&gt;
&lt;br /&gt;
AmigaOS uses a resource map allocator for allocating virtual address space. Basically this is a means of managing a set of resources (not necessarily memory). For performance reason, it uses several optimization techniques.&lt;br /&gt;
&lt;br /&gt;
For one, all free resource blocks are held in space-segregated lists, i.e. there is a list for each power-of-two resource group. This makes allocations a lot faster by providing an upper and lower bound for a search. For example, if you want to allocate a block of 2^10 bytes, you can basically skip searching any block below 2^10 bytes in size simply because it won&#039;t fit. Similarly, you don&#039;t need to search for blocks that are larger than, say, twice the size of the block, since there might still be blocks of a size near to what we need. Size-segregated free lists help narrow down the search, making the search itself faster and the result better in terms of fragmentation.&lt;br /&gt;
&lt;br /&gt;
In addition, the resource maps use object caches for accelerating &amp;quot;small&amp;quot; allocations. Most allocations are below a certain size. For example, the virtual addresses are always allocated in chunks of at least one &amp;quot;page&amp;quot; in memory (4096 bytes). So it&#039;s common to allocate blocks of one, two, four, or eight pages. The object caches provide an easy method for keeping these common sizes, making every allocation of these sizes an exact fit, further reducing fragmentation.&lt;br /&gt;
&lt;br /&gt;
=== Page cache ===&lt;br /&gt;
&lt;br /&gt;
A lot of the time spent in allocating memory was spent looking for the appropriate pages in memory. A 256 MB memory system has 65536 4KB pages. These pages have to be searched for from time to time. Originally, hash tables were used but it turned out that distributing 65536 page entries over a few hash buckets still produced lists of several thousand pages that had to be traversed to find a page. The hash table was replaced with a radix tree. These trees are rather broad, but shallow, making traversal very fast. In usual circumstances, the tree does not grow more than 4 to 5 levels in depth, making searching of a page a matter of maximum 4 to 5 compare operations.&lt;br /&gt;
&lt;br /&gt;
=== Pager ===&lt;br /&gt;
&lt;br /&gt;
AmigaOS has the possibility to swap out parts of memory to disk in order to free up more memory for other applications. This feature allows applications to use more memory than is actually physically installed in the system.&lt;br /&gt;
&lt;br /&gt;
Paging is commonly referred to as &amp;quot;virtual memory&amp;quot; by users and sometimes even software developers. The fact AmigaOS uses virtual memory does not imply the use of the pager.&lt;br /&gt;
&lt;br /&gt;
The system can be tuned to different strategies, either page out only on demand (for highly interactive tasks), or based on other needs (lots of free memory in core for disk caches etc.).&lt;br /&gt;
&lt;br /&gt;
The optimized data structures allow the memory system to operate at a very high speed.&lt;br /&gt;
&lt;br /&gt;
The time for a memory allocation is now in the order of a few microseconds. This is especially true for small allocation (below 8096 bytes). During system testing it was observed that by the time the system has booted up to Workbench, there have already been 40,000 allocations to the global memory pool below 2096 bytes.&lt;br /&gt;
&lt;br /&gt;
== Memory Functions ==&lt;br /&gt;
&lt;br /&gt;
Normally, an application uses the AllocVecTags() function to ask for memory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void *AllocVecTags(uint32 size, uint32 tag1, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The size argument is the amount of memory the application needs and the tag list specifies the type of memory and any special memory characteristics (described later). If AllocVecTags() is successful, it returns a pointer to a block of memory. The memory allocation will fail if the system cannot find a big enough block with the requested attributes. If AllocVecTags() fails, it returns NULL.&lt;br /&gt;
&lt;br /&gt;
Because the system only keeps track of how much free memory is available and not how much is in use, it has no idea what memory has been allocated by any task. This means an application has to explicitly return, or deallocate, any memory it has allocated so the system can reuse it. If an application does not return a block of memory to the system, the system will not be able to reallocate that memory to some other task. That block of memory will be lost until the Amiga is reset. If you are using AllocVecTags() to allocate memory, a call to FreeVec() will return that memory to the system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void FreeVec(void *memoryBlock);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here memoryBlock is a pointer to the memory block the application is returning to the system. The size of the memory block is tracked internally by the system.&lt;br /&gt;
&lt;br /&gt;
Unlike some compiler memory allocation functions, the Amiga system memory allocation functions return memory blocks that are at least longword aligned. This means that the allocated memory will always start on an address which is at least evenly divisible by four. This alignment makes the memory suitable for any system structures or buffers which require word or long word alignment, and also provides optimal alignment for stacks and memory copying.&lt;br /&gt;
&lt;br /&gt;
=== Memory Types ===&lt;br /&gt;
&lt;br /&gt;
There are three primary types of memory in AmigaOS which are summarized in the following table:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Use&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE&lt;br /&gt;
| This memory is private and only accessible within the context of the Task which allocated it. Private memory should always be preferred. Private memory is also swappable by default (i.e. not locked). This memory will not be visible to any other address space.&lt;br /&gt;
	In a future version of AmigaOS, it is planned to have Task specific address spaces. This means each task could potentially address up to 4 GB of private memory each.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED&lt;br /&gt;
| The memory is shared and accessible by any Task in the system without restriction. This memory can be shared between all address spaces and will always appear at the same address in any address space. Shared memory is locked by default and thus is not swappable.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE&lt;br /&gt;
| The memory is used to store executable PowerPC code. This is used two-fold in AmigaOS. First, it allows the system to determine if a function pointer points to real native PowerPC code as opposed to 68k code which needs to be emulated. Second, it prevents common exploits that use stack overflows to execute malicious code. Executable memory is locked by default and thus is not swappable.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory Attributes ===&lt;br /&gt;
&lt;br /&gt;
Memory allocation on AmigaOS has traditionally been rather complicated. Over time, as new hardware models were released, more memory attribute flags were introduced.&lt;br /&gt;
&lt;br /&gt;
All the various memory allocation functions and strategies have been consolidated and distilled into a single AllocVecTags() function call. Programmers are strongly encouraged to stop using any other function to allocate system memory. Using AllocVecTags() is the only way to guarantee future compatibility with more advanced AmigaOS features yet to come.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any attributes when allocating memory, the system tries to satisfy the request with the fastest memory available on the system memory lists.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make Sure You Have Memory|text=Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.}}&lt;br /&gt;
&lt;br /&gt;
==== Using Tags ====&lt;br /&gt;
&lt;br /&gt;
The AllocVecTags() function uses a tag list to define what attributes a block of memory must have. The currently supported tags are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Tag (Default)&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | AVT_Type&lt;br /&gt;
	(MEMF_PRIVATE)&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE: Allocate from the task private heap. This memory will not be visible to any other address space.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED: Allocate from the system shared heap. This memory can be shared between all address spaces and will always appear at the same address in any address space. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE: Allocate memory that is marked executable. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Contiguous&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| Memory allocated with this property is allocated from a contiguous block of physical memory. This makes the memory suitable for DMA purposes when the DMA device does not support scatter/gather operation. For devices that do support scatter/gather use StartDMA() instead.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Physical pages can move at any time, be removed from memory due to paging or otherwise made unavailable unless the memory pages are locked (see LockMemory() function or AVT_Lock tag).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Lock&lt;br /&gt;
	(TRUE or FALSE)&lt;br /&gt;
| After allocating memory, lock the associated pages in memory. This will prevent the pages from being moved, swapped out or otherwise being made unavailable. This is useful in conjunction with the AVT_Contiguous tag since it will ensure that the memory will stay contiguous after allocation.&lt;br /&gt;
	This tag defaults to FALSE for MEMF_PRIVATE allocations and to TRUE for MEMF_SHARED or MEMF_EXECUTABLE.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Alignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block. The returned memory block will be aligned to the given size (in bytes). It&#039;s virtual address will be at least a multiple of the AVT_Alignment value.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_PhysicalAlignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block physical address. See AVT_Alignment for more information.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; This functionality is mainly used for DMA drivers that require a specific alignment. Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_ClearWithValue&lt;br /&gt;
| Clear the newly allocated memory with the given byte value. If this tag is not given the memory block is not cleared.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Wait&lt;br /&gt;
	(TRUE)&lt;br /&gt;
| Wait for the memory subsystem to be available. If TRUE, the calling task will be retired until the memory subsystem is available. This might cause a Forbid() to break. When FALSE and the memory subsystem is not currently available, the function will return immediately and the allocation will fail.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_NoExpunge&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| If allocation fails because of unavailability, prevent invocation of low memory cleanup handlers. The default is FALSE which means that when memory is not available, cleanup handlers are invoked to try and satisfy the allocation.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Using Flags ====&lt;br /&gt;
&lt;br /&gt;
The use of memory flags was the only supported way to allocate memory prior to AmigaOS 4.0. It may be still useful to know more about this obsolete system for porting older applications. For more information about the memory flags system see [[Obsolete Exec Memory Allocation]].&lt;br /&gt;
&lt;br /&gt;
=== Allocating System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples show how to allocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR apointer = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (apointer == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocVecTags() returns the address of the first byte of a memory block that is at least 100 bytes in size or NULL if there is not that much free memory. Because there are no tags specified, private memory is assumed so this memory cannot be shared with any other tasks.&lt;br /&gt;
&lt;br /&gt;
In addition to allocating a block of memory, this function keeps track of the size of the memory block, so your application doesn&#039;t have to remember it when it deallocates that memory block. The AllocVecTags() function allocates a little more memory to store the size of the memory allocation request.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make No Assumptions|It is not legal to peek the longword in front of the returned memory pointer to find out how big the block is. This has always been illegal regardless of what any other documentation may have stated to the contrary. Your application has access to the memory returned by AllocVecTags() and the only guarantee made is that the returned block has n bytes of continuous address space starting at the returned address. Anything outside this area is not to be touched.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR anotherptr = IExec-&amp;gt;AllocVecTags(1000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  AVT_Lock, FALSE,&lt;br /&gt;
  AVT_ClearWithValue, 0,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (anotherptr == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocates shared memory which is accessible by any task in the system and clears the memory contents to zero. MEMF_SHARED memory is locked by default for compatibility with the obsolete MEMF_PUBLIC flag. This is by far the most common case when allocating shared memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR lockedmem = IExec-&amp;gt;AllocVecTags(3000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (lockedmem == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocated shared memory which is accessible by any task in the system. MEMF_SHARED memory is locked by default so the underlying memory pages are not moveable and cannot be swapped out. Such memory could be used to share memory between a Process and an interrupt routine for example.&lt;br /&gt;
&lt;br /&gt;
If the system free memory list does not contain enough contiguous memory bytes in an area matching your requirements, AllocVecTags() returns a zero. You &#039;&#039;must&#039;&#039; check for this condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR yap = IExec-&amp;gt;AllocVec(500, MEMF_CHIP);&lt;br /&gt;
&lt;br /&gt;
if (yap == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The deprecated AllocVec() function is used in the example above because it is the only way to allocate MEMF_CHIP memory on a classic Amiga system.&lt;br /&gt;
&lt;br /&gt;
=== Locking System Memory ===&lt;br /&gt;
&lt;br /&gt;
The LockMem() function is used to explicitly lock a memory block. This function will make sure your memory block is not unmapped, swapped out or somehow made inaccessible. Use this function wisely. If there is no good reason to lock memory then do not do it. It will prevent the memory system from optimizing memory layout and may lead to poor performance. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;LockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before deallocating the memory is must be unlocked as well.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;UnLockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Do not forget to UnlockMem()&#039;&#039;|Failure to unlock memory will have adverse affects on the memory system. Locked memory pages cannot be moved so the system cannot optimize the layout of locked memory pages.}}&lt;br /&gt;
&lt;br /&gt;
Be careful to always match the number of locks and the number of unlocks. If something else may have locked memory in the same page and an extraneous UnlockMem() decreases the lock reference counter to 0, that page can be paged out or moved. If that happens, for example, with data used in an interrupt handler or by the device driver which handles the swap partition the system will crash.&lt;br /&gt;
&lt;br /&gt;
=== Freeing System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples free the memory chunks shown in the previous calls to AllocVecTags().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;FreeVec(apointer);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(anotherptr);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(lockedmem);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(yap);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A memory block allocated with AllocVecTags() or AllocVec() must be returned to the system pool with the FreeVec() function. This function uses the stored size in the allocation to free the memory block, so there is no need to specify the size of the memory block to free.&lt;br /&gt;
&lt;br /&gt;
Any memory that is locked must be explicitly unlocked with UnlockMem() prior to freeing it. Failure to unlock memory will not cause any immediate problems but any pages which are locked cannot be moved or swapped out which can decrease system performance.&lt;br /&gt;
&lt;br /&gt;
FreeVec() returns no status. However, if you attempt to free a memory block in the middle of a chunk that the system believes is already free, you will cause a system crash. It is also illegal to free the same memory block twice and this will lead to a system crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Leave Memory Allocations Out Of Interrupt Code&#039;&#039;|text=Do not allocate or deallocate system memory from within interrupt code. The [[Exec_Interrupts|Exec Interrupts]] section explains that an interrupt may occur at any time, even during a memory allocation process. As a result, system data structures may not be internally consistent at this time.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory may be Locked by Default ===&lt;br /&gt;
&lt;br /&gt;
By default, the system memory allocation routines will allocate MEMF_SHARED and MEMF_EXECUTABLE memory and implicitly lock it. This memory must not be explicitly unlocked. Let the system handle the unlocking internally.&lt;br /&gt;
&lt;br /&gt;
Here is the right way to allocate and free MEMF_SHARED memory:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is the wrong way:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;UnlockMem(ptr, 10);  // Doing this could lead to undefined system behaviour.&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlocking memory not explicitly locked does not immediately cause any issues. However, in a future version of AmigaOS the memory pages may be handled differently than they are handled today. This could lead to incompatibilities with your applications and AmigaOS. The simple rule is that if you called LockMem() you must also call UnlockMem(). All other times you should not call UnlockMem().&lt;br /&gt;
&lt;br /&gt;
{{Note|Memory locking is done at the page level. Even if a single byte of memory is locked in a page that entire page is locked. The programmer has no control over which pages may be used to satisfy a memory allocation.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Shared and executable memory is locked implicitly for 68K backwards compatibility reasons. Use AVT_Lock (or equivalent) to ensure your shared and executable memory is not locked. MEMF_PRIVATE memory has no backwards compatibility issues and is always unlocked by default.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory Information Functions ===&lt;br /&gt;
&lt;br /&gt;
The memory information routines AvailMem() and TypeOfMem() can provide the amount of memory available in the system, and the attributes of a particular block of memory.&lt;br /&gt;
&lt;br /&gt;
==== Memory Requirements ====&lt;br /&gt;
&lt;br /&gt;
The same attribute flags used in memory allocation routines are valid for the memory information routines. There is also an additional flag, MEMF_LARGEST, which can be used in the AvailMem() routine to find out what the largest available memory block of a particular type is. Specifying the MEMF_TOTAL flag will return the total amount of memory currently available.&lt;br /&gt;
&lt;br /&gt;
==== Calling Memory Information Functions ====&lt;br /&gt;
&lt;br /&gt;
The following example shows how to find out how much memory of a particular type is available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 size = IExec-&amp;gt;AvailMem(MEMF_CHIP | MEMF_LARGEST);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AvailMem() returns the size of the largest chunk of available chip memory.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;AvailMem() May Not Be Totally Accurate&#039;&#039;|text=Because of multitasking, the return value from AvailMem() may be inaccurate by the time you receive it.}}&lt;br /&gt;
&lt;br /&gt;
The following example shows how to determine the type of memory of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 memtype = IExec-&amp;gt;TypeOfMem((APTR)0x090000);&lt;br /&gt;
if ((memtype &amp;amp; MEMF_CHIP) == MEMF_CHIP) {  /*  ... It&#039;s chip memory ...  */   }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
TypeOfMem() returns the attributes of the memory at a specific address. If it is passed an invalid memory address, TypeOfMem() returns NULL. This routine is normally used to determine if a particular chunk of memory is in chip memory.&lt;br /&gt;
&lt;br /&gt;
=== Using Memory Copy Functions ===&lt;br /&gt;
&lt;br /&gt;
For memory block copies, the CopyMem() and CopyMemQuick() functions can be used.&lt;br /&gt;
&lt;br /&gt;
==== Copying System Memory ====&lt;br /&gt;
&lt;br /&gt;
The following samples show how to use the copying routines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR source = IExec-&amp;gt;AllocVecTags(1000, AVT_ClearWithValue, 0, TAG_END);&lt;br /&gt;
APTR target = IExec-&amp;gt;AllocVecTags(1000, AVT_Type, MEMF_SHARED, TAG_END);&lt;br /&gt;
IExec-&amp;gt;CopyMem(source, target, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CopyMem() copies the specified number of bytes from the source data region to the target data region. The pointers to the regions can be aligned on arbitrary address boundaries. CopyMem() will attempt to copy the memory as efficiently as it can according to the alignment of the memory blocks, and the amount of data that it has to transfer. These functions are optimized for copying large blocks of memory which can result in unnecessary overhead if used to transfer very small blocks of memory.&lt;br /&gt;
&lt;br /&gt;
CopyMemQuick() is now identical to CopyMem(). In previous versions of the operating system, CopyMemQuick() performed more optimized copying of the specified number of bytes from the source data region to the target data region. The source and target pointers must be longword aligned and the size (in bytes) must be divisible by four. There are no such restrictions starting with AmigaOS 4.0.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Not All Copies Are Supported&#039;&#039;|text=Neither CopyMem() nor CopyMemQuick() supports copying between regions that overlap. For overlapping regions see MoveMem() in the [[Utility Library]].}}&lt;br /&gt;
&lt;br /&gt;
=== System Memory Pools ===&lt;br /&gt;
&lt;br /&gt;
A construct carried over from the AmigaOS 3.x Exec is the memory pool. The code handling memory pools uses an algorithm based on boundary tags and size-segregated memory lists. The speed gain is tremendous: even for the &amp;quot;dumb&amp;quot; case of just allocating 100000 blocks and freeing them again, the speed is ten times faster than the previous implementation. Due to the size-segregated free lists and the easy coalescing due to the boundary tags, real-life performance gain is even higher and would be between 10 and 20 times.&lt;br /&gt;
&lt;br /&gt;
Two types of memory pools are available depending on the needs of the application:&lt;br /&gt;
* [[Exec_Item_Pools|Item Pools]] are built for speed and are used for allocating large numbers of items that are all the same size.&lt;br /&gt;
* Generic [[Exec_Memory_Pools|Memory Pools]] can handle allocations of different sizes at the expense of some speed.&lt;br /&gt;
&lt;br /&gt;
=== Summary of System Controlled Memory Handling Routines ===&lt;br /&gt;
&lt;br /&gt;
; AllocVecTags() and FreeVec()&lt;br /&gt;
: These are system-wide memory allocation and deallocation routines. They use a memory free-list owned and managed by the system.&lt;br /&gt;
&lt;br /&gt;
; LockMem() and UnlockMem()&lt;br /&gt;
: These routines explicitly lock and unlock underlying memory pages.&lt;br /&gt;
&lt;br /&gt;
; AvailMem()&lt;br /&gt;
: This routine returns the number of free bytes in a specified type of memory.&lt;br /&gt;
&lt;br /&gt;
; TypeOfMem()&lt;br /&gt;
: This routine returns the memory attributes of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
; CopyMem() and CopyMemQuick()&lt;br /&gt;
: CopyMem() is a general purpose memory copy routine. CopyMemQuick() is an optimized version of CopyMemQuick(), but has restrictions on the size and alignment of the arguments.&lt;br /&gt;
&lt;br /&gt;
== Allocating DMA Memory ==&lt;br /&gt;
&lt;br /&gt;
Device drivers often use DMA to transfer data to and from the device. The StartDMA(), GetDMAList() and EndDMA() functions are used to create a scatter/gather list suitable for such DMA transfers.&lt;br /&gt;
&lt;br /&gt;
The following conditions are guaranteed to be met when using these DMA functions:&lt;br /&gt;
* The memory region given is guaranteed to be mapped to physical memory.&lt;br /&gt;
* The mapping will not change as long as EndDMA() is not called.&lt;br /&gt;
* All cache entries in this region will be flushed out.&lt;br /&gt;
&lt;br /&gt;
The example code below demonstrates how to perform a DMA write transfer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR addr;&lt;br /&gt;
uint32 size;&lt;br /&gt;
&lt;br /&gt;
/* Tell the system to prepare for DMA */&lt;br /&gt;
uint32 arraySize = IExec-&amp;gt;StartDMA(addr, size, 0);&lt;br /&gt;
if (arraySize &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    /* The memory area is prepared, allocate and retrieve the DMA list */&lt;br /&gt;
    struct DMAEntry *DMAList = IExec-&amp;gt;AllocSysObject(ASOT_DMAENTRY,&lt;br /&gt;
      ASODMAE_NumEntries,  arraySize,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (DMAList != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        IExec-&amp;gt;GetDMAList(addr, size, 0, DMAList);&lt;br /&gt;
 &lt;br /&gt;
        /* Feed the DMA controller and do stuff */&lt;br /&gt;
        ...&lt;br /&gt;
        /* Get rid of the DMAList&#039;s memory */&lt;br /&gt;
 &lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, endFlags);&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_DMAENTRY, DMAList);&lt;br /&gt;
    }&lt;br /&gt;
    else&lt;br /&gt;
    {&lt;br /&gt;
        // Note We still call EndDMA() even though the actual&lt;br /&gt;
        // transfer didn&#039;t happen.&lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, DMAF_NoModify);&lt;br /&gt;
&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t allocate DMA list\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
else&lt;br /&gt;
{&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t initiate DMA transfer\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syncing and Memory Access ==&lt;br /&gt;
&lt;br /&gt;
The PowerPC is a pure load/store architecture. That means, it will never operate on memory arguments like x86, to modify data, you have to load, modify and store.&lt;br /&gt;
&lt;br /&gt;
All PowerPC/POWER cpus have a load/store queue, i.e. a queue where load and store operations are queued to reduce memory latency. If more than one store is made to the same long word (i.e. you write the first byte and then the second byte), then those stores are combined. As you can see, this will cause a problem for a chip register since both are written at the same time which is likely not what you want.&lt;br /&gt;
&lt;br /&gt;
Similar for reading: A read might shortcut through the load/store queue and use a value that&#039;s already been read and is present in the load/store queue. Again, for chip registers, this will cause a problem because you might read an old value.&lt;br /&gt;
&lt;br /&gt;
There are two instructions that deal with this: &#039;&#039;&#039;eieio&#039;&#039;&#039; (Ensure In-order Execution of I/O) and &#039;&#039;&#039;sync&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The eieio instruction simply inserts a &amp;quot;barrier&amp;quot; into the load/store queue. When combining &#039;&#039;stores&#039;&#039; the CPU never searches past this barrier for possible combines. This means that the sequence&lt;br /&gt;
&lt;br /&gt;
# store to some address&lt;br /&gt;
# eieio&lt;br /&gt;
# store to some address + 1&lt;br /&gt;
&lt;br /&gt;
will never be combined because of the eieio barrier between them.&lt;br /&gt;
&lt;br /&gt;
The sync instruction will simply halt all execution and flush the load/store queue, executing and finishing all loads and stores.&lt;br /&gt;
&lt;br /&gt;
As you can imagine, sync is MUCH MORE costly than eieio.&lt;br /&gt;
&lt;br /&gt;
=== When to use eieio and sync ===&lt;br /&gt;
&lt;br /&gt;
If you want to write, post fix your writes with an eieio instruction.&lt;br /&gt;
&lt;br /&gt;
If you want to read, prefix your reads with a sync instruction.&lt;br /&gt;
&lt;br /&gt;
This is also where the &amp;quot;GUARDED&amp;quot; memory flag comes in. GUARDED memory simply ensures program order of loads and stores, that is, it&#039;s an implicit eieio.&lt;br /&gt;
&lt;br /&gt;
{{Note|Knowing when and how to use the &#039;&#039;&#039;eieio&#039;&#039;&#039; and &#039;&#039;&#039;sync&#039;&#039;&#039; instructions is somewhat vital for driver developers.}}&lt;br /&gt;
&lt;br /&gt;
== Allocating Multiple Memory Blocks ==&lt;br /&gt;
&lt;br /&gt;
Exec provides the routines AllocTaskMemEntry() and FreeEntry() to allocate multiple memory blocks in a single call.&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() accepts a data structure called a MemList, which contains the information about the size of the memory blocks to be allocated and the requirements, if any, that you have regarding the allocation.&lt;br /&gt;
&lt;br /&gt;
The MemList structure is found in the include file &amp;lt;exec/memory.h&amp;gt; and is defined as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList&lt;br /&gt;
{&lt;br /&gt;
    struct Node     ml_Node;&lt;br /&gt;
    UWORD           ml_NumEntries;      /* number of MemEntrys */&lt;br /&gt;
    struct MemEntry ml_ME[1];           /* where the MemEntrys begin*/&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ml_Node&lt;br /&gt;
: allows you to link together multiple MemLists. However, the node is ignored by the routines AllocTaskMemEntry() and FreeEntry().&lt;br /&gt;
&lt;br /&gt;
; ml_NumEntries&lt;br /&gt;
: tells the system how many MemEntry sets are contained in this MemList. Notice that a MemList is a variable-length structure and can contain as many sets of entries as you wish.&lt;br /&gt;
&lt;br /&gt;
The MemEntry structure looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemEntry&lt;br /&gt;
{&lt;br /&gt;
    union {&lt;br /&gt;
        ULONG   meu_Reqs;   /* the AllocMem requirements */&lt;br /&gt;
        APTR    meu_Addr;   /* address of your memory */&lt;br /&gt;
        } me_Un;&lt;br /&gt;
    ULONG   me_Length;      /* the size of this request */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sample Code for Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an example of showing how to use the AllocTaskMemEntry() with multiple blocks of memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// alloctaskmementry.c - example of allocating several memory areas.&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MemList *memlist;             /* pointer to a MemList structure        */&lt;br /&gt;
&lt;br /&gt;
struct MemBlocks /* define a new structure because C cannot initialize unions */&lt;br /&gt;
{&lt;br /&gt;
    struct MemList  mn_head;         /* one entry in the header               */&lt;br /&gt;
    struct MemEntry mn_body[3];      /* additional entries follow directly as */&lt;br /&gt;
} memblocks;                         /* part of the same data structure       */&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    memblocks.mn_head.ml_NumEntries = 4; /* 4! Since the MemEntry starts at 1! */&lt;br /&gt;
&lt;br /&gt;
    /* Describe the first piece of memory we want.  Because of our MemBlocks structure */&lt;br /&gt;
    /* setup, we reference the first MemEntry differently when initializing it.        */&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Reqs   = MEMF_CLEAR;&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Length = 4000;&lt;br /&gt;
&lt;br /&gt;
    memblocks.mn_body[0].me_Reqs   = MEMF_PRIVATE | MEMF_CLEAR;/* Describe the other pieces of    */&lt;br /&gt;
    memblocks.mn_body[0].me_Length = 100000;                   /* memory we want. Additional      */&lt;br /&gt;
    memblocks.mn_body[1].me_Reqs   = MEMF_SHARED | MEMF_CLEAR; /* MemEntries are initialized this */&lt;br /&gt;
    memblocks.mn_body[1].me_Length = 200000;                   /* way. If we wanted even more en- */&lt;br /&gt;
    memblocks.mn_body[2].me_Reqs   = MEMF_EXECUTABLE;          /* tries, we would need to declare */&lt;br /&gt;
    memblocks.mn_body[2].me_Length = 25000;                    /* a larger MemEntry array in our  */&lt;br /&gt;
                                                               /* MemBlocks structure.            */&lt;br /&gt;
&lt;br /&gt;
    memlist = (struct MemList *)IExec-&amp;gt;AllocTaskMemEntry((struct MemList *)&amp;amp;memblocks);&lt;br /&gt;
&lt;br /&gt;
    if (memlist == NULL)&lt;br /&gt;
    {&lt;br /&gt;
       IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry FAILED\n&amp;quot;);&lt;br /&gt;
       return RETURN_FAIL;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    /* We got all memory we wanted.  Use it and call FreeEntry() to free it */&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry succeeded - now freeing all allocated blocks\n&amp;quot;);&lt;br /&gt;
    IExec-&amp;gt;FreeEntry(memlist);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() returns a pointer to a new MemList of the same size as the MemList that you passed to it. For example, ROM code can provide a MemList containing the requirements of a task and create a RAM-resident copy of the list containing the addresses of the allocated entries. The pointer to the MemList is used as the argument for FreeEntry() to free the memory blocks.&lt;br /&gt;
&lt;br /&gt;
=== Result of Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
The MemList created by AllocTaskMemEntry() contains MemEntry entries. MemEntrys are defined by a union statement, which allows one memory space to be defined in more than one way.&lt;br /&gt;
&lt;br /&gt;
If AllocTaskMemEntry() returns a non-NULL value then all of the meu_Addr positions in the returned MemList will contain valid memory addresses meeting the requirements you have provided. To use this memory area, you would use code similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *mlist = IExec-&amp;gt;AllocTaskMemEntry(&amp;amp;ML);&lt;br /&gt;
APTR memory = NULL;&lt;br /&gt;
&lt;br /&gt;
if ( mlist != NULL )&lt;br /&gt;
{&lt;br /&gt;
  memory = mlist-&amp;gt;ml_ME[0].me_Un.meu_Addr;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Multiple Memory Blocks and Tasks ===&lt;br /&gt;
&lt;br /&gt;
If you want to take advantage of Exec&#039;s automatic cleanup, use the MemList and AllocTaskMemEntry() facility to do your dynamic memory allocation.&lt;br /&gt;
&lt;br /&gt;
In the Task control block structure, there is a list header named tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
This is the list header that you initialize to include MemLists that your task has created by call(s) to AllocTaskMemEntry(). Here is a short program segment that handles task memory list header initialization only. It assumes that you have already run AllocTaskMemEntry() as shown in the simple AllocTaskMemEntry() example above.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *ml;&lt;br /&gt;
&lt;br /&gt;
struct Task *tc = IExec-&amp;gt;FindTask(0);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;AddTail(tc-&amp;gt;tc_MemEntry, ml);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Assuming that you have only used the AllocTaskMemEntry() method (or AllocVecTags() and built your own custom MemList), the system now knows where to find the blocks of memory that your task has dynamically allocated. The RemTask() function automatically frees all memory found on tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;CreateTask() Sets Up A MemList.&#039;&#039;|text=The CreateTask() function, and other system task and process creation functions use a MemList in tc_MemEntry so that the Task structure and stack will be automatically deallocated when the Task is removed.}}&lt;br /&gt;
&lt;br /&gt;
=== Summary of Multiple Memory Blocks Allocation Routines ===&lt;br /&gt;
&lt;br /&gt;
These are routines for allocating and freeing multiple memory blocks with a single call.&lt;br /&gt;
&lt;br /&gt;
This routine initializes memory from data and offset values in a table. Typically only assembly language programs benefit from using this routine. See the SDK for more details.&lt;br /&gt;
&lt;br /&gt;
== Allocating Memory at an Absolute Address ==&lt;br /&gt;
&lt;br /&gt;
For special advanced applications, AllocAbs() is provided. Using AllocAbs(), an application can allocate a memory block starting at a specified absolute memory address. If the memory is already allocated or if there is not enough memory available for the request, AllocAbs() returns a zero.&lt;br /&gt;
&lt;br /&gt;
Be aware that an absolute memory address which happens to be available on one Amiga may not be available on a machine with a different configuration or different operating system revision, or even on the same machine at a different times. For example, a piece of memory that is available during expansion board configuration might not be available at earlier or later times. Here is an example call to AllocAbs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR absoluteptr = (APTR)IExec-&amp;gt;AllocAbs(10000, 0x2F0000);&lt;br /&gt;
if (!(absoluteptr))&lt;br /&gt;
    { /* Couldn&#039;t get memory, act accordingly. */  }&lt;br /&gt;
&lt;br /&gt;
/* After we&#039;re done using it, we call FreeMem() to free the memory block. */&lt;br /&gt;
IExec-&amp;gt;FreeMem(absoluteptr, 10000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of the Exec functions that handle memory management. See the SDK for details on each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Memory Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AllocMem()&lt;br /&gt;
| Allocate memory with specified attributes. &#039;&#039;&#039;This function is obsolete.&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| AllocAbs()&lt;br /&gt;
| Allocate memory at a specified location.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTaskMemEntry()&lt;br /&gt;
| Allocate multiple memory blocks.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVec()&lt;br /&gt;
| Allocate memory with specified attributes and keep track of the size. This function is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVecTags()&lt;br /&gt;
| Allocate memory with specified attributes defined by tags and keep track of the size. If an application needs to allocate some memory, it will usually use this function.&lt;br /&gt;
|-&lt;br /&gt;
| AvailMem()&lt;br /&gt;
| Return the amount of free memory, given certain conditions.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMem()&lt;br /&gt;
| Copy memory block, which can be non-aligned and of arbitrary length.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMemQuick()&lt;br /&gt;
| Copy aligned memory block.&lt;br /&gt;
|-&lt;br /&gt;
| FreeEntry()&lt;br /&gt;
| Free multiple memory blocks, allocated with AllocTaskMemEntry().&lt;br /&gt;
|-&lt;br /&gt;
| FreeMem()&lt;br /&gt;
| Free a memory block of specified size, allocated with AllocMem() or AllocAbs().&lt;br /&gt;
|-&lt;br /&gt;
| FreeVec()&lt;br /&gt;
| Free a memory block allocated with AllocVecTags() or AllocVec().&lt;br /&gt;
|-&lt;br /&gt;
| InitStruct()&lt;br /&gt;
| Initialize memory from a table.&lt;br /&gt;
|-&lt;br /&gt;
| LockMem()&lt;br /&gt;
| Lock the underlying pages given a memory block address and size.&lt;br /&gt;
|-&lt;br /&gt;
| TypeOfMem()&lt;br /&gt;
| Determine attributes of a specified memory address.&lt;br /&gt;
|-&lt;br /&gt;
| UnlockMem()&lt;br /&gt;
| Unlock the underlying pages given a memory block address and size.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12388</id>
		<title>Exec Memory Allocation</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12388"/>
		<updated>2023-11-25T16:02:56Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: Minor grammar&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Exec Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
Exec manages all of the free memory currently available in the system. Using a slab allocation system, Exec keeps track of memory and provides the functions to allocate and access it.&lt;br /&gt;
&lt;br /&gt;
When an application needs some memory, it can either declare the memory statically within the program or it can ask Exec for some memory. When Exec receives a request for memory, it searches its free memory regions to find a suitably sized block that matches the size and attributes requested.&lt;br /&gt;
&lt;br /&gt;
Prior to AmigaOS 4.0, the OS did not make use of the CPU&#039;s memory management unit and used memory &amp;quot;as-is&amp;quot;. That is, if you have different memory expansions plugged into your system, the memory will be seen as chunks located somewhere in the 4 gigabyte address space. Since version 4.0, the MMU will be used to &amp;quot;map&amp;quot; memory pages from their physical location to a virtual address. There are multiple reasons why this is better than using the verbatim physical addresses - among other things it reduces the effect of &amp;quot;memory fragmentation&amp;quot; and simplifies the possibility to swap currently unused memory pages to persistent storage such as a hard disk.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;downside&amp;quot; is that the virtual address of a memory block is almost never identical to the physical address. This isn&#039;t much of a downside, since an application will never really need to care about it. If an application allocates a block of memory of n bytes, it will get a pointer back that points to at least n continuous addresses as expected. The pages that &amp;quot;fill&amp;quot; this memory block may come from different physical locations scattered throughout the physical memory but a program will never noticed that. For all intent and purpose, the application sees a single continuous block of memory.&lt;br /&gt;
&lt;br /&gt;
=== Program Address Space ===&lt;br /&gt;
&lt;br /&gt;
It is important to remember that, just like in classic AmigaOS, a single address space is used for all programs. Sometimes the mention of an MMU can lead people to assume that each process on the Amiga will have its own personal, partitioned address space. The following two programs demonstrate that, even though they are separate processes, it is possible to read and write another&#039;s memory. The memory locations are the same virtual address and that virtual address maps onto the same physical address.&lt;br /&gt;
&lt;br /&gt;
This program has a global variable. It prints out the virtual address of the global, the value at that address (which can be optionally specified), waits for a keypress and, finally, prints out the value at that same address again in case it has been externally updated (which is done by the subsequent program listing):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
volatile char x = 127;&lt;br /&gt;
&lt;br /&gt;
/* Usage: a [VAL] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(argc==2)&lt;br /&gt;
        x = (char)atoi(argv[1]);&lt;br /&gt;
&lt;br /&gt;
    printf(&amp;quot;Virtual Address    of `x&#039;: %p\n&amp;quot;, (void*)&amp;amp;x);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    (void)getchar();&lt;br /&gt;
    printf(&amp;quot;Final Value        of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This program reads in an address as an argument and prints the value at that address even though it does not &amp;quot;own&amp;quot; the memory. By adding an additional argument, this program can also write to that &amp;quot;foreign&amp;quot; address. After this program is complete, you can press a key on the previous program and see that the value changed:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* Usage: b ADDR [VAL_TO_WRITE] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(!(argc==2 || argc==3))&lt;br /&gt;
        return 10;&lt;br /&gt;
&lt;br /&gt;
    volatile char *byte = (volatile char*)strtol(argv[1],NULL,16);&lt;br /&gt;
    printf(&amp;quot;Selected Virtual Address    : %p\n&amp;quot;,(void*)byte);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of Address: %d\n&amp;quot;,*byte);&lt;br /&gt;
    if(argc==3)&lt;br /&gt;
    {&lt;br /&gt;
        printf(&amp;quot;Writing Value `%d&#039; to Address: %p\n&amp;quot;,atoi(argv[2]),(void*)byte);&lt;br /&gt;
        *byte=(char)atoi(argv[2]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Slab allocation ===&lt;br /&gt;
&lt;br /&gt;
[[File:SlabDiagram.jpg|right]]&lt;br /&gt;
&lt;br /&gt;
The AmigaOS memory architecture is based on the &amp;quot;slab allocator&amp;quot; system or &amp;quot;object cache&amp;quot;. In essence, the slab allocator only allocates objects of a single size, allocating these in larger batches (&amp;quot;slabs&amp;quot;) from the low-level page allocator. These slabs are then divided up into buffers of the required size, and kept within a list in the slab allocator.&lt;br /&gt;
&lt;br /&gt;
Allocating an object with the slab allocator becomes a process of simple node removal: the first node in the first slab containing free nodes is removed and returned for use. Since the slab allocator keeps free slabs or partially free slabs in a separate list from the full slabs, this operation can be carried out in constant time. Freeing memory is accomplished by returning the buffer to its cache, and adding it to its original slab&#039;s free list. Slabs that are completely free can be returned to the system&#039;s page pool (this operation is actually driven by demand, and timestamps are used to avoid unnecessary loading of data, or &amp;quot;thrashing&amp;quot;). External fragmentation is minimal, and internal fragmentation is controlled and guaranteed not to exceed a certain amount.&lt;br /&gt;
&lt;br /&gt;
=== Object caching ===&lt;br /&gt;
&lt;br /&gt;
The slab allocator can also be used to cache objects. In the real world a lot of memory allocation operations will be used to allocate the same object. The system has a number of data structures which are allocated frequently (semaphores, message ports and the like). Every time such structures are allocated, they must be initialised, and when they are deleted again, they must be cleaned up. It&#039;s likely, however, that such a structure will be needed again in the future, so that it can be kept in its initialised state and re-used later. This further reduces the load on the allocator routines, and thus improves system performance.&lt;br /&gt;
&lt;br /&gt;
The object caches work on memory that has already been mapped into the virtual memory space.&lt;br /&gt;
&lt;br /&gt;
=== More Advantages ===&lt;br /&gt;
&lt;br /&gt;
Another advantage is the possibility to improve CPU cache usage. Usually, most objects have &amp;quot;hot spots&amp;quot;, i.e. they have a few fields that are used often. Since most of the time a little memory is left unused in a slab (the object size might not be a multiple of the slab size), this additional memory can be used to &amp;quot;shift&amp;quot; the hot spots by a few bytes to optimise the memory structure, leading to better cache usage.&lt;br /&gt;
&lt;br /&gt;
Finally, the system can be expanded to multiple CPUs with next to no overhead. On multi-CPU system, these expanded slab allocators scale almost linearly with the number of CPUs employed, making it the ideal choice for such systems.&lt;br /&gt;
&lt;br /&gt;
The combination of object caching and keeping caches for different memory blocks (for AllocVec/FreeVec emulation) makes the memory management more efficient, faster, and generally more future-proof than the old free list approach used in AmigaOS 3.x and earlier.&lt;br /&gt;
&lt;br /&gt;
See Wikipedia for more information on [http://en.wikipedia.org/wiki/Slab_allocation slab allocator systems].&lt;br /&gt;
&lt;br /&gt;
=== Physical page allocation ===&lt;br /&gt;
&lt;br /&gt;
Every memory location in a computer system has its own, unique address. That is, there is a byte location at address x where you can store and retrieve a single byte. This address is fixed; there is no way to change it without physically changing the hardware. Therefore, this address is called the “physical” address.&lt;br /&gt;
&lt;br /&gt;
The physical address of a memory page is most often completely irrelevant to the application. The CPU will typically only see the virtual address. AmigaOS will take care of assigning virtual addresses to memory paging. This is often called “mapping” a page. Only in very special cases will the physical address be relevant. For example, device drivers that want to pass memory to a hardware device via DMA. Since the MMU is part of the CPU, any external hardware like an IDE controller will not see the virtual but only physical addresses.&lt;br /&gt;
&lt;br /&gt;
A common operation in memory allocation is the assignment of virtual addresses to physical memory locations. Allocation of physical memory is usually done differently from virtual allocations, since it&#039;s necessary to free up only part of the allocation (when for example the pager kicks in).&lt;br /&gt;
&lt;br /&gt;
The de-facto standard in allocation of physical pages is a method invented by Knuth, called [http://en.wikipedia.org/wiki/Buddy_memory_allocation the &amp;quot;buddy system&amp;quot;]. Basically every modern operating system uses it and AmigaOS is no exception.&lt;br /&gt;
&lt;br /&gt;
Buddy systems are in essence size-segregated free lists. To allocate, the system searches for a free block of at least the size of the allocation. Then, if the block is too large, it&#039;s split into two even-sized blocks. These blocks are called &amp;quot;buddies&amp;quot;. One block is returned to it&#039;s appropriate free list, and the other is considered further, maybe splitting it further until it&#039;s size matches that of the allocation.&lt;br /&gt;
&lt;br /&gt;
In a buddy system, it&#039;s easy to determine whether the &amp;quot;buddy&amp;quot; is free or not, because it&#039;s address can simply be decided based on the address of the block to be freed.&lt;br /&gt;
&lt;br /&gt;
=== Virtual address space allocation ===&lt;br /&gt;
&lt;br /&gt;
Most CPUs come with a special unit that is called a &amp;quot;memory management unit&amp;quot; or &amp;quot;MMU&amp;quot; for short. The MMU&#039;s primary job is to rearrange the physical memory within the 4 gigabytes of address space in a way that is convenient for the operating system and/or applications. To do that effectively, it divides the memory into blocks (called &amp;quot;pages&amp;quot;). For every page the MMU has an entry in a table that specifies where the CPU should &amp;quot;see&amp;quot; this page, and what special attributes the page has. The address where the CPU &amp;quot;sees&amp;quot; this page is a 32 bit address as well, but since the memory is not really located there, we call that a &amp;quot;virtual&amp;quot; address.&lt;br /&gt;
&lt;br /&gt;
AmigaOS uses a resource map allocator for allocating virtual address space. Basically this is a means of managing a set of resources (not necessarily memory). For performance reason, it uses several optimization techniques.&lt;br /&gt;
&lt;br /&gt;
For one, all free resource blocks are held in space-segregated lists, i.e. there is a list for each power-of-two resource group. This makes allocations a lot faster by providing an upper and lower bound for a search. For example, if you want to allocate a block of 2^10 bytes, you can basically skip searching any block below 2^10 bytes in size simply because it won&#039;t fit. Similarly, you don&#039;t need to search for blocks that are larger than, say, twice the size of the block, since there might still be blocks of a size near to what we need. Size-segregated free lists help narrow down the search, making the search itself faster and the result better in terms of fragmentation.&lt;br /&gt;
&lt;br /&gt;
In addition, the resource maps use object caches for accelerating &amp;quot;small&amp;quot; allocations. Most allocations are below a certain size. For example, the virtual addresses are always allocated in chunks of at least one &amp;quot;page&amp;quot; in memory (4096 bytes). So it&#039;s common to allocate blocks of one, two, four, or eight pages. The object caches provide an easy method for keeping these common sizes, making every allocation of these sizes an exact fit, further reducing fragmentation.&lt;br /&gt;
&lt;br /&gt;
=== Page cache ===&lt;br /&gt;
&lt;br /&gt;
A lot of the time spent in allocating memory was spent looking for the appropriate pages in memory. A 256 MB memory system has 65536 4KB pages. These pages have to be searched for from time to time. Originally, hash tables were used but it turned out that distributing 65536 page entries over a few hash buckets still produced lists of several thousand pages that had to be traversed to find a page. The hash table was replaced with a radix tree. These trees are rather broad, but shallow, making traversal very fast. In usual circumstances, the tree does not grow more than 4 to 5 levels in depth, making searching of a page a matter of maximum 4 to 5 compare operations.&lt;br /&gt;
&lt;br /&gt;
=== Pager ===&lt;br /&gt;
&lt;br /&gt;
AmigaOS has the possibility to swap out parts of memory to disk in order to free up more memory for other applications. This feature allows applications to use more memory than is actually physically installed in the system.&lt;br /&gt;
&lt;br /&gt;
Paging is commonly referred to as &amp;quot;virtual memory&amp;quot; by users and sometimes even software developers. The fact AmigaOS uses virtual memory does not imply the use of the pager.&lt;br /&gt;
&lt;br /&gt;
The system can be tuned to different strategies, either page out only on demand (for highly interactive tasks), or based on other needs (lots of free memory in core for disk caches etc.).&lt;br /&gt;
&lt;br /&gt;
The optimized data structures allow the memory system to operate at a very high speed.&lt;br /&gt;
&lt;br /&gt;
The time for a memory allocation is now in the order of a few microseconds. This is especially true for small allocation (below 8096 bytes). During system testing it was observed that by the time the system has booted up to Workbench, there have already been 40,000 allocations to the global memory pool below 2096 bytes.&lt;br /&gt;
&lt;br /&gt;
== Memory Functions ==&lt;br /&gt;
&lt;br /&gt;
Normally, an application uses the AllocVecTags() function to ask for memory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void *AllocVecTags(uint32 size, uint32 tag1, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The size argument is the amount of memory the application needs and the tag list specifies the type of memory and any special memory characteristics (described later). If AllocVecTags() is successful, it returns a pointer to a block of memory. The memory allocation will fail if the system cannot find a big enough block with the requested attributes. If AllocVecTags() fails, it returns NULL.&lt;br /&gt;
&lt;br /&gt;
Because the system only keeps track of how much free memory is available and not how much is in use, it has no idea what memory has been allocated by any task. This means an application has to explicitly return, or deallocate, any memory it has allocated so the system can reuse it. If an application does not return a block of memory to the system, the system will not be able to reallocate that memory to some other task. That block of memory will be lost until the Amiga is reset. If you are using AllocVecTags() to allocate memory, a call to FreeVec() will return that memory to the system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void FreeVec(void *memoryBlock);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here memoryBlock is a pointer to the memory block the application is returning to the system. The size of the memory block is tracked internally by the system.&lt;br /&gt;
&lt;br /&gt;
Unlike some compiler memory allocation functions, the Amiga system memory allocation functions return memory blocks that are at least longword aligned. This means that the allocated memory will always start on an address which is at least evenly divisible by four. This alignment makes the memory suitable for any system structures or buffers which require word or long word alignment, and also provides optimal alignment for stacks and memory copying.&lt;br /&gt;
&lt;br /&gt;
=== Memory Types ===&lt;br /&gt;
&lt;br /&gt;
There are three primary types of memory in AmigaOS which are summarized in the following table:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Use&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE&lt;br /&gt;
| This memory is private and only accessible within the context of the Task which allocated it. Private memory should always be preferred. Private memory is also swappable by default (i.e. not locked). This memory will not be visible to any other address space.&lt;br /&gt;
	In a future version of AmigaOS, it is planned to have Task specific address spaces. This means each task could potentially address up to 4 GB of private memory each.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED&lt;br /&gt;
| The memory is shared and accessible by any Task in the system without restriction. This memory can be shared between all address spaces and will always appear at the same address in any address space. Shared memory is locked by default and thus is not swappable.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE&lt;br /&gt;
| The memory is used to store executable PowerPC code. This is used two-fold in AmigaOS. First, it allows the system to determine if a function pointer points to real native PowerPC code as opposed to 68k code which needs to be emulated. Second, it prevents common exploits that use stack overflows to execute malicious code. Executable memory is locked by default and thus is not swappable.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory Attributes ===&lt;br /&gt;
&lt;br /&gt;
Memory allocation on AmigaOS has traditionally been rather complicated. Over time, as new hardware models were released, more memory attribute flags were introduced.&lt;br /&gt;
&lt;br /&gt;
All the various memory allocation functions and strategies have been consolidated and distilled into a single AllocVecTags() function call. Programmers are strongly encouraged to stop using any other function to allocate system memory. Using AllocVecTags() is the only way to guarantee future compatibility with more advanced AmigaOS features yet to come.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any attributes when allocating memory, the system tries to satisfy the request with the fastest memory available on the system memory lists.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make Sure You Have Memory|text=Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.}}&lt;br /&gt;
&lt;br /&gt;
==== Using Tags ====&lt;br /&gt;
&lt;br /&gt;
The AllocVecTags() function uses a tag list to define what attributes a block of memory must have. The currently supported tags are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Tag (Default)&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | AVT_Type&lt;br /&gt;
	(MEMF_PRIVATE)&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE: Allocate from the task private heap. This memory will not be visible to any other address space.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED: Allocate from the system shared heap. This memory can be shared between all address spaces and will always appear at the same address in any address space. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE: Allocate memory that is marked executable. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Contiguous&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| Memory allocated with this property is allocated from a contiguous block of physical memory. This makes the memory suitable for DMA purposes when the DMA device does not support scatter/gather operation. For devices that do support scatter/gather use StartDMA() instead.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Physical pages can move at any time, be removed from memory due to paging or otherwise made unavailable unless the memory pages are locked (see LockMemory() function or AVT_Lock tag).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Lock&lt;br /&gt;
	(TRUE or FALSE)&lt;br /&gt;
| After allocating memory, lock the associated pages in memory. This will prevent the pages from being moved, swapped out or otherwise being made unavailable. This is useful in conjunction with the AVT_Contiguous tag since it will ensure that the memory will stay contiguous after allocation.&lt;br /&gt;
	This tag defaults to FALSE for MEMF_PRIVATE allocations and to TRUE for MEMF_SHARED or MEMF_EXECUTABLE.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Alignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block. The returned memory block will be aligned to the given size (in bytes). It&#039;s virtual address will be at least a multiple of the AVT_Alignment value.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_PhysicalAlignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block physical address. See AVT_Alignment for more information.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; This functionality is mainly used for DMA drivers that require a specific alignment. Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_ClearWithValue&lt;br /&gt;
| Clear the newly allocated memory with the given byte value. If this tag is not given the memory block is not cleared.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Wait&lt;br /&gt;
	(TRUE)&lt;br /&gt;
| Wait for the memory subsystem to be available. If TRUE, the calling task will be retired until the memory subsystem is available. This might cause a Forbid() to break. When FALSE and the memory subsystem is not currently available, the function will return immediately and the allocation will fail.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_NoExpunge&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| If allocation fails because of unavailability, prevent invocation of low memory cleanup handlers. The default is FALSE which means that when memory is not available, cleanup handlers are invoked to try and satisfy the allocation.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Using Flags ====&lt;br /&gt;
&lt;br /&gt;
The use of memory flags was the only supported way to allocate memory prior to AmigaOS 4.0. It may be still useful to know more about this obsolete system for porting older applications. For more information about the memory flags system see [[Obsolete Exec Memory Allocation]].&lt;br /&gt;
&lt;br /&gt;
=== Allocating System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples show how to allocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR apointer = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (apointer == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocVecTags() returns the address of the first byte of a memory block that is at least 100 bytes in size or NULL if there is not that much free memory. Because there are no tags specified, private memory is assumed so this memory cannot be shared with any other tasks.&lt;br /&gt;
&lt;br /&gt;
In addition to allocating a block of memory, this function keeps track of the size of the memory block, so your application doesn&#039;t have to remember it when it deallocates that memory block. The AllocVecTags() function allocates a little more memory to store the size of the memory allocation request.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make No Assumptions|It is not legal to peek the longword in front of the returned memory pointer to find out how big the block is. This has always been illegal regardless of what any other documentation may have stated to the contrary. Your application has access to the memory returned by AllocVecTags() and the only guarantee made is that the returned block has n bytes of continuous address space starting at the returned address. Anything outside this area is not to be touched.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR anotherptr = IExec-&amp;gt;AllocVecTags(1000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  AVT_Lock, FALSE,&lt;br /&gt;
  AVT_ClearWithValue, 0,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (anotherptr == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocates shared memory which is accessible by any task in the system and clears the memory contents to zero. MEMF_SHARED memory is locked by default for compatibility with the obsolete MEMF_PUBLIC flag. This is by far the most common case when allocating shared memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR lockedmem = IExec-&amp;gt;AllocVecTags(3000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (lockedmem == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocated shared memory which is accessible by any task in the system. MEMF_SHARED memory is locked by default so the underlying memory pages are not moveable and cannot be swapped out. Such memory could be used to share memory between a Process and an interrupt routine for example.&lt;br /&gt;
&lt;br /&gt;
If the system free memory list does not contain enough contiguous memory bytes in an area matching your requirements, AllocVecTags() returns a zero. You &#039;&#039;must&#039;&#039; check for this condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR yap = IExec-&amp;gt;AllocVec(500, MEMF_CHIP);&lt;br /&gt;
&lt;br /&gt;
if (yap == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The deprecated AllocVec() function is used in the example above because it is the only way to allocate MEMF_CHIP memory on a classic Amiga system.&lt;br /&gt;
&lt;br /&gt;
=== Locking System Memory ===&lt;br /&gt;
&lt;br /&gt;
The LockMem() function is used to explicitly lock a memory block. This function will make sure your memory block is not unmapped, swapped out or somehow made inaccessible. Use this function wisely. If there is no good reason to lock memory then do not do it. It will prevent the memory system from optimizing memory layout and may lead to poor performance. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;LockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before deallocating the memory is must be unlocked as well.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;UnLockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Do not forget to UnlockMem()&#039;&#039;|Failure to unlock memory will have adverse affects on the memory system. Locked memory pages cannot be moved so the system cannot optimize the layout of locked memory pages.}}&lt;br /&gt;
&lt;br /&gt;
Be careful to always match the number of locks and the number of unlocks. If something else may have locked memory in the same page and an extraneous UnlockMem() decreases the lock reference counter to 0, that page can be paged out or moved. If that happens, for example, with data used in an interrupt handler or by the device driver which handles the swap partition the system will crash.&lt;br /&gt;
&lt;br /&gt;
=== Freeing System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples free the memory chunks shown in the previous calls to AllocVecTags().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;FreeVec(apointer);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(anotherptr);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(lockedmem);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(yap);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A memory block allocated with AllocVecTags() or AllocVec() must be returned to the system pool with the FreeVec() function. This function uses the stored size in the allocation to free the memory block, so there is no need to specify the size of the memory block to free.&lt;br /&gt;
&lt;br /&gt;
Any memory that is locked must be explicitly unlocked with UnlockMem() prior to freeing it. Failure to unlock memory will not cause any immediate problems but any pages which are locked cannot be moved or swapped out which can decrease system performance. Never unlock memory which has not been explicitly locked with LockMem() as it could lead to undefined behaviour.&lt;br /&gt;
&lt;br /&gt;
FreeVec() returns no status. However, if you attempt to free a memory block in the middle of a chunk that the system believes is already free, you will cause a system crash. It is also illegal to free the same memory block twice and this will lead to a system crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Leave Memory Allocations Out Of Interrupt Code&#039;&#039;|text=Do not allocate or deallocate system memory from within interrupt code. The [[Exec_Interrupts|Exec Interrupts]] section explains that an interrupt may occur at any time, even during a memory allocation process. As a result, system data structures may not be internally consistent at this time.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory may be Locked by Default ===&lt;br /&gt;
&lt;br /&gt;
By default, the system memory allocation routines will allocate MEMF_SHARED and MEMF_EXECUTABLE memory and implicitly lock it. This memory must not be explicitly unlocked. Let the system handle the unlocking internally.&lt;br /&gt;
&lt;br /&gt;
Here is the right way to allocate and free MEMF_SHARED memory:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is the wrong way:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;UnlockMem(ptr, 10);  // Doing this could lead to undefined system behaviour.&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlocking memory not explicitly locked does not immediately cause any issues. However, in a future version of AmigaOS the memory pages may be handled differently than they are handled today. This could lead to incompatibilities with your applications and AmigaOS. The simple rule is that if you called LockMem() you must also call UnlockMem(). All other times you should not call UnlockMem().&lt;br /&gt;
&lt;br /&gt;
{{Note|Memory locking is done at the page level. Even if a single byte of memory is locked in a page that entire page is locked. The programmer has no control over which pages may be used to satisfy a memory allocation.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Shared and executable memory is locked implicitly for 68K backwards compatibility reasons. Use AVT_Lock (or equivalent) to ensure your shared and executable memory is not locked. MEMF_PRIVATE memory has no backwards compatibility issues and is always unlocked by default.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory Information Functions ===&lt;br /&gt;
&lt;br /&gt;
The memory information routines AvailMem() and TypeOfMem() can provide the amount of memory available in the system, and the attributes of a particular block of memory.&lt;br /&gt;
&lt;br /&gt;
==== Memory Requirements ====&lt;br /&gt;
&lt;br /&gt;
The same attribute flags used in memory allocation routines are valid for the memory information routines. There is also an additional flag, MEMF_LARGEST, which can be used in the AvailMem() routine to find out what the largest available memory block of a particular type is. Specifying the MEMF_TOTAL flag will return the total amount of memory currently available.&lt;br /&gt;
&lt;br /&gt;
==== Calling Memory Information Functions ====&lt;br /&gt;
&lt;br /&gt;
The following example shows how to find out how much memory of a particular type is available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 size = IExec-&amp;gt;AvailMem(MEMF_CHIP | MEMF_LARGEST);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AvailMem() returns the size of the largest chunk of available chip memory.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;AvailMem() May Not Be Totally Accurate&#039;&#039;|text=Because of multitasking, the return value from AvailMem() may be inaccurate by the time you receive it.}}&lt;br /&gt;
&lt;br /&gt;
The following example shows how to determine the type of memory of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 memtype = IExec-&amp;gt;TypeOfMem((APTR)0x090000);&lt;br /&gt;
if ((memtype &amp;amp; MEMF_CHIP) == MEMF_CHIP) {  /*  ... It&#039;s chip memory ...  */   }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
TypeOfMem() returns the attributes of the memory at a specific address. If it is passed an invalid memory address, TypeOfMem() returns NULL. This routine is normally used to determine if a particular chunk of memory is in chip memory.&lt;br /&gt;
&lt;br /&gt;
=== Using Memory Copy Functions ===&lt;br /&gt;
&lt;br /&gt;
For memory block copies, the CopyMem() and CopyMemQuick() functions can be used.&lt;br /&gt;
&lt;br /&gt;
==== Copying System Memory ====&lt;br /&gt;
&lt;br /&gt;
The following samples show how to use the copying routines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR source = IExec-&amp;gt;AllocVecTags(1000, AVT_ClearWithValue, 0, TAG_END);&lt;br /&gt;
APTR target = IExec-&amp;gt;AllocVecTags(1000, AVT_Type, MEMF_SHARED, TAG_END);&lt;br /&gt;
IExec-&amp;gt;CopyMem(source, target, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CopyMem() copies the specified number of bytes from the source data region to the target data region. The pointers to the regions can be aligned on arbitrary address boundaries. CopyMem() will attempt to copy the memory as efficiently as it can according to the alignment of the memory blocks, and the amount of data that it has to transfer. These functions are optimized for copying large blocks of memory which can result in unnecessary overhead if used to transfer very small blocks of memory.&lt;br /&gt;
&lt;br /&gt;
CopyMemQuick() is now identical to CopyMem(). In previous versions of the operating system, CopyMemQuick() performed more optimized copying of the specified number of bytes from the source data region to the target data region. The source and target pointers must be longword aligned and the size (in bytes) must be divisible by four. There are no such restrictions starting with AmigaOS 4.0.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Not All Copies Are Supported&#039;&#039;|text=Neither CopyMem() nor CopyMemQuick() supports copying between regions that overlap. For overlapping regions see MoveMem() in the [[Utility Library]].}}&lt;br /&gt;
&lt;br /&gt;
=== System Memory Pools ===&lt;br /&gt;
&lt;br /&gt;
A construct carried over from the AmigaOS 3.x Exec is the memory pool. The code handling memory pools uses an algorithm based on boundary tags and size-segregated memory lists. The speed gain is tremendous: even for the &amp;quot;dumb&amp;quot; case of just allocating 100000 blocks and freeing them again, the speed is ten times faster than the previous implementation. Due to the size-segregated free lists and the easy coalescing due to the boundary tags, real-life performance gain is even higher and would be between 10 and 20 times.&lt;br /&gt;
&lt;br /&gt;
Two types of memory pools are available depending on the needs of the application:&lt;br /&gt;
* [[Exec_Item_Pools|Item Pools]] are built for speed and are used for allocating large numbers of items that are all the same size.&lt;br /&gt;
* Generic [[Exec_Memory_Pools|Memory Pools]] can handle allocations of different sizes at the expense of some speed.&lt;br /&gt;
&lt;br /&gt;
=== Summary of System Controlled Memory Handling Routines ===&lt;br /&gt;
&lt;br /&gt;
; AllocVecTags() and FreeVec()&lt;br /&gt;
: These are system-wide memory allocation and deallocation routines. They use a memory free-list owned and managed by the system.&lt;br /&gt;
&lt;br /&gt;
; LockMem() and UnlockMem()&lt;br /&gt;
: These routines explicitly lock and unlock underlying memory pages.&lt;br /&gt;
&lt;br /&gt;
; AvailMem()&lt;br /&gt;
: This routine returns the number of free bytes in a specified type of memory.&lt;br /&gt;
&lt;br /&gt;
; TypeOfMem()&lt;br /&gt;
: This routine returns the memory attributes of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
; CopyMem() and CopyMemQuick()&lt;br /&gt;
: CopyMem() is a general purpose memory copy routine. CopyMemQuick() is an optimized version of CopyMemQuick(), but has restrictions on the size and alignment of the arguments.&lt;br /&gt;
&lt;br /&gt;
== Allocating DMA Memory ==&lt;br /&gt;
&lt;br /&gt;
Device drivers often use DMA to transfer data to and from the device. The StartDMA(), GetDMAList() and EndDMA() functions are used to create a scatter/gather list suitable for such DMA transfers.&lt;br /&gt;
&lt;br /&gt;
The following conditions are guaranteed to be met when using these DMA functions:&lt;br /&gt;
* The memory region given is guaranteed to be mapped to physical memory.&lt;br /&gt;
* The mapping will not change as long as EndDMA() is not called.&lt;br /&gt;
* All cache entries in this region will be flushed out.&lt;br /&gt;
&lt;br /&gt;
The example code below demonstrates how to perform a DMA write transfer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR addr;&lt;br /&gt;
uint32 size;&lt;br /&gt;
&lt;br /&gt;
/* Tell the system to prepare for DMA */&lt;br /&gt;
uint32 arraySize = IExec-&amp;gt;StartDMA(addr, size, 0);&lt;br /&gt;
if (arraySize &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    /* The memory area is prepared, allocate and retrieve the DMA list */&lt;br /&gt;
    struct DMAEntry *DMAList = IExec-&amp;gt;AllocSysObject(ASOT_DMAENTRY,&lt;br /&gt;
      ASODMAE_NumEntries,  arraySize,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (DMAList != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        IExec-&amp;gt;GetDMAList(addr, size, 0, DMAList);&lt;br /&gt;
 &lt;br /&gt;
        /* Feed the DMA controller and do stuff */&lt;br /&gt;
        ...&lt;br /&gt;
        /* Get rid of the DMAList&#039;s memory */&lt;br /&gt;
 &lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, endFlags);&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_DMAENTRY, DMAList);&lt;br /&gt;
    }&lt;br /&gt;
    else&lt;br /&gt;
    {&lt;br /&gt;
        // Note We still call EndDMA() even though the actual&lt;br /&gt;
        // transfer didn&#039;t happen.&lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, DMAF_NoModify);&lt;br /&gt;
&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t allocate DMA list\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
else&lt;br /&gt;
{&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t initiate DMA transfer\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syncing and Memory Access ==&lt;br /&gt;
&lt;br /&gt;
The PowerPC is a pure load/store architecture. That means, it will never operate on memory arguments like x86, to modify data, you have to load, modify and store.&lt;br /&gt;
&lt;br /&gt;
All PowerPC/POWER cpus have a load/store queue, i.e. a queue where load and store operations are queued to reduce memory latency. If more than one store is made to the same long word (i.e. you write the first byte and then the second byte), then those stores are combined. As you can see, this will cause a problem for a chip register since both are written at the same time which is likely not what you want.&lt;br /&gt;
&lt;br /&gt;
Similar for reading: A read might shortcut through the load/store queue and use a value that&#039;s already been read and is present in the load/store queue. Again, for chip registers, this will cause a problem because you might read an old value.&lt;br /&gt;
&lt;br /&gt;
There are two instructions that deal with this: &#039;&#039;&#039;eieio&#039;&#039;&#039; (Ensure In-order Execution of I/O) and &#039;&#039;&#039;sync&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The eieio instruction simply inserts a &amp;quot;barrier&amp;quot; into the load/store queue. When combining &#039;&#039;stores&#039;&#039; the CPU never searches past this barrier for possible combines. This means that the sequence&lt;br /&gt;
&lt;br /&gt;
# store to some address&lt;br /&gt;
# eieio&lt;br /&gt;
# store to some address + 1&lt;br /&gt;
&lt;br /&gt;
will never be combined because of the eieio barrier between them.&lt;br /&gt;
&lt;br /&gt;
The sync instruction will simply halt all execution and flush the load/store queue, executing and finishing all loads and stores.&lt;br /&gt;
&lt;br /&gt;
As you can imagine, sync is MUCH MORE costly than eieio.&lt;br /&gt;
&lt;br /&gt;
=== When to use eieio and sync ===&lt;br /&gt;
&lt;br /&gt;
If you want to write, post fix your writes with an eieio instruction.&lt;br /&gt;
&lt;br /&gt;
If you want to read, prefix your reads with a sync instruction.&lt;br /&gt;
&lt;br /&gt;
This is also where the &amp;quot;GUARDED&amp;quot; memory flag comes in. GUARDED memory simply ensures program order of loads and stores, that is, it&#039;s an implicit eieio.&lt;br /&gt;
&lt;br /&gt;
{{Note|Knowing when and how to use the &#039;&#039;&#039;eieio&#039;&#039;&#039; and &#039;&#039;&#039;sync&#039;&#039;&#039; instructions is somewhat vital for driver developers.}}&lt;br /&gt;
&lt;br /&gt;
== Allocating Multiple Memory Blocks ==&lt;br /&gt;
&lt;br /&gt;
Exec provides the routines AllocTaskMemEntry() and FreeEntry() to allocate multiple memory blocks in a single call.&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() accepts a data structure called a MemList, which contains the information about the size of the memory blocks to be allocated and the requirements, if any, that you have regarding the allocation.&lt;br /&gt;
&lt;br /&gt;
The MemList structure is found in the include file &amp;lt;exec/memory.h&amp;gt; and is defined as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList&lt;br /&gt;
{&lt;br /&gt;
    struct Node     ml_Node;&lt;br /&gt;
    UWORD           ml_NumEntries;      /* number of MemEntrys */&lt;br /&gt;
    struct MemEntry ml_ME[1];           /* where the MemEntrys begin*/&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ml_Node&lt;br /&gt;
: allows you to link together multiple MemLists. However, the node is ignored by the routines AllocTaskMemEntry() and FreeEntry().&lt;br /&gt;
&lt;br /&gt;
; ml_NumEntries&lt;br /&gt;
: tells the system how many MemEntry sets are contained in this MemList. Notice that a MemList is a variable-length structure and can contain as many sets of entries as you wish.&lt;br /&gt;
&lt;br /&gt;
The MemEntry structure looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemEntry&lt;br /&gt;
{&lt;br /&gt;
    union {&lt;br /&gt;
        ULONG   meu_Reqs;   /* the AllocMem requirements */&lt;br /&gt;
        APTR    meu_Addr;   /* address of your memory */&lt;br /&gt;
        } me_Un;&lt;br /&gt;
    ULONG   me_Length;      /* the size of this request */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sample Code for Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an example of showing how to use the AllocTaskMemEntry() with multiple blocks of memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// alloctaskmementry.c - example of allocating several memory areas.&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MemList *memlist;             /* pointer to a MemList structure        */&lt;br /&gt;
&lt;br /&gt;
struct MemBlocks /* define a new structure because C cannot initialize unions */&lt;br /&gt;
{&lt;br /&gt;
    struct MemList  mn_head;         /* one entry in the header               */&lt;br /&gt;
    struct MemEntry mn_body[3];      /* additional entries follow directly as */&lt;br /&gt;
} memblocks;                         /* part of the same data structure       */&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    memblocks.mn_head.ml_NumEntries = 4; /* 4! Since the MemEntry starts at 1! */&lt;br /&gt;
&lt;br /&gt;
    /* Describe the first piece of memory we want.  Because of our MemBlocks structure */&lt;br /&gt;
    /* setup, we reference the first MemEntry differently when initializing it.        */&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Reqs   = MEMF_CLEAR;&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Length = 4000;&lt;br /&gt;
&lt;br /&gt;
    memblocks.mn_body[0].me_Reqs   = MEMF_PRIVATE | MEMF_CLEAR;/* Describe the other pieces of    */&lt;br /&gt;
    memblocks.mn_body[0].me_Length = 100000;                   /* memory we want. Additional      */&lt;br /&gt;
    memblocks.mn_body[1].me_Reqs   = MEMF_SHARED | MEMF_CLEAR; /* MemEntries are initialized this */&lt;br /&gt;
    memblocks.mn_body[1].me_Length = 200000;                   /* way. If we wanted even more en- */&lt;br /&gt;
    memblocks.mn_body[2].me_Reqs   = MEMF_EXECUTABLE;          /* tries, we would need to declare */&lt;br /&gt;
    memblocks.mn_body[2].me_Length = 25000;                    /* a larger MemEntry array in our  */&lt;br /&gt;
                                                               /* MemBlocks structure.            */&lt;br /&gt;
&lt;br /&gt;
    memlist = (struct MemList *)IExec-&amp;gt;AllocTaskMemEntry((struct MemList *)&amp;amp;memblocks);&lt;br /&gt;
&lt;br /&gt;
    if (memlist == NULL)&lt;br /&gt;
    {&lt;br /&gt;
       IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry FAILED\n&amp;quot;);&lt;br /&gt;
       return RETURN_FAIL;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    /* We got all memory we wanted.  Use it and call FreeEntry() to free it */&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry succeeded - now freeing all allocated blocks\n&amp;quot;);&lt;br /&gt;
    IExec-&amp;gt;FreeEntry(memlist);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() returns a pointer to a new MemList of the same size as the MemList that you passed to it. For example, ROM code can provide a MemList containing the requirements of a task and create a RAM-resident copy of the list containing the addresses of the allocated entries. The pointer to the MemList is used as the argument for FreeEntry() to free the memory blocks.&lt;br /&gt;
&lt;br /&gt;
=== Result of Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
The MemList created by AllocTaskMemEntry() contains MemEntry entries. MemEntrys are defined by a union statement, which allows one memory space to be defined in more than one way.&lt;br /&gt;
&lt;br /&gt;
If AllocTaskMemEntry() returns a non-NULL value then all of the meu_Addr positions in the returned MemList will contain valid memory addresses meeting the requirements you have provided. To use this memory area, you would use code similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *mlist = IExec-&amp;gt;AllocTaskMemEntry(&amp;amp;ML);&lt;br /&gt;
APTR memory = NULL;&lt;br /&gt;
&lt;br /&gt;
if ( mlist != NULL )&lt;br /&gt;
{&lt;br /&gt;
  memory = mlist-&amp;gt;ml_ME[0].me_Un.meu_Addr;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Multiple Memory Blocks and Tasks ===&lt;br /&gt;
&lt;br /&gt;
If you want to take advantage of Exec&#039;s automatic cleanup, use the MemList and AllocTaskMemEntry() facility to do your dynamic memory allocation.&lt;br /&gt;
&lt;br /&gt;
In the Task control block structure, there is a list header named tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
This is the list header that you initialize to include MemLists that your task has created by call(s) to AllocTaskMemEntry(). Here is a short program segment that handles task memory list header initialization only. It assumes that you have already run AllocTaskMemEntry() as shown in the simple AllocTaskMemEntry() example above.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *ml;&lt;br /&gt;
&lt;br /&gt;
struct Task *tc = IExec-&amp;gt;FindTask(0);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;AddTail(tc-&amp;gt;tc_MemEntry, ml);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Assuming that you have only used the AllocTaskMemEntry() method (or AllocVecTags() and built your own custom MemList), the system now knows where to find the blocks of memory that your task has dynamically allocated. The RemTask() function automatically frees all memory found on tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;CreateTask() Sets Up A MemList.&#039;&#039;|text=The CreateTask() function, and other system task and process creation functions use a MemList in tc_MemEntry so that the Task structure and stack will be automatically deallocated when the Task is removed.}}&lt;br /&gt;
&lt;br /&gt;
=== Summary of Multiple Memory Blocks Allocation Routines ===&lt;br /&gt;
&lt;br /&gt;
These are routines for allocating and freeing multiple memory blocks with a single call.&lt;br /&gt;
&lt;br /&gt;
This routine initializes memory from data and offset values in a table. Typically only assembly language programs benefit from using this routine. See the SDK for more details.&lt;br /&gt;
&lt;br /&gt;
== Allocating Memory at an Absolute Address ==&lt;br /&gt;
&lt;br /&gt;
For special advanced applications, AllocAbs() is provided. Using AllocAbs(), an application can allocate a memory block starting at a specified absolute memory address. If the memory is already allocated or if there is not enough memory available for the request, AllocAbs() returns a zero.&lt;br /&gt;
&lt;br /&gt;
Be aware that an absolute memory address which happens to be available on one Amiga may not be available on a machine with a different configuration or different operating system revision, or even on the same machine at a different times. For example, a piece of memory that is available during expansion board configuration might not be available at earlier or later times. Here is an example call to AllocAbs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR absoluteptr = (APTR)IExec-&amp;gt;AllocAbs(10000, 0x2F0000);&lt;br /&gt;
if (!(absoluteptr))&lt;br /&gt;
    { /* Couldn&#039;t get memory, act accordingly. */  }&lt;br /&gt;
&lt;br /&gt;
/* After we&#039;re done using it, we call FreeMem() to free the memory block. */&lt;br /&gt;
IExec-&amp;gt;FreeMem(absoluteptr, 10000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of the Exec functions that handle memory management. See the SDK for details on each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Memory Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AllocMem()&lt;br /&gt;
| Allocate memory with specified attributes. &#039;&#039;&#039;This function is obsolete.&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| AllocAbs()&lt;br /&gt;
| Allocate memory at a specified location.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTaskMemEntry()&lt;br /&gt;
| Allocate multiple memory blocks.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVec()&lt;br /&gt;
| Allocate memory with specified attributes and keep track of the size. This function is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVecTags()&lt;br /&gt;
| Allocate memory with specified attributes defined by tags and keep track of the size. If an application needs to allocate some memory, it will usually use this function.&lt;br /&gt;
|-&lt;br /&gt;
| AvailMem()&lt;br /&gt;
| Return the amount of free memory, given certain conditions.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMem()&lt;br /&gt;
| Copy memory block, which can be non-aligned and of arbitrary length.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMemQuick()&lt;br /&gt;
| Copy aligned memory block.&lt;br /&gt;
|-&lt;br /&gt;
| FreeEntry()&lt;br /&gt;
| Free multiple memory blocks, allocated with AllocTaskMemEntry().&lt;br /&gt;
|-&lt;br /&gt;
| FreeMem()&lt;br /&gt;
| Free a memory block of specified size, allocated with AllocMem() or AllocAbs().&lt;br /&gt;
|-&lt;br /&gt;
| FreeVec()&lt;br /&gt;
| Free a memory block allocated with AllocVecTags() or AllocVec().&lt;br /&gt;
|-&lt;br /&gt;
| InitStruct()&lt;br /&gt;
| Initialize memory from a table.&lt;br /&gt;
|-&lt;br /&gt;
| LockMem()&lt;br /&gt;
| Lock the underlying pages given a memory block address and size.&lt;br /&gt;
|-&lt;br /&gt;
| TypeOfMem()&lt;br /&gt;
| Determine attributes of a specified memory address.&lt;br /&gt;
|-&lt;br /&gt;
| UnlockMem()&lt;br /&gt;
| Unlock the underlying pages given a memory block address and size.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12383</id>
		<title>Exec Memory Allocation</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12383"/>
		<updated>2023-11-04T15:13:45Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: /* Program Address Space */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Exec Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
Exec manages all of the free memory currently available in the system. Using a slab allocation system, Exec keeps track of memory and provides the functions to allocate and access it.&lt;br /&gt;
&lt;br /&gt;
When an application needs some memory, it can either declare the memory statically within the program or it can ask Exec for some memory. When Exec receives a request for memory, it searches its free memory regions to find a suitably sized block that matches the size and attributes requested.&lt;br /&gt;
&lt;br /&gt;
Prior to AmigaOS 4.0, the OS did not make use of the CPU&#039;s memory management unit and used memory &amp;quot;as-is&amp;quot;. That is, if you have different memory expansions plugged into your system, the memory will be seen as chunks located somewhere in the 4 gigabyte address space. Since version 4.0, the MMU will be used to &amp;quot;map&amp;quot; memory pages from their physical location to a virtual address. There are multiple reasons why this is better than using the verbatim physical addresses - among other things it reduces the effect of &amp;quot;memory fragmentation&amp;quot; and simplifies the possibility to swap currently unused memory pages to persistent storage such as a hard disk.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;downside&amp;quot; is that the virtual address of a memory block is almost never identical to the physical address. This isn&#039;t much of a downside, since an application will never really need to care about it. If an application allocates a block of memory of n bytes, it will get a pointer back that points to at least n continuous addresses as expected. The pages that &amp;quot;fill&amp;quot; this memory block may come from different physical locations scattered throughout the physical memory but a program will never noticed that. For all intent and purpose, the application sees a single continuous block of memory.&lt;br /&gt;
&lt;br /&gt;
=== Program Address Space ===&lt;br /&gt;
&lt;br /&gt;
It is important to remember that, just like in classic AmigaOS, a single address space is used for all programs. Sometimes the mention of an MMU can lead people to assume that each process on the Amiga will have its own personal, partitioned address space. The following two programs demonstrate that, even though they are separate processes, it is possible to read and write another&#039;s memory. The memory locations are the same virtual address and that virtual address maps onto the same physical address.&lt;br /&gt;
&lt;br /&gt;
The following demonstrates how it is possible to read/write memory that is &amp;quot;owned&amp;quot; by a different process:&lt;br /&gt;
&lt;br /&gt;
This program has a global variable. It prints out the virtual address of the global, waits for a keypress and then prints out the address again in case it has been externally updated:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
volatile char x = 127;&lt;br /&gt;
&lt;br /&gt;
/* Usage: a [VAL] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(argc==2)&lt;br /&gt;
        x = (char)atoi(argv[1]);&lt;br /&gt;
&lt;br /&gt;
    printf(&amp;quot;Virtual Address    of `x&#039;: %p\n&amp;quot;, (void*)&amp;amp;x);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    (void)getchar();&lt;br /&gt;
    printf(&amp;quot;Final Value        of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This program reads in an address as an argument and reads the value at that location even though it does not &amp;quot;own&amp;quot; the memory. By adding an additional argument, this program can also write to that &amp;quot;foreign&amp;quot; address:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* Usage: b ADDR [VAL_TO_WRITE] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(!(argc==2 || argc==3))&lt;br /&gt;
        return 10;&lt;br /&gt;
&lt;br /&gt;
    volatile char *byte = (volatile char*)strtol(argv[1],NULL,16);&lt;br /&gt;
    printf(&amp;quot;Selected Virtual Address    : %p\n&amp;quot;,(void*)byte);&lt;br /&gt;
    printf(&amp;quot;Dereferencd Value of Address: %d\n&amp;quot;,*byte);&lt;br /&gt;
    if(argc==3)&lt;br /&gt;
    {&lt;br /&gt;
        printf(&amp;quot;Writing Value `%d&#039; to Address: %p\n&amp;quot;,atoi(argv[2]),(void*)byte);&lt;br /&gt;
        *byte=(char)atoi(argv[2]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Slab allocation ===&lt;br /&gt;
&lt;br /&gt;
[[File:SlabDiagram.jpg|right]]&lt;br /&gt;
&lt;br /&gt;
The AmigaOS memory architecture is based on the &amp;quot;slab allocator&amp;quot; system or &amp;quot;object cache&amp;quot;. In essence, the slab allocator only allocates objects of a single size, allocating these in larger batches (&amp;quot;slabs&amp;quot;) from the low-level page allocator. These slabs are then divided up into buffers of the required size, and kept within a list in the slab allocator.&lt;br /&gt;
&lt;br /&gt;
Allocating an object with the slab allocator becomes a process of simple node removal: the first node in the first slab containing free nodes is removed and returned for use. Since the slab allocator keeps free slabs or partially free slabs in a separate list from the full slabs, this operation can be carried out in constant time. Freeing memory is accomplished by returning the buffer to its cache, and adding it to its original slab&#039;s free list. Slabs that are completely free can be returned to the system&#039;s page pool (this operation is actually driven by demand, and timestamps are used to avoid unnecessary loading of data, or &amp;quot;thrashing&amp;quot;). External fragmentation is minimal, and internal fragmentation is controlled and guaranteed not to exceed a certain amount.&lt;br /&gt;
&lt;br /&gt;
=== Object caching ===&lt;br /&gt;
&lt;br /&gt;
The slab allocator can also be used to cache objects. In the real world a lot of memory allocation operations will be used to allocate the same object. The system has a number of data structures which are allocated frequently (semaphores, message ports and the like). Every time such structures are allocated, they must be initialised, and when they are deleted again, they must be cleaned up. It&#039;s likely, however, that such a structure will be needed again in the future, so that it can be kept in its initialised state and re-used later. This further reduces the load on the allocator routines, and thus improves system performance.&lt;br /&gt;
&lt;br /&gt;
The object caches work on memory that has already been mapped into the virtual memory space.&lt;br /&gt;
&lt;br /&gt;
=== More Advantages ===&lt;br /&gt;
&lt;br /&gt;
Another advantage is the possibility to improve CPU cache usage. Usually, most objects have &amp;quot;hot spots&amp;quot;, i.e. they have a few fields that are used often. Since most of the time a little memory is left unused in a slab (the object size might not be a multiple of the slab size), this additional memory can be used to &amp;quot;shift&amp;quot; the hot spots by a few bytes to optimise the memory structure, leading to better cache usage.&lt;br /&gt;
&lt;br /&gt;
Finally, the system can be expanded to multiple CPUs with next to no overhead. On multi-CPU system, these expanded slab allocators scale almost linearly with the number of CPUs employed, making it the ideal choice for such systems.&lt;br /&gt;
&lt;br /&gt;
The combination of object caching and keeping caches for different memory blocks (for AllocVec/FreeVec emulation) makes the memory management more efficient, faster, and generally more future-proof than the old free list approach used in AmigaOS 3.x and earlier.&lt;br /&gt;
&lt;br /&gt;
See Wikipedia for more information on [http://en.wikipedia.org/wiki/Slab_allocation slab allocator systems].&lt;br /&gt;
&lt;br /&gt;
=== Physical page allocation ===&lt;br /&gt;
&lt;br /&gt;
Every memory location in a computer system has its own, unique address. That is, there is a byte location at address x where you can store and retrieve a single byte. This address is fixed; there is no way to change it without physically changing the hardware. Therefore, this address is called the “physical” address.&lt;br /&gt;
&lt;br /&gt;
The physical address of a memory page is most often completely irrelevant to the application. The CPU will typically only see the virtual address. AmigaOS will take care of assigning virtual addresses to memory paging. This is often called “mapping” a page. Only in very special cases will the physical address be relevant. For example, device drivers that want to pass memory to a hardware device via DMA. Since the MMU is part of the CPU, any external hardware like an IDE controller will not see the virtual but only physical addresses.&lt;br /&gt;
&lt;br /&gt;
A common operation in memory allocation is the assignment of virtual addresses to physical memory locations. Allocation of physical memory is usually done differently from virtual allocations, since it&#039;s necessary to free up only part of the allocation (when for example the pager kicks in).&lt;br /&gt;
&lt;br /&gt;
The de-facto standard in allocation of physical pages is a method invented by Knuth, called [http://en.wikipedia.org/wiki/Buddy_memory_allocation the &amp;quot;buddy system&amp;quot;]. Basically every modern operating system uses it and AmigaOS is no exception.&lt;br /&gt;
&lt;br /&gt;
Buddy systems are in essence size-segregated free lists. To allocate, the system searches for a free block of at least the size of the allocation. Then, if the block is too large, it&#039;s split into two even-sized blocks. These blocks are called &amp;quot;buddies&amp;quot;. One block is returned to it&#039;s appropriate free list, and the other is considered further, maybe splitting it further until it&#039;s size matches that of the allocation.&lt;br /&gt;
&lt;br /&gt;
In a buddy system, it&#039;s easy to determine whether the &amp;quot;buddy&amp;quot; is free or not, because it&#039;s address can simply be decided based on the address of the block to be freed.&lt;br /&gt;
&lt;br /&gt;
=== Virtual address space allocation ===&lt;br /&gt;
&lt;br /&gt;
Most CPUs come with a special unit that is called a &amp;quot;memory management unit&amp;quot; or &amp;quot;MMU&amp;quot; for short. The MMU&#039;s primary job is to rearrange the physical memory within the 4 gigabytes of address space in a way that is convenient for the operating system and/or applications. To do that effectively, it divides the memory into blocks (called &amp;quot;pages&amp;quot;). For every page the MMU has an entry in a table that specifies where the CPU should &amp;quot;see&amp;quot; this page, and what special attributes the page has. The address where the CPU &amp;quot;sees&amp;quot; this page is a 32 bit address as well, but since the memory is not really located there, we call that a &amp;quot;virtual&amp;quot; address.&lt;br /&gt;
&lt;br /&gt;
AmigaOS uses a resource map allocator for allocating virtual address space. Basically this is a means of managing a set of resources (not necessarily memory). For performance reason, it uses several optimization techniques.&lt;br /&gt;
&lt;br /&gt;
For one, all free resource blocks are held in space-segregated lists, i.e. there is a list for each power-of-two resource group. This makes allocations a lot faster by providing an upper and lower bound for a search. For example, if you want to allocate a block of 2^10 bytes, you can basically skip searching any block below 2^10 bytes in size simply because it won&#039;t fit. Similarly, you don&#039;t need to search for blocks that are larger than, say, twice the size of the block, since there might still be blocks of a size near to what we need. Size-segregated free lists help narrow down the search, making the search itself faster and the result better in terms of fragmentation.&lt;br /&gt;
&lt;br /&gt;
In addition, the resource maps use object caches for accelerating &amp;quot;small&amp;quot; allocations. Most allocations are below a certain size. For example, the virtual addresses are always allocated in chunks of at least one &amp;quot;page&amp;quot; in memory (4096 bytes). So it&#039;s common to allocate blocks of one, two, four, or eight pages. The object caches provide an easy method for keeping these common sizes, making every allocation of these sizes an exact fit, further reducing fragmentation.&lt;br /&gt;
&lt;br /&gt;
=== Page cache ===&lt;br /&gt;
&lt;br /&gt;
A lot of the time spent in allocating memory was spent looking for the appropriate pages in memory. A 256 MB memory system has 65536 4KB pages. These pages have to be searched for from time to time. Originally, hash tables were used but it turned out that distributing 65536 page entries over a few hash buckets still produced lists of several thousand pages that had to be traversed to find a page. The hash table was replaced with a radix tree. These trees are rather broad, but shallow, making traversal very fast. In usual circumstances, the tree does not grow more than 4 to 5 levels in depth, making searching of a page a matter of maximum 4 to 5 compare operations.&lt;br /&gt;
&lt;br /&gt;
=== Pager ===&lt;br /&gt;
&lt;br /&gt;
AmigaOS has the possibility to swap out parts of memory to disk in order to free up more memory for other applications. This feature allows applications to use more memory than is actually physically installed in the system.&lt;br /&gt;
&lt;br /&gt;
Paging is commonly referred to as &amp;quot;virtual memory&amp;quot; by users and sometimes even software developers. The fact AmigaOS uses virtual memory does not imply the use of the pager.&lt;br /&gt;
&lt;br /&gt;
The system can be tuned to different strategies, either page out only on demand (for highly interactive tasks), or based on other needs (lots of free memory in core for disk caches etc.).&lt;br /&gt;
&lt;br /&gt;
The optimized data structures allow the memory system to operate at a very high speed.&lt;br /&gt;
&lt;br /&gt;
The time for a memory allocation is now in the order of a few microseconds. This is especially true for small allocation (below 8096 bytes). During system testing it was observed that by the time the system has booted up to Workbench, there have already been 40,000 allocations to the global memory pool below 2096 bytes.&lt;br /&gt;
&lt;br /&gt;
== Memory Functions ==&lt;br /&gt;
&lt;br /&gt;
Normally, an application uses the AllocVecTags() function to ask for memory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void *AllocVecTags(uint32 size, uint32 tag1, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The size argument is the amount of memory the application needs and the tag list specifies the type of memory and any special memory characteristics (described later). If AllocVecTags() is successful, it returns a pointer to a block of memory. The memory allocation will fail if the system cannot find a big enough block with the requested attributes. If AllocVecTags() fails, it returns NULL.&lt;br /&gt;
&lt;br /&gt;
Because the system only keeps track of how much free memory is available and not how much is in use, it has no idea what memory has been allocated by any task. This means an application has to explicitly return, or deallocate, any memory it has allocated so the system can reuse it. If an application does not return a block of memory to the system, the system will not be able to reallocate that memory to some other task. That block of memory will be lost until the Amiga is reset. If you are using AllocVecTags() to allocate memory, a call to FreeVec() will return that memory to the system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void FreeVec(void *memoryBlock);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here memoryBlock is a pointer to the memory block the application is returning to the system. The size of the memory block is tracked internally by the system.&lt;br /&gt;
&lt;br /&gt;
Unlike some compiler memory allocation functions, the Amiga system memory allocation functions return memory blocks that are at least longword aligned. This means that the allocated memory will always start on an address which is at least evenly divisible by four. This alignment makes the memory suitable for any system structures or buffers which require word or long word alignment, and also provides optimal alignment for stacks and memory copying.&lt;br /&gt;
&lt;br /&gt;
=== Memory Types ===&lt;br /&gt;
&lt;br /&gt;
There are three primary types of memory in AmigaOS which are summarized in the following table:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Use&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE&lt;br /&gt;
| This memory is private and only accessible within the context of the Task which allocated it. Private memory should always be preferred. Private memory is also swappable by default (i.e. not locked). This memory will not be visible to any other address space.&lt;br /&gt;
	In a future version of AmigaOS, it is planned to have Task specific address spaces. This means each task could potentially address up to 4 GB of private memory each.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED&lt;br /&gt;
| The memory is shared and accessible by any Task in the system without restriction. This memory can be shared between all address spaces and will always appear at the same address in any address space. Shared memory is locked by default and thus is not swappable.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE&lt;br /&gt;
| The memory is used to store executable PowerPC code. This is used two-fold in AmigaOS. First, it allows the system to determine if a function pointer points to real native PowerPC code as opposed to 68k code which needs to be emulated. Second, it prevents common exploits that use stack overflows to execute malicious code. Executable memory is locked by default and thus is not swappable.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory Attributes ===&lt;br /&gt;
&lt;br /&gt;
Memory allocation on AmigaOS has traditionally been rather complicated. Over time, as new hardware models were released, more memory attribute flags were introduced.&lt;br /&gt;
&lt;br /&gt;
All the various memory allocation functions and strategies have been consolidated and distilled into a single AllocVecTags() function call. Programmers are strongly encouraged to stop using any other function to allocate system memory. Using AllocVecTags() is the only way to guarantee future compatibility with more advanced AmigaOS features yet to come.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any attributes when allocating memory, the system tries to satisfy the request with the fastest memory available on the system memory lists.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make Sure You Have Memory|text=Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.}}&lt;br /&gt;
&lt;br /&gt;
==== Using Tags ====&lt;br /&gt;
&lt;br /&gt;
The AllocVecTags() function uses a tag list to define what attributes a block of memory must have. The currently supported tags are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Tag (Default)&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | AVT_Type&lt;br /&gt;
	(MEMF_PRIVATE)&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE: Allocate from the task private heap. This memory will not be visible to any other address space.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED: Allocate from the system shared heap. This memory can be shared between all address spaces and will always appear at the same address in any address space. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE: Allocate memory that is marked executable. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Contiguous&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| Memory allocated with this property is allocated from a contiguous block of physical memory. This makes the memory suitable for DMA purposes when the DMA device does not support scatter/gather operation. For devices that do support scatter/gather use StartDMA() instead.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Physical pages can move at any time, be removed from memory due to paging or otherwise made unavailable unless the memory pages are locked (see LockMemory() function or AVT_Lock tag).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Lock&lt;br /&gt;
	(TRUE or FALSE)&lt;br /&gt;
| After allocating memory, lock the associated pages in memory. This will prevent the pages from being moved, swapped out or otherwise being made unavailable. This is useful in conjunction with the AVT_Contiguous tag since it will ensure that the memory will stay contiguous after allocation.&lt;br /&gt;
	This tag defaults to FALSE for MEMF_PRIVATE allocations and to TRUE for MEMF_SHARED or MEMF_EXECUTABLE.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Alignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block. The returned memory block will be aligned to the given size (in bytes). It&#039;s virtual address will be at least a multiple of the AVT_Alignment value.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_PhysicalAlignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block physical address. See AVT_Alignment for more information.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; This functionality is mainly used for DMA drivers that require a specific alignment. Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_ClearWithValue&lt;br /&gt;
| Clear the newly allocated memory with the given byte value. If this tag is not given the memory block is not cleared.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Wait&lt;br /&gt;
	(TRUE)&lt;br /&gt;
| Wait for the memory subsystem to be available. If TRUE, the calling task will be retired until the memory subsystem is available. This might cause a Forbid() to break. When FALSE and the memory subsystem is not currently available, the function will return immediately and the allocation will fail.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_NoExpunge&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| If allocation fails because of unavailability, prevent invocation of low memory cleanup handlers. The default is FALSE which means that when memory is not available, cleanup handlers are invoked to try and satisfy the allocation.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Using Flags ====&lt;br /&gt;
&lt;br /&gt;
The use of memory flags was the only supported way to allocate memory prior to AmigaOS 4.0. It may be still useful to know more about this obsolete system for porting older applications. For more information about the memory flags system see [[Obsolete Exec Memory Allocation]].&lt;br /&gt;
&lt;br /&gt;
=== Allocating System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples show how to allocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR apointer = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (apointer == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocVecTags() returns the address of the first byte of a memory block that is at least 100 bytes in size or NULL if there is not that much free memory. Because there are no tags specified, private memory is assumed so this memory cannot be shared with any other tasks.&lt;br /&gt;
&lt;br /&gt;
In addition to allocating a block of memory, this function keeps track of the size of the memory block, so your application doesn&#039;t have to remember it when it deallocates that memory block. The AllocVecTags() function allocates a little more memory to store the size of the memory allocation request.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make No Assumptions|It is not legal to peek the longword in front of the returned memory pointer to find out how big the block is. This has always been illegal regardless of what any other documentation may have stated to the contrary. Your application has access to the memory returned by AllocVecTags() and the only guarantee made is that the returned block has n bytes of continuous address space starting at the returned address. Anything outside this area is not to be touched.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR anotherptr = IExec-&amp;gt;AllocVecTags(1000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  AVT_Lock, FALSE,&lt;br /&gt;
  AVT_ClearWithValue, 0,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (anotherptr == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocates shared memory which is accessible by any task in the system and clears the memory contents to zero. MEMF_SHARED memory is locked by default for compatibility with the obsolete MEMF_PUBLIC flag. This is by far the most common case when allocating shared memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR lockedmem = IExec-&amp;gt;AllocVecTags(3000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (lockedmem == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocated shared memory which is accessible by any task in the system. MEMF_SHARED memory is locked by default so the underlying memory pages are not moveable and cannot be swapped out. Such memory could be used to share memory between a Process and an interrupt routine for example.&lt;br /&gt;
&lt;br /&gt;
If the system free memory list does not contain enough contiguous memory bytes in an area matching your requirements, AllocVecTags() returns a zero. You &#039;&#039;must&#039;&#039; check for this condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR yap = IExec-&amp;gt;AllocVec(500, MEMF_CHIP);&lt;br /&gt;
&lt;br /&gt;
if (yap == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The deprecated AllocVec() function is used in the example above because it is the only way to allocate MEMF_CHIP memory on a classic Amiga system.&lt;br /&gt;
&lt;br /&gt;
=== Locking System Memory ===&lt;br /&gt;
&lt;br /&gt;
The LockMem() function is used to explicitly lock a memory block. This function will make sure your memory block is not unmapped, swapped out or somehow made inaccessible. Use this function wisely. If there is no good reason to lock memory then do not do it. It will prevent the memory system from optimizing memory layout and may lead to poor performance. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;LockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before deallocating the memory is must be unlocked as well.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;UnLockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Do not forget to UnlockMem()&#039;&#039;|Failure to unlock memory will have adverse affects on the memory system. Locked memory pages cannot be moved so the system cannot optimize the layout of locked memory pages.}}&lt;br /&gt;
&lt;br /&gt;
Be careful to always match the number of locks and the number of unlocks. If something else may have locked memory in the same page and an extraneous UnlockMem() decreases the lock reference counter to 0, that page can be paged out or moved. If that happens, for example, with data used in an interrupt handler or by the device driver which handles the swap partition the system will crash.&lt;br /&gt;
&lt;br /&gt;
=== Freeing System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples free the memory chunks shown in the previous calls to AllocVecTags().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;FreeVec(apointer);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(anotherptr);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(lockedmem);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(yap);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A memory block allocated with AllocVecTags() or AllocVec() must be returned to the system pool with the FreeVec() function. This function uses the stored size in the allocation to free the memory block, so there is no need to specify the size of the memory block to free.&lt;br /&gt;
&lt;br /&gt;
Any memory that is locked must be explicitly unlocked with UnlockMem() prior to freeing it. Failure to unlock memory will not cause any immediate problems but any pages which are locked cannot be moved or swapped out which can decrease system performance. Never unlock memory which has not been explicitly locked with LockMem() as it could lead to undefined behaviour.&lt;br /&gt;
&lt;br /&gt;
FreeVec() returns no status. However, if you attempt to free a memory block in the middle of a chunk that the system believes is already free, you will cause a system crash. It is also illegal to free the same memory block twice and this will lead to a system crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Leave Memory Allocations Out Of Interrupt Code&#039;&#039;|text=Do not allocate or deallocate system memory from within interrupt code. The [[Exec_Interrupts|Exec Interrupts]] section explains that an interrupt may occur at any time, even during a memory allocation process. As a result, system data structures may not be internally consistent at this time.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory may be Locked by Default ===&lt;br /&gt;
&lt;br /&gt;
By default, the system memory allocation routines will allocate MEMF_SHARED and MEMF_EXECUTABLE memory and implicitly lock it. This memory must not be explicitly unlocked. Let the system handle the unlocking internally.&lt;br /&gt;
&lt;br /&gt;
Here is the right way to allocate and free MEMF_SHARED memory:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is the wrong way:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;UnlockMem(ptr, 10);  // Doing this could lead to undefined system behaviour.&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlocking memory not explicitly locked does not immediately cause any issues. However, in a future version of AmigaOS the memory pages may be handled differently than they are handled today. This could lead to incompatibilities with your applications and AmigaOS. The simple rule is that if you called LockMem() you must also call UnlockMem(). All other times you should not call UnlockMem().&lt;br /&gt;
&lt;br /&gt;
{{Note|Memory locking is done at the page level. Even if a single byte of memory is locked in a page that entire page is locked. The programmer has no control over which pages may be used to satisfy a memory allocation.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Shared and executable memory is locked implicitly for 68K backwards compatibility reasons. Use AVT_Lock (or equivalent) to ensure your shared and executable memory is not locked. MEMF_PRIVATE memory has no backwards compatibility issues and is always unlocked by default.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory Information Functions ===&lt;br /&gt;
&lt;br /&gt;
The memory information routines AvailMem() and TypeOfMem() can provide the amount of memory available in the system, and the attributes of a particular block of memory.&lt;br /&gt;
&lt;br /&gt;
==== Memory Requirements ====&lt;br /&gt;
&lt;br /&gt;
The same attribute flags used in memory allocation routines are valid for the memory information routines. There is also an additional flag, MEMF_LARGEST, which can be used in the AvailMem() routine to find out what the largest available memory block of a particular type is. Specifying the MEMF_TOTAL flag will return the total amount of memory currently available.&lt;br /&gt;
&lt;br /&gt;
==== Calling Memory Information Functions ====&lt;br /&gt;
&lt;br /&gt;
The following example shows how to find out how much memory of a particular type is available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 size = IExec-&amp;gt;AvailMem(MEMF_CHIP | MEMF_LARGEST);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AvailMem() returns the size of the largest chunk of available chip memory.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;AvailMem() May Not Be Totally Accurate&#039;&#039;|text=Because of multitasking, the return value from AvailMem() may be inaccurate by the time you receive it.}}&lt;br /&gt;
&lt;br /&gt;
The following example shows how to determine the type of memory of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 memtype = IExec-&amp;gt;TypeOfMem((APTR)0x090000);&lt;br /&gt;
if ((memtype &amp;amp; MEMF_CHIP) == MEMF_CHIP) {  /*  ... It&#039;s chip memory ...  */   }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
TypeOfMem() returns the attributes of the memory at a specific address. If it is passed an invalid memory address, TypeOfMem() returns NULL. This routine is normally used to determine if a particular chunk of memory is in chip memory.&lt;br /&gt;
&lt;br /&gt;
=== Using Memory Copy Functions ===&lt;br /&gt;
&lt;br /&gt;
For memory block copies, the CopyMem() and CopyMemQuick() functions can be used.&lt;br /&gt;
&lt;br /&gt;
==== Copying System Memory ====&lt;br /&gt;
&lt;br /&gt;
The following samples show how to use the copying routines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR source = IExec-&amp;gt;AllocVecTags(1000, AVT_ClearWithValue, 0, TAG_END);&lt;br /&gt;
APTR target = IExec-&amp;gt;AllocVecTags(1000, AVT_Type, MEMF_SHARED, TAG_END);&lt;br /&gt;
IExec-&amp;gt;CopyMem(source, target, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CopyMem() copies the specified number of bytes from the source data region to the target data region. The pointers to the regions can be aligned on arbitrary address boundaries. CopyMem() will attempt to copy the memory as efficiently as it can according to the alignment of the memory blocks, and the amount of data that it has to transfer. These functions are optimized for copying large blocks of memory which can result in unnecessary overhead if used to transfer very small blocks of memory.&lt;br /&gt;
&lt;br /&gt;
CopyMemQuick() is now identical to CopyMem(). In previous versions of the operating system, CopyMemQuick() performed more optimized copying of the specified number of bytes from the source data region to the target data region. The source and target pointers must be longword aligned and the size (in bytes) must be divisible by four. There are no such restrictions starting with AmigaOS 4.0.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Not All Copies Are Supported&#039;&#039;|text=Neither CopyMem() nor CopyMemQuick() supports copying between regions that overlap. For overlapping regions see MoveMem() in the [[Utility Library]].}}&lt;br /&gt;
&lt;br /&gt;
=== System Memory Pools ===&lt;br /&gt;
&lt;br /&gt;
A construct carried over from the AmigaOS 3.x Exec is the memory pool. The code handling memory pools uses an algorithm based on boundary tags and size-segregated memory lists. The speed gain is tremendous: even for the &amp;quot;dumb&amp;quot; case of just allocating 100000 blocks and freeing them again, the speed is ten times faster than the previous implementation. Due to the size-segregated free lists and the easy coalescing due to the boundary tags, real-life performance gain is even higher and would be between 10 and 20 times.&lt;br /&gt;
&lt;br /&gt;
Two types of memory pools are available depending on the needs of the application:&lt;br /&gt;
* [[Exec_Item_Pools|Item Pools]] are built for speed and are used for allocating large numbers of items that are all the same size.&lt;br /&gt;
* Generic [[Exec_Memory_Pools|Memory Pools]] can handle allocations of different sizes at the expense of some speed.&lt;br /&gt;
&lt;br /&gt;
=== Summary of System Controlled Memory Handling Routines ===&lt;br /&gt;
&lt;br /&gt;
; AllocVecTags() and FreeVec()&lt;br /&gt;
: These are system-wide memory allocation and deallocation routines. They use a memory free-list owned and managed by the system.&lt;br /&gt;
&lt;br /&gt;
; LockMem() and UnlockMem()&lt;br /&gt;
: These routines explicitly lock and unlock underlying memory pages.&lt;br /&gt;
&lt;br /&gt;
; AvailMem()&lt;br /&gt;
: This routine returns the number of free bytes in a specified type of memory.&lt;br /&gt;
&lt;br /&gt;
; TypeOfMem()&lt;br /&gt;
: This routine returns the memory attributes of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
; CopyMem() and CopyMemQuick()&lt;br /&gt;
: CopyMem() is a general purpose memory copy routine. CopyMemQuick() is an optimized version of CopyMemQuick(), but has restrictions on the size and alignment of the arguments.&lt;br /&gt;
&lt;br /&gt;
== Allocating DMA Memory ==&lt;br /&gt;
&lt;br /&gt;
Device drivers often use DMA to transfer data to and from the device. The StartDMA(), GetDMAList() and EndDMA() functions are used to create a scatter/gather list suitable for such DMA transfers.&lt;br /&gt;
&lt;br /&gt;
The following conditions are guaranteed to be met when using these DMA functions:&lt;br /&gt;
* The memory region given is guaranteed to be mapped to physical memory.&lt;br /&gt;
* The mapping will not change as long as EndDMA() is not called.&lt;br /&gt;
* All cache entries in this region will be flushed out.&lt;br /&gt;
&lt;br /&gt;
The example code below demonstrates how to perform a DMA write transfer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR addr;&lt;br /&gt;
uint32 size;&lt;br /&gt;
&lt;br /&gt;
/* Tell the system to prepare for DMA */&lt;br /&gt;
uint32 arraySize = IExec-&amp;gt;StartDMA(addr, size, 0);&lt;br /&gt;
if (arraySize &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    /* The memory area is prepared, allocate and retrieve the DMA list */&lt;br /&gt;
    struct DMAEntry *DMAList = IExec-&amp;gt;AllocSysObject(ASOT_DMAENTRY,&lt;br /&gt;
      ASODMAE_NumEntries,  arraySize,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (DMAList != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        IExec-&amp;gt;GetDMAList(addr, size, 0, DMAList);&lt;br /&gt;
 &lt;br /&gt;
        /* Feed the DMA controller and do stuff */&lt;br /&gt;
        ...&lt;br /&gt;
        /* Get rid of the DMAList&#039;s memory */&lt;br /&gt;
 &lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, endFlags);&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_DMAENTRY, DMAList);&lt;br /&gt;
    }&lt;br /&gt;
    else&lt;br /&gt;
    {&lt;br /&gt;
        // Note We still call EndDMA() even though the actual&lt;br /&gt;
        // transfer didn&#039;t happen.&lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, DMAF_NoModify);&lt;br /&gt;
&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t allocate DMA list\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
else&lt;br /&gt;
{&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t initiate DMA transfer\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syncing and Memory Access ==&lt;br /&gt;
&lt;br /&gt;
The PowerPC is a pure load/store architecture. That means, it will never operate on memory arguments like x86, to modify data, you have to load, modify and store.&lt;br /&gt;
&lt;br /&gt;
All PowerPC/POWER cpus have a load/store queue, i.e. a queue where load and store operations are queued to reduce memory latency. If more than one store is made to the same long word (i.e. you write the first byte and then the second byte), then those stores are combined. As you can see, this will cause a problem for a chip register since both are written at the same time which is likely not what you want.&lt;br /&gt;
&lt;br /&gt;
Similar for reading: A read might shortcut through the load/store queue and use a value that&#039;s already been read and is present in the load/store queue. Again, for chip registers, this will cause a problem because you might read an old value.&lt;br /&gt;
&lt;br /&gt;
There are two instructions that deal with this: &#039;&#039;&#039;eieio&#039;&#039;&#039; (Ensure In-order Execution of I/O) and &#039;&#039;&#039;sync&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The eieio instruction simply inserts a &amp;quot;barrier&amp;quot; into the load/store queue. When combining &#039;&#039;stores&#039;&#039; the CPU never searches past this barrier for possible combines. This means that the sequence&lt;br /&gt;
&lt;br /&gt;
# store to some address&lt;br /&gt;
# eieio&lt;br /&gt;
# store to some address + 1&lt;br /&gt;
&lt;br /&gt;
will never be combined because of the eieio barrier between them.&lt;br /&gt;
&lt;br /&gt;
The sync instruction will simply halt all execution and flush the load/store queue, executing and finishing all loads and stores.&lt;br /&gt;
&lt;br /&gt;
As you can imagine, sync is MUCH MORE costly than eieio.&lt;br /&gt;
&lt;br /&gt;
=== When to use eieio and sync ===&lt;br /&gt;
&lt;br /&gt;
If you want to write, post fix your writes with an eieio instruction.&lt;br /&gt;
&lt;br /&gt;
If you want to read, prefix your reads with a sync instruction.&lt;br /&gt;
&lt;br /&gt;
This is also where the &amp;quot;GUARDED&amp;quot; memory flag comes in. GUARDED memory simply ensures program order of loads and stores, that is, it&#039;s an implicit eieio.&lt;br /&gt;
&lt;br /&gt;
{{Note|Knowing when and how to use the &#039;&#039;&#039;eieio&#039;&#039;&#039; and &#039;&#039;&#039;sync&#039;&#039;&#039; instructions is somewhat vital for driver developers.}}&lt;br /&gt;
&lt;br /&gt;
== Allocating Multiple Memory Blocks ==&lt;br /&gt;
&lt;br /&gt;
Exec provides the routines AllocTaskMemEntry() and FreeEntry() to allocate multiple memory blocks in a single call.&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() accepts a data structure called a MemList, which contains the information about the size of the memory blocks to be allocated and the requirements, if any, that you have regarding the allocation.&lt;br /&gt;
&lt;br /&gt;
The MemList structure is found in the include file &amp;lt;exec/memory.h&amp;gt; and is defined as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList&lt;br /&gt;
{&lt;br /&gt;
    struct Node     ml_Node;&lt;br /&gt;
    UWORD           ml_NumEntries;      /* number of MemEntrys */&lt;br /&gt;
    struct MemEntry ml_ME[1];           /* where the MemEntrys begin*/&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ml_Node&lt;br /&gt;
: allows you to link together multiple MemLists. However, the node is ignored by the routines AllocTaskMemEntry() and FreeEntry().&lt;br /&gt;
&lt;br /&gt;
; ml_NumEntries&lt;br /&gt;
: tells the system how many MemEntry sets are contained in this MemList. Notice that a MemList is a variable-length structure and can contain as many sets of entries as you wish.&lt;br /&gt;
&lt;br /&gt;
The MemEntry structure looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemEntry&lt;br /&gt;
{&lt;br /&gt;
    union {&lt;br /&gt;
        ULONG   meu_Reqs;   /* the AllocMem requirements */&lt;br /&gt;
        APTR    meu_Addr;   /* address of your memory */&lt;br /&gt;
        } me_Un;&lt;br /&gt;
    ULONG   me_Length;      /* the size of this request */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sample Code for Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an example of showing how to use the AllocTaskMemEntry() with multiple blocks of memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// alloctaskmementry.c - example of allocating several memory areas.&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MemList *memlist;             /* pointer to a MemList structure        */&lt;br /&gt;
&lt;br /&gt;
struct MemBlocks /* define a new structure because C cannot initialize unions */&lt;br /&gt;
{&lt;br /&gt;
    struct MemList  mn_head;         /* one entry in the header               */&lt;br /&gt;
    struct MemEntry mn_body[3];      /* additional entries follow directly as */&lt;br /&gt;
} memblocks;                         /* part of the same data structure       */&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    memblocks.mn_head.ml_NumEntries = 4; /* 4! Since the MemEntry starts at 1! */&lt;br /&gt;
&lt;br /&gt;
    /* Describe the first piece of memory we want.  Because of our MemBlocks structure */&lt;br /&gt;
    /* setup, we reference the first MemEntry differently when initializing it.        */&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Reqs   = MEMF_CLEAR;&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Length = 4000;&lt;br /&gt;
&lt;br /&gt;
    memblocks.mn_body[0].me_Reqs   = MEMF_PRIVATE | MEMF_CLEAR;/* Describe the other pieces of    */&lt;br /&gt;
    memblocks.mn_body[0].me_Length = 100000;                   /* memory we want. Additional      */&lt;br /&gt;
    memblocks.mn_body[1].me_Reqs   = MEMF_SHARED | MEMF_CLEAR; /* MemEntries are initialized this */&lt;br /&gt;
    memblocks.mn_body[1].me_Length = 200000;                   /* way. If we wanted even more en- */&lt;br /&gt;
    memblocks.mn_body[2].me_Reqs   = MEMF_EXECUTABLE;          /* tries, we would need to declare */&lt;br /&gt;
    memblocks.mn_body[2].me_Length = 25000;                    /* a larger MemEntry array in our  */&lt;br /&gt;
                                                               /* MemBlocks structure.            */&lt;br /&gt;
&lt;br /&gt;
    memlist = (struct MemList *)IExec-&amp;gt;AllocTaskMemEntry((struct MemList *)&amp;amp;memblocks);&lt;br /&gt;
&lt;br /&gt;
    if (memlist == NULL)&lt;br /&gt;
    {&lt;br /&gt;
       IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry FAILED\n&amp;quot;);&lt;br /&gt;
       return RETURN_FAIL;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    /* We got all memory we wanted.  Use it and call FreeEntry() to free it */&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry succeeded - now freeing all allocated blocks\n&amp;quot;);&lt;br /&gt;
    IExec-&amp;gt;FreeEntry(memlist);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() returns a pointer to a new MemList of the same size as the MemList that you passed to it. For example, ROM code can provide a MemList containing the requirements of a task and create a RAM-resident copy of the list containing the addresses of the allocated entries. The pointer to the MemList is used as the argument for FreeEntry() to free the memory blocks.&lt;br /&gt;
&lt;br /&gt;
=== Result of Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
The MemList created by AllocTaskMemEntry() contains MemEntry entries. MemEntrys are defined by a union statement, which allows one memory space to be defined in more than one way.&lt;br /&gt;
&lt;br /&gt;
If AllocTaskMemEntry() returns a non-NULL value then all of the meu_Addr positions in the returned MemList will contain valid memory addresses meeting the requirements you have provided. To use this memory area, you would use code similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *mlist = IExec-&amp;gt;AllocTaskMemEntry(&amp;amp;ML);&lt;br /&gt;
APTR memory = NULL;&lt;br /&gt;
&lt;br /&gt;
if ( mlist != NULL )&lt;br /&gt;
{&lt;br /&gt;
  memory = mlist-&amp;gt;ml_ME[0].me_Un.meu_Addr;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Multiple Memory Blocks and Tasks ===&lt;br /&gt;
&lt;br /&gt;
If you want to take advantage of Exec&#039;s automatic cleanup, use the MemList and AllocTaskMemEntry() facility to do your dynamic memory allocation.&lt;br /&gt;
&lt;br /&gt;
In the Task control block structure, there is a list header named tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
This is the list header that you initialize to include MemLists that your task has created by call(s) to AllocTaskMemEntry(). Here is a short program segment that handles task memory list header initialization only. It assumes that you have already run AllocTaskMemEntry() as shown in the simple AllocTaskMemEntry() example above.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *ml;&lt;br /&gt;
&lt;br /&gt;
struct Task *tc = IExec-&amp;gt;FindTask(0);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;AddTail(tc-&amp;gt;tc_MemEntry, ml);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Assuming that you have only used the AllocTaskMemEntry() method (or AllocVecTags() and built your own custom MemList), the system now knows where to find the blocks of memory that your task has dynamically allocated. The RemTask() function automatically frees all memory found on tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;CreateTask() Sets Up A MemList.&#039;&#039;|text=The CreateTask() function, and other system task and process creation functions use a MemList in tc_MemEntry so that the Task structure and stack will be automatically deallocated when the Task is removed.}}&lt;br /&gt;
&lt;br /&gt;
=== Summary of Multiple Memory Blocks Allocation Routines ===&lt;br /&gt;
&lt;br /&gt;
These are routines for allocating and freeing multiple memory blocks with a single call.&lt;br /&gt;
&lt;br /&gt;
This routine initializes memory from data and offset values in a table. Typically only assembly language programs benefit from using this routine. See the SDK for more details.&lt;br /&gt;
&lt;br /&gt;
== Allocating Memory at an Absolute Address ==&lt;br /&gt;
&lt;br /&gt;
For special advanced applications, AllocAbs() is provided. Using AllocAbs(), an application can allocate a memory block starting at a specified absolute memory address. If the memory is already allocated or if there is not enough memory available for the request, AllocAbs() returns a zero.&lt;br /&gt;
&lt;br /&gt;
Be aware that an absolute memory address which happens to be available on one Amiga may not be available on a machine with a different configuration or different operating system revision, or even on the same machine at a different times. For example, a piece of memory that is available during expansion board configuration might not be available at earlier or later times. Here is an example call to AllocAbs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR absoluteptr = (APTR)IExec-&amp;gt;AllocAbs(10000, 0x2F0000);&lt;br /&gt;
if (!(absoluteptr))&lt;br /&gt;
    { /* Couldn&#039;t get memory, act accordingly. */  }&lt;br /&gt;
&lt;br /&gt;
/* After we&#039;re done using it, we call FreeMem() to free the memory block. */&lt;br /&gt;
IExec-&amp;gt;FreeMem(absoluteptr, 10000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of the Exec functions that handle memory management. See the SDK for details on each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Memory Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AllocMem()&lt;br /&gt;
| Allocate memory with specified attributes. &#039;&#039;&#039;This function is obsolete.&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| AllocAbs()&lt;br /&gt;
| Allocate memory at a specified location.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTaskMemEntry()&lt;br /&gt;
| Allocate multiple memory blocks.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVec()&lt;br /&gt;
| Allocate memory with specified attributes and keep track of the size. This function is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVecTags()&lt;br /&gt;
| Allocate memory with specified attributes defined by tags and keep track of the size. If an application needs to allocate some memory, it will usually use this function.&lt;br /&gt;
|-&lt;br /&gt;
| AvailMem()&lt;br /&gt;
| Return the amount of free memory, given certain conditions.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMem()&lt;br /&gt;
| Copy memory block, which can be non-aligned and of arbitrary length.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMemQuick()&lt;br /&gt;
| Copy aligned memory block.&lt;br /&gt;
|-&lt;br /&gt;
| FreeEntry()&lt;br /&gt;
| Free multiple memory blocks, allocated with AllocTaskMemEntry().&lt;br /&gt;
|-&lt;br /&gt;
| FreeMem()&lt;br /&gt;
| Free a memory block of specified size, allocated with AllocMem() or AllocAbs().&lt;br /&gt;
|-&lt;br /&gt;
| FreeVec()&lt;br /&gt;
| Free a memory block allocated with AllocVecTags() or AllocVec().&lt;br /&gt;
|-&lt;br /&gt;
| InitStruct()&lt;br /&gt;
| Initialize memory from a table.&lt;br /&gt;
|-&lt;br /&gt;
| LockMem()&lt;br /&gt;
| Lock the underlying pages given a memory block address and size.&lt;br /&gt;
|-&lt;br /&gt;
| TypeOfMem()&lt;br /&gt;
| Determine attributes of a specified memory address.&lt;br /&gt;
|-&lt;br /&gt;
| UnlockMem()&lt;br /&gt;
| Unlock the underlying pages given a memory block address and size.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12382</id>
		<title>Exec Memory Allocation</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12382"/>
		<updated>2023-11-04T15:13:09Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Exec Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
Exec manages all of the free memory currently available in the system. Using a slab allocation system, Exec keeps track of memory and provides the functions to allocate and access it.&lt;br /&gt;
&lt;br /&gt;
When an application needs some memory, it can either declare the memory statically within the program or it can ask Exec for some memory. When Exec receives a request for memory, it searches its free memory regions to find a suitably sized block that matches the size and attributes requested.&lt;br /&gt;
&lt;br /&gt;
Prior to AmigaOS 4.0, the OS did not make use of the CPU&#039;s memory management unit and used memory &amp;quot;as-is&amp;quot;. That is, if you have different memory expansions plugged into your system, the memory will be seen as chunks located somewhere in the 4 gigabyte address space. Since version 4.0, the MMU will be used to &amp;quot;map&amp;quot; memory pages from their physical location to a virtual address. There are multiple reasons why this is better than using the verbatim physical addresses - among other things it reduces the effect of &amp;quot;memory fragmentation&amp;quot; and simplifies the possibility to swap currently unused memory pages to persistent storage such as a hard disk.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;downside&amp;quot; is that the virtual address of a memory block is almost never identical to the physical address. This isn&#039;t much of a downside, since an application will never really need to care about it. If an application allocates a block of memory of n bytes, it will get a pointer back that points to at least n continuous addresses as expected. The pages that &amp;quot;fill&amp;quot; this memory block may come from different physical locations scattered throughout the physical memory but a program will never noticed that. For all intent and purpose, the application sees a single continuous block of memory.&lt;br /&gt;
&lt;br /&gt;
=== Program Address Space ===&lt;br /&gt;
&lt;br /&gt;
It is important to remember that, just like in classic AmigaOS, a single address space is used for all programs. Sometimes the mention of an MMU can lead people to assume that each process on the Amiga will have its own personal, partitioned address space. The following two programs demonstrate that, even though they are separate processes, it is possible to read and write another&#039;s memory. The memory locations are the same virtual address and that virtual address maps onto the same physical address.&lt;br /&gt;
&lt;br /&gt;
The following demonstrates how it is possible to read/write memory that is &amp;quot;owned&amp;quot; by a different process:&lt;br /&gt;
&lt;br /&gt;
This program has a global variable. It prints out the virtual address of the global, waits for a keypress and then prints out the address again in case it has been externally updated:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
volatile char x = 128;&lt;br /&gt;
&lt;br /&gt;
/* Usage: a [VAL] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(argc==2)&lt;br /&gt;
        x = (char)atoi(argv[1]);&lt;br /&gt;
&lt;br /&gt;
    printf(&amp;quot;Virtual Address    of `x&#039;: %p\n&amp;quot;, (void*)&amp;amp;x);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    (void)getchar();&lt;br /&gt;
    printf(&amp;quot;Final Value        of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This program reads in an address as an argument and reads the value at that location even though it does not &amp;quot;own&amp;quot; the memory. By adding an additional argument, this program can also write to that &amp;quot;foreign&amp;quot; address:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* Usage: b ADDR [VAL_TO_WRITE] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(!(argc==2 || argc==3))&lt;br /&gt;
        return 10;&lt;br /&gt;
&lt;br /&gt;
    volatile char *byte = (volatile char*)strtol(argv[1],NULL,16);&lt;br /&gt;
    printf(&amp;quot;Selected Virtual Address    : %p\n&amp;quot;,(void*)byte);&lt;br /&gt;
    printf(&amp;quot;Dereferencd Value of Address: %d\n&amp;quot;,*byte);&lt;br /&gt;
    if(argc==3)&lt;br /&gt;
    {&lt;br /&gt;
        printf(&amp;quot;Writing Value `%d&#039; to Address: %p\n&amp;quot;,atoi(argv[2]),(void*)byte);&lt;br /&gt;
        *byte=(char)atoi(argv[2]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Slab allocation ===&lt;br /&gt;
&lt;br /&gt;
[[File:SlabDiagram.jpg|right]]&lt;br /&gt;
&lt;br /&gt;
The AmigaOS memory architecture is based on the &amp;quot;slab allocator&amp;quot; system or &amp;quot;object cache&amp;quot;. In essence, the slab allocator only allocates objects of a single size, allocating these in larger batches (&amp;quot;slabs&amp;quot;) from the low-level page allocator. These slabs are then divided up into buffers of the required size, and kept within a list in the slab allocator.&lt;br /&gt;
&lt;br /&gt;
Allocating an object with the slab allocator becomes a process of simple node removal: the first node in the first slab containing free nodes is removed and returned for use. Since the slab allocator keeps free slabs or partially free slabs in a separate list from the full slabs, this operation can be carried out in constant time. Freeing memory is accomplished by returning the buffer to its cache, and adding it to its original slab&#039;s free list. Slabs that are completely free can be returned to the system&#039;s page pool (this operation is actually driven by demand, and timestamps are used to avoid unnecessary loading of data, or &amp;quot;thrashing&amp;quot;). External fragmentation is minimal, and internal fragmentation is controlled and guaranteed not to exceed a certain amount.&lt;br /&gt;
&lt;br /&gt;
=== Object caching ===&lt;br /&gt;
&lt;br /&gt;
The slab allocator can also be used to cache objects. In the real world a lot of memory allocation operations will be used to allocate the same object. The system has a number of data structures which are allocated frequently (semaphores, message ports and the like). Every time such structures are allocated, they must be initialised, and when they are deleted again, they must be cleaned up. It&#039;s likely, however, that such a structure will be needed again in the future, so that it can be kept in its initialised state and re-used later. This further reduces the load on the allocator routines, and thus improves system performance.&lt;br /&gt;
&lt;br /&gt;
The object caches work on memory that has already been mapped into the virtual memory space.&lt;br /&gt;
&lt;br /&gt;
=== More Advantages ===&lt;br /&gt;
&lt;br /&gt;
Another advantage is the possibility to improve CPU cache usage. Usually, most objects have &amp;quot;hot spots&amp;quot;, i.e. they have a few fields that are used often. Since most of the time a little memory is left unused in a slab (the object size might not be a multiple of the slab size), this additional memory can be used to &amp;quot;shift&amp;quot; the hot spots by a few bytes to optimise the memory structure, leading to better cache usage.&lt;br /&gt;
&lt;br /&gt;
Finally, the system can be expanded to multiple CPUs with next to no overhead. On multi-CPU system, these expanded slab allocators scale almost linearly with the number of CPUs employed, making it the ideal choice for such systems.&lt;br /&gt;
&lt;br /&gt;
The combination of object caching and keeping caches for different memory blocks (for AllocVec/FreeVec emulation) makes the memory management more efficient, faster, and generally more future-proof than the old free list approach used in AmigaOS 3.x and earlier.&lt;br /&gt;
&lt;br /&gt;
See Wikipedia for more information on [http://en.wikipedia.org/wiki/Slab_allocation slab allocator systems].&lt;br /&gt;
&lt;br /&gt;
=== Physical page allocation ===&lt;br /&gt;
&lt;br /&gt;
Every memory location in a computer system has its own, unique address. That is, there is a byte location at address x where you can store and retrieve a single byte. This address is fixed; there is no way to change it without physically changing the hardware. Therefore, this address is called the “physical” address.&lt;br /&gt;
&lt;br /&gt;
The physical address of a memory page is most often completely irrelevant to the application. The CPU will typically only see the virtual address. AmigaOS will take care of assigning virtual addresses to memory paging. This is often called “mapping” a page. Only in very special cases will the physical address be relevant. For example, device drivers that want to pass memory to a hardware device via DMA. Since the MMU is part of the CPU, any external hardware like an IDE controller will not see the virtual but only physical addresses.&lt;br /&gt;
&lt;br /&gt;
A common operation in memory allocation is the assignment of virtual addresses to physical memory locations. Allocation of physical memory is usually done differently from virtual allocations, since it&#039;s necessary to free up only part of the allocation (when for example the pager kicks in).&lt;br /&gt;
&lt;br /&gt;
The de-facto standard in allocation of physical pages is a method invented by Knuth, called [http://en.wikipedia.org/wiki/Buddy_memory_allocation the &amp;quot;buddy system&amp;quot;]. Basically every modern operating system uses it and AmigaOS is no exception.&lt;br /&gt;
&lt;br /&gt;
Buddy systems are in essence size-segregated free lists. To allocate, the system searches for a free block of at least the size of the allocation. Then, if the block is too large, it&#039;s split into two even-sized blocks. These blocks are called &amp;quot;buddies&amp;quot;. One block is returned to it&#039;s appropriate free list, and the other is considered further, maybe splitting it further until it&#039;s size matches that of the allocation.&lt;br /&gt;
&lt;br /&gt;
In a buddy system, it&#039;s easy to determine whether the &amp;quot;buddy&amp;quot; is free or not, because it&#039;s address can simply be decided based on the address of the block to be freed.&lt;br /&gt;
&lt;br /&gt;
=== Virtual address space allocation ===&lt;br /&gt;
&lt;br /&gt;
Most CPUs come with a special unit that is called a &amp;quot;memory management unit&amp;quot; or &amp;quot;MMU&amp;quot; for short. The MMU&#039;s primary job is to rearrange the physical memory within the 4 gigabytes of address space in a way that is convenient for the operating system and/or applications. To do that effectively, it divides the memory into blocks (called &amp;quot;pages&amp;quot;). For every page the MMU has an entry in a table that specifies where the CPU should &amp;quot;see&amp;quot; this page, and what special attributes the page has. The address where the CPU &amp;quot;sees&amp;quot; this page is a 32 bit address as well, but since the memory is not really located there, we call that a &amp;quot;virtual&amp;quot; address.&lt;br /&gt;
&lt;br /&gt;
AmigaOS uses a resource map allocator for allocating virtual address space. Basically this is a means of managing a set of resources (not necessarily memory). For performance reason, it uses several optimization techniques.&lt;br /&gt;
&lt;br /&gt;
For one, all free resource blocks are held in space-segregated lists, i.e. there is a list for each power-of-two resource group. This makes allocations a lot faster by providing an upper and lower bound for a search. For example, if you want to allocate a block of 2^10 bytes, you can basically skip searching any block below 2^10 bytes in size simply because it won&#039;t fit. Similarly, you don&#039;t need to search for blocks that are larger than, say, twice the size of the block, since there might still be blocks of a size near to what we need. Size-segregated free lists help narrow down the search, making the search itself faster and the result better in terms of fragmentation.&lt;br /&gt;
&lt;br /&gt;
In addition, the resource maps use object caches for accelerating &amp;quot;small&amp;quot; allocations. Most allocations are below a certain size. For example, the virtual addresses are always allocated in chunks of at least one &amp;quot;page&amp;quot; in memory (4096 bytes). So it&#039;s common to allocate blocks of one, two, four, or eight pages. The object caches provide an easy method for keeping these common sizes, making every allocation of these sizes an exact fit, further reducing fragmentation.&lt;br /&gt;
&lt;br /&gt;
=== Page cache ===&lt;br /&gt;
&lt;br /&gt;
A lot of the time spent in allocating memory was spent looking for the appropriate pages in memory. A 256 MB memory system has 65536 4KB pages. These pages have to be searched for from time to time. Originally, hash tables were used but it turned out that distributing 65536 page entries over a few hash buckets still produced lists of several thousand pages that had to be traversed to find a page. The hash table was replaced with a radix tree. These trees are rather broad, but shallow, making traversal very fast. In usual circumstances, the tree does not grow more than 4 to 5 levels in depth, making searching of a page a matter of maximum 4 to 5 compare operations.&lt;br /&gt;
&lt;br /&gt;
=== Pager ===&lt;br /&gt;
&lt;br /&gt;
AmigaOS has the possibility to swap out parts of memory to disk in order to free up more memory for other applications. This feature allows applications to use more memory than is actually physically installed in the system.&lt;br /&gt;
&lt;br /&gt;
Paging is commonly referred to as &amp;quot;virtual memory&amp;quot; by users and sometimes even software developers. The fact AmigaOS uses virtual memory does not imply the use of the pager.&lt;br /&gt;
&lt;br /&gt;
The system can be tuned to different strategies, either page out only on demand (for highly interactive tasks), or based on other needs (lots of free memory in core for disk caches etc.).&lt;br /&gt;
&lt;br /&gt;
The optimized data structures allow the memory system to operate at a very high speed.&lt;br /&gt;
&lt;br /&gt;
The time for a memory allocation is now in the order of a few microseconds. This is especially true for small allocation (below 8096 bytes). During system testing it was observed that by the time the system has booted up to Workbench, there have already been 40,000 allocations to the global memory pool below 2096 bytes.&lt;br /&gt;
&lt;br /&gt;
== Memory Functions ==&lt;br /&gt;
&lt;br /&gt;
Normally, an application uses the AllocVecTags() function to ask for memory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void *AllocVecTags(uint32 size, uint32 tag1, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The size argument is the amount of memory the application needs and the tag list specifies the type of memory and any special memory characteristics (described later). If AllocVecTags() is successful, it returns a pointer to a block of memory. The memory allocation will fail if the system cannot find a big enough block with the requested attributes. If AllocVecTags() fails, it returns NULL.&lt;br /&gt;
&lt;br /&gt;
Because the system only keeps track of how much free memory is available and not how much is in use, it has no idea what memory has been allocated by any task. This means an application has to explicitly return, or deallocate, any memory it has allocated so the system can reuse it. If an application does not return a block of memory to the system, the system will not be able to reallocate that memory to some other task. That block of memory will be lost until the Amiga is reset. If you are using AllocVecTags() to allocate memory, a call to FreeVec() will return that memory to the system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void FreeVec(void *memoryBlock);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here memoryBlock is a pointer to the memory block the application is returning to the system. The size of the memory block is tracked internally by the system.&lt;br /&gt;
&lt;br /&gt;
Unlike some compiler memory allocation functions, the Amiga system memory allocation functions return memory blocks that are at least longword aligned. This means that the allocated memory will always start on an address which is at least evenly divisible by four. This alignment makes the memory suitable for any system structures or buffers which require word or long word alignment, and also provides optimal alignment for stacks and memory copying.&lt;br /&gt;
&lt;br /&gt;
=== Memory Types ===&lt;br /&gt;
&lt;br /&gt;
There are three primary types of memory in AmigaOS which are summarized in the following table:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Use&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE&lt;br /&gt;
| This memory is private and only accessible within the context of the Task which allocated it. Private memory should always be preferred. Private memory is also swappable by default (i.e. not locked). This memory will not be visible to any other address space.&lt;br /&gt;
	In a future version of AmigaOS, it is planned to have Task specific address spaces. This means each task could potentially address up to 4 GB of private memory each.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED&lt;br /&gt;
| The memory is shared and accessible by any Task in the system without restriction. This memory can be shared between all address spaces and will always appear at the same address in any address space. Shared memory is locked by default and thus is not swappable.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE&lt;br /&gt;
| The memory is used to store executable PowerPC code. This is used two-fold in AmigaOS. First, it allows the system to determine if a function pointer points to real native PowerPC code as opposed to 68k code which needs to be emulated. Second, it prevents common exploits that use stack overflows to execute malicious code. Executable memory is locked by default and thus is not swappable.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory Attributes ===&lt;br /&gt;
&lt;br /&gt;
Memory allocation on AmigaOS has traditionally been rather complicated. Over time, as new hardware models were released, more memory attribute flags were introduced.&lt;br /&gt;
&lt;br /&gt;
All the various memory allocation functions and strategies have been consolidated and distilled into a single AllocVecTags() function call. Programmers are strongly encouraged to stop using any other function to allocate system memory. Using AllocVecTags() is the only way to guarantee future compatibility with more advanced AmigaOS features yet to come.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any attributes when allocating memory, the system tries to satisfy the request with the fastest memory available on the system memory lists.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make Sure You Have Memory|text=Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.}}&lt;br /&gt;
&lt;br /&gt;
==== Using Tags ====&lt;br /&gt;
&lt;br /&gt;
The AllocVecTags() function uses a tag list to define what attributes a block of memory must have. The currently supported tags are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Tag (Default)&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | AVT_Type&lt;br /&gt;
	(MEMF_PRIVATE)&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE: Allocate from the task private heap. This memory will not be visible to any other address space.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED: Allocate from the system shared heap. This memory can be shared between all address spaces and will always appear at the same address in any address space. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE: Allocate memory that is marked executable. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Contiguous&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| Memory allocated with this property is allocated from a contiguous block of physical memory. This makes the memory suitable for DMA purposes when the DMA device does not support scatter/gather operation. For devices that do support scatter/gather use StartDMA() instead.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Physical pages can move at any time, be removed from memory due to paging or otherwise made unavailable unless the memory pages are locked (see LockMemory() function or AVT_Lock tag).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Lock&lt;br /&gt;
	(TRUE or FALSE)&lt;br /&gt;
| After allocating memory, lock the associated pages in memory. This will prevent the pages from being moved, swapped out or otherwise being made unavailable. This is useful in conjunction with the AVT_Contiguous tag since it will ensure that the memory will stay contiguous after allocation.&lt;br /&gt;
	This tag defaults to FALSE for MEMF_PRIVATE allocations and to TRUE for MEMF_SHARED or MEMF_EXECUTABLE.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Alignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block. The returned memory block will be aligned to the given size (in bytes). It&#039;s virtual address will be at least a multiple of the AVT_Alignment value.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_PhysicalAlignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block physical address. See AVT_Alignment for more information.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; This functionality is mainly used for DMA drivers that require a specific alignment. Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_ClearWithValue&lt;br /&gt;
| Clear the newly allocated memory with the given byte value. If this tag is not given the memory block is not cleared.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Wait&lt;br /&gt;
	(TRUE)&lt;br /&gt;
| Wait for the memory subsystem to be available. If TRUE, the calling task will be retired until the memory subsystem is available. This might cause a Forbid() to break. When FALSE and the memory subsystem is not currently available, the function will return immediately and the allocation will fail.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_NoExpunge&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| If allocation fails because of unavailability, prevent invocation of low memory cleanup handlers. The default is FALSE which means that when memory is not available, cleanup handlers are invoked to try and satisfy the allocation.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Using Flags ====&lt;br /&gt;
&lt;br /&gt;
The use of memory flags was the only supported way to allocate memory prior to AmigaOS 4.0. It may be still useful to know more about this obsolete system for porting older applications. For more information about the memory flags system see [[Obsolete Exec Memory Allocation]].&lt;br /&gt;
&lt;br /&gt;
=== Allocating System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples show how to allocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR apointer = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (apointer == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocVecTags() returns the address of the first byte of a memory block that is at least 100 bytes in size or NULL if there is not that much free memory. Because there are no tags specified, private memory is assumed so this memory cannot be shared with any other tasks.&lt;br /&gt;
&lt;br /&gt;
In addition to allocating a block of memory, this function keeps track of the size of the memory block, so your application doesn&#039;t have to remember it when it deallocates that memory block. The AllocVecTags() function allocates a little more memory to store the size of the memory allocation request.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make No Assumptions|It is not legal to peek the longword in front of the returned memory pointer to find out how big the block is. This has always been illegal regardless of what any other documentation may have stated to the contrary. Your application has access to the memory returned by AllocVecTags() and the only guarantee made is that the returned block has n bytes of continuous address space starting at the returned address. Anything outside this area is not to be touched.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR anotherptr = IExec-&amp;gt;AllocVecTags(1000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  AVT_Lock, FALSE,&lt;br /&gt;
  AVT_ClearWithValue, 0,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (anotherptr == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocates shared memory which is accessible by any task in the system and clears the memory contents to zero. MEMF_SHARED memory is locked by default for compatibility with the obsolete MEMF_PUBLIC flag. This is by far the most common case when allocating shared memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR lockedmem = IExec-&amp;gt;AllocVecTags(3000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (lockedmem == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocated shared memory which is accessible by any task in the system. MEMF_SHARED memory is locked by default so the underlying memory pages are not moveable and cannot be swapped out. Such memory could be used to share memory between a Process and an interrupt routine for example.&lt;br /&gt;
&lt;br /&gt;
If the system free memory list does not contain enough contiguous memory bytes in an area matching your requirements, AllocVecTags() returns a zero. You &#039;&#039;must&#039;&#039; check for this condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR yap = IExec-&amp;gt;AllocVec(500, MEMF_CHIP);&lt;br /&gt;
&lt;br /&gt;
if (yap == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The deprecated AllocVec() function is used in the example above because it is the only way to allocate MEMF_CHIP memory on a classic Amiga system.&lt;br /&gt;
&lt;br /&gt;
=== Locking System Memory ===&lt;br /&gt;
&lt;br /&gt;
The LockMem() function is used to explicitly lock a memory block. This function will make sure your memory block is not unmapped, swapped out or somehow made inaccessible. Use this function wisely. If there is no good reason to lock memory then do not do it. It will prevent the memory system from optimizing memory layout and may lead to poor performance. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;LockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before deallocating the memory is must be unlocked as well.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;UnLockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Do not forget to UnlockMem()&#039;&#039;|Failure to unlock memory will have adverse affects on the memory system. Locked memory pages cannot be moved so the system cannot optimize the layout of locked memory pages.}}&lt;br /&gt;
&lt;br /&gt;
Be careful to always match the number of locks and the number of unlocks. If something else may have locked memory in the same page and an extraneous UnlockMem() decreases the lock reference counter to 0, that page can be paged out or moved. If that happens, for example, with data used in an interrupt handler or by the device driver which handles the swap partition the system will crash.&lt;br /&gt;
&lt;br /&gt;
=== Freeing System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples free the memory chunks shown in the previous calls to AllocVecTags().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;FreeVec(apointer);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(anotherptr);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(lockedmem);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(yap);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A memory block allocated with AllocVecTags() or AllocVec() must be returned to the system pool with the FreeVec() function. This function uses the stored size in the allocation to free the memory block, so there is no need to specify the size of the memory block to free.&lt;br /&gt;
&lt;br /&gt;
Any memory that is locked must be explicitly unlocked with UnlockMem() prior to freeing it. Failure to unlock memory will not cause any immediate problems but any pages which are locked cannot be moved or swapped out which can decrease system performance. Never unlock memory which has not been explicitly locked with LockMem() as it could lead to undefined behaviour.&lt;br /&gt;
&lt;br /&gt;
FreeVec() returns no status. However, if you attempt to free a memory block in the middle of a chunk that the system believes is already free, you will cause a system crash. It is also illegal to free the same memory block twice and this will lead to a system crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Leave Memory Allocations Out Of Interrupt Code&#039;&#039;|text=Do not allocate or deallocate system memory from within interrupt code. The [[Exec_Interrupts|Exec Interrupts]] section explains that an interrupt may occur at any time, even during a memory allocation process. As a result, system data structures may not be internally consistent at this time.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory may be Locked by Default ===&lt;br /&gt;
&lt;br /&gt;
By default, the system memory allocation routines will allocate MEMF_SHARED and MEMF_EXECUTABLE memory and implicitly lock it. This memory must not be explicitly unlocked. Let the system handle the unlocking internally.&lt;br /&gt;
&lt;br /&gt;
Here is the right way to allocate and free MEMF_SHARED memory:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is the wrong way:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;UnlockMem(ptr, 10);  // Doing this could lead to undefined system behaviour.&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlocking memory not explicitly locked does not immediately cause any issues. However, in a future version of AmigaOS the memory pages may be handled differently than they are handled today. This could lead to incompatibilities with your applications and AmigaOS. The simple rule is that if you called LockMem() you must also call UnlockMem(). All other times you should not call UnlockMem().&lt;br /&gt;
&lt;br /&gt;
{{Note|Memory locking is done at the page level. Even if a single byte of memory is locked in a page that entire page is locked. The programmer has no control over which pages may be used to satisfy a memory allocation.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Shared and executable memory is locked implicitly for 68K backwards compatibility reasons. Use AVT_Lock (or equivalent) to ensure your shared and executable memory is not locked. MEMF_PRIVATE memory has no backwards compatibility issues and is always unlocked by default.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory Information Functions ===&lt;br /&gt;
&lt;br /&gt;
The memory information routines AvailMem() and TypeOfMem() can provide the amount of memory available in the system, and the attributes of a particular block of memory.&lt;br /&gt;
&lt;br /&gt;
==== Memory Requirements ====&lt;br /&gt;
&lt;br /&gt;
The same attribute flags used in memory allocation routines are valid for the memory information routines. There is also an additional flag, MEMF_LARGEST, which can be used in the AvailMem() routine to find out what the largest available memory block of a particular type is. Specifying the MEMF_TOTAL flag will return the total amount of memory currently available.&lt;br /&gt;
&lt;br /&gt;
==== Calling Memory Information Functions ====&lt;br /&gt;
&lt;br /&gt;
The following example shows how to find out how much memory of a particular type is available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 size = IExec-&amp;gt;AvailMem(MEMF_CHIP | MEMF_LARGEST);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AvailMem() returns the size of the largest chunk of available chip memory.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;AvailMem() May Not Be Totally Accurate&#039;&#039;|text=Because of multitasking, the return value from AvailMem() may be inaccurate by the time you receive it.}}&lt;br /&gt;
&lt;br /&gt;
The following example shows how to determine the type of memory of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 memtype = IExec-&amp;gt;TypeOfMem((APTR)0x090000);&lt;br /&gt;
if ((memtype &amp;amp; MEMF_CHIP) == MEMF_CHIP) {  /*  ... It&#039;s chip memory ...  */   }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
TypeOfMem() returns the attributes of the memory at a specific address. If it is passed an invalid memory address, TypeOfMem() returns NULL. This routine is normally used to determine if a particular chunk of memory is in chip memory.&lt;br /&gt;
&lt;br /&gt;
=== Using Memory Copy Functions ===&lt;br /&gt;
&lt;br /&gt;
For memory block copies, the CopyMem() and CopyMemQuick() functions can be used.&lt;br /&gt;
&lt;br /&gt;
==== Copying System Memory ====&lt;br /&gt;
&lt;br /&gt;
The following samples show how to use the copying routines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR source = IExec-&amp;gt;AllocVecTags(1000, AVT_ClearWithValue, 0, TAG_END);&lt;br /&gt;
APTR target = IExec-&amp;gt;AllocVecTags(1000, AVT_Type, MEMF_SHARED, TAG_END);&lt;br /&gt;
IExec-&amp;gt;CopyMem(source, target, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CopyMem() copies the specified number of bytes from the source data region to the target data region. The pointers to the regions can be aligned on arbitrary address boundaries. CopyMem() will attempt to copy the memory as efficiently as it can according to the alignment of the memory blocks, and the amount of data that it has to transfer. These functions are optimized for copying large blocks of memory which can result in unnecessary overhead if used to transfer very small blocks of memory.&lt;br /&gt;
&lt;br /&gt;
CopyMemQuick() is now identical to CopyMem(). In previous versions of the operating system, CopyMemQuick() performed more optimized copying of the specified number of bytes from the source data region to the target data region. The source and target pointers must be longword aligned and the size (in bytes) must be divisible by four. There are no such restrictions starting with AmigaOS 4.0.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Not All Copies Are Supported&#039;&#039;|text=Neither CopyMem() nor CopyMemQuick() supports copying between regions that overlap. For overlapping regions see MoveMem() in the [[Utility Library]].}}&lt;br /&gt;
&lt;br /&gt;
=== System Memory Pools ===&lt;br /&gt;
&lt;br /&gt;
A construct carried over from the AmigaOS 3.x Exec is the memory pool. The code handling memory pools uses an algorithm based on boundary tags and size-segregated memory lists. The speed gain is tremendous: even for the &amp;quot;dumb&amp;quot; case of just allocating 100000 blocks and freeing them again, the speed is ten times faster than the previous implementation. Due to the size-segregated free lists and the easy coalescing due to the boundary tags, real-life performance gain is even higher and would be between 10 and 20 times.&lt;br /&gt;
&lt;br /&gt;
Two types of memory pools are available depending on the needs of the application:&lt;br /&gt;
* [[Exec_Item_Pools|Item Pools]] are built for speed and are used for allocating large numbers of items that are all the same size.&lt;br /&gt;
* Generic [[Exec_Memory_Pools|Memory Pools]] can handle allocations of different sizes at the expense of some speed.&lt;br /&gt;
&lt;br /&gt;
=== Summary of System Controlled Memory Handling Routines ===&lt;br /&gt;
&lt;br /&gt;
; AllocVecTags() and FreeVec()&lt;br /&gt;
: These are system-wide memory allocation and deallocation routines. They use a memory free-list owned and managed by the system.&lt;br /&gt;
&lt;br /&gt;
; LockMem() and UnlockMem()&lt;br /&gt;
: These routines explicitly lock and unlock underlying memory pages.&lt;br /&gt;
&lt;br /&gt;
; AvailMem()&lt;br /&gt;
: This routine returns the number of free bytes in a specified type of memory.&lt;br /&gt;
&lt;br /&gt;
; TypeOfMem()&lt;br /&gt;
: This routine returns the memory attributes of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
; CopyMem() and CopyMemQuick()&lt;br /&gt;
: CopyMem() is a general purpose memory copy routine. CopyMemQuick() is an optimized version of CopyMemQuick(), but has restrictions on the size and alignment of the arguments.&lt;br /&gt;
&lt;br /&gt;
== Allocating DMA Memory ==&lt;br /&gt;
&lt;br /&gt;
Device drivers often use DMA to transfer data to and from the device. The StartDMA(), GetDMAList() and EndDMA() functions are used to create a scatter/gather list suitable for such DMA transfers.&lt;br /&gt;
&lt;br /&gt;
The following conditions are guaranteed to be met when using these DMA functions:&lt;br /&gt;
* The memory region given is guaranteed to be mapped to physical memory.&lt;br /&gt;
* The mapping will not change as long as EndDMA() is not called.&lt;br /&gt;
* All cache entries in this region will be flushed out.&lt;br /&gt;
&lt;br /&gt;
The example code below demonstrates how to perform a DMA write transfer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR addr;&lt;br /&gt;
uint32 size;&lt;br /&gt;
&lt;br /&gt;
/* Tell the system to prepare for DMA */&lt;br /&gt;
uint32 arraySize = IExec-&amp;gt;StartDMA(addr, size, 0);&lt;br /&gt;
if (arraySize &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    /* The memory area is prepared, allocate and retrieve the DMA list */&lt;br /&gt;
    struct DMAEntry *DMAList = IExec-&amp;gt;AllocSysObject(ASOT_DMAENTRY,&lt;br /&gt;
      ASODMAE_NumEntries,  arraySize,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (DMAList != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        IExec-&amp;gt;GetDMAList(addr, size, 0, DMAList);&lt;br /&gt;
 &lt;br /&gt;
        /* Feed the DMA controller and do stuff */&lt;br /&gt;
        ...&lt;br /&gt;
        /* Get rid of the DMAList&#039;s memory */&lt;br /&gt;
 &lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, endFlags);&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_DMAENTRY, DMAList);&lt;br /&gt;
    }&lt;br /&gt;
    else&lt;br /&gt;
    {&lt;br /&gt;
        // Note We still call EndDMA() even though the actual&lt;br /&gt;
        // transfer didn&#039;t happen.&lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, DMAF_NoModify);&lt;br /&gt;
&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t allocate DMA list\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
else&lt;br /&gt;
{&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t initiate DMA transfer\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syncing and Memory Access ==&lt;br /&gt;
&lt;br /&gt;
The PowerPC is a pure load/store architecture. That means, it will never operate on memory arguments like x86, to modify data, you have to load, modify and store.&lt;br /&gt;
&lt;br /&gt;
All PowerPC/POWER cpus have a load/store queue, i.e. a queue where load and store operations are queued to reduce memory latency. If more than one store is made to the same long word (i.e. you write the first byte and then the second byte), then those stores are combined. As you can see, this will cause a problem for a chip register since both are written at the same time which is likely not what you want.&lt;br /&gt;
&lt;br /&gt;
Similar for reading: A read might shortcut through the load/store queue and use a value that&#039;s already been read and is present in the load/store queue. Again, for chip registers, this will cause a problem because you might read an old value.&lt;br /&gt;
&lt;br /&gt;
There are two instructions that deal with this: &#039;&#039;&#039;eieio&#039;&#039;&#039; (Ensure In-order Execution of I/O) and &#039;&#039;&#039;sync&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The eieio instruction simply inserts a &amp;quot;barrier&amp;quot; into the load/store queue. When combining &#039;&#039;stores&#039;&#039; the CPU never searches past this barrier for possible combines. This means that the sequence&lt;br /&gt;
&lt;br /&gt;
# store to some address&lt;br /&gt;
# eieio&lt;br /&gt;
# store to some address + 1&lt;br /&gt;
&lt;br /&gt;
will never be combined because of the eieio barrier between them.&lt;br /&gt;
&lt;br /&gt;
The sync instruction will simply halt all execution and flush the load/store queue, executing and finishing all loads and stores.&lt;br /&gt;
&lt;br /&gt;
As you can imagine, sync is MUCH MORE costly than eieio.&lt;br /&gt;
&lt;br /&gt;
=== When to use eieio and sync ===&lt;br /&gt;
&lt;br /&gt;
If you want to write, post fix your writes with an eieio instruction.&lt;br /&gt;
&lt;br /&gt;
If you want to read, prefix your reads with a sync instruction.&lt;br /&gt;
&lt;br /&gt;
This is also where the &amp;quot;GUARDED&amp;quot; memory flag comes in. GUARDED memory simply ensures program order of loads and stores, that is, it&#039;s an implicit eieio.&lt;br /&gt;
&lt;br /&gt;
{{Note|Knowing when and how to use the &#039;&#039;&#039;eieio&#039;&#039;&#039; and &#039;&#039;&#039;sync&#039;&#039;&#039; instructions is somewhat vital for driver developers.}}&lt;br /&gt;
&lt;br /&gt;
== Allocating Multiple Memory Blocks ==&lt;br /&gt;
&lt;br /&gt;
Exec provides the routines AllocTaskMemEntry() and FreeEntry() to allocate multiple memory blocks in a single call.&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() accepts a data structure called a MemList, which contains the information about the size of the memory blocks to be allocated and the requirements, if any, that you have regarding the allocation.&lt;br /&gt;
&lt;br /&gt;
The MemList structure is found in the include file &amp;lt;exec/memory.h&amp;gt; and is defined as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList&lt;br /&gt;
{&lt;br /&gt;
    struct Node     ml_Node;&lt;br /&gt;
    UWORD           ml_NumEntries;      /* number of MemEntrys */&lt;br /&gt;
    struct MemEntry ml_ME[1];           /* where the MemEntrys begin*/&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ml_Node&lt;br /&gt;
: allows you to link together multiple MemLists. However, the node is ignored by the routines AllocTaskMemEntry() and FreeEntry().&lt;br /&gt;
&lt;br /&gt;
; ml_NumEntries&lt;br /&gt;
: tells the system how many MemEntry sets are contained in this MemList. Notice that a MemList is a variable-length structure and can contain as many sets of entries as you wish.&lt;br /&gt;
&lt;br /&gt;
The MemEntry structure looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemEntry&lt;br /&gt;
{&lt;br /&gt;
    union {&lt;br /&gt;
        ULONG   meu_Reqs;   /* the AllocMem requirements */&lt;br /&gt;
        APTR    meu_Addr;   /* address of your memory */&lt;br /&gt;
        } me_Un;&lt;br /&gt;
    ULONG   me_Length;      /* the size of this request */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sample Code for Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an example of showing how to use the AllocTaskMemEntry() with multiple blocks of memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// alloctaskmementry.c - example of allocating several memory areas.&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MemList *memlist;             /* pointer to a MemList structure        */&lt;br /&gt;
&lt;br /&gt;
struct MemBlocks /* define a new structure because C cannot initialize unions */&lt;br /&gt;
{&lt;br /&gt;
    struct MemList  mn_head;         /* one entry in the header               */&lt;br /&gt;
    struct MemEntry mn_body[3];      /* additional entries follow directly as */&lt;br /&gt;
} memblocks;                         /* part of the same data structure       */&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    memblocks.mn_head.ml_NumEntries = 4; /* 4! Since the MemEntry starts at 1! */&lt;br /&gt;
&lt;br /&gt;
    /* Describe the first piece of memory we want.  Because of our MemBlocks structure */&lt;br /&gt;
    /* setup, we reference the first MemEntry differently when initializing it.        */&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Reqs   = MEMF_CLEAR;&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Length = 4000;&lt;br /&gt;
&lt;br /&gt;
    memblocks.mn_body[0].me_Reqs   = MEMF_PRIVATE | MEMF_CLEAR;/* Describe the other pieces of    */&lt;br /&gt;
    memblocks.mn_body[0].me_Length = 100000;                   /* memory we want. Additional      */&lt;br /&gt;
    memblocks.mn_body[1].me_Reqs   = MEMF_SHARED | MEMF_CLEAR; /* MemEntries are initialized this */&lt;br /&gt;
    memblocks.mn_body[1].me_Length = 200000;                   /* way. If we wanted even more en- */&lt;br /&gt;
    memblocks.mn_body[2].me_Reqs   = MEMF_EXECUTABLE;          /* tries, we would need to declare */&lt;br /&gt;
    memblocks.mn_body[2].me_Length = 25000;                    /* a larger MemEntry array in our  */&lt;br /&gt;
                                                               /* MemBlocks structure.            */&lt;br /&gt;
&lt;br /&gt;
    memlist = (struct MemList *)IExec-&amp;gt;AllocTaskMemEntry((struct MemList *)&amp;amp;memblocks);&lt;br /&gt;
&lt;br /&gt;
    if (memlist == NULL)&lt;br /&gt;
    {&lt;br /&gt;
       IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry FAILED\n&amp;quot;);&lt;br /&gt;
       return RETURN_FAIL;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    /* We got all memory we wanted.  Use it and call FreeEntry() to free it */&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry succeeded - now freeing all allocated blocks\n&amp;quot;);&lt;br /&gt;
    IExec-&amp;gt;FreeEntry(memlist);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() returns a pointer to a new MemList of the same size as the MemList that you passed to it. For example, ROM code can provide a MemList containing the requirements of a task and create a RAM-resident copy of the list containing the addresses of the allocated entries. The pointer to the MemList is used as the argument for FreeEntry() to free the memory blocks.&lt;br /&gt;
&lt;br /&gt;
=== Result of Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
The MemList created by AllocTaskMemEntry() contains MemEntry entries. MemEntrys are defined by a union statement, which allows one memory space to be defined in more than one way.&lt;br /&gt;
&lt;br /&gt;
If AllocTaskMemEntry() returns a non-NULL value then all of the meu_Addr positions in the returned MemList will contain valid memory addresses meeting the requirements you have provided. To use this memory area, you would use code similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *mlist = IExec-&amp;gt;AllocTaskMemEntry(&amp;amp;ML);&lt;br /&gt;
APTR memory = NULL;&lt;br /&gt;
&lt;br /&gt;
if ( mlist != NULL )&lt;br /&gt;
{&lt;br /&gt;
  memory = mlist-&amp;gt;ml_ME[0].me_Un.meu_Addr;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Multiple Memory Blocks and Tasks ===&lt;br /&gt;
&lt;br /&gt;
If you want to take advantage of Exec&#039;s automatic cleanup, use the MemList and AllocTaskMemEntry() facility to do your dynamic memory allocation.&lt;br /&gt;
&lt;br /&gt;
In the Task control block structure, there is a list header named tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
This is the list header that you initialize to include MemLists that your task has created by call(s) to AllocTaskMemEntry(). Here is a short program segment that handles task memory list header initialization only. It assumes that you have already run AllocTaskMemEntry() as shown in the simple AllocTaskMemEntry() example above.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *ml;&lt;br /&gt;
&lt;br /&gt;
struct Task *tc = IExec-&amp;gt;FindTask(0);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;AddTail(tc-&amp;gt;tc_MemEntry, ml);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Assuming that you have only used the AllocTaskMemEntry() method (or AllocVecTags() and built your own custom MemList), the system now knows where to find the blocks of memory that your task has dynamically allocated. The RemTask() function automatically frees all memory found on tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;CreateTask() Sets Up A MemList.&#039;&#039;|text=The CreateTask() function, and other system task and process creation functions use a MemList in tc_MemEntry so that the Task structure and stack will be automatically deallocated when the Task is removed.}}&lt;br /&gt;
&lt;br /&gt;
=== Summary of Multiple Memory Blocks Allocation Routines ===&lt;br /&gt;
&lt;br /&gt;
These are routines for allocating and freeing multiple memory blocks with a single call.&lt;br /&gt;
&lt;br /&gt;
This routine initializes memory from data and offset values in a table. Typically only assembly language programs benefit from using this routine. See the SDK for more details.&lt;br /&gt;
&lt;br /&gt;
== Allocating Memory at an Absolute Address ==&lt;br /&gt;
&lt;br /&gt;
For special advanced applications, AllocAbs() is provided. Using AllocAbs(), an application can allocate a memory block starting at a specified absolute memory address. If the memory is already allocated or if there is not enough memory available for the request, AllocAbs() returns a zero.&lt;br /&gt;
&lt;br /&gt;
Be aware that an absolute memory address which happens to be available on one Amiga may not be available on a machine with a different configuration or different operating system revision, or even on the same machine at a different times. For example, a piece of memory that is available during expansion board configuration might not be available at earlier or later times. Here is an example call to AllocAbs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR absoluteptr = (APTR)IExec-&amp;gt;AllocAbs(10000, 0x2F0000);&lt;br /&gt;
if (!(absoluteptr))&lt;br /&gt;
    { /* Couldn&#039;t get memory, act accordingly. */  }&lt;br /&gt;
&lt;br /&gt;
/* After we&#039;re done using it, we call FreeMem() to free the memory block. */&lt;br /&gt;
IExec-&amp;gt;FreeMem(absoluteptr, 10000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of the Exec functions that handle memory management. See the SDK for details on each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Memory Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AllocMem()&lt;br /&gt;
| Allocate memory with specified attributes. &#039;&#039;&#039;This function is obsolete.&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| AllocAbs()&lt;br /&gt;
| Allocate memory at a specified location.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTaskMemEntry()&lt;br /&gt;
| Allocate multiple memory blocks.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVec()&lt;br /&gt;
| Allocate memory with specified attributes and keep track of the size. This function is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVecTags()&lt;br /&gt;
| Allocate memory with specified attributes defined by tags and keep track of the size. If an application needs to allocate some memory, it will usually use this function.&lt;br /&gt;
|-&lt;br /&gt;
| AvailMem()&lt;br /&gt;
| Return the amount of free memory, given certain conditions.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMem()&lt;br /&gt;
| Copy memory block, which can be non-aligned and of arbitrary length.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMemQuick()&lt;br /&gt;
| Copy aligned memory block.&lt;br /&gt;
|-&lt;br /&gt;
| FreeEntry()&lt;br /&gt;
| Free multiple memory blocks, allocated with AllocTaskMemEntry().&lt;br /&gt;
|-&lt;br /&gt;
| FreeMem()&lt;br /&gt;
| Free a memory block of specified size, allocated with AllocMem() or AllocAbs().&lt;br /&gt;
|-&lt;br /&gt;
| FreeVec()&lt;br /&gt;
| Free a memory block allocated with AllocVecTags() or AllocVec().&lt;br /&gt;
|-&lt;br /&gt;
| InitStruct()&lt;br /&gt;
| Initialize memory from a table.&lt;br /&gt;
|-&lt;br /&gt;
| LockMem()&lt;br /&gt;
| Lock the underlying pages given a memory block address and size.&lt;br /&gt;
|-&lt;br /&gt;
| TypeOfMem()&lt;br /&gt;
| Determine attributes of a specified memory address.&lt;br /&gt;
|-&lt;br /&gt;
| UnlockMem()&lt;br /&gt;
| Unlock the underlying pages given a memory block address and size.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12381</id>
		<title>Exec Memory Allocation</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Memory_Allocation&amp;diff=12381"/>
		<updated>2023-11-04T15:08:59Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: A mention on AmigaOS&amp;#039; Program Address Space&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Exec Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
Exec manages all of the free memory currently available in the system. Using a slab allocation system, Exec keeps track of memory and provides the functions to allocate and access it.&lt;br /&gt;
&lt;br /&gt;
When an application needs some memory, it can either declare the memory statically within the program or it can ask Exec for some memory. When Exec receives a request for memory, it searches its free memory regions to find a suitably sized block that matches the size and attributes requested.&lt;br /&gt;
&lt;br /&gt;
Prior to AmigaOS 4.0, the OS did not make use of the CPU&#039;s memory management unit and used memory &amp;quot;as-is&amp;quot;. That is, if you have different memory expansions plugged into your system, the memory will be seen as chunks located somewhere in the 4 gigabyte address space. Since version 4.0, the MMU will be used to &amp;quot;map&amp;quot; memory pages from their physical location to a virtual address. There are multiple reasons why this is better than using the verbatim physical addresses - among other things it reduces the effect of &amp;quot;memory fragmentation&amp;quot; and simplifies the possibility to swap currently unused memory pages to persistent storage such as a hard disk.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;downside&amp;quot; is that the virtual address of a memory block is almost never identical to the physical address. This isn&#039;t much of a downside, since an application will never really need to care about it. If an application allocates a block of memory of n bytes, it will get a pointer back that points to at least n continuous addresses as expected. The pages that &amp;quot;fill&amp;quot; this memory block may come from different physical locations scattered throughout the physical memory but a program will never noticed that. For all intent and purpose, the application sees a single continuous block of memory.&lt;br /&gt;
&lt;br /&gt;
=== Program Address Space ===&lt;br /&gt;
&lt;br /&gt;
It is important to remember that, just like in classic AmigaOS, a single address space is used for all programs. Sometimes the mention of an MMU can lead people to assume that each process on the Amiga will have its own personal, partitioned address space. The following two programs demonstrate that, even though they are separate processes, it is possible to read and write another&#039;s memory. The memory locations are the same virtual address and that virtual address maps onto the same physical address.&lt;br /&gt;
&lt;br /&gt;
The following demonstrates how it is possible to read/write memory that is &amp;quot;owned&amp;quot; by a different process:&lt;br /&gt;
&lt;br /&gt;
This program has a global variable. It prints out the virtual address of the global, waits for a keypress and then prints out the address again in case it has been externally updated:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
volatile char x = 255;&lt;br /&gt;
&lt;br /&gt;
/* Usage: a [VAL] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(argc==2)&lt;br /&gt;
        x = (char)atoi(argv[1]);&lt;br /&gt;
&lt;br /&gt;
    printf(&amp;quot;Virtual Address    of `x&#039;: %p\n&amp;quot;, (void*)&amp;amp;x);&lt;br /&gt;
    printf(&amp;quot;Dereferenced Value of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    (void)getchar();&lt;br /&gt;
    printf(&amp;quot;Final Value        of `x&#039;: %d\n&amp;quot;, x);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This program reads in an address as an argument and reads the value at that location even though it does not &amp;quot;own&amp;quot; the memory. By adding an additional argument, this program can also write to that &amp;quot;foreign&amp;quot; address:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* Usage: b ADDR [VAL_TO_WRITE] */&lt;br /&gt;
int main(int argc, char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    if(!(argc==2 || argc==3))&lt;br /&gt;
        return 10;&lt;br /&gt;
&lt;br /&gt;
    volatile char *byte = (volatile char*)strtol(argv[1],NULL,16);&lt;br /&gt;
    printf(&amp;quot;Selected Virtual Address    : %p\n&amp;quot;,(void*)byte);&lt;br /&gt;
    printf(&amp;quot;Dereferencd Value of Address: %d\n&amp;quot;,*byte);&lt;br /&gt;
    if(argc==3)&lt;br /&gt;
    {&lt;br /&gt;
        printf(&amp;quot;Writing Value `%d&#039; to Address: %p\n&amp;quot;,atoi(argv[2]),(void*)byte);&lt;br /&gt;
        *byte=(char)atoi(argv[2]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Slab allocation ===&lt;br /&gt;
&lt;br /&gt;
[[File:SlabDiagram.jpg|right]]&lt;br /&gt;
&lt;br /&gt;
The AmigaOS memory architecture is based on the &amp;quot;slab allocator&amp;quot; system or &amp;quot;object cache&amp;quot;. In essence, the slab allocator only allocates objects of a single size, allocating these in larger batches (&amp;quot;slabs&amp;quot;) from the low-level page allocator. These slabs are then divided up into buffers of the required size, and kept within a list in the slab allocator.&lt;br /&gt;
&lt;br /&gt;
Allocating an object with the slab allocator becomes a process of simple node removal: the first node in the first slab containing free nodes is removed and returned for use. Since the slab allocator keeps free slabs or partially free slabs in a separate list from the full slabs, this operation can be carried out in constant time. Freeing memory is accomplished by returning the buffer to its cache, and adding it to its original slab&#039;s free list. Slabs that are completely free can be returned to the system&#039;s page pool (this operation is actually driven by demand, and timestamps are used to avoid unnecessary loading of data, or &amp;quot;thrashing&amp;quot;). External fragmentation is minimal, and internal fragmentation is controlled and guaranteed not to exceed a certain amount.&lt;br /&gt;
&lt;br /&gt;
=== Object caching ===&lt;br /&gt;
&lt;br /&gt;
The slab allocator can also be used to cache objects. In the real world a lot of memory allocation operations will be used to allocate the same object. The system has a number of data structures which are allocated frequently (semaphores, message ports and the like). Every time such structures are allocated, they must be initialised, and when they are deleted again, they must be cleaned up. It&#039;s likely, however, that such a structure will be needed again in the future, so that it can be kept in its initialised state and re-used later. This further reduces the load on the allocator routines, and thus improves system performance.&lt;br /&gt;
&lt;br /&gt;
The object caches work on memory that has already been mapped into the virtual memory space.&lt;br /&gt;
&lt;br /&gt;
=== More Advantages ===&lt;br /&gt;
&lt;br /&gt;
Another advantage is the possibility to improve CPU cache usage. Usually, most objects have &amp;quot;hot spots&amp;quot;, i.e. they have a few fields that are used often. Since most of the time a little memory is left unused in a slab (the object size might not be a multiple of the slab size), this additional memory can be used to &amp;quot;shift&amp;quot; the hot spots by a few bytes to optimise the memory structure, leading to better cache usage.&lt;br /&gt;
&lt;br /&gt;
Finally, the system can be expanded to multiple CPUs with next to no overhead. On multi-CPU system, these expanded slab allocators scale almost linearly with the number of CPUs employed, making it the ideal choice for such systems.&lt;br /&gt;
&lt;br /&gt;
The combination of object caching and keeping caches for different memory blocks (for AllocVec/FreeVec emulation) makes the memory management more efficient, faster, and generally more future-proof than the old free list approach used in AmigaOS 3.x and earlier.&lt;br /&gt;
&lt;br /&gt;
See Wikipedia for more information on [http://en.wikipedia.org/wiki/Slab_allocation slab allocator systems].&lt;br /&gt;
&lt;br /&gt;
=== Physical page allocation ===&lt;br /&gt;
&lt;br /&gt;
Every memory location in a computer system has its own, unique address. That is, there is a byte location at address x where you can store and retrieve a single byte. This address is fixed; there is no way to change it without physically changing the hardware. Therefore, this address is called the “physical” address.&lt;br /&gt;
&lt;br /&gt;
The physical address of a memory page is most often completely irrelevant to the application. The CPU will typically only see the virtual address. AmigaOS will take care of assigning virtual addresses to memory paging. This is often called “mapping” a page. Only in very special cases will the physical address be relevant. For example, device drivers that want to pass memory to a hardware device via DMA. Since the MMU is part of the CPU, any external hardware like an IDE controller will not see the virtual but only physical addresses.&lt;br /&gt;
&lt;br /&gt;
A common operation in memory allocation is the assignment of virtual addresses to physical memory locations. Allocation of physical memory is usually done differently from virtual allocations, since it&#039;s necessary to free up only part of the allocation (when for example the pager kicks in).&lt;br /&gt;
&lt;br /&gt;
The de-facto standard in allocation of physical pages is a method invented by Knuth, called [http://en.wikipedia.org/wiki/Buddy_memory_allocation the &amp;quot;buddy system&amp;quot;]. Basically every modern operating system uses it and AmigaOS is no exception.&lt;br /&gt;
&lt;br /&gt;
Buddy systems are in essence size-segregated free lists. To allocate, the system searches for a free block of at least the size of the allocation. Then, if the block is too large, it&#039;s split into two even-sized blocks. These blocks are called &amp;quot;buddies&amp;quot;. One block is returned to it&#039;s appropriate free list, and the other is considered further, maybe splitting it further until it&#039;s size matches that of the allocation.&lt;br /&gt;
&lt;br /&gt;
In a buddy system, it&#039;s easy to determine whether the &amp;quot;buddy&amp;quot; is free or not, because it&#039;s address can simply be decided based on the address of the block to be freed.&lt;br /&gt;
&lt;br /&gt;
=== Virtual address space allocation ===&lt;br /&gt;
&lt;br /&gt;
Most CPUs come with a special unit that is called a &amp;quot;memory management unit&amp;quot; or &amp;quot;MMU&amp;quot; for short. The MMU&#039;s primary job is to rearrange the physical memory within the 4 gigabytes of address space in a way that is convenient for the operating system and/or applications. To do that effectively, it divides the memory into blocks (called &amp;quot;pages&amp;quot;). For every page the MMU has an entry in a table that specifies where the CPU should &amp;quot;see&amp;quot; this page, and what special attributes the page has. The address where the CPU &amp;quot;sees&amp;quot; this page is a 32 bit address as well, but since the memory is not really located there, we call that a &amp;quot;virtual&amp;quot; address.&lt;br /&gt;
&lt;br /&gt;
AmigaOS uses a resource map allocator for allocating virtual address space. Basically this is a means of managing a set of resources (not necessarily memory). For performance reason, it uses several optimization techniques.&lt;br /&gt;
&lt;br /&gt;
For one, all free resource blocks are held in space-segregated lists, i.e. there is a list for each power-of-two resource group. This makes allocations a lot faster by providing an upper and lower bound for a search. For example, if you want to allocate a block of 2^10 bytes, you can basically skip searching any block below 2^10 bytes in size simply because it won&#039;t fit. Similarly, you don&#039;t need to search for blocks that are larger than, say, twice the size of the block, since there might still be blocks of a size near to what we need. Size-segregated free lists help narrow down the search, making the search itself faster and the result better in terms of fragmentation.&lt;br /&gt;
&lt;br /&gt;
In addition, the resource maps use object caches for accelerating &amp;quot;small&amp;quot; allocations. Most allocations are below a certain size. For example, the virtual addresses are always allocated in chunks of at least one &amp;quot;page&amp;quot; in memory (4096 bytes). So it&#039;s common to allocate blocks of one, two, four, or eight pages. The object caches provide an easy method for keeping these common sizes, making every allocation of these sizes an exact fit, further reducing fragmentation.&lt;br /&gt;
&lt;br /&gt;
=== Page cache ===&lt;br /&gt;
&lt;br /&gt;
A lot of the time spent in allocating memory was spent looking for the appropriate pages in memory. A 256 MB memory system has 65536 4KB pages. These pages have to be searched for from time to time. Originally, hash tables were used but it turned out that distributing 65536 page entries over a few hash buckets still produced lists of several thousand pages that had to be traversed to find a page. The hash table was replaced with a radix tree. These trees are rather broad, but shallow, making traversal very fast. In usual circumstances, the tree does not grow more than 4 to 5 levels in depth, making searching of a page a matter of maximum 4 to 5 compare operations.&lt;br /&gt;
&lt;br /&gt;
=== Pager ===&lt;br /&gt;
&lt;br /&gt;
AmigaOS has the possibility to swap out parts of memory to disk in order to free up more memory for other applications. This feature allows applications to use more memory than is actually physically installed in the system.&lt;br /&gt;
&lt;br /&gt;
Paging is commonly referred to as &amp;quot;virtual memory&amp;quot; by users and sometimes even software developers. The fact AmigaOS uses virtual memory does not imply the use of the pager.&lt;br /&gt;
&lt;br /&gt;
The system can be tuned to different strategies, either page out only on demand (for highly interactive tasks), or based on other needs (lots of free memory in core for disk caches etc.).&lt;br /&gt;
&lt;br /&gt;
The optimized data structures allow the memory system to operate at a very high speed.&lt;br /&gt;
&lt;br /&gt;
The time for a memory allocation is now in the order of a few microseconds. This is especially true for small allocation (below 8096 bytes). During system testing it was observed that by the time the system has booted up to Workbench, there have already been 40,000 allocations to the global memory pool below 2096 bytes.&lt;br /&gt;
&lt;br /&gt;
== Memory Functions ==&lt;br /&gt;
&lt;br /&gt;
Normally, an application uses the AllocVecTags() function to ask for memory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void *AllocVecTags(uint32 size, uint32 tag1, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The size argument is the amount of memory the application needs and the tag list specifies the type of memory and any special memory characteristics (described later). If AllocVecTags() is successful, it returns a pointer to a block of memory. The memory allocation will fail if the system cannot find a big enough block with the requested attributes. If AllocVecTags() fails, it returns NULL.&lt;br /&gt;
&lt;br /&gt;
Because the system only keeps track of how much free memory is available and not how much is in use, it has no idea what memory has been allocated by any task. This means an application has to explicitly return, or deallocate, any memory it has allocated so the system can reuse it. If an application does not return a block of memory to the system, the system will not be able to reallocate that memory to some other task. That block of memory will be lost until the Amiga is reset. If you are using AllocVecTags() to allocate memory, a call to FreeVec() will return that memory to the system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
void FreeVec(void *memoryBlock);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here memoryBlock is a pointer to the memory block the application is returning to the system. The size of the memory block is tracked internally by the system.&lt;br /&gt;
&lt;br /&gt;
Unlike some compiler memory allocation functions, the Amiga system memory allocation functions return memory blocks that are at least longword aligned. This means that the allocated memory will always start on an address which is at least evenly divisible by four. This alignment makes the memory suitable for any system structures or buffers which require word or long word alignment, and also provides optimal alignment for stacks and memory copying.&lt;br /&gt;
&lt;br /&gt;
=== Memory Types ===&lt;br /&gt;
&lt;br /&gt;
There are three primary types of memory in AmigaOS which are summarized in the following table:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Use&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE&lt;br /&gt;
| This memory is private and only accessible within the context of the Task which allocated it. Private memory should always be preferred. Private memory is also swappable by default (i.e. not locked). This memory will not be visible to any other address space.&lt;br /&gt;
	In a future version of AmigaOS, it is planned to have Task specific address spaces. This means each task could potentially address up to 4 GB of private memory each.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED&lt;br /&gt;
| The memory is shared and accessible by any Task in the system without restriction. This memory can be shared between all address spaces and will always appear at the same address in any address space. Shared memory is locked by default and thus is not swappable.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE&lt;br /&gt;
| The memory is used to store executable PowerPC code. This is used two-fold in AmigaOS. First, it allows the system to determine if a function pointer points to real native PowerPC code as opposed to 68k code which needs to be emulated. Second, it prevents common exploits that use stack overflows to execute malicious code. Executable memory is locked by default and thus is not swappable.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory Attributes ===&lt;br /&gt;
&lt;br /&gt;
Memory allocation on AmigaOS has traditionally been rather complicated. Over time, as new hardware models were released, more memory attribute flags were introduced.&lt;br /&gt;
&lt;br /&gt;
All the various memory allocation functions and strategies have been consolidated and distilled into a single AllocVecTags() function call. Programmers are strongly encouraged to stop using any other function to allocate system memory. Using AllocVecTags() is the only way to guarantee future compatibility with more advanced AmigaOS features yet to come.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any attributes when allocating memory, the system tries to satisfy the request with the fastest memory available on the system memory lists.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make Sure You Have Memory|text=Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.}}&lt;br /&gt;
&lt;br /&gt;
==== Using Tags ====&lt;br /&gt;
&lt;br /&gt;
The AllocVecTags() function uses a tag list to define what attributes a block of memory must have. The currently supported tags are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Tag (Default)&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | AVT_Type&lt;br /&gt;
	(MEMF_PRIVATE)&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_PRIVATE: Allocate from the task private heap. This memory will not be visible to any other address space.&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_SHARED: Allocate from the system shared heap. This memory can be shared between all address spaces and will always appear at the same address in any address space. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| MEMF_EXECUTABLE: Allocate memory that is marked executable. This memory is locked by default (see AVT_Lock tag below).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Contiguous&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| Memory allocated with this property is allocated from a contiguous block of physical memory. This makes the memory suitable for DMA purposes when the DMA device does not support scatter/gather operation. For devices that do support scatter/gather use StartDMA() instead.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Physical pages can move at any time, be removed from memory due to paging or otherwise made unavailable unless the memory pages are locked (see LockMemory() function or AVT_Lock tag).&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Lock&lt;br /&gt;
	(TRUE or FALSE)&lt;br /&gt;
| After allocating memory, lock the associated pages in memory. This will prevent the pages from being moved, swapped out or otherwise being made unavailable. This is useful in conjunction with the AVT_Contiguous tag since it will ensure that the memory will stay contiguous after allocation.&lt;br /&gt;
	This tag defaults to FALSE for MEMF_PRIVATE allocations and to TRUE for MEMF_SHARED or MEMF_EXECUTABLE.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Alignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block. The returned memory block will be aligned to the given size (in bytes). It&#039;s virtual address will be at least a multiple of the AVT_Alignment value.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_PhysicalAlignment&lt;br /&gt;
	(16)&lt;br /&gt;
| Define an alignment constraint for the allocated memory block physical address. See AVT_Alignment for more information.&lt;br /&gt;
	&#039;&#039;Note:&#039;&#039; This functionality is mainly used for DMA drivers that require a specific alignment. Alignment values must be powers of two. MEMF_EXECUTABLE memory is always aligned to the current page size.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_ClearWithValue&lt;br /&gt;
| Clear the newly allocated memory with the given byte value. If this tag is not given the memory block is not cleared.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_Wait&lt;br /&gt;
	(TRUE)&lt;br /&gt;
| Wait for the memory subsystem to be available. If TRUE, the calling task will be retired until the memory subsystem is available. This might cause a Forbid() to break. When FALSE and the memory subsystem is not currently available, the function will return immediately and the allocation will fail.&lt;br /&gt;
|-&lt;br /&gt;
| AVT_NoExpunge&lt;br /&gt;
	(FALSE)&lt;br /&gt;
| If allocation fails because of unavailability, prevent invocation of low memory cleanup handlers. The default is FALSE which means that when memory is not available, cleanup handlers are invoked to try and satisfy the allocation.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Using Flags ====&lt;br /&gt;
&lt;br /&gt;
The use of memory flags was the only supported way to allocate memory prior to AmigaOS 4.0. It may be still useful to know more about this obsolete system for porting older applications. For more information about the memory flags system see [[Obsolete Exec Memory Allocation]].&lt;br /&gt;
&lt;br /&gt;
=== Allocating System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples show how to allocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR apointer = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (apointer == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocVecTags() returns the address of the first byte of a memory block that is at least 100 bytes in size or NULL if there is not that much free memory. Because there are no tags specified, private memory is assumed so this memory cannot be shared with any other tasks.&lt;br /&gt;
&lt;br /&gt;
In addition to allocating a block of memory, this function keeps track of the size of the memory block, so your application doesn&#039;t have to remember it when it deallocates that memory block. The AllocVecTags() function allocates a little more memory to store the size of the memory allocation request.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Make No Assumptions|It is not legal to peek the longword in front of the returned memory pointer to find out how big the block is. This has always been illegal regardless of what any other documentation may have stated to the contrary. Your application has access to the memory returned by AllocVecTags() and the only guarantee made is that the returned block has n bytes of continuous address space starting at the returned address. Anything outside this area is not to be touched.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR anotherptr = IExec-&amp;gt;AllocVecTags(1000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  AVT_Lock, FALSE,&lt;br /&gt;
  AVT_ClearWithValue, 0,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (anotherptr == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocates shared memory which is accessible by any task in the system and clears the memory contents to zero. MEMF_SHARED memory is locked by default for compatibility with the obsolete MEMF_PUBLIC flag. This is by far the most common case when allocating shared memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR lockedmem = IExec-&amp;gt;AllocVecTags(3000,&lt;br /&gt;
  AVT_Type, MEMF_SHARED,&lt;br /&gt;
  TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (lockedmem == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above allocated shared memory which is accessible by any task in the system. MEMF_SHARED memory is locked by default so the underlying memory pages are not moveable and cannot be swapped out. Such memory could be used to share memory between a Process and an interrupt routine for example.&lt;br /&gt;
&lt;br /&gt;
If the system free memory list does not contain enough contiguous memory bytes in an area matching your requirements, AllocVecTags() returns a zero. You &#039;&#039;must&#039;&#039; check for this condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR yap = IExec-&amp;gt;AllocVec(500, MEMF_CHIP);&lt;br /&gt;
&lt;br /&gt;
if (yap == NULL)&lt;br /&gt;
    {  /* COULDN&#039;T GET MEMORY, EXIT */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The deprecated AllocVec() function is used in the example above because it is the only way to allocate MEMF_CHIP memory on a classic Amiga system.&lt;br /&gt;
&lt;br /&gt;
=== Locking System Memory ===&lt;br /&gt;
&lt;br /&gt;
The LockMem() function is used to explicitly lock a memory block. This function will make sure your memory block is not unmapped, swapped out or somehow made inaccessible. Use this function wisely. If there is no good reason to lock memory then do not do it. It will prevent the memory system from optimizing memory layout and may lead to poor performance. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;LockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before deallocating the memory is must be unlocked as well.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;UnLockMem(mem, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Do not forget to UnlockMem()&#039;&#039;|Failure to unlock memory will have adverse affects on the memory system. Locked memory pages cannot be moved so the system cannot optimize the layout of locked memory pages.}}&lt;br /&gt;
&lt;br /&gt;
Be careful to always match the number of locks and the number of unlocks. If something else may have locked memory in the same page and an extraneous UnlockMem() decreases the lock reference counter to 0, that page can be paged out or moved. If that happens, for example, with data used in an interrupt handler or by the device driver which handles the swap partition the system will crash.&lt;br /&gt;
&lt;br /&gt;
=== Freeing System Memory ===&lt;br /&gt;
&lt;br /&gt;
The following examples free the memory chunks shown in the previous calls to AllocVecTags().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;FreeVec(apointer);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(anotherptr);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(lockedmem);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;FreeVec(yap);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A memory block allocated with AllocVecTags() or AllocVec() must be returned to the system pool with the FreeVec() function. This function uses the stored size in the allocation to free the memory block, so there is no need to specify the size of the memory block to free.&lt;br /&gt;
&lt;br /&gt;
Any memory that is locked must be explicitly unlocked with UnlockMem() prior to freeing it. Failure to unlock memory will not cause any immediate problems but any pages which are locked cannot be moved or swapped out which can decrease system performance. Never unlock memory which has not been explicitly locked with LockMem() as it could lead to undefined behaviour.&lt;br /&gt;
&lt;br /&gt;
FreeVec() returns no status. However, if you attempt to free a memory block in the middle of a chunk that the system believes is already free, you will cause a system crash. It is also illegal to free the same memory block twice and this will lead to a system crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Leave Memory Allocations Out Of Interrupt Code&#039;&#039;|text=Do not allocate or deallocate system memory from within interrupt code. The [[Exec_Interrupts|Exec Interrupts]] section explains that an interrupt may occur at any time, even during a memory allocation process. As a result, system data structures may not be internally consistent at this time.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory may be Locked by Default ===&lt;br /&gt;
&lt;br /&gt;
By default, the system memory allocation routines will allocate MEMF_SHARED and MEMF_EXECUTABLE memory and implicitly lock it. This memory must not be explicitly unlocked. Let the system handle the unlocking internally.&lt;br /&gt;
&lt;br /&gt;
Here is the right way to allocate and free MEMF_SHARED memory:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is the wrong way:&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR ptr = IExec-&amp;gt;AllocVecTags(10,&lt;br /&gt;
           AVT_Type, MEMF_SHARED,&lt;br /&gt;
           TAG_END);&lt;br /&gt;
IExec-&amp;gt;UnlockMem(ptr, 10);  // Doing this could lead to undefined system behaviour.&lt;br /&gt;
IExec-&amp;gt;FreeVec(ptr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlocking memory not explicitly locked does not immediately cause any issues. However, in a future version of AmigaOS the memory pages may be handled differently than they are handled today. This could lead to incompatibilities with your applications and AmigaOS. The simple rule is that if you called LockMem() you must also call UnlockMem(). All other times you should not call UnlockMem().&lt;br /&gt;
&lt;br /&gt;
{{Note|Memory locking is done at the page level. Even if a single byte of memory is locked in a page that entire page is locked. The programmer has no control over which pages may be used to satisfy a memory allocation.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Shared and executable memory is locked implicitly for 68K backwards compatibility reasons. Use AVT_Lock (or equivalent) to ensure your shared and executable memory is not locked. MEMF_PRIVATE memory has no backwards compatibility issues and is always unlocked by default.}}&lt;br /&gt;
&lt;br /&gt;
=== Memory Information Functions ===&lt;br /&gt;
&lt;br /&gt;
The memory information routines AvailMem() and TypeOfMem() can provide the amount of memory available in the system, and the attributes of a particular block of memory.&lt;br /&gt;
&lt;br /&gt;
==== Memory Requirements ====&lt;br /&gt;
&lt;br /&gt;
The same attribute flags used in memory allocation routines are valid for the memory information routines. There is also an additional flag, MEMF_LARGEST, which can be used in the AvailMem() routine to find out what the largest available memory block of a particular type is. Specifying the MEMF_TOTAL flag will return the total amount of memory currently available.&lt;br /&gt;
&lt;br /&gt;
==== Calling Memory Information Functions ====&lt;br /&gt;
&lt;br /&gt;
The following example shows how to find out how much memory of a particular type is available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 size = IExec-&amp;gt;AvailMem(MEMF_CHIP | MEMF_LARGEST);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AvailMem() returns the size of the largest chunk of available chip memory.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;AvailMem() May Not Be Totally Accurate&#039;&#039;|text=Because of multitasking, the return value from AvailMem() may be inaccurate by the time you receive it.}}&lt;br /&gt;
&lt;br /&gt;
The following example shows how to determine the type of memory of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
uint32 memtype = IExec-&amp;gt;TypeOfMem((APTR)0x090000);&lt;br /&gt;
if ((memtype &amp;amp; MEMF_CHIP) == MEMF_CHIP) {  /*  ... It&#039;s chip memory ...  */   }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
TypeOfMem() returns the attributes of the memory at a specific address. If it is passed an invalid memory address, TypeOfMem() returns NULL. This routine is normally used to determine if a particular chunk of memory is in chip memory.&lt;br /&gt;
&lt;br /&gt;
=== Using Memory Copy Functions ===&lt;br /&gt;
&lt;br /&gt;
For memory block copies, the CopyMem() and CopyMemQuick() functions can be used.&lt;br /&gt;
&lt;br /&gt;
==== Copying System Memory ====&lt;br /&gt;
&lt;br /&gt;
The following samples show how to use the copying routines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR source = IExec-&amp;gt;AllocVecTags(1000, AVT_ClearWithValue, 0, TAG_END);&lt;br /&gt;
APTR target = IExec-&amp;gt;AllocVecTags(1000, AVT_Type, MEMF_SHARED, TAG_END);&lt;br /&gt;
IExec-&amp;gt;CopyMem(source, target, 1000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CopyMem() copies the specified number of bytes from the source data region to the target data region. The pointers to the regions can be aligned on arbitrary address boundaries. CopyMem() will attempt to copy the memory as efficiently as it can according to the alignment of the memory blocks, and the amount of data that it has to transfer. These functions are optimized for copying large blocks of memory which can result in unnecessary overhead if used to transfer very small blocks of memory.&lt;br /&gt;
&lt;br /&gt;
CopyMemQuick() is now identical to CopyMem(). In previous versions of the operating system, CopyMemQuick() performed more optimized copying of the specified number of bytes from the source data region to the target data region. The source and target pointers must be longword aligned and the size (in bytes) must be divisible by four. There are no such restrictions starting with AmigaOS 4.0.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;Not All Copies Are Supported&#039;&#039;|text=Neither CopyMem() nor CopyMemQuick() supports copying between regions that overlap. For overlapping regions see MoveMem() in the [[Utility Library]].}}&lt;br /&gt;
&lt;br /&gt;
=== System Memory Pools ===&lt;br /&gt;
&lt;br /&gt;
A construct carried over from the AmigaOS 3.x Exec is the memory pool. The code handling memory pools uses an algorithm based on boundary tags and size-segregated memory lists. The speed gain is tremendous: even for the &amp;quot;dumb&amp;quot; case of just allocating 100000 blocks and freeing them again, the speed is ten times faster than the previous implementation. Due to the size-segregated free lists and the easy coalescing due to the boundary tags, real-life performance gain is even higher and would be between 10 and 20 times.&lt;br /&gt;
&lt;br /&gt;
Two types of memory pools are available depending on the needs of the application:&lt;br /&gt;
* [[Exec_Item_Pools|Item Pools]] are built for speed and are used for allocating large numbers of items that are all the same size.&lt;br /&gt;
* Generic [[Exec_Memory_Pools|Memory Pools]] can handle allocations of different sizes at the expense of some speed.&lt;br /&gt;
&lt;br /&gt;
=== Summary of System Controlled Memory Handling Routines ===&lt;br /&gt;
&lt;br /&gt;
; AllocVecTags() and FreeVec()&lt;br /&gt;
: These are system-wide memory allocation and deallocation routines. They use a memory free-list owned and managed by the system.&lt;br /&gt;
&lt;br /&gt;
; LockMem() and UnlockMem()&lt;br /&gt;
: These routines explicitly lock and unlock underlying memory pages.&lt;br /&gt;
&lt;br /&gt;
; AvailMem()&lt;br /&gt;
: This routine returns the number of free bytes in a specified type of memory.&lt;br /&gt;
&lt;br /&gt;
; TypeOfMem()&lt;br /&gt;
: This routine returns the memory attributes of a specified memory address.&lt;br /&gt;
&lt;br /&gt;
; CopyMem() and CopyMemQuick()&lt;br /&gt;
: CopyMem() is a general purpose memory copy routine. CopyMemQuick() is an optimized version of CopyMemQuick(), but has restrictions on the size and alignment of the arguments.&lt;br /&gt;
&lt;br /&gt;
== Allocating DMA Memory ==&lt;br /&gt;
&lt;br /&gt;
Device drivers often use DMA to transfer data to and from the device. The StartDMA(), GetDMAList() and EndDMA() functions are used to create a scatter/gather list suitable for such DMA transfers.&lt;br /&gt;
&lt;br /&gt;
The following conditions are guaranteed to be met when using these DMA functions:&lt;br /&gt;
* The memory region given is guaranteed to be mapped to physical memory.&lt;br /&gt;
* The mapping will not change as long as EndDMA() is not called.&lt;br /&gt;
* All cache entries in this region will be flushed out.&lt;br /&gt;
&lt;br /&gt;
The example code below demonstrates how to perform a DMA write transfer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR addr;&lt;br /&gt;
uint32 size;&lt;br /&gt;
&lt;br /&gt;
/* Tell the system to prepare for DMA */&lt;br /&gt;
uint32 arraySize = IExec-&amp;gt;StartDMA(addr, size, 0);&lt;br /&gt;
if (arraySize &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    /* The memory area is prepared, allocate and retrieve the DMA list */&lt;br /&gt;
    struct DMAEntry *DMAList = IExec-&amp;gt;AllocSysObject(ASOT_DMAENTRY,&lt;br /&gt;
      ASODMAE_NumEntries,  arraySize,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (DMAList != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        IExec-&amp;gt;GetDMAList(addr, size, 0, DMAList);&lt;br /&gt;
 &lt;br /&gt;
        /* Feed the DMA controller and do stuff */&lt;br /&gt;
        ...&lt;br /&gt;
        /* Get rid of the DMAList&#039;s memory */&lt;br /&gt;
 &lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, endFlags);&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_DMAENTRY, DMAList);&lt;br /&gt;
    }&lt;br /&gt;
    else&lt;br /&gt;
    {&lt;br /&gt;
        // Note We still call EndDMA() even though the actual&lt;br /&gt;
        // transfer didn&#039;t happen.&lt;br /&gt;
        IExec-&amp;gt;EndDMA(addr, size, DMAF_NoModify);&lt;br /&gt;
&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t allocate DMA list\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
else&lt;br /&gt;
{&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Can&#039;t initiate DMA transfer\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syncing and Memory Access ==&lt;br /&gt;
&lt;br /&gt;
The PowerPC is a pure load/store architecture. That means, it will never operate on memory arguments like x86, to modify data, you have to load, modify and store.&lt;br /&gt;
&lt;br /&gt;
All PowerPC/POWER cpus have a load/store queue, i.e. a queue where load and store operations are queued to reduce memory latency. If more than one store is made to the same long word (i.e. you write the first byte and then the second byte), then those stores are combined. As you can see, this will cause a problem for a chip register since both are written at the same time which is likely not what you want.&lt;br /&gt;
&lt;br /&gt;
Similar for reading: A read might shortcut through the load/store queue and use a value that&#039;s already been read and is present in the load/store queue. Again, for chip registers, this will cause a problem because you might read an old value.&lt;br /&gt;
&lt;br /&gt;
There are two instructions that deal with this: &#039;&#039;&#039;eieio&#039;&#039;&#039; (Ensure In-order Execution of I/O) and &#039;&#039;&#039;sync&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The eieio instruction simply inserts a &amp;quot;barrier&amp;quot; into the load/store queue. When combining &#039;&#039;stores&#039;&#039; the CPU never searches past this barrier for possible combines. This means that the sequence&lt;br /&gt;
&lt;br /&gt;
# store to some address&lt;br /&gt;
# eieio&lt;br /&gt;
# store to some address + 1&lt;br /&gt;
&lt;br /&gt;
will never be combined because of the eieio barrier between them.&lt;br /&gt;
&lt;br /&gt;
The sync instruction will simply halt all execution and flush the load/store queue, executing and finishing all loads and stores.&lt;br /&gt;
&lt;br /&gt;
As you can imagine, sync is MUCH MORE costly than eieio.&lt;br /&gt;
&lt;br /&gt;
=== When to use eieio and sync ===&lt;br /&gt;
&lt;br /&gt;
If you want to write, post fix your writes with an eieio instruction.&lt;br /&gt;
&lt;br /&gt;
If you want to read, prefix your reads with a sync instruction.&lt;br /&gt;
&lt;br /&gt;
This is also where the &amp;quot;GUARDED&amp;quot; memory flag comes in. GUARDED memory simply ensures program order of loads and stores, that is, it&#039;s an implicit eieio.&lt;br /&gt;
&lt;br /&gt;
{{Note|Knowing when and how to use the &#039;&#039;&#039;eieio&#039;&#039;&#039; and &#039;&#039;&#039;sync&#039;&#039;&#039; instructions is somewhat vital for driver developers.}}&lt;br /&gt;
&lt;br /&gt;
== Allocating Multiple Memory Blocks ==&lt;br /&gt;
&lt;br /&gt;
Exec provides the routines AllocTaskMemEntry() and FreeEntry() to allocate multiple memory blocks in a single call.&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() accepts a data structure called a MemList, which contains the information about the size of the memory blocks to be allocated and the requirements, if any, that you have regarding the allocation.&lt;br /&gt;
&lt;br /&gt;
The MemList structure is found in the include file &amp;lt;exec/memory.h&amp;gt; and is defined as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList&lt;br /&gt;
{&lt;br /&gt;
    struct Node     ml_Node;&lt;br /&gt;
    UWORD           ml_NumEntries;      /* number of MemEntrys */&lt;br /&gt;
    struct MemEntry ml_ME[1];           /* where the MemEntrys begin*/&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ml_Node&lt;br /&gt;
: allows you to link together multiple MemLists. However, the node is ignored by the routines AllocTaskMemEntry() and FreeEntry().&lt;br /&gt;
&lt;br /&gt;
; ml_NumEntries&lt;br /&gt;
: tells the system how many MemEntry sets are contained in this MemList. Notice that a MemList is a variable-length structure and can contain as many sets of entries as you wish.&lt;br /&gt;
&lt;br /&gt;
The MemEntry structure looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemEntry&lt;br /&gt;
{&lt;br /&gt;
    union {&lt;br /&gt;
        ULONG   meu_Reqs;   /* the AllocMem requirements */&lt;br /&gt;
        APTR    meu_Addr;   /* address of your memory */&lt;br /&gt;
        } me_Un;&lt;br /&gt;
    ULONG   me_Length;      /* the size of this request */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sample Code for Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an example of showing how to use the AllocTaskMemEntry() with multiple blocks of memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// alloctaskmementry.c - example of allocating several memory areas.&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MemList *memlist;             /* pointer to a MemList structure        */&lt;br /&gt;
&lt;br /&gt;
struct MemBlocks /* define a new structure because C cannot initialize unions */&lt;br /&gt;
{&lt;br /&gt;
    struct MemList  mn_head;         /* one entry in the header               */&lt;br /&gt;
    struct MemEntry mn_body[3];      /* additional entries follow directly as */&lt;br /&gt;
} memblocks;                         /* part of the same data structure       */&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    memblocks.mn_head.ml_NumEntries = 4; /* 4! Since the MemEntry starts at 1! */&lt;br /&gt;
&lt;br /&gt;
    /* Describe the first piece of memory we want.  Because of our MemBlocks structure */&lt;br /&gt;
    /* setup, we reference the first MemEntry differently when initializing it.        */&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Reqs   = MEMF_CLEAR;&lt;br /&gt;
    memblocks.mn_head.ml_ME[0].me_Length = 4000;&lt;br /&gt;
&lt;br /&gt;
    memblocks.mn_body[0].me_Reqs   = MEMF_PRIVATE | MEMF_CLEAR;/* Describe the other pieces of    */&lt;br /&gt;
    memblocks.mn_body[0].me_Length = 100000;                   /* memory we want. Additional      */&lt;br /&gt;
    memblocks.mn_body[1].me_Reqs   = MEMF_SHARED | MEMF_CLEAR; /* MemEntries are initialized this */&lt;br /&gt;
    memblocks.mn_body[1].me_Length = 200000;                   /* way. If we wanted even more en- */&lt;br /&gt;
    memblocks.mn_body[2].me_Reqs   = MEMF_EXECUTABLE;          /* tries, we would need to declare */&lt;br /&gt;
    memblocks.mn_body[2].me_Length = 25000;                    /* a larger MemEntry array in our  */&lt;br /&gt;
                                                               /* MemBlocks structure.            */&lt;br /&gt;
&lt;br /&gt;
    memlist = (struct MemList *)IExec-&amp;gt;AllocTaskMemEntry((struct MemList *)&amp;amp;memblocks);&lt;br /&gt;
&lt;br /&gt;
    if (memlist == NULL)&lt;br /&gt;
    {&lt;br /&gt;
       IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry FAILED\n&amp;quot;);&lt;br /&gt;
       return RETURN_FAIL;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    /* We got all memory we wanted.  Use it and call FreeEntry() to free it */&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;AllocTaskMemEntry succeeded - now freeing all allocated blocks\n&amp;quot;);&lt;br /&gt;
    IExec-&amp;gt;FreeEntry(memlist);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AllocTaskMemEntry() returns a pointer to a new MemList of the same size as the MemList that you passed to it. For example, ROM code can provide a MemList containing the requirements of a task and create a RAM-resident copy of the list containing the addresses of the allocated entries. The pointer to the MemList is used as the argument for FreeEntry() to free the memory blocks.&lt;br /&gt;
&lt;br /&gt;
=== Result of Allocating Multiple Memory Blocks ===&lt;br /&gt;
&lt;br /&gt;
The MemList created by AllocTaskMemEntry() contains MemEntry entries. MemEntrys are defined by a union statement, which allows one memory space to be defined in more than one way.&lt;br /&gt;
&lt;br /&gt;
If AllocTaskMemEntry() returns a non-NULL value then all of the meu_Addr positions in the returned MemList will contain valid memory addresses meeting the requirements you have provided. To use this memory area, you would use code similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *mlist = IExec-&amp;gt;AllocTaskMemEntry(&amp;amp;ML);&lt;br /&gt;
APTR memory = NULL;&lt;br /&gt;
&lt;br /&gt;
if ( mlist != NULL )&lt;br /&gt;
{&lt;br /&gt;
  memory = mlist-&amp;gt;ml_ME[0].me_Un.meu_Addr;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Multiple Memory Blocks and Tasks ===&lt;br /&gt;
&lt;br /&gt;
If you want to take advantage of Exec&#039;s automatic cleanup, use the MemList and AllocTaskMemEntry() facility to do your dynamic memory allocation.&lt;br /&gt;
&lt;br /&gt;
In the Task control block structure, there is a list header named tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
This is the list header that you initialize to include MemLists that your task has created by call(s) to AllocTaskMemEntry(). Here is a short program segment that handles task memory list header initialization only. It assumes that you have already run AllocTaskMemEntry() as shown in the simple AllocTaskMemEntry() example above.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MemList *ml;&lt;br /&gt;
&lt;br /&gt;
struct Task *tc = IExec-&amp;gt;FindTask(0);&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;AddTail(tc-&amp;gt;tc_MemEntry, ml);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Assuming that you have only used the AllocTaskMemEntry() method (or AllocVecTags() and built your own custom MemList), the system now knows where to find the blocks of memory that your task has dynamically allocated. The RemTask() function automatically frees all memory found on tc_MemEntry.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=&#039;&#039;CreateTask() Sets Up A MemList.&#039;&#039;|text=The CreateTask() function, and other system task and process creation functions use a MemList in tc_MemEntry so that the Task structure and stack will be automatically deallocated when the Task is removed.}}&lt;br /&gt;
&lt;br /&gt;
=== Summary of Multiple Memory Blocks Allocation Routines ===&lt;br /&gt;
&lt;br /&gt;
These are routines for allocating and freeing multiple memory blocks with a single call.&lt;br /&gt;
&lt;br /&gt;
This routine initializes memory from data and offset values in a table. Typically only assembly language programs benefit from using this routine. See the SDK for more details.&lt;br /&gt;
&lt;br /&gt;
== Allocating Memory at an Absolute Address ==&lt;br /&gt;
&lt;br /&gt;
For special advanced applications, AllocAbs() is provided. Using AllocAbs(), an application can allocate a memory block starting at a specified absolute memory address. If the memory is already allocated or if there is not enough memory available for the request, AllocAbs() returns a zero.&lt;br /&gt;
&lt;br /&gt;
Be aware that an absolute memory address which happens to be available on one Amiga may not be available on a machine with a different configuration or different operating system revision, or even on the same machine at a different times. For example, a piece of memory that is available during expansion board configuration might not be available at earlier or later times. Here is an example call to AllocAbs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR absoluteptr = (APTR)IExec-&amp;gt;AllocAbs(10000, 0x2F0000);&lt;br /&gt;
if (!(absoluteptr))&lt;br /&gt;
    { /* Couldn&#039;t get memory, act accordingly. */  }&lt;br /&gt;
&lt;br /&gt;
/* After we&#039;re done using it, we call FreeMem() to free the memory block. */&lt;br /&gt;
IExec-&amp;gt;FreeMem(absoluteptr, 10000);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of the Exec functions that handle memory management. See the SDK for details on each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Memory Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AllocMem()&lt;br /&gt;
| Allocate memory with specified attributes. &#039;&#039;&#039;This function is obsolete.&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| AllocAbs()&lt;br /&gt;
| Allocate memory at a specified location.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTaskMemEntry()&lt;br /&gt;
| Allocate multiple memory blocks.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVec()&lt;br /&gt;
| Allocate memory with specified attributes and keep track of the size. This function is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| AllocVecTags()&lt;br /&gt;
| Allocate memory with specified attributes defined by tags and keep track of the size. If an application needs to allocate some memory, it will usually use this function.&lt;br /&gt;
|-&lt;br /&gt;
| AvailMem()&lt;br /&gt;
| Return the amount of free memory, given certain conditions.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMem()&lt;br /&gt;
| Copy memory block, which can be non-aligned and of arbitrary length.&lt;br /&gt;
|-&lt;br /&gt;
| CopyMemQuick()&lt;br /&gt;
| Copy aligned memory block.&lt;br /&gt;
|-&lt;br /&gt;
| FreeEntry()&lt;br /&gt;
| Free multiple memory blocks, allocated with AllocTaskMemEntry().&lt;br /&gt;
|-&lt;br /&gt;
| FreeMem()&lt;br /&gt;
| Free a memory block of specified size, allocated with AllocMem() or AllocAbs().&lt;br /&gt;
|-&lt;br /&gt;
| FreeVec()&lt;br /&gt;
| Free a memory block allocated with AllocVecTags() or AllocVec().&lt;br /&gt;
|-&lt;br /&gt;
| InitStruct()&lt;br /&gt;
| Initialize memory from a table.&lt;br /&gt;
|-&lt;br /&gt;
| LockMem()&lt;br /&gt;
| Lock the underlying pages given a memory block address and size.&lt;br /&gt;
|-&lt;br /&gt;
| TypeOfMem()&lt;br /&gt;
| Determine attributes of a specified memory address.&lt;br /&gt;
|-&lt;br /&gt;
| UnlockMem()&lt;br /&gt;
| Unlock the underlying pages given a memory block address and size.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Input_Device&amp;diff=12380</id>
		<title>Input Device</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Input_Device&amp;diff=12380"/>
		<updated>2023-11-04T01:13:56Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Devices|Input]]{{CodeReview}}&lt;br /&gt;
== Input Device ==&lt;br /&gt;
&lt;br /&gt;
The input device is the central collection point for input events disseminated throughout the system. The best way to describe the input device is a manager of a stream with feeders. The input device itself and other modules such as the file system add events to the stream; so do input device “users”—programs or other devices that use parts of the stream or change it in some way. Feeders of the input device include the keyboard, timer and gameport devices. The keyboard, gameport, and timer devices are special cases in that the input device opens them and asks them for input. Users of the input device include Intuition and the console device.&lt;br /&gt;
&lt;br /&gt;
== Input Device Commands and Functions ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Command&lt;br /&gt;
! Command Operation&lt;br /&gt;
|-&lt;br /&gt;
| CMD_FLUSH || Purge all active and queued requests for the input device.&lt;br /&gt;
|-&lt;br /&gt;
| CMD_RESET || Reset the input port to its initialized state. All active and queued I/O requests will be aborted. Restarts the device if it has been stopped.&lt;br /&gt;
|-&lt;br /&gt;
| CMD_START || Restart the currently active input (if any) and resume queued I/O requests.&lt;br /&gt;
|-&lt;br /&gt;
| CMD_STOP || Stop any currently active input and prevent queued I/O requests from starting.&lt;br /&gt;
|-&lt;br /&gt;
| IND_ADDEVENT || Propagate an input event to all handlers, updating event qualifiers.&lt;br /&gt;
|-&lt;br /&gt;
| IND_ADDHANDLER || Add an input-stream handler into the handler chain.&lt;br /&gt;
|-&lt;br /&gt;
| IND_ADDNOTIFY || Add a hook which will be invoked whenever a configuration option changes.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETHANDLERLIST || Obtain a pointer to the list of input handlers.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETKDEVICE || Query the name and unit number of the device keyboard input events are collected from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETMDEVICE || Query the name and unit number of the device mouse input events are collected from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETPERIOD || Get the key repeat period.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETTHRESH || Get the key repeat threshold.&lt;br /&gt;
|-&lt;br /&gt;
| IND_IMMEDIATEADDNOTIFY || Add a hook which will be invoked whenever a configuration option changes. The hook will be invoked on the current configuration first. &lt;br /&gt;
|-&lt;br /&gt;
| IND_REMHANDLER || Remove an input-stream handler from the handler chain.&lt;br /&gt;
|-&lt;br /&gt;
| IND_REMOVENOTIFY || Remove a hook previously installed with the IND_ADDNOTIFY command.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETKDEVICE || Choose the device to collect keyboard input events from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMDEVICE || Choose the device to collect mouse input events from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMPORT || Set the controller port to which the mouse is connected.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMTRIG || Set conditions that must be met by a mouse before a pending read request will be satisfied.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMTYPE || Set the type of device at the mouse port.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETPERIOD || Set the period at which a repeating key repeats.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETTHRESH || Set the repeating key hold-down time before repeat starts.&lt;br /&gt;
|-&lt;br /&gt;
| IND_WRITEEVENT || Propagate an input event stream to all devices.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Input Device Functions&lt;br /&gt;
| PeekQualifier() || Return the input device’s current qualifiers.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Device Interface ==&lt;br /&gt;
&lt;br /&gt;
The input device operates like the other Amiga devices. To use it, you must first open the input device, then send I/O requests to it, and then close it when finished. See [[Exec_Device_I/O|Exec Device I/O]] for general information on device usage.&lt;br /&gt;
&lt;br /&gt;
A number of structures are used by the input device to do its processing. Some are used to pass commands and data to the device, some are used to describe input events like mouse movements and key depressions, and one structure is used to describe the environment for input event handlers.&lt;br /&gt;
&lt;br /&gt;
The I/O request used by the input device for most commands is IOStdReq.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IOStdReq&lt;br /&gt;
{&lt;br /&gt;
    struct Message io_Message; /* message reply port */&lt;br /&gt;
    struct Device  *io_Device; /* device node pointer */&lt;br /&gt;
    struct Unit    *io_Unit;   /* unit */&lt;br /&gt;
    UWORD  io_Command;         /* input device command */&lt;br /&gt;
    UBYTE  io_Flags;           /* input device flags */&lt;br /&gt;
    BYTE   io_Error;           /* error code */&lt;br /&gt;
    ULONG  io_Length;          /* number of bytes to transfer */&lt;br /&gt;
    APTR   io_Data;            /* pointer to data area */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See the include file exec/io.h for the complete structure definition.&lt;br /&gt;
&lt;br /&gt;
Two of the input device commands—IND_SETTHRESH and IND_SETPERIOD—require a time specification and &#039;&#039;must&#039;&#039; use a TimeRequest structure instead of an IOStdReq.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct TimeRequest&lt;br /&gt;
{&lt;br /&gt;
    struct IORequest Request;&lt;br /&gt;
    struct TimeVal Time;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the TimeRequest structure includes an IORequest structure. The io_Command field of the IORequest indicates the command to the input device and the TimeVal structure sets the time values. See the include file devices/timer.h for the complete structure definition.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=In Case You Feel Like Reinventing the Wheel...|text=You could define a “super-IORequest” structure for the input device which would combine the IOStdReq fields with the TimeVal structure of the TimeRequest structure.}}&lt;br /&gt;
&lt;br /&gt;
=== Opening the Input Device ===&lt;br /&gt;
&lt;br /&gt;
Three primary steps are required to open the input device:&lt;br /&gt;
&lt;br /&gt;
* Create a message port using AllocSysObject() and ASOT_PORT type. Reply messages from the device must be directed to a message port.&lt;br /&gt;
* Create an extended I/O request structure of type IOStdReq or TimeRequest using AllocSysObject() and ASOT_IOREQUEST type. The I/O request created by the AllocSysObject() function will be used to pass commands and data to the input device.&lt;br /&gt;
* Open the Input device. Call OpenDevice(), passing the I/O request.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MsgPort *InputMP = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (InputMP != NULL)&lt;br /&gt;
{&lt;br /&gt;
    struct IOStdReq *InputIO = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
        ASOIOR_Size, sizeof(struct IOStdReq),&lt;br /&gt;
        ASOIOR_ReplyPort, InputMP,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ClipIO != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        if (IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, 0, (struct IORequest *)InputIO, 0))&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;input.device did not open\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above code will work for all the input device commands except for the ones which require a time specification. For those, the code would look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/timer.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MsgPort *InputMP = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (InputMP != NULL)&lt;br /&gt;
{&lt;br /&gt;
    struct TimeRequest *InputIO = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
        ASOIOR_Size, sizeof(struct TimeRequest),&lt;br /&gt;
        ASOIOR_ReplyPort, InputMP,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ClipIO != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        if (IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, 0, (struct IORequest *)InputIO, 0))&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;input.device did not open\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Input Device Event Types ===&lt;br /&gt;
&lt;br /&gt;
The input device is automatically opened by the console device when the system boots. When the input device is opened, a task named “input.device” is started. The input device task communicates directly with the keyboard device to obtain raw key events. It also communicates with the gameport device to obtain mouse button and mouse movement events and with the timer device to obtain time events. In addition to these events, you can add your own input events to the input device, to be fed to the handler chain (see below).&lt;br /&gt;
&lt;br /&gt;
The keyboard device is accessible directly (see [[Keyboard_Device|Keyboard Device]]). However, once the input.device task has started, you should not read events from the keyboard device directly, since doing so will deprive the input device of the events and confuse key repeating.&lt;br /&gt;
&lt;br /&gt;
The gameport device has two units. As you view the Amiga, looking at the gameport connectors, the left connector is assigned as the primary mouse input for Intuition and contributes gameport input events to the input event stream.&lt;br /&gt;
&lt;br /&gt;
The right connector is handled by the other gameport unit and is currently unassigned. While the input device task is running, that task expects to read the input from the left connector. Direct use of the gameport device is covered in [[Gameport_Device|Gameport Device]].&lt;br /&gt;
&lt;br /&gt;
The timer device is used to generate time events for the input device. It is also used to control key repeat rate and key repeat threshold. The timer device is a shared-access device and is described in [[Timer_Device|Timer Device]].&lt;br /&gt;
&lt;br /&gt;
The device-specific commands are described below. First though, it may be helpful to consider the types of input events that the input device deals with. An input event is a data structure that describes the following:&lt;br /&gt;
&lt;br /&gt;
* The class of the event — often describes the device that generated the event.&lt;br /&gt;
* The subclass of the event — space for more information if needed.&lt;br /&gt;
* The code — keycode if keyboard, button information if mouse, others.&lt;br /&gt;
* A qualifier such as “Alt key also down,”or “key repeat active”.&lt;br /&gt;
* A position field that contains a data address or a mouse position count.&lt;br /&gt;
* A time stamp, to determine the sequence in which the events occurred.&lt;br /&gt;
* A link-field by which input events are linked together.&lt;br /&gt;
* The class, subclass, code and qualifier of the previous down key.&lt;br /&gt;
&lt;br /&gt;
The full definitions for each field can be found in the include file devices/inputevent.h. You can find more information about input events in [[Gameport_Device|Gameport Device]] and [[Console_Device|Console Device]].&lt;br /&gt;
&lt;br /&gt;
The various types of input events are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Input Device Event Types&lt;br /&gt;
| IECLASS_NULL || A NOP input event&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_RAWKEY || A raw keycode from the keyboard device&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_RAWMOUSE || The raw mouse report from the gameport device&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_EVENT || A private console event&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_POINTERPOS || A pointer position report&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_TIMER || A timer event&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_GADGETDOWN || Select button pressed down over a gadget (address in ie_EventAddress)&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_GADGETUP || Select button released over the same gadget (address in ie_EventAddress)&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_REQUESTER || Some requester activity has taken place.&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_MENULIST || This is a menu number transmission (menu number is in ie_Code)&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_CLOSEWINDOW || User has selected the active window’s Close Gadget&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_SIZEWINDOW || This window has a new size&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_REFRESHWINDOW || The window pointed to by ie_EventAddress needs to be refreshed&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_NEWPREFS || New preferences are available&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_DISKREMOVED || The disk has been removed&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_DISKINSERTED || The disk has been inserted&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_ACTIVEWINDOW || The window is about to be been made active&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_INACTIVEWINDOW || The window is about to be made inactive&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_NEWPOINTERPOS || Extended-function pointer position report&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_MENUHELP || Help key report during Menu session &lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_CHANGEWINDOW || The Window has been modified with move, size, zoom, or change&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
There is a difference between simply receiving an input event from a device and actually becoming a handler of an input event stream. A handler is a routine that is passed an input event list. It is up to the handler to decide if it can process the input events. If the handler does not recognize an event, it leaves it undisturbed in the event list.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=It All Flows Downhill|text=Handlers can themselves generate new linked lists of events which can be passed down to lower priority handlers.}}&lt;br /&gt;
&lt;br /&gt;
The InputEvent structure is used by the input device to describe an input event such as a keypress or a mouse movement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct InputEvent&lt;br /&gt;
{&lt;br /&gt;
    struct  InputEvent *ie_NextEvent;   /* the chronologically next event */&lt;br /&gt;
    UBYTE   ie_Class;                   /* the input event class */&lt;br /&gt;
    UBYTE   ie_SubClass;                /* optional subclass of the class */&lt;br /&gt;
    UWORD   ie_Code;                    /* the input event code */&lt;br /&gt;
    UWORD   ie_Qualifier;               /* qualifiers in effect for the event*/&lt;br /&gt;
    union&lt;br /&gt;
    {&lt;br /&gt;
        struct&lt;br /&gt;
        {&lt;br /&gt;
            WORD    ie_x;               /* the pointer position for the event*/&lt;br /&gt;
            WORD    ie_y;&lt;br /&gt;
        } ie_xy;&lt;br /&gt;
        APTR    ie_addr;                /* the event address */&lt;br /&gt;
        struct&lt;br /&gt;
        {&lt;br /&gt;
            UBYTE   ie_prev1DownCode;   /* previous down keys for dead */&lt;br /&gt;
            UBYTE   ie_prev1DownQual;   /*   key translation: the ie_Code */&lt;br /&gt;
            UBYTE   ie_prev2DownCode;   /*   &amp;amp; low byte of ie_Qualifier for */&lt;br /&gt;
            UBYTE   ie_prev2DownQual;   /*   last &amp;amp; second last down keys */&lt;br /&gt;
        } ie_dead;&lt;br /&gt;
    } ie_position;&lt;br /&gt;
    struct timeval ie_TimeStamp;        /* the system tick at the event */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The IEPointerPixel and IEPointerTablet structures are used to set the mouse position with the IECLASS_NEWPOINTERPOS input event class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IEPointerPixel&lt;br /&gt;
{&lt;br /&gt;
    struct Screen       *iepp_Screen;   /* pointer to an open screen */&lt;br /&gt;
    struct&lt;br /&gt;
    {                           /* pixel coordinates in iepp_Screen */&lt;br /&gt;
        WORD    X;&lt;br /&gt;
        WORD    Y;&lt;br /&gt;
    } iepp_Position;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
struct IEPointerTablet&lt;br /&gt;
{&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        UWORD   X;&lt;br /&gt;
        UWORD   Y;&lt;br /&gt;
    } iept_Range;       /* 0 is min, these are max      */&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        UWORD   X;&lt;br /&gt;
        UWORD   Y;&lt;br /&gt;
    } iept_Value;       /* between 0 and iept_Range     */&lt;br /&gt;
&lt;br /&gt;
    WORD iept_Pressure; /* -128 to 127 (unused, set to 0)  */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See the include file devices/inputevent.h for the complete structure definitions.&lt;br /&gt;
&lt;br /&gt;
For input device handler installation, the Interrupt structure is used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Interrupt&lt;br /&gt;
{&lt;br /&gt;
    struct Node is_Node;&lt;br /&gt;
    APTR   is_Data;         /* server data segment */&lt;br /&gt;
    VOID   (*is_Code)();    /* server code entry   */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See the include file exec/interrupts.h for the complete structure definition.&lt;br /&gt;
&lt;br /&gt;
=== Closing the Input Device ===&lt;br /&gt;
&lt;br /&gt;
Each OpenDevice() must eventually be matched by a call to CloseDevice(). All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (!(IExec-&amp;gt;CheckIO(InputIO)))&lt;br /&gt;
    {&lt;br /&gt;
    IExec-&amp;gt;AbortIO(InputIO);  /* Ask device to abort request, if pending */&lt;br /&gt;
    }&lt;br /&gt;
IExec-&amp;gt;WaitIO(InputIO);   /* Wait for abort, then clean up */&lt;br /&gt;
IExec-&amp;gt;CloseDevice((struct IORequest *)InputIO);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using the Mouse Port With the Input Device ==&lt;br /&gt;
&lt;br /&gt;
To get mouse port information you must first set the current mouse port by passing an IOStdReq to the device with IND_SETMPORT set in io_Command and a pointer to a byte set in io_Data. If the byte is set to 0 the left controller port will be used as the current mouse port; if it is set to 1, the right controller port will be used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int8 port = 1;      /* set mouse port to right controller */&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = &amp;amp;port;&lt;br /&gt;
InputIO-&amp;gt;io_Flags   = IOF_QUICK;&lt;br /&gt;
InputIO-&amp;gt;io_Length  = 1;&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_SETMPORT;&lt;br /&gt;
IExec-&amp;gt;BeginIO((struct IORequest *)InputIO);&lt;br /&gt;
if (InputIO-&amp;gt;io_Error)&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;\nSETMPORT failed %ld\n&amp;quot;, InputIO-&amp;gt;io_Error);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Put That Back!|text=The default mouse port is the left controller. Don’t forget to set the mouse port back to the left controller before exiting if you change it to the right controller during your application.}}&lt;br /&gt;
&lt;br /&gt;
=== Setting the Conditions for a Mouse Port Report ===&lt;br /&gt;
&lt;br /&gt;
You set the conditions for a mouse port report by passing an IOStdReq to the device with IND_SETMTRIG set in io_Command, the address of a GamePortTrigger structure set in io_Data and the length of the structure set in io_Length.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct GamePortTrigger InputTR;&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)InputTR;  /* set trigger conditions */&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_SETMTRIG;   /* from InputTR */&lt;br /&gt;
InputIO-&amp;gt;io_Length  = sizeof(struct GamePortTrigger);&lt;br /&gt;
IExec-&amp;gt;DoIO(InputIO);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The information needed for mouse port report setting is contained in a GamePortTrigger data structure which is defined in the include file devices/gameport.h.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct GamePortTrigger&lt;br /&gt;
{&lt;br /&gt;
    UWORD    gpt_Keys;      /* key transition triggers */&lt;br /&gt;
    UWORD    gpt_Timeout;   /* time trigger (vertical blank units) */&lt;br /&gt;
    UWORD    gpt_XDelta;    /* X distance trigger */&lt;br /&gt;
    UWORD    gpt_YDelta;    /* Y distance trigger */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See [[Gameport Device]] for a full description of setting mouse port trigger conditions.&lt;br /&gt;
&lt;br /&gt;
== Adding an Input Handler ==&lt;br /&gt;
&lt;br /&gt;
You add an input-stream handler to the input chain by passing an IOStdReq to the device with IND_ADDHANDLER set in io_Command and a pointer to an Interrupt structure set in io_Data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Interrupt *InputHandler;&lt;br /&gt;
struct IOStdReq  *InputIO&lt;br /&gt;
&lt;br /&gt;
InputHandler-&amp;gt;is_Code         = ButtonSwap; /* Address of code */&lt;br /&gt;
InputHandler-&amp;gt;is_Data         = NULL;       /* User Value passed in A1 */&lt;br /&gt;
InputHandler-&amp;gt;is_Node.ln_Pri  = 100;        /* Priority in food chain */&lt;br /&gt;
InputHandler-&amp;gt;is_Node.ln_Name = NameString; /* Name of handler */&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)inputHandler;   /* Point to the structure */&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_ADDHANDLER;       /* Set command ... */&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputIO);   /* DoIO( ) the command */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Intuition is one of the input device handlers and normally distributes most of the input events.&lt;br /&gt;
&lt;br /&gt;
Intuition inserts itself at priority position 50. The console device sits at priority position 0. You can choose the position in the chain at which your handler will be inserted by setting the priority field in the list-node part of the interrupt data structure you pass to this routine.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Speed Saves|text=&#039;&#039;Any&#039;&#039; processing time expended by a handler subtracts from the time available before the next event happens. Therefore, handlers for the input stream &#039;&#039;must&#039;&#039; be fast.}}&lt;br /&gt;
&lt;br /&gt;
=== Rules for Input Device Handlers ===&lt;br /&gt;
&lt;br /&gt;
The following rules should be followed when you are designing an input handler:&lt;br /&gt;
&lt;br /&gt;
* If an input handler is capable of processing a specific kind of an input event and that event has no links (ie_NextEvent = 0), the handler can end the handler chain by returning a NULL (0) value.&lt;br /&gt;
&lt;br /&gt;
* If there are multiple events linked together, the handler is free to unlink an event from the input event chain, thereby passing a shorter list of events to subsequent handlers. The starting address of the modified list is the return value.&lt;br /&gt;
&lt;br /&gt;
* If a handler wishes to add new events to the chain that it passes to a lower-priority handler, it may initialize memory to contain the new event or event chain. The handler, when it again gets control on the next round of event handling, should assume nothing about the current contents of the memory blocks attached to the event chain. Lower priority handlers may have modified the memory as they handled their part of the event. The handler that allocates the memory for this purpose should keep track of the starting address and the size of this memory chunk so that the memory can be returned to the free memory list when it is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Your handler routine should be structured similar to the following pseudo-language statement:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
newEventChain = yourHandlerCode(oldEventChain, yourHandlerData);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where:&lt;br /&gt;
&lt;br /&gt;
; yourHandlerCode&lt;br /&gt;
: is the entry point to your routine.&lt;br /&gt;
; oldEventChain&lt;br /&gt;
: is the starting address for the current chain of input events.&lt;br /&gt;
; yourHandlerData&lt;br /&gt;
: is a user-definable value, usually a pointer to some data structure your handler requires.&lt;br /&gt;
; newEventChain&lt;br /&gt;
: is the starting address of an event chain which you are passing to the next handler, if any.&lt;br /&gt;
&lt;br /&gt;
When your handler code is called, the event chain and the handler data is passed in. When your code returns, it should return the pointer to the event chain. If all of the events were removed by the routine, return NULL. A NULL (0) value terminates the handling thus freeing more CPU resources.&lt;br /&gt;
&lt;br /&gt;
Memory that you use to describe a new input event that you have added to the event chain is available for reuse or deallocation when the handler is called again or after the IND_REMHANDLER command for the handler is complete. There is no guarantee that any field in the event is unchanged since a handler may change any field of an event that comes through the food chain.&lt;br /&gt;
&lt;br /&gt;
Because IND_ADDHANDLER installs a handler in any position in the handler chain, it can, for example, ignore specific types of input events as well as act upon and modify existing streams of input. It can even create new input events for Intuition or other programs to interpret.&lt;br /&gt;
&lt;br /&gt;
=== Removing an Input Handler ===&lt;br /&gt;
&lt;br /&gt;
You remove a handler from the handler chain by passing an IOStdReq to the device IND_REMHANDLER set in io_Command and a pointer to the Interrupt structure used to add the handler.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Interrupt *InputHandler;&lt;br /&gt;
struct IOStdReq  *InputIO;&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)InputHandler;   /* Which handler to REM */&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_REMHANDLER;       /* The REM command */&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputIO);   /* Send the command */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing Events to the Input Device Stream ==&lt;br /&gt;
&lt;br /&gt;
Typically, input events are internally generated by the timer device, keyboard device, and input device.&lt;br /&gt;
&lt;br /&gt;
An application can also generate an input event by setting the appropriate fields for the event in an InputEvent structure and sending it to the input device. It will then be treated as any other event and passed through to the input handler chain. However, I/O requests for IND_WRITEVENT cannot be made from interrupt code.&lt;br /&gt;
&lt;br /&gt;
You generate an input event by passing an IOStdReq to the device with IND_WRITEEVENT set in io_Command, a pointer to an InputEvent structure set in io_Data and the length of the structure set in io_Length.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct InputEvent *FakeEvent;&lt;br /&gt;
struct IOStdReq   *InputIO;&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)FakeEvent;&lt;br /&gt;
InputIO-&amp;gt;io_Length  = sizeof(struct InputEvent);&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_WRITEEVENT;&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputIO);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=You Know What Happens When You Assume|text=This command propagates the input event through the handler chain. The handlers may link other events onto the end of this event or modify the contents of the data structure you constructed in any way they wish. Therefore, do not assume any of the data will be the same from event to event.}}&lt;br /&gt;
&lt;br /&gt;
=== Setting the Position of the Mouse ===&lt;br /&gt;
&lt;br /&gt;
One use of writing input events to the input device is to set the position of the mouse pointer. The mouse pointer can be positioned by using the input classes IECLASS_POINTERPOS and IECLASS_NEWPOINTERPOS.&lt;br /&gt;
&lt;br /&gt;
There are two ways to set the position of the mouse pointer using the input class IECLASS_POINTERPOS:&lt;br /&gt;
&lt;br /&gt;
* At an absolute position on the current screen.&lt;br /&gt;
* At a position relative to the current mouse pointer position on the current screen.&lt;br /&gt;
&lt;br /&gt;
In both cases, you set the Class field of the InputEvent structure to IECLASS_POINTERPOS, ie_X with the new x-coordinate and ie_Y with the new y-coordinate. Absolute positioning is done by setting ie_Qualifier to 0 and relative positioning is done by setting ie_Qualifier to IEQUALIFIER_RELATIVEMOUSE.&lt;br /&gt;
&lt;br /&gt;
Once the proper values are set, pass an IOStdReq to the input device with a pointer to the InputEvent structure set in io_Data and io_Command set to IND_WRITEEVENT.&lt;br /&gt;
&lt;br /&gt;
There are three ways to set the mouse pointer position using IECLASS_NEWPOINTERPOS:&lt;br /&gt;
&lt;br /&gt;
* At an absolute x-y coordinate on a screen—you specify the exact location of the pointer and which screen.&lt;br /&gt;
* At an relative x-y coordinate—you specify where it will go in relation to the current pointer position and which screen.&lt;br /&gt;
* At a normalized position on a tablet device—you specify the maximum x-value and y-value of the tablet and an x-y coordinate between them and the input device will normalize it to fit.&lt;br /&gt;
&lt;br /&gt;
The basic steps required are the same for all three methods.&lt;br /&gt;
&lt;br /&gt;
* Get a pointer to the screen where you want to position the pointer. This is not necessary for the tablet device.&lt;br /&gt;
* Set up a structure to indicate the new position of the pointer.&lt;br /&gt;
&lt;br /&gt;
For absolute and relative positioning, you set up an IEPointerPixel structure with iepp_Position.X set to the new x-coordinate, iepp_Position.Y set to the new y-coordinate and iepp_Screen set to the screen pointer. You set up an InputEvent structure with ie_SubClass set to IESUBCLASS_PIXEL, a pointer to the IEPointerPixel structure set in ie_EventAddress, IECLASS_NEWPOINTERPOS set in Class, and ie_Qualifier set to either IEQUALIFIER_RELATIVEMOUSE for relative positioning or 0 for absolute positioning.&lt;br /&gt;
&lt;br /&gt;
For tablet positioning, you set up an IEPointerTablet structure with iept_Range.X set to the maximum x-coordinate and iept_Range.Y set to the maximum y-coordinate, and iept_Value.X set to the new x-coordinate and iept_Value.Y set to the new y-coordinate. You set up an InputEvent structure with a pointer to the IEPointerTablet structure set in ie_EventAddress, ie_SubClass to IESUBCLASS_TABLET and Class set to IECLASS_NEWPOINTERPOS.&lt;br /&gt;
&lt;br /&gt;
Finally, for all three methods, pass an IOStdReq to the device with a pointer to the InputEvent structure set in io_Data and io_Command set to IND_WRITEEVENT.&lt;br /&gt;
&lt;br /&gt;
The following example sets the mouse pointer at an absolute position on a public screen using IECLASS_NEWPOINTERPOS.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
 * Set_Mouse.c&lt;br /&gt;
 *&lt;br /&gt;
 * This example sets the mouse at x=100 and y=200&lt;br /&gt;
 *&lt;br /&gt;
 * Run from CLI only&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/input.h&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/inputevent.h&amp;gt;&lt;br /&gt;
#include &amp;lt;intuition/screens.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/intuition.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct IntuitionIFace *IIntuition = NULL;&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
struct MsgPort *InputMP = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
if (InputMP != NULL)&lt;br /&gt;
    {&lt;br /&gt;
    struct InputEvent *FakeEvent = IExec-&amp;gt;AllocVecTags(sizeof(struct InputEvent),&lt;br /&gt;
        AVT_Type, MEMF_SHARED,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
        &lt;br /&gt;
    if (FakeEvent != NULL)&lt;br /&gt;
        {&lt;br /&gt;
        struct IEPointerPixel *NeoPix = IExec-&amp;gt;AllocVecTags(sizeof(struct IEPointerPixel),&lt;br /&gt;
            AVT_Type, MEMF_SHARED,&lt;br /&gt;
            TAG_END);&lt;br /&gt;
            &lt;br /&gt;
        if (NeoPix != NULL)&lt;br /&gt;
            {&lt;br /&gt;
            struct IOStdReq *InputIO = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
                ASOIOR_Size, sizeof(struct IOStdReq),&lt;br /&gt;
                ASOIOR_ReplyPort, InputMP,&lt;br /&gt;
                TAG_END);&lt;br /&gt;
                &lt;br /&gt;
            if (InputIO != NULL)&lt;br /&gt;
                {&lt;br /&gt;
                if (!IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, NULL, (struct IORequest *)InputIO, NULL))&lt;br /&gt;
                    {&lt;br /&gt;
                    struct Library *IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
                    IIntuition = (struct IntuitionIFace*)IExec-&amp;gt;GetInterface(IntuitionBase, &amp;quot;main&amp;quot;, 1, NULL);&lt;br /&gt;
&lt;br /&gt;
                    if (IIntuition != NULL)&lt;br /&gt;
                        {&lt;br /&gt;
                        struct Screen *PubScreen;&lt;br /&gt;
                        /* Get pointer to screen and lock screen */&lt;br /&gt;
                        if (PubScreen = (struct Screen *)IIntuition-&amp;gt;LockPubScreen(NULL))&lt;br /&gt;
                            {&lt;br /&gt;
                            /* Set up IEPointerPixel fields */&lt;br /&gt;
                            NeoPix-&amp;gt;iepp_Screen     = (struct Screen *)PubScreen; /* WB screen */&lt;br /&gt;
                            NeoPix-&amp;gt;iepp_Position.X = 100;  /* put pointer at x = 100 */&lt;br /&gt;
                            NeoPix-&amp;gt;iepp_Position.Y = 200;  /* put pointer at y = 200 */&lt;br /&gt;
&lt;br /&gt;
                            /* Set up InputEvent fields */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_EventAddress = (APTR)NeoPix;          /* IEPointerPixel */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_NextEvent    = NULL;&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_Class        = IECLASS_NEWPOINTERPOS; /* new mouse pos */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_SubClass     = IESUBCLASS_PIXEL;      /* on pixel */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_Code         = IECODE_NOBUTTON;&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_Qualifier    = 0;                     /* absolute positioning */&lt;br /&gt;
&lt;br /&gt;
                            InputIO-&amp;gt;io_Data    = (APTR)FakeEvent;    /* InputEvent */&lt;br /&gt;
                            InputIO-&amp;gt;io_Length  = sizeof(struct InputEvent);&lt;br /&gt;
                            InputIO-&amp;gt;io_Command = IND_WRITEEVENT;&lt;br /&gt;
                            IExec-&amp;gt;DoIO((struct IORequest *)InputIO);&lt;br /&gt;
&lt;br /&gt;
                            IIntuition-&amp;gt;UnlockPubScreen(NULL, PubScreen);  /* Unlock screen */&lt;br /&gt;
                            }&lt;br /&gt;
                        else&lt;br /&gt;
                            IDOS-&amp;gt;Printf(&amp;quot;Could not get pointer to screen\n&amp;quot;);&lt;br /&gt;
                        }&lt;br /&gt;
                    else&lt;br /&gt;
                        IDOS-&amp;gt;Printf(&amp;quot;Error: Could not open intuition.library\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
                    IExec-&amp;gt;DropInterface((struct Interface*)IIntuition);&lt;br /&gt;
                    IExec-&amp;gt;CloseLibrary(IntuitionBase);&lt;br /&gt;
&lt;br /&gt;
                    IExec-&amp;gt;CloseDevice((struct IORequest *)InputIO);&lt;br /&gt;
                    }&lt;br /&gt;
                else&lt;br /&gt;
                    IDOS-&amp;gt;Printf(&amp;quot;Error: Could not open input.device\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
                IExec-&amp;gt;FreeSysObject(ASOT_IOREQUEST, InputIO);&lt;br /&gt;
                }&lt;br /&gt;
            else&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create IORequest\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
            IExec-&amp;gt;FreeVec(NeoPix);&lt;br /&gt;
            }&lt;br /&gt;
        else&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;Error: Could not allocate memory for NeoPix\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        IExec-&amp;gt;FreeVec(FakeEvent);&lt;br /&gt;
        }&lt;br /&gt;
    else&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Error: Could not allocate memory for FakeEvent\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;FreeSysObject(ASOT_PORT, InputMP);&lt;br /&gt;
    }&lt;br /&gt;
else&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create message port\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Setting the Key Repeat Threshold ==&lt;br /&gt;
&lt;br /&gt;
The key repeat threshold is the number of seconds and microseconds a user must hold down a key before it begins to repeat. This delay is normally set by the Preferences tool or by Intuition when it notices that the Preferences have been changed, but you can also do it directly through the input device.&lt;br /&gt;
&lt;br /&gt;
You set the key repeat threshold by passing a TimeRequest with IND_SETTHRESH set in io_Command and the number of seconds to delay set in Seconds and the number of microseconds to delay set in Microseconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/timer.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct TimeRequest *InputTime;  /* Initialize with AllocSysObject(ASOT_IOREQUEST) before using */&lt;br /&gt;
&lt;br /&gt;
InputTime-&amp;gt;Time.Seconds       = 1;        /* 1 second */&lt;br /&gt;
InputTime-&amp;gt;Time.Microseconds  = 500000;   /* 500,000 microseconds */&lt;br /&gt;
InputTime-&amp;gt;Request.io_Command = IND_SETTHRESH;&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputTime);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code above will set the key repeat threshold to 1.5 seconds.&lt;br /&gt;
&lt;br /&gt;
== Setting the Key Repeat Interval ==&lt;br /&gt;
&lt;br /&gt;
The key repeat interval is the time period, in seconds and microseconds, between key repeat events once the initial key repeat threshold has elapsed. (See &amp;quot;Setting the Key Repeat Threshold&amp;quot; above.) Like the key repeat threshold, this is normally issued by Intuition and preset by the Preferences tool.&lt;br /&gt;
&lt;br /&gt;
You set the key repeat interval by passing a TimeRequest with IND_SETPERIOD set in io_Command and the number of seconds set in Seconds and the number of microseconds set in Microseconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct TimeRequest *InputTime; /* Initialize with AllocSysObject(ASOT_IOREQUEST) before using */&lt;br /&gt;
&lt;br /&gt;
InputTime-&amp;gt;Time.Seconds       = 0;&lt;br /&gt;
InputTime-&amp;gt;Time.Microseconds  = 12000;  /* 0.012 seconds */&lt;br /&gt;
InputTime-&amp;gt;Request.io_Command = IND_SETPERIOD;&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputTime);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code above sets the key repeat interval to 0.012 seconds.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=The Right Tool For The Right Job|text=As previously stated, you &#039;&#039;must&#039;&#039; use a TimeRequest structure with IND_SETTHRESH and IND_SETPERIOD.}}&lt;br /&gt;
&lt;br /&gt;
== Determining the Current Qualifiers ==&lt;br /&gt;
&lt;br /&gt;
Some applications need to know whether the user is holding down a qualifier key or a mouse button during an operation. To determine the current qualifiers, you call the input device function PeekQualifier().&lt;br /&gt;
&lt;br /&gt;
PeekQualifier() returns what the input device &#039;&#039;considers&#039;&#039; to be the current qualifiers at the time PeekQualifier() is called (e.g., keyboard qualifiers and mouse buttons). This does not include any qualifiers which have been added, removed or otherwise modified by input handlers.&lt;br /&gt;
&lt;br /&gt;
In order to call the function, you must set a pointer to the input device base address and get the interface. Once you set the interface pointer, you can call the function. &#039;&#039;You must open the device in order to access the device base address and interface.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
PeekQualifier() returns an unsigned word with bits set according to the qualifiers in effect at the &#039;&#039;time&#039;&#039; the function is called. It takes no parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
struct IOStdReq   *InputIO;           /* I/O request block */&lt;br /&gt;
uint16 Quals;                         /* qualifiers */&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
if (!IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, NULL, (struct IORequest *)InputIO, NULL))&lt;br /&gt;
     {&lt;br /&gt;
     /* Set input device base address in InputBase */&lt;br /&gt;
     struct Library *InputBase = (struct Library *)InputIO-&amp;gt;io_Device;&lt;br /&gt;
     struct InputIFace *IInput = (struct InputIFace *)IExec-&amp;gt;GetInterface(InputBase, &amp;quot;main&amp;quot;, 1, NULL);&lt;br /&gt;
&lt;br /&gt;
     /* Call the function */&lt;br /&gt;
     Quals = IInput-&amp;gt;PeekQualifier();&lt;br /&gt;
     .&lt;br /&gt;
     .&lt;br /&gt;
     .&lt;br /&gt;
     IExec-&amp;gt;DropInterface((struct Interface *)IInput);&lt;br /&gt;
     IExec-&amp;gt;CloseDevice(InputIO);&lt;br /&gt;
     }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The qualifiers returned are listed in the table below.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Bit&lt;br /&gt;
! Qualifier&lt;br /&gt;
! Key or Button&lt;br /&gt;
|-&lt;br /&gt;
| 0 || IEQUALIFIER_LSHIFT || Left Shift&lt;br /&gt;
|-&lt;br /&gt;
| 1 || IEQUALIFIER_RSHIFT || Right Shift&lt;br /&gt;
|-&lt;br /&gt;
| 2 || IEQUALIFIER_CAPSLOCK || Caps Lock&lt;br /&gt;
|-&lt;br /&gt;
| 3 || IEQUALIFIER_CONTROL || Control&lt;br /&gt;
|-&lt;br /&gt;
| 4 || IEQUALIFIER_LALT || Left Alt&lt;br /&gt;
|-&lt;br /&gt;
| 5 || IEQUALIFIER_RALT || Right Alt&lt;br /&gt;
|-&lt;br /&gt;
| 6 || IEQUALIFIER_LCOMMAND || Left-Amiga&lt;br /&gt;
|-&lt;br /&gt;
| 7 || IEQUALIFIER_RCOMMAND || Right-Amiga&lt;br /&gt;
|-&lt;br /&gt;
| 12 || IEQUALIFIER_MIDBUTTON || Middle Mouse&lt;br /&gt;
|-&lt;br /&gt;
| 13 || IEQUALIFIER_RBUTTON || Right Mouse&lt;br /&gt;
|-&lt;br /&gt;
| 14 || IEQUALIFIER_LEFTBUTTON || Left Mouse&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Input Device and Intuition ==&lt;br /&gt;
&lt;br /&gt;
There are several ways to receive information from the various devices that are part of the input device. The first way is to communicate directly with the device. This method is not recommended while the input device task is running – which is most of the time. The second way is to become a handler for the stream of events which the input device produces. That method is shown above.&lt;br /&gt;
&lt;br /&gt;
The third method of getting input from the input device is to retrieve the data from the console device or from the IDCMP (Intuition Direct Communications Message Port). These are the preferred methods for applications in a multitasking environment because each application can receive juts its own input (i.e., only the input which occurs when one of its window is active). See the [[Intuition_Input_and_Output_Methods|Intuition Input and Output Methods]] for more information on IDCMP messages. See [[Console_Device|Console Device]] for more information on console device I/O.&lt;br /&gt;
&lt;br /&gt;
== Example Input Device Program ==&lt;br /&gt;
&lt;br /&gt;
=== Swap_Buttons.c ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/interrupts.h&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/input.h&amp;gt;&lt;br /&gt;
#include &amp;lt;intuition/intuition.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/intuition.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct IntuitionIFace *IIntuition = NULL;&lt;br /&gt;
&lt;br /&gt;
UBYTE NameString[] = &amp;quot;Swap Buttons&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
struct NewWindow mywin={50,40,124,18,0,1,CLOSEWINDOW,&lt;br /&gt;
                        WINDOWDRAG|WINDOWCLOSE|SIMPLE_REFRESH|NOCAREREFRESH,&lt;br /&gt;
                        NULL,NULL,NameString,NULL,NULL,0,0,0,0,WBENCHSCREEN};&lt;br /&gt;
&lt;br /&gt;
extern VOID ButtonSwap();&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * This routine opens a window and waits for the one event that&lt;br /&gt;
 * can happen (CLOSEWINDOW)  This is just to let the user play with&lt;br /&gt;
 * the swapped buttons and then close the program...&lt;br /&gt;
 */&lt;br /&gt;
VOID WaitForUser(VOID)&lt;br /&gt;
{&lt;br /&gt;
struct Library *IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
IIntuition = (struct IntuitionIFace *)IExec-&amp;gt;GetInterface(IntuitionBase, &amp;quot;main&amp;quot;, 1, NULL);&lt;br /&gt;
&lt;br /&gt;
if (IIntuition != NULL)&lt;br /&gt;
    {&lt;br /&gt;
    struct Window *win = IIntuition-&amp;gt;OpenWindow(&amp;amp;mywin);&lt;br /&gt;
    if (win != NULL)&lt;br /&gt;
        {&lt;br /&gt;
        IExec-&amp;gt;WaitPort(win-&amp;gt;UserPort);&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(IExec-&amp;gt;GetMsg(win-&amp;gt;UserPort));&lt;br /&gt;
&lt;br /&gt;
        IIntuition-&amp;gt;CloseWindow(win);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;DropInterface((struct Interface *)IIntuition);&lt;br /&gt;
IExec-&amp;gt;CloseLibrary(IntuitionBase);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
struct MsgPort *inputPort = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (inputPort != NULL)&lt;br /&gt;
    {&lt;br /&gt;
    struct Interrupt *inputHandler = IExec-&amp;gt;AllocSysObjectTags(ASOT_INTERRUPT,&lt;br /&gt;
        ASOINTR_Code, ButtonSwap,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (inputHandler != NULL)&lt;br /&gt;
        {&lt;br /&gt;
        struct IOStdReq *inputReqBlk = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
            ASOIOR_Size, sizeof(struct IOStdReq),&lt;br /&gt;
            ASOIOR_ReplyPort, inputPort,&lt;br /&gt;
            TAG_END);&lt;br /&gt;
&lt;br /&gt;
        if (inputReqBlk != NULL)&lt;br /&gt;
            {&lt;br /&gt;
            if (!IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, NULL,&lt;br /&gt;
                             (struct IORequest *)inputReqBlk, NULL))&lt;br /&gt;
                {&lt;br /&gt;
                inputHandler-&amp;gt;is_Node.ln_Pri  = 100;&lt;br /&gt;
                inputHandler-&amp;gt;is_Node.ln_Name = NameString;&lt;br /&gt;
                &lt;br /&gt;
                inputReqBlk-&amp;gt;io_Data    = (APTR)inputHandler;&lt;br /&gt;
                inputReqBlk-&amp;gt;io_Command = IND_ADDHANDLER;&lt;br /&gt;
                IExec-&amp;gt;DoIO((struct IORequest *)inputReqBlk);&lt;br /&gt;
&lt;br /&gt;
                WaitForUser();&lt;br /&gt;
&lt;br /&gt;
                inputReqBlk-&amp;gt;io_Data    = (APTR)inputHandler;&lt;br /&gt;
                inputReqBlk-&amp;gt;io_Command = IND_REMHANDLER;&lt;br /&gt;
                IExec-&amp;gt;DoIO((struct IORequest *)inputReqBlk);&lt;br /&gt;
&lt;br /&gt;
                IExec-&amp;gt;CloseDevice((struct IORequest *)inputReqBlk);&lt;br /&gt;
                }&lt;br /&gt;
            else&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;Error: Could not open input.device\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
            IExec-&amp;gt;FreeSysObject(ASOT_IOREQUEST, inputReqBlk);&lt;br /&gt;
            }&lt;br /&gt;
        else&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create I/O request\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_INTERRUPT, inputHandler);&lt;br /&gt;
        }&lt;br /&gt;
    else&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Error: Could not allocate interrupt\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;FreeSysObject(ASOT_PORT, inputPort);&lt;br /&gt;
    }&lt;br /&gt;
else&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create message port\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== InputHandler.c ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct InputEvent *ButtonSwap(struct InputEvent *eventList, struct MsgPort *port)&lt;br /&gt;
{&lt;br /&gt;
    /*&lt;br /&gt;
     * Since the event list could be a linked list, we start a loop&lt;br /&gt;
     * here to handle all of the events passed to us.&lt;br /&gt;
     */&lt;br /&gt;
    struct InputEvent *currEvent = eventList;&lt;br /&gt;
    while (currEvent) /* Do some more. */&lt;br /&gt;
    {&lt;br /&gt;
        /* &lt;br /&gt;
         * Since we are changing left and right mouse buttons, we need to make&lt;br /&gt;
         * sure that we change the qualifiers on all of the messages.  The&lt;br /&gt;
         * left and right mouse buttons are tracked in the message qualifiers&lt;br /&gt;
         * for use in such things as dragging.  To make sure that we continue&lt;br /&gt;
         * to drag correctly, we change the qualifiers.&lt;br /&gt;
         *&lt;br /&gt;
         * Save a copy &lt;br /&gt;
         */&lt;br /&gt;
        &lt;br /&gt;
        uint16 qualActual = currEvent-&amp;gt;ie_Qualifier, qualFinal = qualActual;&lt;br /&gt;
        int RBUTTON = 1&amp;lt;&amp;lt;IEQUALIFIER_RBUTTON;&lt;br /&gt;
        int LBUTTON = 1&amp;lt;&amp;lt;IEQUALIFIER_LEFTBUTTON;&lt;br /&gt;
&lt;br /&gt;
        if (RBUTTON &amp;amp; qualActual) /* Check for right */&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal |= LBUTTON; /* Set the left */&lt;br /&gt;
        }&lt;br /&gt;
        else&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal &amp;amp;= ~LBUTTON; /* Clear the left */&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (LBUTTON &amp;amp; qualActual) /* Check for left */&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal |= RBUTTON; /* Set the right */&lt;br /&gt;
        }&lt;br /&gt;
        else&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal &amp;amp;= ~RBUTTON; /* Set the right */&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currEvent-&amp;gt;ie_Qualifier = qualFinal; /* Save back */&lt;br /&gt;
&lt;br /&gt;
        /* &lt;br /&gt;
         * The actual button up/down events are transmitted as the&lt;br /&gt;
         * code field in RAWMOUSE events.  The code field must the be&lt;br /&gt;
         * checked and modified when needed on RAWMOUSE events.  If the&lt;br /&gt;
         * event is not a RAWMOUSE, we are done with it.&lt;br /&gt;
         */&lt;br /&gt;
        if (IECLASS_RAWMOUSE == currEvent-&amp;gt;ie_Class) /* Check for mouse */&lt;br /&gt;
        {&lt;br /&gt;
            /* Get code */&lt;br /&gt;
            /* Save a copy */&lt;br /&gt;
            /* We do not care if it is an UP or DOWN click */&lt;br /&gt;
            uint16 codeActual = currEvent-&amp;gt;ie_Code, codeFinal = (IECODE_UP_PREFIX-0x1) &amp;amp; codeActual;&lt;br /&gt;
            &lt;br /&gt;
            /* Check for left/right */&lt;br /&gt;
            if (IECODE_LBUTTON &amp;amp; codeActual || IECODE_RBUTTON &amp;amp; codeActual)&lt;br /&gt;
            {&lt;br /&gt;
                /* If so, swap */&lt;br /&gt;
                /* Flip bottom bit */&lt;br /&gt;
                codeFinal = 0x1 ^ codeActual;&lt;br /&gt;
                currEvent-&amp;gt;ie_Code = codeFinal;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currEvent=currEvent-&amp;gt;ie_NextEvent; /* Get next event */&lt;br /&gt;
    }&lt;br /&gt;
    return eventList;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Additional Information on the Input Device ==&lt;br /&gt;
&lt;br /&gt;
Additional programming information on the input device can be found in the include files and the autodocs for the input device. Both are contained in the SDK.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Includes&lt;br /&gt;
|-&lt;br /&gt;
| devices/input.h&lt;br /&gt;
|-&lt;br /&gt;
| devices/inputevent.h&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! AutoDocs&lt;br /&gt;
|-&lt;br /&gt;
| input.doc&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Input_Device&amp;diff=12379</id>
		<title>Input Device</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Input_Device&amp;diff=12379"/>
		<updated>2023-10-30T00:26:50Z</updated>

		<summary type="html">&lt;p&gt;Ryan Dixon: Change from the 68k Reference Manual example to more generic C&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Devices|Input]]{{CodeReview}}&lt;br /&gt;
== Input Device ==&lt;br /&gt;
&lt;br /&gt;
The input device is the central collection point for input events disseminated throughout the system. The best way to describe the input device is a manager of a stream with feeders. The input device itself and other modules such as the file system add events to the stream; so do input device “users”—programs or other devices that use parts of the stream or change it in some way. Feeders of the input device include the keyboard, timer and gameport devices. The keyboard, gameport, and timer devices are special cases in that the input device opens them and asks them for input. Users of the input device include Intuition and the console device.&lt;br /&gt;
&lt;br /&gt;
== Input Device Commands and Functions ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Command&lt;br /&gt;
! Command Operation&lt;br /&gt;
|-&lt;br /&gt;
| CMD_FLUSH || Purge all active and queued requests for the input device.&lt;br /&gt;
|-&lt;br /&gt;
| CMD_RESET || Reset the input port to its initialized state. All active and queued I/O requests will be aborted. Restarts the device if it has been stopped.&lt;br /&gt;
|-&lt;br /&gt;
| CMD_START || Restart the currently active input (if any) and resume queued I/O requests.&lt;br /&gt;
|-&lt;br /&gt;
| CMD_STOP || Stop any currently active input and prevent queued I/O requests from starting.&lt;br /&gt;
|-&lt;br /&gt;
| IND_ADDEVENT || Propagate an input event to all handlers, updating event qualifiers.&lt;br /&gt;
|-&lt;br /&gt;
| IND_ADDHANDLER || Add an input-stream handler into the handler chain.&lt;br /&gt;
|-&lt;br /&gt;
| IND_ADDNOTIFY || Add a hook which will be invoked whenever a configuration option changes.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETHANDLERLIST || Obtain a pointer to the list of input handlers.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETKDEVICE || Query the name and unit number of the device keyboard input events are collected from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETMDEVICE || Query the name and unit number of the device mouse input events are collected from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETPERIOD || Get the key repeat period.&lt;br /&gt;
|-&lt;br /&gt;
| IND_GETTHRESH || Get the key repeat threshold.&lt;br /&gt;
|-&lt;br /&gt;
| IND_IMMEDIATEADDNOTIFY || Add a hook which will be invoked whenever a configuration option changes. The hook will be invoked on the current configuration first. &lt;br /&gt;
|-&lt;br /&gt;
| IND_REMHANDLER || Remove an input-stream handler from the handler chain.&lt;br /&gt;
|-&lt;br /&gt;
| IND_REMOVENOTIFY || Remove a hook previously installed with the IND_ADDNOTIFY command.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETKDEVICE || Choose the device to collect keyboard input events from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMDEVICE || Choose the device to collect mouse input events from.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMPORT || Set the controller port to which the mouse is connected.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMTRIG || Set conditions that must be met by a mouse before a pending read request will be satisfied.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETMTYPE || Set the type of device at the mouse port.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETPERIOD || Set the period at which a repeating key repeats.&lt;br /&gt;
|-&lt;br /&gt;
| IND_SETTHRESH || Set the repeating key hold-down time before repeat starts.&lt;br /&gt;
|-&lt;br /&gt;
| IND_WRITEEVENT || Propagate an input event stream to all devices.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Input Device Functions&lt;br /&gt;
| PeekQualifier() || Return the input device’s current qualifiers.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Device Interface ==&lt;br /&gt;
&lt;br /&gt;
The input device operates like the other Amiga devices. To use it, you must first open the input device, then send I/O requests to it, and then close it when finished. See [[Exec_Device_I/O|Exec Device I/O]] for general information on device usage.&lt;br /&gt;
&lt;br /&gt;
A number of structures are used by the input device to do its processing. Some are used to pass commands and data to the device, some are used to describe input events like mouse movements and key depressions, and one structure is used to describe the environment for input event handlers.&lt;br /&gt;
&lt;br /&gt;
The I/O request used by the input device for most commands is IOStdReq.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IOStdReq&lt;br /&gt;
{&lt;br /&gt;
    struct Message io_Message; /* message reply port */&lt;br /&gt;
    struct Device  *io_Device; /* device node pointer */&lt;br /&gt;
    struct Unit    *io_Unit;   /* unit */&lt;br /&gt;
    UWORD  io_Command;         /* input device command */&lt;br /&gt;
    UBYTE  io_Flags;           /* input device flags */&lt;br /&gt;
    BYTE   io_Error;           /* error code */&lt;br /&gt;
    ULONG  io_Length;          /* number of bytes to transfer */&lt;br /&gt;
    APTR   io_Data;            /* pointer to data area */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See the include file exec/io.h for the complete structure definition.&lt;br /&gt;
&lt;br /&gt;
Two of the input device commands—IND_SETTHRESH and IND_SETPERIOD—require a time specification and &#039;&#039;must&#039;&#039; use a TimeRequest structure instead of an IOStdReq.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct TimeRequest&lt;br /&gt;
{&lt;br /&gt;
    struct IORequest Request;&lt;br /&gt;
    struct TimeVal Time;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the TimeRequest structure includes an IORequest structure. The io_Command field of the IORequest indicates the command to the input device and the TimeVal structure sets the time values. See the include file devices/timer.h for the complete structure definition.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=In Case You Feel Like Reinventing the Wheel...|text=You could define a “super-IORequest” structure for the input device which would combine the IOStdReq fields with the TimeVal structure of the TimeRequest structure.}}&lt;br /&gt;
&lt;br /&gt;
=== Opening the Input Device ===&lt;br /&gt;
&lt;br /&gt;
Three primary steps are required to open the input device:&lt;br /&gt;
&lt;br /&gt;
* Create a message port using AllocSysObject() and ASOT_PORT type. Reply messages from the device must be directed to a message port.&lt;br /&gt;
* Create an extended I/O request structure of type IOStdReq or TimeRequest using AllocSysObject() and ASOT_IOREQUEST type. The I/O request created by the AllocSysObject() function will be used to pass commands and data to the input device.&lt;br /&gt;
* Open the Input device. Call OpenDevice(), passing the I/O request.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct MsgPort *InputMP = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (InputMP != NULL)&lt;br /&gt;
{&lt;br /&gt;
    struct IOStdReq *InputIO = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
        ASOIOR_Size, sizeof(struct IOStdReq),&lt;br /&gt;
        ASOIOR_ReplyPort, InputMP,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ClipIO != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        if (IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, 0, (struct IORequest *)InputIO, 0))&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;input.device did not open\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above code will work for all the input device commands except for the ones which require a time specification. For those, the code would look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/timer.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct MsgPort *InputMP = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (InputMP != NULL)&lt;br /&gt;
{&lt;br /&gt;
    struct TimeRequest *InputIO = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
        ASOIOR_Size, sizeof(struct TimeRequest),&lt;br /&gt;
        ASOIOR_ReplyPort, InputMP,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ClipIO != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        if (IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, 0, (struct IORequest *)InputIO, 0))&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;input.device did not open\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Input Device Event Types ===&lt;br /&gt;
&lt;br /&gt;
The input device is automatically opened by the console device when the system boots. When the input device is opened, a task named “input.device” is started. The input device task communicates directly with the keyboard device to obtain raw key events. It also communicates with the gameport device to obtain mouse button and mouse movement events and with the timer device to obtain time events. In addition to these events, you can add your own input events to the input device, to be fed to the handler chain (see below).&lt;br /&gt;
&lt;br /&gt;
The keyboard device is accessible directly (see [[Keyboard_Device|Keyboard Device]]). However, once the input.device task has started, you should not read events from the keyboard device directly, since doing so will deprive the input device of the events and confuse key repeating.&lt;br /&gt;
&lt;br /&gt;
The gameport device has two units. As you view the Amiga, looking at the gameport connectors, the left connector is assigned as the primary mouse input for Intuition and contributes gameport input events to the input event stream.&lt;br /&gt;
&lt;br /&gt;
The right connector is handled by the other gameport unit and is currently unassigned. While the input device task is running, that task expects to read the input from the left connector. Direct use of the gameport device is covered in [[Gameport_Device|Gameport Device]].&lt;br /&gt;
&lt;br /&gt;
The timer device is used to generate time events for the input device. It is also used to control key repeat rate and key repeat threshold. The timer device is a shared-access device and is described in [[Timer_Device|Timer Device]].&lt;br /&gt;
&lt;br /&gt;
The device-specific commands are described below. First though, it may be helpful to consider the types of input events that the input device deals with. An input event is a data structure that describes the following:&lt;br /&gt;
&lt;br /&gt;
* The class of the event — often describes the device that generated the event.&lt;br /&gt;
* The subclass of the event — space for more information if needed.&lt;br /&gt;
* The code — keycode if keyboard, button information if mouse, others.&lt;br /&gt;
* A qualifier such as “Alt key also down,”or “key repeat active”.&lt;br /&gt;
* A position field that contains a data address or a mouse position count.&lt;br /&gt;
* A time stamp, to determine the sequence in which the events occurred.&lt;br /&gt;
* A link-field by which input events are linked together.&lt;br /&gt;
* The class, subclass, code and qualifier of the previous down key.&lt;br /&gt;
&lt;br /&gt;
The full definitions for each field can be found in the include file devices/inputevent.h. You can find more information about input events in [[Gameport_Device|Gameport Device]] and [[Console_Device|Console Device]].&lt;br /&gt;
&lt;br /&gt;
The various types of input events are listed below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Input Device Event Types&lt;br /&gt;
| IECLASS_NULL || A NOP input event&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_RAWKEY || A raw keycode from the keyboard device&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_RAWMOUSE || The raw mouse report from the gameport device&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_EVENT || A private console event&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_POINTERPOS || A pointer position report&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_TIMER || A timer event&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_GADGETDOWN || Select button pressed down over a gadget (address in ie_EventAddress)&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_GADGETUP || Select button released over the same gadget (address in ie_EventAddress)&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_REQUESTER || Some requester activity has taken place.&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_MENULIST || This is a menu number transmission (menu number is in ie_Code)&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_CLOSEWINDOW || User has selected the active window’s Close Gadget&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_SIZEWINDOW || This window has a new size&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_REFRESHWINDOW || The window pointed to by ie_EventAddress needs to be refreshed&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_NEWPREFS || New preferences are available&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_DISKREMOVED || The disk has been removed&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_DISKINSERTED || The disk has been inserted&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_ACTIVEWINDOW || The window is about to be been made active&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_INACTIVEWINDOW || The window is about to be made inactive&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_NEWPOINTERPOS || Extended-function pointer position report&lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_MENUHELP || Help key report during Menu session &lt;br /&gt;
|-&lt;br /&gt;
| IECLASS_CHANGEWINDOW || The Window has been modified with move, size, zoom, or change&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
There is a difference between simply receiving an input event from a device and actually becoming a handler of an input event stream. A handler is a routine that is passed an input event list. It is up to the handler to decide if it can process the input events. If the handler does not recognize an event, it leaves it undisturbed in the event list.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=It All Flows Downhill|text=Handlers can themselves generate new linked lists of events which can be passed down to lower priority handlers.}}&lt;br /&gt;
&lt;br /&gt;
The InputEvent structure is used by the input device to describe an input event such as a keypress or a mouse movement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct InputEvent&lt;br /&gt;
{&lt;br /&gt;
    struct  InputEvent *ie_NextEvent;   /* the chronologically next event */&lt;br /&gt;
    UBYTE   ie_Class;                   /* the input event class */&lt;br /&gt;
    UBYTE   ie_SubClass;                /* optional subclass of the class */&lt;br /&gt;
    UWORD   ie_Code;                    /* the input event code */&lt;br /&gt;
    UWORD   ie_Qualifier;               /* qualifiers in effect for the event*/&lt;br /&gt;
    union&lt;br /&gt;
    {&lt;br /&gt;
        struct&lt;br /&gt;
        {&lt;br /&gt;
            WORD    ie_x;               /* the pointer position for the event*/&lt;br /&gt;
            WORD    ie_y;&lt;br /&gt;
        } ie_xy;&lt;br /&gt;
        APTR    ie_addr;                /* the event address */&lt;br /&gt;
        struct&lt;br /&gt;
        {&lt;br /&gt;
            UBYTE   ie_prev1DownCode;   /* previous down keys for dead */&lt;br /&gt;
            UBYTE   ie_prev1DownQual;   /*   key translation: the ie_Code */&lt;br /&gt;
            UBYTE   ie_prev2DownCode;   /*   &amp;amp; low byte of ie_Qualifier for */&lt;br /&gt;
            UBYTE   ie_prev2DownQual;   /*   last &amp;amp; second last down keys */&lt;br /&gt;
        } ie_dead;&lt;br /&gt;
    } ie_position;&lt;br /&gt;
    struct timeval ie_TimeStamp;        /* the system tick at the event */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The IEPointerPixel and IEPointerTablet structures are used to set the mouse position with the IECLASS_NEWPOINTERPOS input event class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IEPointerPixel&lt;br /&gt;
{&lt;br /&gt;
    struct Screen       *iepp_Screen;   /* pointer to an open screen */&lt;br /&gt;
    struct&lt;br /&gt;
    {                           /* pixel coordinates in iepp_Screen */&lt;br /&gt;
        WORD    X;&lt;br /&gt;
        WORD    Y;&lt;br /&gt;
    } iepp_Position;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
struct IEPointerTablet&lt;br /&gt;
{&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        UWORD   X;&lt;br /&gt;
        UWORD   Y;&lt;br /&gt;
    } iept_Range;       /* 0 is min, these are max      */&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        UWORD   X;&lt;br /&gt;
        UWORD   Y;&lt;br /&gt;
    } iept_Value;       /* between 0 and iept_Range     */&lt;br /&gt;
&lt;br /&gt;
    WORD iept_Pressure; /* -128 to 127 (unused, set to 0)  */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See the include file devices/inputevent.h for the complete structure definitions.&lt;br /&gt;
&lt;br /&gt;
For input device handler installation, the Interrupt structure is used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Interrupt&lt;br /&gt;
{&lt;br /&gt;
    struct Node is_Node;&lt;br /&gt;
    APTR   is_Data;         /* server data segment */&lt;br /&gt;
    VOID   (*is_Code)();    /* server code entry   */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See the include file exec/interrupts.h for the complete structure definition.&lt;br /&gt;
&lt;br /&gt;
=== Closing the Input Device ===&lt;br /&gt;
&lt;br /&gt;
Each OpenDevice() must eventually be matched by a call to CloseDevice(). All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (!(IExec-&amp;gt;CheckIO(InputIO)))&lt;br /&gt;
    {&lt;br /&gt;
    IExec-&amp;gt;AbortIO(InputIO);  /* Ask device to abort request, if pending */&lt;br /&gt;
    }&lt;br /&gt;
IExec-&amp;gt;WaitIO(InputIO);   /* Wait for abort, then clean up */&lt;br /&gt;
IExec-&amp;gt;CloseDevice((struct IORequest *)InputIO);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using the Mouse Port With the Input Device ==&lt;br /&gt;
&lt;br /&gt;
To get mouse port information you must first set the current mouse port by passing an IOStdReq to the device with IND_SETMPORT set in io_Command and a pointer to a byte set in io_Data. If the byte is set to 0 the left controller port will be used as the current mouse port; if it is set to 1, the right controller port will be used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int8 port = 1;      /* set mouse port to right controller */&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = &amp;amp;port;&lt;br /&gt;
InputIO-&amp;gt;io_Flags   = IOF_QUICK;&lt;br /&gt;
InputIO-&amp;gt;io_Length  = 1;&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_SETMPORT;&lt;br /&gt;
IExec-&amp;gt;BeginIO((struct IORequest *)InputIO);&lt;br /&gt;
if (InputIO-&amp;gt;io_Error)&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;\nSETMPORT failed %ld\n&amp;quot;, InputIO-&amp;gt;io_Error);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Put That Back!|text=The default mouse port is the left controller. Don’t forget to set the mouse port back to the left controller before exiting if you change it to the right controller during your application.}}&lt;br /&gt;
&lt;br /&gt;
=== Setting the Conditions for a Mouse Port Report ===&lt;br /&gt;
&lt;br /&gt;
You set the conditions for a mouse port report by passing an IOStdReq to the device with IND_SETMTRIG set in io_Command, the address of a GamePortTrigger structure set in io_Data and the length of the structure set in io_Length.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct GamePortTrigger InputTR;&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)InputTR;  /* set trigger conditions */&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_SETMTRIG;   /* from InputTR */&lt;br /&gt;
InputIO-&amp;gt;io_Length  = sizeof(struct GamePortTrigger);&lt;br /&gt;
IExec-&amp;gt;DoIO(InputIO);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The information needed for mouse port report setting is contained in a GamePortTrigger data structure which is defined in the include file devices/gameport.h.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct GamePortTrigger&lt;br /&gt;
{&lt;br /&gt;
    UWORD    gpt_Keys;      /* key transition triggers */&lt;br /&gt;
    UWORD    gpt_Timeout;   /* time trigger (vertical blank units) */&lt;br /&gt;
    UWORD    gpt_XDelta;    /* X distance trigger */&lt;br /&gt;
    UWORD    gpt_YDelta;    /* Y distance trigger */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See [[Gameport Device]] for a full description of setting mouse port trigger conditions.&lt;br /&gt;
&lt;br /&gt;
== Adding an Input Handler ==&lt;br /&gt;
&lt;br /&gt;
You add an input-stream handler to the input chain by passing an IOStdReq to the device with IND_ADDHANDLER set in io_Command and a pointer to an Interrupt structure set in io_Data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Interrupt *InputHandler;&lt;br /&gt;
struct IOStdReq  *InputIO&lt;br /&gt;
&lt;br /&gt;
InputHandler-&amp;gt;is_Code         = ButtonSwap; /* Address of code */&lt;br /&gt;
InputHandler-&amp;gt;is_Data         = NULL;       /* User Value passed in A1 */&lt;br /&gt;
InputHandler-&amp;gt;is_Node.ln_Pri  = 100;        /* Priority in food chain */&lt;br /&gt;
InputHandler-&amp;gt;is_Node.ln_Name = NameString; /* Name of handler */&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)inputHandler;   /* Point to the structure */&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_ADDHANDLER;       /* Set command ... */&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputIO);   /* DoIO( ) the command */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Intuition is one of the input device handlers and normally distributes most of the input events.&lt;br /&gt;
&lt;br /&gt;
Intuition inserts itself at priority position 50. The console device sits at priority position 0. You can choose the position in the chain at which your handler will be inserted by setting the priority field in the list-node part of the interrupt data structure you pass to this routine.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=Speed Saves|text=&#039;&#039;Any&#039;&#039; processing time expended by a handler subtracts from the time available before the next event happens. Therefore, handlers for the input stream &#039;&#039;must&#039;&#039; be fast.}}&lt;br /&gt;
&lt;br /&gt;
=== Rules for Input Device Handlers ===&lt;br /&gt;
&lt;br /&gt;
The following rules should be followed when you are designing an input handler:&lt;br /&gt;
&lt;br /&gt;
* If an input handler is capable of processing a specific kind of an input event and that event has no links (ie_NextEvent = 0), the handler can end the handler chain by returning a NULL (0) value.&lt;br /&gt;
&lt;br /&gt;
* If there are multiple events linked together, the handler is free to unlink an event from the input event chain, thereby passing a shorter list of events to subsequent handlers. The starting address of the modified list is the return value.&lt;br /&gt;
&lt;br /&gt;
* If a handler wishes to add new events to the chain that it passes to a lower-priority handler, it may initialize memory to contain the new event or event chain. The handler, when it again gets control on the next round of event handling, should assume nothing about the current contents of the memory blocks attached to the event chain. Lower priority handlers may have modified the memory as they handled their part of the event. The handler that allocates the memory for this purpose should keep track of the starting address and the size of this memory chunk so that the memory can be returned to the free memory list when it is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Your handler routine should be structured similar to the following pseudo-language statement:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
newEventChain = yourHandlerCode(oldEventChain, yourHandlerData);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where:&lt;br /&gt;
&lt;br /&gt;
; yourHandlerCode&lt;br /&gt;
: is the entry point to your routine.&lt;br /&gt;
; oldEventChain&lt;br /&gt;
: is the starting address for the current chain of input events.&lt;br /&gt;
; yourHandlerData&lt;br /&gt;
: is a user-definable value, usually a pointer to some data structure your handler requires.&lt;br /&gt;
; newEventChain&lt;br /&gt;
: is the starting address of an event chain which you are passing to the next handler, if any.&lt;br /&gt;
&lt;br /&gt;
When your handler code is called, the event chain and the handler data is passed in. When your code returns, it should return the pointer to the event chain. If all of the events were removed by the routine, return NULL. A NULL (0) value terminates the handling thus freeing more CPU resources.&lt;br /&gt;
&lt;br /&gt;
Memory that you use to describe a new input event that you have added to the event chain is available for reuse or deallocation when the handler is called again or after the IND_REMHANDLER command for the handler is complete. There is no guarantee that any field in the event is unchanged since a handler may change any field of an event that comes through the food chain.&lt;br /&gt;
&lt;br /&gt;
Because IND_ADDHANDLER installs a handler in any position in the handler chain, it can, for example, ignore specific types of input events as well as act upon and modify existing streams of input. It can even create new input events for Intuition or other programs to interpret.&lt;br /&gt;
&lt;br /&gt;
=== Removing an Input Handler ===&lt;br /&gt;
&lt;br /&gt;
You remove a handler from the handler chain by passing an IOStdReq to the device IND_REMHANDLER set in io_Command and a pointer to the Interrupt structure used to add the handler.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Interrupt *InputHandler;&lt;br /&gt;
struct IOStdReq  *InputIO;&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)InputHandler;   /* Which handler to REM */&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_REMHANDLER;       /* The REM command */&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputIO);   /* Send the command */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing Events to the Input Device Stream ==&lt;br /&gt;
&lt;br /&gt;
Typically, input events are internally generated by the timer device, keyboard device, and input device.&lt;br /&gt;
&lt;br /&gt;
An application can also generate an input event by setting the appropriate fields for the event in an InputEvent structure and sending it to the input device. It will then be treated as any other event and passed through to the input handler chain. However, I/O requests for IND_WRITEVENT cannot be made from interrupt code.&lt;br /&gt;
&lt;br /&gt;
You generate an input event by passing an IOStdReq to the device with IND_WRITEEVENT set in io_Command, a pointer to an InputEvent structure set in io_Data and the length of the structure set in io_Length.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct InputEvent *FakeEvent;&lt;br /&gt;
struct IOStdReq   *InputIO;&lt;br /&gt;
&lt;br /&gt;
InputIO-&amp;gt;io_Data    = (APTR)FakeEvent;&lt;br /&gt;
InputIO-&amp;gt;io_Length  = sizeof(struct InputEvent);&lt;br /&gt;
InputIO-&amp;gt;io_Command = IND_WRITEEVENT;&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputIO);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|title=You Know What Happens When You Assume|text=This command propagates the input event through the handler chain. The handlers may link other events onto the end of this event or modify the contents of the data structure you constructed in any way they wish. Therefore, do not assume any of the data will be the same from event to event.}}&lt;br /&gt;
&lt;br /&gt;
=== Setting the Position of the Mouse ===&lt;br /&gt;
&lt;br /&gt;
One use of writing input events to the input device is to set the position of the mouse pointer. The mouse pointer can be positioned by using the input classes IECLASS_POINTERPOS and IECLASS_NEWPOINTERPOS.&lt;br /&gt;
&lt;br /&gt;
There are two ways to set the position of the mouse pointer using the input class IECLASS_POINTERPOS:&lt;br /&gt;
&lt;br /&gt;
* At an absolute position on the current screen.&lt;br /&gt;
* At a position relative to the current mouse pointer position on the current screen.&lt;br /&gt;
&lt;br /&gt;
In both cases, you set the Class field of the InputEvent structure to IECLASS_POINTERPOS, ie_X with the new x-coordinate and ie_Y with the new y-coordinate. Absolute positioning is done by setting ie_Qualifier to 0 and relative positioning is done by setting ie_Qualifier to IEQUALIFIER_RELATIVEMOUSE.&lt;br /&gt;
&lt;br /&gt;
Once the proper values are set, pass an IOStdReq to the input device with a pointer to the InputEvent structure set in io_Data and io_Command set to IND_WRITEEVENT.&lt;br /&gt;
&lt;br /&gt;
There are three ways to set the mouse pointer position using IECLASS_NEWPOINTERPOS:&lt;br /&gt;
&lt;br /&gt;
* At an absolute x-y coordinate on a screen—you specify the exact location of the pointer and which screen.&lt;br /&gt;
* At an relative x-y coordinate—you specify where it will go in relation to the current pointer position and which screen.&lt;br /&gt;
* At a normalized position on a tablet device—you specify the maximum x-value and y-value of the tablet and an x-y coordinate between them and the input device will normalize it to fit.&lt;br /&gt;
&lt;br /&gt;
The basic steps required are the same for all three methods.&lt;br /&gt;
&lt;br /&gt;
* Get a pointer to the screen where you want to position the pointer. This is not necessary for the tablet device.&lt;br /&gt;
* Set up a structure to indicate the new position of the pointer.&lt;br /&gt;
&lt;br /&gt;
For absolute and relative positioning, you set up an IEPointerPixel structure with iepp_Position.X set to the new x-coordinate, iepp_Position.Y set to the new y-coordinate and iepp_Screen set to the screen pointer. You set up an InputEvent structure with ie_SubClass set to IESUBCLASS_PIXEL, a pointer to the IEPointerPixel structure set in ie_EventAddress, IECLASS_NEWPOINTERPOS set in Class, and ie_Qualifier set to either IEQUALIFIER_RELATIVEMOUSE for relative positioning or 0 for absolute positioning.&lt;br /&gt;
&lt;br /&gt;
For tablet positioning, you set up an IEPointerTablet structure with iept_Range.X set to the maximum x-coordinate and iept_Range.Y set to the maximum y-coordinate, and iept_Value.X set to the new x-coordinate and iept_Value.Y set to the new y-coordinate. You set up an InputEvent structure with a pointer to the IEPointerTablet structure set in ie_EventAddress, ie_SubClass to IESUBCLASS_TABLET and Class set to IECLASS_NEWPOINTERPOS.&lt;br /&gt;
&lt;br /&gt;
Finally, for all three methods, pass an IOStdReq to the device with a pointer to the InputEvent structure set in io_Data and io_Command set to IND_WRITEEVENT.&lt;br /&gt;
&lt;br /&gt;
The following example sets the mouse pointer at an absolute position on a public screen using IECLASS_NEWPOINTERPOS.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
 * Set_Mouse.c&lt;br /&gt;
 *&lt;br /&gt;
 * This example sets the mouse at x=100 and y=200&lt;br /&gt;
 *&lt;br /&gt;
 * Run from CLI only&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/input.h&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/inputevent.h&amp;gt;&lt;br /&gt;
#include &amp;lt;intuition/screens.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/intuition.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct IntuitionIFace *IIntuition = NULL;&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
struct MsgPort *InputMP = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
if (InputMP != NULL)&lt;br /&gt;
    {&lt;br /&gt;
    struct InputEvent *FakeEvent = IExec-&amp;gt;AllocVecTags(sizeof(struct InputEvent),&lt;br /&gt;
        AVT_Type, MEMF_SHARED,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
        &lt;br /&gt;
    if (FakeEvent != NULL)&lt;br /&gt;
        {&lt;br /&gt;
        struct IEPointerPixel *NeoPix = IExec-&amp;gt;AllocVecTags(sizeof(struct IEPointerPixel),&lt;br /&gt;
            AVT_Type, MEMF_SHARED,&lt;br /&gt;
            TAG_END);&lt;br /&gt;
            &lt;br /&gt;
        if (NeoPix != NULL)&lt;br /&gt;
            {&lt;br /&gt;
            struct IOStdReq *InputIO = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
                ASOIOR_Size, sizeof(struct IOStdReq),&lt;br /&gt;
                ASOIOR_ReplyPort, InputMP,&lt;br /&gt;
                TAG_END);&lt;br /&gt;
                &lt;br /&gt;
            if (InputIO != NULL)&lt;br /&gt;
                {&lt;br /&gt;
                if (!IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, NULL, (struct IORequest *)InputIO, NULL))&lt;br /&gt;
                    {&lt;br /&gt;
                    struct Library *IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
                    IIntuition = (struct IntuitionIFace*)IExec-&amp;gt;GetInterface(IntuitionBase, &amp;quot;main&amp;quot;, 1, NULL);&lt;br /&gt;
&lt;br /&gt;
                    if (IIntuition != NULL)&lt;br /&gt;
                        {&lt;br /&gt;
                        struct Screen *PubScreen;&lt;br /&gt;
                        /* Get pointer to screen and lock screen */&lt;br /&gt;
                        if (PubScreen = (struct Screen *)IIntuition-&amp;gt;LockPubScreen(NULL))&lt;br /&gt;
                            {&lt;br /&gt;
                            /* Set up IEPointerPixel fields */&lt;br /&gt;
                            NeoPix-&amp;gt;iepp_Screen     = (struct Screen *)PubScreen; /* WB screen */&lt;br /&gt;
                            NeoPix-&amp;gt;iepp_Position.X = 100;  /* put pointer at x = 100 */&lt;br /&gt;
                            NeoPix-&amp;gt;iepp_Position.Y = 200;  /* put pointer at y = 200 */&lt;br /&gt;
&lt;br /&gt;
                            /* Set up InputEvent fields */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_EventAddress = (APTR)NeoPix;          /* IEPointerPixel */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_NextEvent    = NULL;&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_Class        = IECLASS_NEWPOINTERPOS; /* new mouse pos */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_SubClass     = IESUBCLASS_PIXEL;      /* on pixel */&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_Code         = IECODE_NOBUTTON;&lt;br /&gt;
                            FakeEvent-&amp;gt;ie_Qualifier    = 0;                     /* absolute positioning */&lt;br /&gt;
&lt;br /&gt;
                            InputIO-&amp;gt;io_Data    = (APTR)FakeEvent;    /* InputEvent */&lt;br /&gt;
                            InputIO-&amp;gt;io_Length  = sizeof(struct InputEvent);&lt;br /&gt;
                            InputIO-&amp;gt;io_Command = IND_WRITEEVENT;&lt;br /&gt;
                            IExec-&amp;gt;DoIO((struct IORequest *)InputIO);&lt;br /&gt;
&lt;br /&gt;
                            IIntuition-&amp;gt;UnlockPubScreen(NULL, PubScreen);  /* Unlock screen */&lt;br /&gt;
                            }&lt;br /&gt;
                        else&lt;br /&gt;
                            IDOS-&amp;gt;Printf(&amp;quot;Could not get pointer to screen\n&amp;quot;);&lt;br /&gt;
                        }&lt;br /&gt;
                    else&lt;br /&gt;
                        IDOS-&amp;gt;Printf(&amp;quot;Error: Could not open intuition.library\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
                    IExec-&amp;gt;DropInterface((struct Interface*)IIntuition);&lt;br /&gt;
                    IExec-&amp;gt;CloseLibrary(IntuitionBase);&lt;br /&gt;
&lt;br /&gt;
                    IExec-&amp;gt;CloseDevice((struct IORequest *)InputIO);&lt;br /&gt;
                    }&lt;br /&gt;
                else&lt;br /&gt;
                    IDOS-&amp;gt;Printf(&amp;quot;Error: Could not open input.device\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
                IExec-&amp;gt;FreeSysObject(ASOT_IOREQUEST, InputIO);&lt;br /&gt;
                }&lt;br /&gt;
            else&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create IORequest\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
            IExec-&amp;gt;FreeVec(NeoPix);&lt;br /&gt;
            }&lt;br /&gt;
        else&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;Error: Could not allocate memory for NeoPix\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        IExec-&amp;gt;FreeVec(FakeEvent);&lt;br /&gt;
        }&lt;br /&gt;
    else&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Error: Could not allocate memory for FakeEvent\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;FreeSysObject(ASOT_PORT, InputMP);&lt;br /&gt;
    }&lt;br /&gt;
else&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create message port\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Setting the Key Repeat Threshold ==&lt;br /&gt;
&lt;br /&gt;
The key repeat threshold is the number of seconds and microseconds a user must hold down a key before it begins to repeat. This delay is normally set by the Preferences tool or by Intuition when it notices that the Preferences have been changed, but you can also do it directly through the input device.&lt;br /&gt;
&lt;br /&gt;
You set the key repeat threshold by passing a TimeRequest with IND_SETTHRESH set in io_Command and the number of seconds to delay set in Seconds and the number of microseconds to delay set in Microseconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/timer.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct TimeRequest *InputTime;  /* Initialize with AllocSysObject(ASOT_IOREQUEST) before using */&lt;br /&gt;
&lt;br /&gt;
InputTime-&amp;gt;Time.Seconds       = 1;        /* 1 second */&lt;br /&gt;
InputTime-&amp;gt;Time.Microseconds  = 500000;   /* 500,000 microseconds */&lt;br /&gt;
InputTime-&amp;gt;Request.io_Command = IND_SETTHRESH;&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputTime);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code above will set the key repeat threshold to 1.5 seconds.&lt;br /&gt;
&lt;br /&gt;
== Setting the Key Repeat Interval ==&lt;br /&gt;
&lt;br /&gt;
The key repeat interval is the time period, in seconds and microseconds, between key repeat events once the initial key repeat threshold has elapsed. (See &amp;quot;Setting the Key Repeat Threshold&amp;quot; above.) Like the key repeat threshold, this is normally issued by Intuition and preset by the Preferences tool.&lt;br /&gt;
&lt;br /&gt;
You set the key repeat interval by passing a TimeRequest with IND_SETPERIOD set in io_Command and the number of seconds set in Seconds and the number of microseconds set in Microseconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct TimeRequest *InputTime; /* Initialize with AllocSysObject(ASOT_IOREQUEST) before using */&lt;br /&gt;
&lt;br /&gt;
InputTime-&amp;gt;Time.Seconds       = 0;&lt;br /&gt;
InputTime-&amp;gt;Time.Microseconds  = 12000;  /* 0.012 seconds */&lt;br /&gt;
InputTime-&amp;gt;Request.io_Command = IND_SETPERIOD;&lt;br /&gt;
IExec-&amp;gt;DoIO((struct IORequest *)InputTime);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code above sets the key repeat interval to 0.012 seconds.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=The Right Tool For The Right Job|text=As previously stated, you &#039;&#039;must&#039;&#039; use a TimeRequest structure with IND_SETTHRESH and IND_SETPERIOD.}}&lt;br /&gt;
&lt;br /&gt;
== Determining the Current Qualifiers ==&lt;br /&gt;
&lt;br /&gt;
Some applications need to know whether the user is holding down a qualifier key or a mouse button during an operation. To determine the current qualifiers, you call the input device function PeekQualifier().&lt;br /&gt;
&lt;br /&gt;
PeekQualifier() returns what the input device &#039;&#039;considers&#039;&#039; to be the current qualifiers at the time PeekQualifier() is called (e.g., keyboard qualifiers and mouse buttons). This does not include any qualifiers which have been added, removed or otherwise modified by input handlers.&lt;br /&gt;
&lt;br /&gt;
In order to call the function, you must set a pointer to the input device base address and get the interface. Once you set the interface pointer, you can call the function. &#039;&#039;You must open the device in order to access the device base address and interface.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
PeekQualifier() returns an unsigned word with bits set according to the qualifiers in effect at the &#039;&#039;time&#039;&#039; the function is called. It takes no parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
struct IOStdReq   *InputIO;           /* I/O request block */&lt;br /&gt;
uint16 Quals;                         /* qualifiers */&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
if (!IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, NULL, (struct IORequest *)InputIO, NULL))&lt;br /&gt;
     {&lt;br /&gt;
     /* Set input device base address in InputBase */&lt;br /&gt;
     struct Library *InputBase = (struct Library *)InputIO-&amp;gt;io_Device;&lt;br /&gt;
     struct InputIFace *IInput = (struct InputIFace *)IExec-&amp;gt;GetInterface(InputBase, &amp;quot;main&amp;quot;, 1, NULL);&lt;br /&gt;
&lt;br /&gt;
     /* Call the function */&lt;br /&gt;
     Quals = IInput-&amp;gt;PeekQualifier();&lt;br /&gt;
     .&lt;br /&gt;
     .&lt;br /&gt;
     .&lt;br /&gt;
     IExec-&amp;gt;DropInterface((struct Interface *)IInput);&lt;br /&gt;
     IExec-&amp;gt;CloseDevice(InputIO);&lt;br /&gt;
     }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The qualifiers returned are listed in the table below.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Bit&lt;br /&gt;
! Qualifier&lt;br /&gt;
! Key or Button&lt;br /&gt;
|-&lt;br /&gt;
| 0 || IEQUALIFIER_LSHIFT || Left Shift&lt;br /&gt;
|-&lt;br /&gt;
| 1 || IEQUALIFIER_RSHIFT || Right Shift&lt;br /&gt;
|-&lt;br /&gt;
| 2 || IEQUALIFIER_CAPSLOCK || Caps Lock&lt;br /&gt;
|-&lt;br /&gt;
| 3 || IEQUALIFIER_CONTROL || Control&lt;br /&gt;
|-&lt;br /&gt;
| 4 || IEQUALIFIER_LALT || Left Alt&lt;br /&gt;
|-&lt;br /&gt;
| 5 || IEQUALIFIER_RALT || Right Alt&lt;br /&gt;
|-&lt;br /&gt;
| 6 || IEQUALIFIER_LCOMMAND || Left-Amiga&lt;br /&gt;
|-&lt;br /&gt;
| 7 || IEQUALIFIER_RCOMMAND || Right-Amiga&lt;br /&gt;
|-&lt;br /&gt;
| 12 || IEQUALIFIER_MIDBUTTON || Middle Mouse&lt;br /&gt;
|-&lt;br /&gt;
| 13 || IEQUALIFIER_RBUTTON || Right Mouse&lt;br /&gt;
|-&lt;br /&gt;
| 14 || IEQUALIFIER_LEFTBUTTON || Left Mouse&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Input Device and Intuition ==&lt;br /&gt;
&lt;br /&gt;
There are several ways to receive information from the various devices that are part of the input device. The first way is to communicate directly with the device. This method is not recommended while the input device task is running – which is most of the time. The second way is to become a handler for the stream of events which the input device produces. That method is shown above.&lt;br /&gt;
&lt;br /&gt;
The third method of getting input from the input device is to retrieve the data from the console device or from the IDCMP (Intuition Direct Communications Message Port). These are the preferred methods for applications in a multitasking environment because each application can receive juts its own input (i.e., only the input which occurs when one of its window is active). See the [[Intuition_Input_and_Output_Methods|Intuition Input and Output Methods]] for more information on IDCMP messages. See [[Console_Device|Console Device]] for more information on console device I/O.&lt;br /&gt;
&lt;br /&gt;
== Example Input Device Program ==&lt;br /&gt;
&lt;br /&gt;
=== Swap_Buttons.c ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/interrupts.h&amp;gt;&lt;br /&gt;
#include &amp;lt;devices/input.h&amp;gt;&lt;br /&gt;
#include &amp;lt;intuition/intuition.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/intuition.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct IntuitionIFace *IIntuition = NULL;&lt;br /&gt;
&lt;br /&gt;
UBYTE NameString[] = &amp;quot;Swap Buttons&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
struct NewWindow mywin={50,40,124,18,0,1,CLOSEWINDOW,&lt;br /&gt;
                        WINDOWDRAG|WINDOWCLOSE|SIMPLE_REFRESH|NOCAREREFRESH,&lt;br /&gt;
                        NULL,NULL,NameString,NULL,NULL,0,0,0,0,WBENCHSCREEN};&lt;br /&gt;
&lt;br /&gt;
extern VOID ButtonSwap();&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * This routine opens a window and waits for the one event that&lt;br /&gt;
 * can happen (CLOSEWINDOW)  This is just to let the user play with&lt;br /&gt;
 * the swapped buttons and then close the program...&lt;br /&gt;
 */&lt;br /&gt;
VOID WaitForUser(VOID)&lt;br /&gt;
{&lt;br /&gt;
struct Library *IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
IIntuition = (struct IntuitionIFace *)IExec-&amp;gt;GetInterface(IntuitionBase, &amp;quot;main&amp;quot;, 1, NULL);&lt;br /&gt;
&lt;br /&gt;
if (IIntuition != NULL)&lt;br /&gt;
    {&lt;br /&gt;
    struct Window *win = IIntuition-&amp;gt;OpenWindow(&amp;amp;mywin);&lt;br /&gt;
    if (win != NULL)&lt;br /&gt;
        {&lt;br /&gt;
        IExec-&amp;gt;WaitPort(win-&amp;gt;UserPort);&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(IExec-&amp;gt;GetMsg(win-&amp;gt;UserPort));&lt;br /&gt;
&lt;br /&gt;
        IIntuition-&amp;gt;CloseWindow(win);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;DropInterface((struct Interface *)IIntuition);&lt;br /&gt;
IExec-&amp;gt;CloseLibrary(IntuitionBase);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
struct MsgPort *inputPort = IExec-&amp;gt;AllocSysObjectTags(ASOT_PORT, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (inputPort != NULL)&lt;br /&gt;
    {&lt;br /&gt;
    struct Interrupt *inputHandler = IExec-&amp;gt;AllocSysObjectTags(ASOT_INTERRUPT,&lt;br /&gt;
        ASOINTR_Code, ButtonSwap,&lt;br /&gt;
        TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (inputHandler != NULL)&lt;br /&gt;
        {&lt;br /&gt;
        struct IOStdReq *inputReqBlk = IExec-&amp;gt;AllocSysObjectTags(ASOT_IOREQUEST,&lt;br /&gt;
            ASOIOR_Size, sizeof(struct IOStdReq),&lt;br /&gt;
            ASOIOR_ReplyPort, inputPort,&lt;br /&gt;
            TAG_END);&lt;br /&gt;
&lt;br /&gt;
        if (inputReqBlk != NULL)&lt;br /&gt;
            {&lt;br /&gt;
            if (!IExec-&amp;gt;OpenDevice(&amp;quot;input.device&amp;quot;, NULL,&lt;br /&gt;
                             (struct IORequest *)inputReqBlk, NULL))&lt;br /&gt;
                {&lt;br /&gt;
                inputHandler-&amp;gt;is_Node.ln_Pri  = 100;&lt;br /&gt;
                inputHandler-&amp;gt;is_Node.ln_Name = NameString;&lt;br /&gt;
                &lt;br /&gt;
                inputReqBlk-&amp;gt;io_Data    = (APTR)inputHandler;&lt;br /&gt;
                inputReqBlk-&amp;gt;io_Command = IND_ADDHANDLER;&lt;br /&gt;
                IExec-&amp;gt;DoIO((struct IORequest *)inputReqBlk);&lt;br /&gt;
&lt;br /&gt;
                WaitForUser();&lt;br /&gt;
&lt;br /&gt;
                inputReqBlk-&amp;gt;io_Data    = (APTR)inputHandler;&lt;br /&gt;
                inputReqBlk-&amp;gt;io_Command = IND_REMHANDLER;&lt;br /&gt;
                IExec-&amp;gt;DoIO((struct IORequest *)inputReqBlk);&lt;br /&gt;
&lt;br /&gt;
                IExec-&amp;gt;CloseDevice((struct IORequest *)inputReqBlk);&lt;br /&gt;
                }&lt;br /&gt;
            else&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;Error: Could not open input.device\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
            IExec-&amp;gt;FreeSysObject(ASOT_IOREQUEST, inputReqBlk);&lt;br /&gt;
            }&lt;br /&gt;
        else&lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create I/O request\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_INTERRUPT, inputHandler);&lt;br /&gt;
        }&lt;br /&gt;
    else&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Error: Could not allocate interrupt\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;FreeSysObject(ASOT_PORT, inputPort);&lt;br /&gt;
    }&lt;br /&gt;
else&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Error: Could not create message port\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== InputHandler.c ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct InputEvent *ButtonSwap(struct InputEvent *eventList, struct MsgPort *port)&lt;br /&gt;
{&lt;br /&gt;
    /*&lt;br /&gt;
     * Since the event list could be a linked list, we start a loop&lt;br /&gt;
     * here to handle all of the events passed to us.&lt;br /&gt;
     */&lt;br /&gt;
    struct InputEvent *currEvent = eventList;&lt;br /&gt;
    while (currEvent) /* Do some more. */&lt;br /&gt;
    {&lt;br /&gt;
        /* &lt;br /&gt;
         * Since we are changing left and right mouse buttons, we need to make&lt;br /&gt;
         * sure that we change the qualifiers on all of the messages.  The&lt;br /&gt;
         * left and right mouse buttons are tracked in the message qualifiers&lt;br /&gt;
         * for use in such things as dragging.  To make sure that we continue&lt;br /&gt;
         * to drag correctly, we change the qualifiers.&lt;br /&gt;
         *&lt;br /&gt;
         * Save a copy &lt;br /&gt;
         */&lt;br /&gt;
        &lt;br /&gt;
        uint16 qualActual = currEvent-&amp;gt;ie_Qualifier, qualFinal = qualActual;&lt;br /&gt;
&lt;br /&gt;
        if (IEQUALIFIERB_RBUTTON &amp;amp; qualActual) /* Check for right */&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal |= IEQUALIFIERB_LEFTBUTTON; /* Set the left */&lt;br /&gt;
        }&lt;br /&gt;
        else&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal &amp;amp;= ~IEQUALIFIERB_RBUTTON; /* Clear the left */&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (IEQUALIFIERB_LEFTBUTTON &amp;amp; qualActual) /* Check for left */&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal |= IEQUALIFIERB_RBUTTON; /* Set the right */&lt;br /&gt;
        }&lt;br /&gt;
        else&lt;br /&gt;
        {&lt;br /&gt;
            qualFinal &amp;amp;= ~IEQUALIFIERB_LEFTBUTTON; /* Set the left */&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currEvent-&amp;gt;ie_Qualifier = qualFinal; /* Save back */&lt;br /&gt;
&lt;br /&gt;
        /* &lt;br /&gt;
         * The actual button up/down events are transmitted as the&lt;br /&gt;
         * code field in RAWMOUSE events.  The code field must the be&lt;br /&gt;
         * checked and modified when needed on RAWMOUSE events.  If the&lt;br /&gt;
         * event is not a RAWMOUSE, we are done with it.&lt;br /&gt;
         */&lt;br /&gt;
        if (IECLASS_RAWMOUSE == currEvent-&amp;gt;ie_Class) /* Check for mouse */&lt;br /&gt;
        {&lt;br /&gt;
            /* Get code */&lt;br /&gt;
            /* Save a copy */&lt;br /&gt;
            /* We do not care if it is an UP or DOWN click */&lt;br /&gt;
            uint16 codeActual = currEvent-&amp;gt;ie_Code, codeFinal = (IECODE_UP_PREFIX-0x1) &amp;amp; codeActual;&lt;br /&gt;
            &lt;br /&gt;
            /* Check for left/right */&lt;br /&gt;
            if (IECODE_LBUTTON &amp;amp; codeActual || IECODE_RBUTTON &amp;amp; codeActual)&lt;br /&gt;
            {&lt;br /&gt;
                /* If so, swap */&lt;br /&gt;
                /* Flip bottom bit */&lt;br /&gt;
                codeFinal = 0x1 ^ codeActual;&lt;br /&gt;
                currEvent-&amp;gt;ie_Code = codeFinal;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currEvent=currEvent-&amp;gt;ie_NextEvent; /* Get next event */&lt;br /&gt;
    }&lt;br /&gt;
    return eventList;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Additional Information on the Input Device ==&lt;br /&gt;
&lt;br /&gt;
Additional programming information on the input device can be found in the include files and the autodocs for the input device. Both are contained in the SDK.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Includes&lt;br /&gt;
|-&lt;br /&gt;
| devices/input.h&lt;br /&gt;
|-&lt;br /&gt;
| devices/inputevent.h&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! AutoDocs&lt;br /&gt;
|-&lt;br /&gt;
| input.doc&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ryan Dixon</name></author>
	</entry>
</feed>