Copyright (c) Hyperion Entertainment and contributors.

Programming in the Amiga Environment

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
Warning.png This page is not yet fully updated to AmigaOS 4.x some of the information contained here may not be applicable in part or totally.

Programming in the Amiga Environment

To program in the Amiga's dynamic environment you need to understand these special features of the Amiga's design:

  • Multitasking (without memory protection)
  • Shared libraries of functions
  • Dynamic memory architecture (no memory map)
  • Operating system versions
  • Custom chips with DMA access (two kinds of memory)

Multitasking

The key feature of the Amiga's operating system design is multitasking. Multitasking means many programs, or tasks, reside in memory at the same time sharing system resources with one another. Programs take turns running so it appears that many programs are running simultaneously.

Multitasking is based on the concept that a program spends most of its time waiting for things to happen. A program waits for events like key presses, mouse movement, or disk activity. While a program is waiting, the CPU is idle. The CPU could be used to run a different program during this idle period if there was a convenient method for rapidly switching from one program to another. This is what multitasking does.

What the System Does For You

The Amiga uses preemptive multitasking which means that the operating system keeps track of all the tasks in memory and decides which one should run. The system checks hundreds of times per second to see which task should be run based on whether or not it is waiting, and other factors. Since the system handles all the work of task switching, multitasking is transparent to the application. From the application's point of view, it appears to have the machine all to itself.

The Amiga OS also manages the sharing of resources between tasks. This is important because in order for a variety of tasks to run independently in the Amiga's multitasking environment, tasks must be prevented from interfering with one another. Imagine if five tasks were allowed to use the parallel port at the same time. The result would be I/O chaos. To prevent this, the operating system provides an arbitration method (usually a function call) for every system resource. For instance you must call a function, AllocMem(), to get exclusive access to a block of memory.

What the System Doesn't Do For You

The Amiga operating system handles most of the housekeeping needed for multitasking, but this does not mean that applications don't have to worry about multitasking at all. The current generation of Amiga systems do not have hardware memory protection, so there is nothing to stop a task from using memory it has not legally acquired. An errant task can easily corrupt some other task by accidentally overwriting its instructions or data. Amiga programmers need to be extra careful with memory; one bad memory pointer can cause the machine to crash (debugging utilities such as MungWall and Enforcer will prevent this).

In fact, Amiga programmers need to be careful with every system resource, not just memory. All system resources from audio channels to the floppy disk drives are shared among tasks. Before using a resource, you must ask the system for access to the resource. This may fail if the resource is already being used by another task. Once you have control of a resource, no other task can use it, so give it up as soon as you are finished. When your program exits, you must give everything back whether it's memory, access to a file, or an I/O port. You are responsible for this, the system will not do it for you automatically.

What Every Amiga Programmer Should Know
The Amiga is a multitasking computer. Keep in mind that other tasks are running at the same time as your application. Always ask the system for control of any resource you need; some other task may already be using it. Give it back as soon as you are done; another task may want to use it. This applies to just about every computing activity your application can perform.

Libraries of functions

Most of the routines that make up the Amiga's operating system are organized into groups called libraries. Each library then contains one or more interfaces. In order to call a function on the Amiga you must first open the library that contains the function. Next, you get the interface which contains the function you want to call. For example, if you want to call the Read() function to read data from disk you must first open the DOS library and then get the "main" interface the Read() function is defined in.

The system's master library, called Exec, is always open. Exec keeps track of all the other libraries and is in charge of opening and closing them. Exec's "main" interface contains the OpenLibrary() function which is used to open all the other libraries.

Almost any program you write for the Amiga will have to call the OpenLibrary() and GetInterface() functions. Usage is as follows:

// Global: declare this above main()
struct Library *LibBase;
struct Interface *LibIFace;
 
int main()
{
  LibBase  = IExec->OpenLibrary("library.name", libversion);
  if(LibBase == NULL)
  {
    // Library did not open, so exit.
  }
  else
  {
    LibIFace = IExec->GetInterface(LibBase, "main", ifaceversion, NULL);
    if(LibIFace == NULL)
    {
        IExec->CloseLibrary(LibBase);
       // Could not get Interface, so exit.
    }
    else
    {
      // Interface obtained, so use its functions.
    }
  }
}
LibBase
This is a pointer to the library structure in memory, often referred to as the library base. The library may or may not be global depending on your needs. The name of this pointer may be changed although it is proper to use the established standard name. Refer to the list below for the appropriate name.
LibIFace
This is a pointer to the interface structure in memory. The interface may or may not be global depending on your needs. The name of this pointer may be changed although it is proper to use the established standard name. Refer to the list below for the appropriate name.
library.name
This is a C string that describes the name of the library you wish to open. The list of Amiga library names is given below.
main
This is a C string that describes the name of the interface you wish to use. The list of Amiga interface names depends on the library and is given below.
libversion
This should be set to the earliest acceptable library version. A value of 0 matches any version. A value of 53 means you require at least version 53 or a later version of the library. If the library version in the system is older than the one you specify, OpenLibrary() will fail (return 0).
ifaceversion
This should be set to the interface version you require. Each interface has a unique version number which defines all the functions in that interface. Most often an interface will have a single interface version of 1. If the interface version in the system does not match, GetInterface() will fail (return 0).

The table listed below shows all the function libraries that are currently part of the Amiga system software.

Parameters to use with OpenLibrary()
Library Name Library Base Oldest Library Version In Use Interface Name:Version
library.name* LibBase version iface.name:version
asl.library AslBase 50 main:1
commodities.library CxBase 50 main:1
diskfont.library DiskfontBase 50 main:1
dos.library DOSBase 50 main:1
exec.library ExecBase 50 main:1
expansion.library ExpansionBase 50 main:1
gadtools.library GadToolsBase 50 main:1
graphics.library GfxBase 50 main:1
icon.library IconBase 50 main:1
iffparse.library IFFParseBase 50 main:1
intuition.library IntuitionBase 50 main:1
keymap.library KeymapBase 50 main:1
layers.library LayersBase 50 main:1
mathffp.library MathBase 50 main:1
mathtrans.library MathTransBase 50 main:1
mathieeedoubbas.library MathIeeeDoubBasBase 50 main:1
mathieeedoubtrans.library MathIeeeDoubTransBase 50 main:1
mathieeesingbas.library MathIeeeSingBasBase 50 main:1
mathieeesingtrans.library MathIeeeSingTransBase 50 main:1
rexxsyslib.library RexxSysBase 50 main:1
translator.library TranslatorBase 50 main:1
utility.library UtilityBase 50 main:1
workbench.library WorkbenchBase 50 main:1

* Other libraries may exist that are not supplied by the AmigaOS development team since it is a feature of the operating system to allow such libraries.

Opening a Library in C

Here's a brief example showing how OpenLibrary() and GetInterface() are used in C.

/* easy.c: a complete example of how to open an Amiga function library in C.
 * In this case the function library is Intuition. Once the Intuition
 * function library is open and the interface obtains, any Intuition function
 * can be called. This example uses the DisplayBeep() function of Intuition to
 * flash the screen.
 */
 
#include <proto/exec.h>
#include <proto/intuition.h>
 
struct Library *IntuitionBase;
struct IntuitionIFace *IIntuition;
 
int main()
{
    IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
 
    // Note it is safe to call GetInterface() with a NULL library pointer.
    IIntuition = (struct IntuitionIFace *)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
 
    if(IIntuition != NULL)           /* Check to see if it actually opened.   */
    {                                /* The Intuition library is now open so  */
        IIntuition->DisplayBeep(0);  /* any of its functions may be used.     */
    }
 
    // Always drop the interface and close the library if not in use.
    // Note it is safe to call DropInterface() and CloseLibrary() with NULL pointers.
    IExec->DropInterface((struct Interface *)IIntuition);
    IExec->CloseLibrary(IntuitionBase);
}

Another Kind of Function Library

The Amiga has two kinds of libraries: run-time libraries and link libraries. All the libraries discussed so far are run-time libraries. Run-time libraries make up most of the Amiga's operating system and are the main topic of this book.

There is another type of library known as a link library. Even though a link library is a collection of functions just like a run-time library, there are some major differences in the two types.

A run-time, or shared library is a group of functions managed by Exec that resides either in ROM or on disk (in the "LIBS:" directory). A run-time library must be opened before it can be used (as explained above). The functions in a run-time library are accessed dynamically at run-time and can be used by many programs at once even though only one copy of the library is in memory. A disk based run-time library is loaded into memory only if requested by a program and can be automatically flushed from memory when no longer needed.

A link library is a group of functions on disk that are managed by the compiler at link time. Link libraries do not have to be opened before they are used, instead you must link your code with the library when you compile a program. The functions in a link library are actually copied into every program that uses them. For instance the exit() function used in the C program listed above is not part of any of the libraries that make up the Amiga OS. It comes from the link library supplied with the compiler. The code that performs the exit() function is copied into the program when it is compiled.

Libraries, Devices and Resources

Most of the Amiga's OS routines are organized into groups of shared run-time libraries. The Amiga also has specialized function groups called devices and resources that programmers use to perform basic I/O operations or access low-level hardware.

Devices and resources are similar in concept to a shared run-time library. They are managed by Exec and must be opened before they can be used. Their functions are separate from the programs that use them and are accessed dynamically at run time. Multiple programs can access the device or resource even though only one copy exists in memory (a few resources can only be used by one program at a time.)

Figure 1-1: Amiga System Software Hierarchy

Devices and resources are managed by Exec just as libraries are. For more information on devices and resources, see the respective sections.

What Every Amiga Programmer Should Know
The functions in the Amiga OS are accessed through shared run-time libraries. Libraries must be opened before their functions may be used. The system's master library, Exec, is always open. The Exec function OpenLibrary() is used to open all other libraries.

Dynamic memory architecture

Unlike some microcomputer operating systems, the AmigaOS relies on absolute memory addresses as little as possible. Instead the Amiga OS uses a technique (sometimes referred to as soft machine architecture) which allows system routines and data structures to be positioned anywhere in memory.

Amiga run-time libraries may be positioned anywhere in memory because they are always accessed through a jump table. Each library whether in ROM or loaded from disk has an associated Library structure and jump table in RAM.

Amiga Library Structure and Jump Table

The system knows where the jump table starts in RAM because when a library is opened for the first time, Exec creates the library structure and keeps track of its location. The order of the entries in the library's jump table is always preserved between versions of the OS but the functions they point to can be anywhere in memory. Hence, system routines in ROM may be moved from one version of the OS to another. Given the location of the jump table and the appropriate offset into the table, any function can always be found.

Not only are system routines relocatable but system data structures are too. In the Amiga's multitasking environment, multiple applications run at the same time and each may have its own screen, memory, open files, and even its own subtasks. Since any number of application tasks are run and stopped at the user's option, system data structures have to be set up as needed. They cannot be set up ahead of time at a fixed memory location because there is no way to tell how many and what type will be needed.

The Amiga system software manages this confusion by using linked lists of information about items such as libraries, tasks, screens, files and available memory. A linked list is a chain of data items with each data item containing a pointer to the next item in the chain. Given a pointer to the first item in a linked list, pointers to all the other items in the chain can be found.

Exec: The System Executive

On the Amiga, the module that keeps track of linked lists is Exec, the system executive. Exec is the heart of the Amiga operating system since it also is in charge of multitasking, granting access to system resources (like memory) and managing the Amiga library system.

As previously discussed, memory location 4 ($0000 0004), also known as SysBase, contains a pointer to the Exec library structure. This is the only absolutely defined location in the Amiga operating system. A program need only know where to find the Exec library to find, use and manipulate all other system code and data.

Exec and the Organization of the Amiga OS

The diagram above shows how the entire Amiga operating system is built as a tree starting at SysBase. Exec keeps linked lists of all the system libraries, devices, memory, tasks and other data structures. Each of these in turn can have its own variables and linked lists of data structures built onto it. In this way, the flexibility of the OS is preserved so that upgrades can be made without jeopardizing compatibility.

What Every Amiga Programmer Should Know
The Amiga has a dynamic memory map. There are no fixed locations for operating system variables and routines. Do not call ROM routines or access system data structures directly. Instead use the indirect access methods provided by the system.

Operating system versions

The Amiga operating system has undergone several major revisions. The latest revision is Release 4.1 (corresponds to library version 53).

See the table in the Libraries and Devices section for details on which version corresponds to which OS release.

The examples listed throughout this wiki assume you are using Release 4.0 or higher.

Many of the libraries and functions documented in this manual are available in all versions of the Amiga operating system. Others are completely new and cannot be used unless you have successfully opened the appropriate version of the library.

To find out which functions are new, refer to the SDK. The functions which are new are marked with (V50) or (V51) in the NAME line of the function Autodoc. These new functions require you to use the corresponding version number (50, 51, or higher) when opening the library.

Exit gracefully and informatively if the required library version is not available.

About Release 4

Release 4 first appeared on the AmigaOne XE. This initial version corresponds to Kickstart 4.0, system library version number V50.

What Every Amiga Programmer Should Know
Some libraries or specific functions are not available in older versions of the Amiga operating system. Be sure to ask for the lowest library version that meets the requirements of your program.

Two Kinds of Memory

To keep the Classic Amiga running efficiently, the Classic Amiga has two memory buses and two kinds of memory. Chip memory is memory that both the CPU and custom chips can access. Fast memory is memory that only the CPU (and certain expansion cards) can access. Since Chip memory is shared, CPU access may be slowed down if the custom chips are doing heavy-duty processing. CPU access to Fast memory is never slowed down by contention with the custom chips.

The distinction between Chip memory and Fast memory is very important for Classic Amiga programmers to keep in mind because any data accessed directly by the custom chips such as video display data, audio data or sprite data must be in Chip memory.

What Every Amiga Programmer Should Know: The Classic Amiga has two kinds of memory: Chip memory and Fast memory. Use the right kind.

NOTE There is no such thing as Chip memory in any of new PowerPC-based Amiga systems. The two types of memory only applies to the Classic Amiga hardware platforms such as the A1200 and A4000 models.

About the Examples

For the most part, the examples in this book are written in C.

C examples have been compiled using the standard Software Development Kit (SDK) using GCC.

In general, the examples are also compatible with vbcc and other C compilers, however some changes will usually be necessary.

Specifically, all the C examples assume that the automatic Ctrl-C feature of the compiler has been disabled. For SAS C (and Lattice C revisions 4.0 and greater) this is handled with:

/* Add this before main() to override the default Ctrl-C handling
 * provided in SAS (Lattice) C.  Ctrl-C event will be ignored */

int CXBRK ( void )   { return(0); }
int chkabort( void ) { return(0); }

Other changes may be required depending on the example and the C compiler you are using. Most of the C examples do not require any special options and rely on the defaults provided by the SDK.

Except where noted, each example was linked with the standard newlib startup code which provides the following interfaces: IExec, IDOS and IUtility.

General Amiga Development Guidelines

This sections presents specific guidelines that all Amiga programmers must follow. Some of these guidelines are for advanced programmers.

  • Check for memory loss. Arrange your Workbench screen so that you have a Shell available and can start your program without rearranging any windows. In the Shell window type Avail flush several times (the flush option requires the Release 2 version of the Avail command). Note the total amount of free memory. Run your program (do not rearrange any windows other than those created by the program) and then exit. At the Shell, type Avail flush several times again. Compare the total amount of free memory with the earlier figure. They should be the same. Any difference indicates that your application is not freeing some memory it used or is not closing a disk-loaded library, device or font it opened. Note that under Release 2, a small amount of memory loss is normal if your application is the first to use the audio or narrator device.
  • Use all of the program debugging and stress tools that are available when writing and testing your code. New debugging tools such as Enforcer, MungWall, and Scratch can help find uninitialized pointers, attempted use of freed memory and misuse of scratch registers or condition codes (even in programs that appear to work perfectly).
  • Always make sure you actually get any system resource that you ask for. This applies to memory, windows, screens, file handles, libraries, devices, ports, etc. Where an error value or return is possible, ensure that there is a reasonable failure path. Many poorly written programs will appear to be reliable, until some error condition (such as memory full or a disk problem) causes the program to continue with an invalid or null pointer, or branch to untested error handling code.
  • Always clean up after yourself. This applies for both normal program exit and program termination due to error conditions. Anything that was opened must be closed, anything allocated must be deallocated. It is generally correct to do closes and deallocations in reverse order of the opens and allocations. Be sure to check your development language manual and startup code; some items may be closed or deallocated automatically for you, especially in abort conditions. If you write in the C language, make sure your code handles Ctrl-C properly.
  • Remember that memory, peripheral configurations, and ROMs differ between models and between individual systems. Do not make assumptions about memory address ranges, storage device names, or the locations of system structures or code. Never call ROM routines directly. Beware of any example code you find that calls routines at addresses in the $F00000 $FFFFFF range. These are ROM routines and they will move with every OS release. The only supported interface to system ROM code is through the library, device, and resource calls.
  • Never assume library bases or structures will exist at any particular memory location. The only absolute address in the system is $00000004, which contains a pointer to the Exec library base. Do not modify or depend on the format of private system structures. This includes the poking of copper lists, memory lists, and library bases.
  • Never assume that programs can access hardware resources directly. Most hardware is controlled by system software that will not respond well to interference from other programs. Shared hardware requires programs to use the proper sharing protocols. Use the defined interface; it is the best way to ensure that your software will continue to operate on future models of the Amiga.
  • Never access shared data structures directly without the proper mutual exclusion (locking). Remember that other tasks may be accessing the same structures.
  • The system does not monitor the size of a program's stack. (Your compiler may have an option to do this for you.) Take care that your program does not cause stack overflow and provide extra stack space for the possibility that some functions may use up additional stack space in future versions of the OS.
  • Never use a polling loop to test signal bits. If your program waits for external events like menu selection or keystrokes, do not bog down the multitasking system by busy-waiting in a loop. Instead, let your task go to sleep by Wait()ing on its signal bits. For example:
 signals = (ULONG)Wait(  (1<<windowPtr->UserPort->mp_SigBit) |
                         (1<<consoleMsgPortPtr->mp_SigBit)  );
  • This turns the signal bit number for each port into a mask, then combines them as the argument for the Exec library Wait() function. When your task wakes up, handle all of the messages at each port where the mp_SigBit is set. There may be more than one message per port, or no messages at the port. Make sure that you ReplyMsg() to all messages that are not replies themselves. If you have no signal bits to Wait() on, use Delay() or WaitTOF() to provide a measured delay.
  • Tasks (and processes) execute in 680x0 user mode. Supervisor mode is reserved for interrupts, traps, and task dispatching. Take extreme care if your code executes in supervisor mode. Exceptions while in supervisor mode are deadly.
  • Most system functions require a particular execution environment. All DOS functions and any functions that might call DOS (such as the opening of a disk-resident library, font, or device) can only be executed from a process. A task is not sufficient. Most other kernel functions may be executed from tasks. Only a few may be executed from interrupts.
  • Never disable interrupts or multitasking for long periods. If you use Forbid() or Disable(), you should be aware that execution of any system function that performs the Wait() function will temporarily suspend the Forbid() or Disable() state, and allow multitasking and interrupts to occur. Such functions include almost all forms of DOS and device I/O, including common stdio functions like printf().
  • Never tie up system resources unless it is absolutely necessary. For example, if your program does not require constant use of the printer, open the printer device only when you need it. This will allow other tasks to use the printer while your program is running. You must provide a reasonable error response if a resource is not available when you need it.
  • All data for the custom chips must reside in Chip memory (type MEMF_CHIP). This includes bitplanes, sound samples, trackdisk buffers, and images for sprites, bobs, pointers, and gadgets. The AllocMem() call takes a flag for specifying the type of memory. A program that specifies the wrong type of memory may appear to run correctly because many Amigas have only Chip memory. (On all models of the Amiga, the first 512K of memory is Chip memory. In later models, Chip memory may occupy up to the first one or two megabytes).
  • However, once expansion memory has been added to an Amiga (type MEMF_FAST), any memory allocations will be made in the expansion memory area by default. Hence, a program can run correctly on an unexpanded Amiga which has only Chip memory while crashing on an Amiga which has expanded memory. A developer with only Chip memory may fail to notice that memory was incorrectly specified.
  • Most compilers have options to mark specific data structures or object modules so that they will load into Chip RAM. Some older compilers provide the Atom utility for marking object modules. If this method is unacceptable, use the AllocMem() call to dynamically allocate Chip memory, and copy your data there.
  • When making allocations that do not require Chip memory, do not explicitly ask for Fast memory. Instead ask for memory type MEMF_PUBLIC or 0L as appropriate. If Fast memory is available, you will get it.
  • Never use software delay loops! Under the multitasking operating system, the time spent in a loop can be better used by other tasks. Even ignoring the effect it has on multitasking, timing loops are inaccurate and will wait different amounts of time depending on the specific model of Amiga computer. The timer device provides precision timing for use under the multitasking system and it works the same on all models of the Amiga. The AmigaDOS Delay() function or the graphics library WaitTOF() function provide a simple interface for longer delays. The 8520 I/O chips provide timers for developers who are bypassing the operating system (see the Amiga Hardware Reference Manual for more information).

Always obey structure conventions!

  • All non-byte fields must be word-aligned. Longwords should be longword-aligned for performance.
  • All address pointers should be 32 bits (not 24 bits). Never use the upper byte for data.
  • Fields that are not defined to contain particular initial values must be initialized to zero. This includes pointer fields.
  • All reserved or unused fields must be initialized to zero for future compatibility.
  • Data structures to be accessed by the custom chips, public data structures (such as a task control block), and structures which must be longword aligned must not be allocated on a program's stack.
  • Dynamic allocation of structures with AllocMem() provides longword aligned memory of a specified type with optional initialization to zero, which is useful in the allocation of structures.

Hardware programming guidelines

If you find it necessary to program the hardware directly, then it is your responsibility to write code that will work correctly on the various models and configurations of the Amiga. Be sure to properly request and gain control of the hardware resources you are manipulating, and be especially careful in the following areas:

  • Kickstart 2.0 uses the 8520 Complex Interface Adaptor (CIA) chips differently than 1.3 did. To ensure compatibility, you must always ask for CIA access using the cia.resource AddICRVector() and RemICRVector() functions. Do not make assumptions about what the system might be using the CIA chips for. If you write directly to the CIA chip registers, do not expect system services such as the trackdisk device to function. If you are leaving the system up, do not read or write to the CIA Interrupt Control Registers directly; use the cia.resource AbleICR(), and SetICR() functions. Even if you are taking over the machine, do not assume the initial contents of any of the CIA registers or the state of any enabled interrupts.
  • All custom chip registers are Read-only or Write-only. Do not read Write-only registers, and do not write to Read-only registers.
  • Never write data to, or interpret data from the unused bits or addresses in the custom chip space. To be software-compatible with future chip revisions, all undefined bits must be set to zeros on writes, and must be masked out on reads before interpreting the contents of the register.
  • Never write past the current end of custom chip space. Custom chips may be extended or enhanced to provide additional registers, or to use bits that are currently undefined in existing registers.
  • Never read, write, or use any currently undefined address ranges or registers. The current and future usage of such areas is reserved by the AmigaOS development team and is subject to change.
  • Never assume that a hardware register will be initialized to any particular value. Different versions of the OS may leave registers set to different values. Check the Amiga Hardware Reference Manual to ensure that you are setting up all the registers that affect your code.

AmigaOS Interface Style Guide

In order to make all programs in AmigaOS look consistent, AmigaOS provides some default thumbnails that developers must use in their GUI. These pictures are located in the tbimages: assign. They are installed in any AmigaOS installation. Note that something may still go wrong and these pictures may not be found. In this case, the program must provide a fallback method instead of just failing. The developer can choose to display text-only toolbars or include default pictures with their program.

While the look of windows and gadgets can be changed by the user, the default look is similar to this:

About.png

Error Reports

All corrections and bug report should be made by posting a new topic at AmigaOS' support forum. The support forum is located at http://support.amigaos.net