Primitive Types

Gone are the days of ambiguous types on AmigaOS. The move to explicit types was not made just for purely cosmetics reasons. The PowerPC platform has different meanings for types like SHORT, WORD and LONG when compared to the 68000 world. By moving to a more explicit type system, the Amiga can be more easily ported to future hardware platforms as well.

A majority of the operating system still makes use of the original types due to the very large job of changing them all. Over time, the new types will be used more and more so it makes sense to begin using them immediately in your code.

Here is a partial list of the new types from the exec/types.h include:
int8 - signed 8 bit quantity
uint16 - unsigned 16 bit quantity
int32 - signed 32 bit quantity
uint64 - unsigned 64 bit quantity

Avid C99 users will notice these types bear a strong resemblance to the basic C types defined in the stdint.h include:
int8_t - signed 8 bit quantity
uint16_t - unsigned 16 bit quantity
int32_t - signed 32 bit quantity
uint64_t - unsigned 64 bit quantity

It is a good idea to keep using the standard Amiga types when using the Amiga API. When using the C API use the standard C types. Mixing the two type conventions is possible since they do refer to the same underlying types but conceptually it can become a mess for readers and is best avoided.

For truly portable applications, it is best to rely on the stdint.h types when possible given it is an international standard.

Outputting Debug Text

By default, debugging text is stored in a persistent area of Exec memory which can survive warm reboots. The tool DumpDebugBuffer is then used to extract what is in this internal buffer. The buffer will wrap around when full so it is important to dump the buffer often.

By adding the keyword serial to the os4_commandline firmware variable the debug text will be output to the serial port instead. The serial port defaults depend on the particular Amiga hardware platform you are using so check your hardware's documentation.

In both cases, the IExec->DebugPrintF() function is used to output text and is also safe to call from interrupts.

Exec Interfaces

Exec Interfaces have been introduced to the AmigaOS. This is a major change from previous versions of the operating system and needs to be understood by all Amiga coders.

Each dynamic library or device in the system now includes one or more interfaces. An interface is basically a function jump table. You invoke an entry in the jump table and it calls a specific function. This is nothing new to old timers who are used to the classic Amiga library system. The difference is that you may have one or more interfaces per library now.

For example, some components need to be accessible from 68000-based legacy code. An interface is provided with a jump table for those 68000 applications. Each vector in the 68000 jump table (interface) can also have different behaviour if required to work around legacy-specific issues if needed. Using an interface nicely encapsulates that code.

A majority of components will only include a single interface named main by convention. There is also a version number associated with the interfaces. This is currently in use by the application.library which had a compatibility break in the interface. Old applications continue to use the old version and new applications get the new version of the interface. Both versions co-exist in the same binary at the same time and each interface keeps its specific code encapsulated.

Each child process or thread should call the Obtain() vector on an interface as explained in the exec.doc autodoc. The idea here is that the main program calls IExec->GetInterface() and any sub-processes just call IFace->Obtain(). In this way, the proper use count will be recorded and any interface-specific behaviour in IFace->Obtain() will be applied to each sub-process appropriately.

Compiler support for Exec Interfaces has been added so that expressions can be shortened directly in code. Without this special syntax, a programmer would have had to write IExec->OpenLibrary(IExec, "asl.library", 53) instead of just IExec->OpenLibrary("asl.library", 53). There is no real advantage to the syntax beyond readability.

Exec Objects

Many of the common objects required for applications are now handled though IExec->AllocSysObjectTags() and IExec->FreeSysObject(). This function pair replaces obsolete functions like CreatePort(), CreateIORequest() and CreatePool(). Use of the new functions is strongly encouraged to better future proof your software.
ASOT_INTERRUPT
Note the ASOT_INTERRUPT type can be tricky to use correctly. For example, IExec->Cause() depends on the ln_Type field being initialized to NT_UNKNOWN.

It is always a good idea to initialize the ln_Name field and ln_Pri fields explicitly as well.

Memory Types

The memory system in AmigaOS has been simplified from a programmer's point of view. There are only three types of memory to be concerned with now:
MEMF_PRIVATE
MEMF_SHARED and
MEMF_EXECUTABLE.

All other memory types are mapped onto these three types of memory. There is but one exception and that would be MEMF_CHIP memory when AmigaOS is running on the classic platform.

Private memory is marked private for the Task that created it and cannot be used by any other Task. It is a good idea to use private memory because it would allow Task-specific memory in a future version of the operating system. Shared memory is meant for any memory shared between Tasks. Shared memory should be considered less plentiful than private memory. Executable memory contains only executable code and is generally not specified by applications.

All memory is allocated using IExec->AllocVecTags() either directly or indirectly. The only reason to use an obsolete memory allocation function like IExec->AllocMem() would be to allocate MEMF_CHIP memory. Always prefer to use IExec->AllocVecTags() which is a more future proof way to extend the memory system. Also prefer to use MEMF_PRIVATE memory when possible.

Memory Pager

Memory paging is an optional feature of AmigaOS used to extend the amount of physical memory available to the system. The programmer usually does not have to be concerned with the pager. Driver writers and performance sensitive applications may want to consider what the pager is up to and can control what happens with dynamic memory and stack allocation.

When creating a new process, the NP_LockStack tag may be used to lock the stack so that it will not be paged out. When allocating memory, the AVT_Lock tag may be used to lock pages in memory and prevent them from being swapped out. Memory pools may also be locked using the ASOPOOL_LockMem tag. Finally, the IExec->LockMem() function is also available to lock memory in place.

Intelligent Expunge

The Amiga now uses a greedy memory model. In this model, free memory is considered wasted memory. When memory is full and more memory is required, the system performs an expunge to make more room. Caching is heavily employed to speed access to commonly used components. All of this occurs behind the scenes and without user intervention. In theory, users should not have to be worried about how much free memory they have at any specific point in time. The system will find the best way to take care of things because the system knows more than the user about its current state.

Previous versions of Amiga OS relied on each system component explicitly expunging memory as soon as possible. For example, when the lib_OpenCnt reached zero in the close vector it was expected that the library or device would implicitly call the expunge vector. If this was not possible, the LIBF_DELEXP flag would be set to notify the system to expunge the component as soon as possible.

Here is an example close vector for a typical component (library or device):
BPTR LibClose(struct Interface* self)
{
 struct Library* base = self->Data.LibBase;

 if (base->lib_OpenCnt > 0)
 {
  --base.lib_OpenCnt;
 }

 return ZERO; // Always return ZERO now.
}

Here is an example of the expunge vector:
BPTR LibExpunge(struct Interface* self)
{
 struct Library* lib = self->Data.LibBase;
 BPTR retValue = ZERO;

 if (lib->lib_OpenCnt == 0)
 {
  // Close libraries, etc.

  // Return the segment so it can be unloaded.
  // This needs to be initialized in the LibInit vector.
  retValue = ((struct MyLibBase*)lib)->SegList;

  // Remove the library from the public list.
  Remove((struct Node*)lib);

  // Free the vector table and the library data.
  DeleteLibrary(lib);
 }
 else
 {
  lib->lib_Flags |= LIBF_DELEXP;
 }

 return retValue;
}

In this manner, the system has control of when the expunge vector is being invoked. There is no implicit call to the LibExpunge() function and the LibClose() function is simplified and doing only what its name implies.

For programmers, a new Expunge command has been provided which will force the system to invoke the expunge vector for testing purposes.

To summarize, the user no longer has to worry about explicitly flushing memory and the programmer no longer has to worry about expunging in the close vector. Amiga OS will take of both things from now on.