AROS Vision
Modern and free Amiga-Compatible Experience on Amiga and PC
Article 18: Programming the Amiga
How Applications
Communicate with AmigaOS
In the previous articles, we examined the Amiga from several different perspectives.
We have explored:
the Motorola 68000 CPU
Chip RAM and Fast RAM
the custom chipset
Exec and multitasking
libraries and devices
AmigaDOS
filesystems and handlers
DMA
interrupts
Now we can bring many of these concepts together and look at the machine from the perspective of an application programmer.
How does an Amiga program actually use the operating system?
How does it allocate memory?
How does it open a window?
How does it receive mouse and keyboard events?
How does it read files?
And how does all of this interact with Exec?
The answer reveals one of the most interesting characteristics of AmigaOS:
An Amiga application does not simply run on top of the operating system. It participates in a system of libraries, tasks, messages, signals and shared resources.
1. Two Ways to Program the Amiga
There are broadly two approaches to classic Amiga programming.
Operating-system programming
The application uses AmigaOS services.
Application
|
v
AmigaOS
|
v
Hardware
Typical examples include:
Workbench applications
utilities
editors
productivity software
communication programs
Direct hardware programming
The program accesses the custom-chip hardware more directly.
Program
|
v
Custom-chip registers
|
v
Hardware
This approach became particularly common in:
games
demos
multimedia software
The two approaches can also be combined, but doing so requires care.
2. Why Use the Operating System?
Suppose you want to create a window.
You could theoretically try to manage everything yourself:
display timing
mouse input
clipping
window borders
screen coordinates
keyboard input
drawing
memory
But AmigaOS already provides these services.
Instead the application can use:
intuition.library
for user-interface functionality and:
graphics.library
for graphics operations.
This dramatically reduces the amount of code required.
3. An Amiga Application Is Usually Part of the Multitasking System
An ordinary Amiga application runs under Exec's multitasking system.
Conceptually:
Exec Scheduler
|
+-----------+-----------+
| | |
Workbench Editor Your Program
| | |
Task Task Task/Process
Your program is not normally the only software executing.
Exec continuously coordinates the runnable tasks.
This has an important consequence:
A well-behaved application must cooperate with the rest of the system.
4. Tasks and Processes Revisited
As we learned earlier, Exec understands:
Task
while AmigaDOS adds the higher-level:
Process
A DOS process includes additional information such as:
current directory
file handles
standard input
standard output
DOS-related state
A program launched through AmigaDOS will therefore commonly execute as a process.
Conceptually:
Exec Task
+
DOS information
=
DOS Process
5. Program Startup
An Amiga program can be launched in several ways.
Two important examples are:
Shell
and:
Workbench
These environments start applications differently.
From the Shell:
Shell
|
| command line
v
Program
From Workbench:
Workbench
|
| startup message
v
Program
A program supporting both environments needs to handle the appropriate startup mechanism.
6. Opening Libraries
One of the first things many traditional Amiga applications do is open the libraries they require.
For example:
OpenLibrary()
Suppose the application needs Intuition.
Conceptually:
IntuitionBase =
OpenLibrary("intuition.library", 0);
The application receives the library base if the library is available.
The library base provides access to the library interface.
7. Always Check the Result
Applications should never assume that a requested resource is available.
Conceptually:
LibraryBase = OpenLibrary(...);
if (LibraryBase == NULL)
{
/* handle error */
}
This principle applies throughout Amiga programming.
Resources may fail to become available because of:
insufficient memory
missing libraries
incompatible versions
unavailable devices
unavailable files
Robust software checks these conditions.
8. Library Versions
Programs can request a minimum library version.
For example:
Application requires version 39
|
v
OpenLibrary()
|
+------+------+
| |
V39 or newer older
| |
OK fail
This allows an application to ensure that the APIs it depends on actually exist.
It also contributed to compatibility between different versions of AmigaOS.
9. Allocating Memory
Applications frequently need memory for:
data structures
buffers
graphics
text
file data
temporary calculations
Exec provides memory-management functions.
A classic example is:
AllocMem()
As we saw in Article 13, the application can specify memory requirements.
For example:
MEMF_CHIP
or:
MEMF_FAST
or:
MEMF_CLEAR
10. Choosing the Correct Memory
Suppose the program needs a buffer for calculations.
Fast RAM may be appropriate.
CPU
|
v
Fast RAM
But suppose it needs a bitmap that the classic custom chipset must access through DMA.
Then Chip RAM may be required.
CPU
|
v
Chip RAM
^
|
DMA
Therefore memory allocation on an Amiga is not only about:
How many bytes are required?
It can also be about:
Who needs to access those bytes?
11. Creating a Window
Suppose our application wants to create an Intuition window.
Conceptually:
Program
|
| request
v
intuition.library
|
v
Window
The application supplies information describing the desired window.
This may include:
position
width
height
title
gadgets
event types
screen
Intuition then manages the window within the graphical environment.
12. The Application Does Not Manage Everything
Once the window exists, the application does not need to reinvent the complete graphical user interface.
Intuition helps manage things such as:
window activation
gadget interaction
menus
window movement
resizing
user-interface events
This is precisely what an operating-system abstraction should provide.
The application concentrates on its own functionality.
13. Event-Driven Programming
GUI programs cannot simply perform one fixed sequence of commands and then exit.
They must respond to the user.
For example:
Mouse click
Keyboard input
Window close
Menu selection
Gadget pressed
Window resized
The program therefore spends much of its time waiting for events.
This leads to an important programming style:
Event-driven programming
14. The Event Loop
A simplified GUI program might work like this:
Open libraries
|
v
Open window
|
v
+-------------------+
| Wait for event |
+-------------------+
|
v
Process event
|
v
Continue?
/ </span>
Yes No
| |
+---------+--> cleanup
This loop may continue until the user closes the application.
15. Intuition Messages
Intuition communicates many user-interface events through messages.
T
hese are commonly known as:
IntuiMessages
For example, an application may receive messages indicating:
gadget activity
mouse buttons
raw key events
window refresh
menu selections
window close requests
This brings us directly back to Exec's message system.
16. Message Ports
Again
A window can be associated with a message port.
Conceptually:
User
|
v
Intuition
|
v
IntuiMessage
|
v
Message Port
|
v
Application
The application receives events from the port.
This illustrates how the Amiga's GUI is built upon the same fundamental communication mechanisms used elsewhere in the operating system.
17. Waiting Efficiently
A badly designed event loop might repeatedly check:
Any message?
Any message?
Any message?
Any message?
This wastes CPU time.
Instead, the application can wait.
Conceptually:
Application
|
v
Wait()
|
| asleep
|
Event occurs
|
v
Signal
|
v
Application wakes
#
While the program waits, Exec can run other tasks.
This is cooperative use of the multitasking environment.
18. Signals and Message Ports
When a message arrives at a port, the corresponding task can be signaled.
The relationship looks roughly like this:
Event
|
v
Message
|
v
Port
|
v
Signal
|
v
Task wakes
The task then reads the message from the port.
Again, several pieces of Exec's architecture work together.
19. Processing a Message
A simplified program structure might look conceptually like:
Wait(signal);
message = GetMsg(port);
if (message)
{
/* inspect message */
}
The program examines the message type and decides what action to perform.
For example:
CLOSEWINDOW
|
v
Terminate event loop
or:
GADGETUP
|
v
Perform gadget action
20. Replying to Messages
One important feature of Exec messages is ownership.
When a program has finished processing certain messages, it must return them appropriately.
For Intuition messages, this normally involves:
ReplyMsg()
The principle is important:
Receiving a message does not necessarily mean that the receiver owns the underlying message memory permanently.
Once the information has been processed, the message is returned.
This avoids unnecessary copying and fits the Amiga's memory-efficient design.
21. Message Passing Is Everywhere
At this point we have seen messages in many parts of AmigaOS:
Intuition
Devices
DOS handlers
Processes
Applications
Inter-task communication
This is not accidental.
Message passing is one of the architectural foundations of AmigaOS.
Instead of every component directly manipulating every other component, messages allow independent tasks to communicate.
22. Using AmigaDOS
Suppose our program needs to open a file.
It can use functions provided by:
dos.library
Conceptually:
file = Open("Work:Data/Test", MODE_OLDFILE);
The application receives a handle representing the open file.
It can then perform operations such as:
Read()
Write()
Seek()
Close()
The application does not need to know whether the file resides on:
a floppy
a hard disk
a RAM disk
another filesystem implementation
AmigaDOS provides the abstraction.
23. Several OS Layers
May Be Involved
Opening a file may look simple from the application's perspective:
Open("Work:Data")
But underneath:
Application
|
v
dos.library
|
v
AmigaDOS
|
v
Filesystem handler
|
v
Device driver
|
v
Hardware
This is precisely why operating-system APIs are valuable.
The application deals with a logical operation rather than physical implementation details.
24. Using Devices
Sometimes an application needs lower-level I/O.
For this, it may use an Exec device.
The general structure involves an I/O request.
Application
|
v
I/O Request
|
v
Device
|
v
Hardware/service
Functions such as:
OpenDevice()
DoIO()
SendIO()
provide access to this mechanism.
25. Asynchronous I/O
A particularly powerful technique is asynchronous I/O.
Instead of:
Start operation
|
v
Wait doing nothing
|
v
Finished
the application may:
Start operation
|
+------------------+
| |
v v
Device works Program continues
| |
+---------+--------+
|
completion
This fits naturally into AmigaOS multitasking.
26. Waiting for Multiple Events
An application may need to wait for several things simultaneously.
For example:
GUI message
Serial input
Timer event
Other process
Exec signals allow a task to wait for combinations of events.
Conceptually:
Application
|
Wait()
|
+--------------+--------------+
| | |
GUI event Timer event I/O event
Whichever relevant event occurs can wake the task.
This is an extremely powerful feature for event-driven software.
27. Example: A Terminal Program
Imagine a serial terminal application.
It needs to react to:
keyboard input,
window events,
incoming serial data.
A primitive design might constantly poll all three.
But an AmigaOS-oriented design can use asynchronous communication.
Terminal Task
|
Wait()
|
+-------------+-------------+
| | |
Intuition Keyboard Serial I/O
event event event
The program sleeps when nothing needs attention.
When something happens, it wakes and processes the event.
28. Why This Is Efficient
Consider ten applications running simultaneously.
If every application continuously executed loops like:
check
check
check
check
check
they would waste significant processor time.
If idle applications instead call:
Wait()
Exec can schedule CPU time for applications that actually have work to perform.
This was especially important on early Amigas running at approximately 7 MHz.
Efficient event-driven design allowed the system to feel responsive despite limited processor power.
29. Resource Ownership
Amiga programming places significant responsibility on the programmer.
If your application opens:
library
device
window
screen
file
message port
memory block
it should normally release the resource appropriately.
For example:
OpenLibrary()
|
v
CloseLibrary()
AllocMem()
|
v
FreeMem()
Open()
|
v
Close()
OpenWindow()
|
v
CloseWindow()
Correct cleanup is essential.
30. Cleanup Order Matters
Suppose a window depends on Intuition.
You should not normally close intuition.library and then attempt to close the window using Intuition.
The dependent resource should be released first.
Conceptually:
Open library
|
Open window
|
Use window
|
Close window
|
Close library
Notice the pattern.
Resources are commonly released in roughly the reverse order in which they were acquired.
31. Error Handling and Cleanup
Imagine this startup sequence:
Open DOS
|
Open Intuition
|
Allocate memory
|
Open window
What happens if the memory allocation fails?
The program must still release resources that were successfully obtained earlier.
Conceptually:
Open DOS OK
Open Intuition OK
AllocMem FAIL
|
v
Close Intuition
Close DOS
Exit
Resource management is one of the most important practical skills in Amiga programming.
32. No Strong Memory Protection
Classic AmigaOS makes correct programming especially important because applications normally share a common address space.
A bad pointer can potentially corrupt:
application data
another application
library structures
Exec structures
This is very different from modern operating systems with strong per-process memory protection.
Consequently, a programming error can crash the entire machine.
33. The Power and Danger of Shared Memory
The shared-address-space architecture has an advantage:
Communication can be extremely efficient.
A message can contain a pointer to data.
Task A
|
| pointer
v
Message
|
v
Task B
The data does not necessarily need to be copied between isolated process address spaces.
But the disadvantage is obvious.
Task B must use the pointer correctly.
There is relatively little protection if it does not.
The Amiga architecture therefore favors speed and flexibility over isolation.
34. OS-Friendly Graphics Programming
An application using Intuition and Graphics might follow a structure such as:
Application
|
+--> intuition.library
| |
| +--> windows
| +--> gadgets
| +--> events
|
+--> graphics.library
|
+--> drawing
+--> bitmaps
+--> text
The application requests services instead of manipulating the chipset directly.
This provides better cooperation with the operating system.
35. Direct Hardware Graphics
Games and demos sometimes take another route:
Program
|
+--> Custom registers
|
+--> Copper
|
+--> Blitter
|
+--> DMA
This provides much greater control over timing and hardware behavior.
But it also ties the program more closely to a specific Amiga chipset configuration.
36. Why Games Often Took Over the Machine
Many classic games were designed for systems with limited CPU and memory resources.
Maximum predictable performance mattered.
A game might therefore disable or avoid much of the normal operating-system environment and directly control:
display
interrupts
Copper
Blitter
audio
input
This created an environment where the game effectively owned the machine.
The advantage was control.
The cost was reduced compatibility with multitasking and future hardware configurations.
37. Why Applications Usually Should Not Do This
Consider a word processor.
It benefits from:
filesystem compatibility
printers
fonts
Workbench integration
multitasking
clipboard-style interaction
higher-level GUI functions
Taking exclusive control of the machine would throw away many valuable operating-system services.
Therefore productivity applications normally have strong reasons to remain OS-friendly.
38. System-Friendly Software Survives Better
Software written through documented operating-system APIs has a better chance of working across different Amiga configurations.
For example:
Amiga 500
Amiga 2000
Amiga 3000
Amiga 4000
may differ significantly in:
CPU
chipset
RAM
storage
graphics capabilities
Operating-system abstraction helps hide some of those differences.
Directly manipulating assumptions about one particular machine can reduce portability.
39. A Small Conceptual Amiga Program
We can now imagine the structure of a simple GUI application.
START
|
v
Open required libraries
|
v
Allocate resources
|
v
Open window
|
v
+----------------------+
| Wait for signals |
| |
| Receive messages |
| |
| Process events |
| |
| Perform program work |
+----------+-----------+
|
close requested
|
v
Close window
|
v
Free memory
|
v
Close libraries
|
v
END
This simple structure contains many of the concepts from our entire series.
40. What Happens When the User Clicks?
Suppose the user clicks a gadget.
A simplified path might look like:
Mouse hardware
|
v
Input system
|
v
Intuition
|
v
IntuiMessage
|
v
Application's message port
|
v
Signal
|
v
Application wakes
|
v
GetMsg()
|
v
Process gadget event
|
v
ReplyMsg()
A simple mouse click therefore connects hardware, the operating system and the application.
41. What Happens When the Program Loads a Picture?
Suppose the application then loads an image.
Conceptually:
Application
|
v
dos.library
|
v
Filesystem
|
v
Storage
|
v
File data
|
v
Memory
If the picture must eventually become a chipset-accessible bitmap:
Image data
|
CPU processing
|
v
Chip RAM bitmap
|
v
Graphics system / DMA
|
v
Display
This links DOS, memory management and graphics together.
42. What Happens When Nothing Happens?
Perhaps the most elegant case is when the user does nothing.
A correctly designed application can simply:
Wait()
Meanwhile:
Exec
|
+--> runs another program
|
+--> handles system tasks
|
+--> waits for hardware events
The application does not consume CPU time unnecessarily.
This is one reason AmigaOS multitasking could work effectively on comparatively slow processors.
43. The Programmer's Mental Model
A useful way to think about Amiga programming is:
Do not ask:
"How do I control every piece of hardware?"
Instead ask:
"Which AmigaOS component provides the service I need?"
Examples:
Need memory?
-> Exec
Need files?
-> dos.library
Need a window?
-> intuition.library
Need drawing?
-> graphics.library
Need low-level I/O?
-> Exec devices
Need communication?
-> Messages and ports
Need to wait efficiently?
-> Signals
This mental model makes AmigaOS programming much easier to understand.
44. The Complete Application Stack
We can now construct a more comprehensive model:
User
|
v
Application
|
+-----------------+-----------------+
| | |
DOS Intuition Exec
| | / | </span>
| Graphics Memory | Messages
| | |
Handler | Devices
| | |
+-----------------+----------------+
|
Hardware
|
+---------+---------+
| | |
CPU Memory Custom Chips
This is simplified, but it shows how an Amiga program fits into the larger system.
45. Why Amiga Programming Is Interesting
Amiga programming exposes the relationship between hardware and software more clearly than many modern systems.
A programmer can work at several levels.
At a high level:
OpenWindow()
Open()
Read()
Write()
At a lower level:
Tasks
Signals
Messages
Devices
Interrupts
And, when necessary:
DMA
Copper
Blitter
Custom registers
The programmer can decide how far down the machine's architecture to go.
That flexibility is one of the reasons the Amiga became such an attractive platform for programmers, game developers and demo coders.
Conclusion
An Amiga application operates inside a rich environment provided by AmigaOS.
At the foundation sits
Exec, managing: tasks
memory
signals
messages
devices
interrupts
Higher-level libraries provide more specialized functionality:
dos.library
intuition.library
graphics.library
Applications obtain resources, use them, wait for events and release them when they are finished.
A typical program therefore follows a pattern such as:
Initialize
|
v
Acquire resources
|
v
Perform work
|
v
Wait for events
|
v
Respond to messages
|
v
Release resources
|
v
Exit
Most importantly, concepts that may have seemed unrelated earlier in this series now begin to connect.
Memory allocation determines where an application's data lives.
Libraries provide operating-system services.
Processes and tasks allow applications to run simultaneously.
Signals let programs sleep efficiently.
Message ports allow components to communicate.
AmigaDOS provides access to files and processes.
Intuition turns hardware input into GUI events.
Devices and interrupts connect software with hardware.
The result is an operating system where many small mechanisms work together.
And that is the key to understanding Amiga programming:
You are not programming just the CPU. You are programming within an entire architecture of cooperating software and hardware components.
In Article 19, the next logical step is to leave the comfortable OS abstraction behind and look at programming the Amiga custom chips directly, especially the Copper, Blitter, DMA registers and Chip RAM.