docs(amiga): complete AmigaOS 3.1/3.2 developer reference — 172 files across 17 sections

Comprehensive technical documentation covering:
- Hardware: OCS/ECS/AGA custom chip registers, Copper & Blitter deep dives
- Boot sequence: cold boot through startup-sequence
- Binary format: HUNK executable spec, relocation, debug info
- Linking & ABI: .fd files, LVO tables, register calling conventions
- Exec kernel: tasks, interrupts, memory, signals, semaphores
- AmigaDOS: file I/O, FFS/OFS layout, CLI/Shell scripting
- Graphics: planar bitmaps, Copper programming, HAM/EHB modes
- Intuition: screens, windows, IDCMP, BOOPSI
- Devices: trackdisk, SCSI, serial, timer, audio, keyboard
- Libraries: utility, expansion, IFFParse, locale, ARexx
- Networking: bsdsocket API, SANA-II, TCP/IP stack comparison
- Toolchain: GCC, vasm/vlink, SAS/C, NDK, debugging
- Reverse engineering: IDA/Ghidra setup, compiler fingerprints, case studies
- CPU & MMU: 68040/060 emulation libs, PMMU, cache management
- Driver development: SANA-II, Picasso96/RTG, AHI audio

All files include breadcrumb navigation. No local paths or proprietary content.
This commit is contained in:
Ilia Sharin 2026-04-23 12:16:52 -04:00
parent f07a368bf1
commit 21751c0025
172 changed files with 19701 additions and 0 deletions

20
06_exec_os/README.md Normal file
View file

@ -0,0 +1,20 @@
[← Home](../README.md)
# exec.library — Kernel Overview
## Section Index
| File | Description |
|---|---|
| [exec_base.md](exec_base.md) | ExecBase structure — full field listing |
| [library_system.md](library_system.md) | Library node, OpenLibrary lifecycle |
| [library_vectors.md](library_vectors.md) | JMP table, LVO offsets, MakeFunctions |
| [tasks_processes.md](tasks_processes.md) | Task/Process structs, scheduling |
| [interrupts.md](interrupts.md) | Interrupt levels, INTENA, AddIntServer |
| [memory_management.md](memory_management.md) | AllocMem, FreeMem, MemHeader |
| [message_ports.md](message_ports.md) | MsgPort, PutMsg, GetMsg, WaitPort |
| [signals.md](signals.md) | AllocSignal, SetSignal, Wait |
| [semaphores.md](semaphores.md) | SignalSemaphore, ObtainSemaphore |
| [io_requests.md](io_requests.md) | IORequest, DoIO, SendIO, AbortIO |
| [lists_nodes.md](lists_nodes.md) | MinList/List/Node traversal |
| [resident_modules.md](resident_modules.md) | RomTag, RTF_AUTOINIT, FindResident |

View file

@ -0,0 +1,106 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Exception and Trap Handling — M68k on AmigaOS
## Overview
The M68k CPU provides a **256-entry exception vector table** starting at address `$000000`. AmigaOS manages these vectors through `exec.library`, allowing both the OS and user code to install handlers for hardware interrupts, bus errors, and software traps.
---
## Exception Vector Table
| Vector | Address | Exception | AmigaOS Handler |
|---|---|---|---|
| 0 | `$000` | Reset: Initial SSP | (boot value) |
| 1 | `$004` | Reset: Initial PC | ROM entry point |
| 2 | `$008` | Bus Error | Guru Meditation / Enforcer |
| 3 | `$00C` | Address Error | Guru Meditation |
| 4 | `$010` | Illegal Instruction | Guru Meditation |
| 5 | `$014` | Zero Divide | Alert |
| 6 | `$018` | CHK Instruction | Alert |
| 7 | `$01C` | TRAPV | Alert |
| 8 | `$020` | Privilege Violation | Alert |
| 9 | `$024` | Trace | Debug (wack/BareFoot) |
| 10 | `$028` | Line-A Emulator | Unused (soft trap space) |
| 11 | `$02C` | Line-F Emulator | 68040/060.library FPU emulation |
| 1214 | `$030$038` | Reserved | — |
| 15 | `$03C` | Uninitialised Interrupt | Alert |
| 24 | `$060` | Spurious Interrupt | — |
| 2531 | `$064$07C` | Auto-vector interrupts 17 | Exec interrupt dispatcher |
| 3247 | `$080$0BC` | TRAP #0#15 | User-installable traps |
| 4863 | `$0C0$0FC` | Reserved (FPU) | 68881/68882 exception handlers |
| 64255 | `$100$3FC` | User-defined vectors | User |
---
## TRAP Instructions — Software Interrupts
`TRAP #n` (n = 015) generates a software exception. AmigaOS uses:
| TRAP | User |
|---|---|
| `TRAP #0` | exec.library `Supervisor()` — switch to supervisor mode |
| `TRAP #1#14` | Available for user programs |
| `TRAP #15` | Remote debugger breakpoint (BareFoot/wack) |
---
## Installing an Exception Handler
```c
/* Using exec.library SetExcept/SetTrapHandler (not recommended): */
/* Direct vector patching in supervisor mode: */
APTR OldVector;
__asm void MyTrapHandler(void)
{
/* Save registers, examine stack frame */
/* ... handle trap ... */
rte
}
/* Install: */
Supervisor(function() {
OldVector = *(APTR *)0x0B0; /* TRAP #12 vector */
*(APTR *)0x0B0 = MyTrapHandler;
});
```
---
## Guru Meditation
When a fatal exception occurs (Bus Error, Address Error), exec.library displays:
```
Software Failure. Press left mouse button to continue.
Guru Meditation #XXYYYYYY.ZZZZZZZZ
```
| Field | Meaning |
|---|---|
| `XX` | Alert type: $00=recovery possible, $80=dead-end |
| `YYYYYY` | Error code (subsystem + specific error) |
| `ZZZZZZZZ` | Address where error occurred |
### Common Guru Codes
| Code | Meaning |
|---|---|
| `$80000003` | Address Error (dead-end) |
| `$80000004` | Illegal instruction (dead-end) |
| `$81000005` | exec: No memory |
| `$82000005` | graphics: No memory |
| `$87000007` | trackdisk: No disk |
| `$04000001` | exec: Recoverable alert |
| `$00000001` | No memory (recoverable) |
---
## References
- Motorola: *MC68000 Family Reference Manual* — exception processing
- NDK39: `exec/alerts.h` — alert code definitions
- RKRM: Exception chapter

110
06_exec_os/exec_base.md Normal file
View file

@ -0,0 +1,110 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# ExecBase — Full Structure Reference
## Overview
`ExecBase` is the root structure of AmigaOS, located at absolute address `$4`. It is a `struct Library` extended with all exec kernel state: memory lists, task queues, interrupt vectors, library lists, and hardware abstraction fields.
---
## Locating ExecBase
```c
struct ExecBase *SysBase = *((struct ExecBase **)4);
```
In assembly:
```asm
MOVEA.L 4.W, A6 ; A6 = SysBase
```
---
## Key Field Groups
### Library Header (offset 0)
```c
struct Library LibNode; /* +0 — ln_Name = "exec.library" */
/* +20 — lib_Version (40 = OS3.1, 44 = OS3.2) */
```
### Interrupts (offset +84)
```c
UWORD AttnFlags; /* +0x128 — processor capability flags */
UWORD AttnResched; /* +0x12A — reschedule attention flag */
```
### Task Scheduling
| Offset | Field | Description |
|---|---|---|
| +0x128 | `TaskReady` | `struct List` — tasks ready to run |
| +0x132 | `TaskWait` | `struct List` — tasks waiting on signals |
| +0x126 | `IDNestCnt` | Interrupt disable nesting count |
| +0x127 | `TDNestCnt` | Task disable nesting count |
### Memory
| Offset | Field | Description |
|---|---|---|
| +0x130 | `MemList` | `struct List` of `MemHeader` regions |
| +0x134 | `ResourceList` | Resources list |
### Library and Device Lists
| Offset | Field | Description |
|---|---|---|
| +0x17A | `LibList` | `struct List` — loaded libraries |
| +0x182 | `DeviceList` | `struct List` — loaded devices |
| +0x18A | `IntrList` | Interrupt server list |
| +0x192 | `PortList` | Public message ports |
| +0x19A | `TaskList` | All tasks (not just ready/waiting) |
### Vectors and ROM
| Offset | Field | Description |
|---|---|---|
| +0x26 | `SoftVer` | Kickstart software revision |
| +0x10 | `ChkBase` | Checksum of library header |
| +0x222 | `PowerSupplyFrequency` | 50 or 60 Hz |
| +0x21E | `ChipRevBits0` | Chip revision detection flags |
### Chip Revision Flags (`ChipRevBits0`)
| Bit | Constant | Chip |
|---|---|---|
| 4 | `ATNF_68010` | 68010 or better |
| 5 | `ATNF_68020` | 68020 or better |
| 6 | `ATNF_68030` | 68030 |
| 7 | `ATNF_68040` | 68040 |
| 10 | `ATNF_FPU40` | 68040 internal FPU |
---
## Detecting CPU and Chipset
```c
/* CPU: */
if (SysBase->AttnFlags & AFF_68020) { /* 020+ */ }
if (SysBase->AttnFlags & AFF_68040) { /* 040 */ }
/* Chipset (via graphics.library): */
struct GfxBase *gfx = (struct GfxBase *)OpenLibrary("graphics.library", 36);
if (gfx->ChipRevBits0 & GFXB_AA_ALICE) { /* AGA Alice chip */ }
```
---
## ExecBase in IDA Pro
After loading Kickstart ROM:
1. Create a segment at `$4` containing a pointer
2. Follow the pointer to the ExecBase (in ROM)
3. Apply `struct ExecBase` type (from NDK39 headers parsed via `File → Parse C header`)
4. All `N(A6)` offsets auto-annotate as field names
---
## References
- NDK39: `exec/execbase.h` — authoritative field definitions
- ADCD 2.1: exec.library autodoc
- *Amiga ROM Kernel Reference Manual: Exec* — ExecBase chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0072.html

126
06_exec_os/interrupts.md Normal file
View file

@ -0,0 +1,126 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Interrupts — Levels, INTENA, AddIntServer, CIA Interrupts
## Overview
AmigaOS supports 7 hardware interrupt levels (68k IPL0IPL6) plus a software interrupt mechanism. Custom chip interrupts are filtered through the `INTENA` / `INTREQ` registers; CIA-generated interrupts arrive on level 2 (CIA-A) and level 6 (CIA-B).
---
## Interrupt Priority Levels
| IPL | Source | AmigaOS Use |
|---|---|---|
| 1 | TBE, DSKBLK, SOFTINT | Software interrupts (`SoftInt`) |
| 2 | PORTS (CIA-A) | Keyboard, timer, parallel, floppy motor |
| 3 | COPER, VERTB, BLIT | Copper, vertical blank, blitter |
| 4 | AUD0AUD3 | Audio DMA completion |
| 5 | RBF, DSKSYNC | Serial receive, disk sync |
| 6 | EXTER (CIA-B) | External interrupts, CIA-B timers, TOD |
| 7 | NMI | Non-maskable (unused on stock Amiga) |
---
## Custom Chip Interrupt Registers
| Register | Address | Description |
|---|---|---|
| `INTENAR` | `$DFF01C` | Interrupt enable status (read) |
| `INTENA` | `$DFF09A` | Interrupt enable set/clear (write) |
| `INTREQR` | `$DFF01E` | Interrupt request status (read) |
| `INTREQ` | `$DFF09C` | Interrupt request clear/set (write) |
### INTENA / INTREQ Bit Map
| Bit | Constant | Source |
|---|---|---|
| 0 | `INTF_TBE` | Serial transmit buffer empty |
| 1 | `INTF_DSKBLK` | Disk DMA block complete |
| 2 | `INTF_SOFTINT` | Software interrupt |
| 3 | `INTF_PORTS` | CIA-A interrupt (level 2) |
| 4 | `INTF_COPER` | Copper interrupt |
| 5 | `INTF_VERTB` | Vertical blank |
| 6 | `INTF_BLIT` | Blitter interrupt |
| 7 | `INTF_AUD0` | Audio channel 0 |
| 8 | `INTF_AUD1` | Audio channel 1 |
| 9 | `INTF_AUD2` | Audio channel 2 |
| 10 | `INTF_AUD3` | Audio channel 3 |
| 11 | `INTF_RBF` | Serial receive buffer full |
| 12 | `INTF_DSKSYNC` | Disk sync word match |
| 13 | `INTF_EXTER` | CIA-B / external interrupt |
| 14 | `INTF_INTEN` | Master interrupt enable bit |
To enable vertical blank: write `$C005` to `INTENA` (bit 14 set = enable, bit 5 = VERTB).
To clear vertical blank request: write `$0020` to `INTREQ`.
---
## Adding an Interrupt Server
```c
struct Interrupt myVBL = {
{ NULL, NULL, NT_INTERRUPT, 0, "My VBL" },
NULL, /* is_Data (passed to handler) */
myVBLHandler /* is_Code */
};
AddIntServer(INTB_VERTB, &myVBL); /* INTB_VERTB = 5 */
/* ... run ... */
RemIntServer(INTB_VERTB, &myVBL); /* always remove before exit */
```
### Interrupt Handler Rules
- Handler is called at interrupt level — **no OS calls that Wait()**
- D0D1, A0A1 may be trashed; all others preserved
- Return D0 = 0 if you did not handle it (pass to next server)
- Return D0 ≠ 0 if you handled it (stop server chain)
Handler signature:
```asm
myVBLHandler:
; A1 = is_Data pointer (from struct Interrupt)
; do fast work only
MOVEQ #1, D0 ; handled — stop chain
RTS
```
---
## CIA Interrupts
CIA-A (at `$BFEC01`) generates level 2 interrupts. CIA-B (at `$BFD000`) generates level 6. Each CIA has an ICR (Interrupt Control Register) with 5 sources:
| Bit | Source |
|---|---|
| 0 | Timer A underflow |
| 1 | Timer B underflow |
| 2 | TOD alarm |
| 3 | Serial register full |
| 4 | Flag pin / FLG |
CIA interrupts are serviced via AddIntServer on `INTB_PORTS` (level 2, CIA-A) or `INTB_EXTER` (level 6, CIA-B).
---
## Disable / Enable vs Forbid / Permit
| Function | Effect | Scope |
|---|---|---|
| `Forbid()` | Disables task switching | Task-level (interrupts still run) |
| `Permit()` | Re-enables task switching | Reverses `Forbid()` |
| `Disable()` | Masks all hardware interrupts | Hardware + task switching |
| `Enable()` | Unmasks hardware interrupts | Reverses `Disable()` |
> [!CAUTION]
> `Disable()` / `Enable()` can be held for only a few microseconds — never do I/O or complex operations inside a `Disable()` section.
---
## References
- NDK39: `hardware/intbits.h`, `hardware/cia.h`
- ADCD 2.1: `AddIntServer`, `RemIntServer`, `SetIntVector`, `Disable`, `Forbid`
- `01_hardware/common/cia_chips.md` — CIA timer and ICR details
- `01_hardware/ocs_a500/custom_registers.md` — INTENA/INTREQ register listing

153
06_exec_os/io_requests.md Normal file
View file

@ -0,0 +1,153 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# IO Requests — IORequest, DoIO, SendIO, CheckIO, AbortIO
## Overview
AmigaOS device I/O uses a **message-based** asynchronous protocol. Every device operation is described by an `IORequest` structure sent to a device's command port. The device processes it (synchronously or in the background) and replies when done.
---
## Structures
```c
/* exec/io.h — NDK39 */
struct IORequest {
struct Message io_Message; /* embedded Message (has MsgPort reply port) */
struct Device *io_Device; /* filled by OpenDevice */
struct Unit *io_Unit; /* filled by OpenDevice */
UWORD io_Command; /* CMD_READ, CMD_WRITE, TD_FORMAT, ... */
UBYTE io_Flags; /* IOF_QUICK = attempt synchronous fast path */
BYTE io_Error; /* result: 0 = success, negative = error code */
};
struct IOStdReq { /* extended version with data fields */
struct IORequest io_Request;
ULONG io_Actual; /* actual bytes transferred */
ULONG io_Length; /* requested byte count */
APTR io_Data; /* data buffer pointer */
ULONG io_Offset; /* byte offset (for random-access devices) */
};
```
---
## Standard Command Codes
```c
/* exec/io.h */
#define CMD_INVALID 0 /* not a valid command */
#define CMD_RESET 1 /* reset the device/unit to initial state */
#define CMD_READ 2 /* read io_Length bytes into io_Data from io_Offset */
#define CMD_WRITE 3 /* write io_Length bytes from io_Data at io_Offset */
#define CMD_UPDATE 4 /* flush write cache to media */
#define CMD_CLEAR 5 /* discard device read buffers */
#define CMD_STOP 6 /* suspend device operation */
#define CMD_START 7 /* resume device operation */
#define CMD_FLUSH 8 /* abort all pending requests */
#define CMD_NONSTD 9 /* first device-specific command number */
```
Device-specific commands start at `CMD_NONSTD` (9). Example: trackdisk uses `TD_FORMAT` (10), `TD_MOTOR` (11), `TD_SEEK` (12).
---
## Error Codes (`io_Error`)
```c
/* exec/errors.h — NDK39 */
#define IOERR_OPENFAIL -1 /* device/unit could not be opened */
#define IOERR_ABORTED -2 /* request was aborted via AbortIO */
#define IOERR_NOCMD -3 /* unknown command */
#define IOERR_BADLENGTH -4 /* io_Length invalid for this command */
#define IOERR_BADADDRESS -5 /* io_Data not aligned or accessible */
#define IOERR_UNITBUSY -6 /* unit in use, cannot complete */
#define IOERR_SELFTEST -7 /* hardware self-test failed */
```
---
## Opening a Device
```c
struct IOStdReq *ior = CreateStdIO(reply_port); /* alloc + fill reply port */
if (OpenDevice("trackdisk.device", unit, (struct IORequest *)ior, 0) != 0) {
/* open failed — ior->io_Error set */
}
```
Or manually:
```c
struct IOStdReq *ior = AllocMem(sizeof(struct IOStdReq), MEMF_PUBLIC|MEMF_CLEAR);
ior->io_Message.mn_ReplyPort = my_reply_port;
ior->io_Message.mn_Length = sizeof(struct IOStdReq);
OpenDevice("audio.device", 0, (struct IORequest *)ior, 0);
```
---
## Synchronous I/O: `DoIO`
Blocks the calling task until the device completes the request:
```c
ior->io_Command = CMD_READ;
ior->io_Data = buffer;
ior->io_Length = 512;
ior->io_Offset = 0;
LONG err = DoIO((struct IORequest *)ior);
/* io_Actual = bytes actually read; io_Error = error code */
```
---
## Asynchronous I/O: `SendIO` + `WaitIO`
```c
/* Queue the request — returns immediately: */
SendIO((struct IORequest *)ior);
/* Do other work while device operates... */
/* Block until this specific request completes: */
WaitIO((struct IORequest *)ior);
err = ior->io_Error;
```
### Poll without blocking: `CheckIO`
```c
/* Returns non-NULL if request is done (removed from device queue): */
if (CheckIO((struct IORequest *)ior)) {
WaitIO((struct IORequest *)ior); /* must still call WaitIO to dequeue reply */
}
```
---
## Aborting a Request: `AbortIO`
```c
AbortIO((struct IORequest *)ior); /* ask device to cancel */
WaitIO((struct IORequest *)ior); /* wait for confirmation */
/* io_Error will be IOERR_ABORTED (-2) */
```
---
## Closing a Device
```c
CloseDevice((struct IORequest *)ior);
DeleteStdIO(ior); /* or FreeMem */
```
---
## References
- NDK39: `exec/io.h`, `exec/errors.h`
- ADCD 2.1: `OpenDevice`, `CloseDevice`, `DoIO`, `SendIO`, `WaitIO`, `CheckIO`, `AbortIO`
- `10_devices/` — per-device command codes and structures
- *Amiga ROM Kernel Reference Manual: Exec* — I/O requests chapter

View file

@ -0,0 +1,97 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Library System — OpenLibrary Lifecycle
## Overview
The AmigaOS library system provides **versioned, shared code** via a standardised interface. Libraries are identified by name, opened with a version check, and reference-counted for safe unloading.
---
## Library Node
Every library is an `NT_LIBRARY` node on `SysBase->LibList`:
```c
struct Library {
struct Node lib_Node; /* ln_Name = "dos.library" */
UBYTE lib_Flags; /* LIBF_SUMUSED | LIBF_DELEXP */
UBYTE lib_Pad;
UWORD lib_NegSize; /* size of JMP table in bytes */
UWORD lib_PosSize; /* size of library base struct */
UWORD lib_Version; /* major version */
UWORD lib_Revision; /* minor revision */
APTR lib_IdString; /* "dos.library 40.1 (16.7.93)" */
ULONG lib_Sum; /* JMP table checksum */
UWORD lib_OpenCnt; /* reference count */
};
```
---
## OpenLibrary / CloseLibrary
```c
/* Open — get a reference: */
struct DosLibrary *DOSBase =
(struct DosLibrary *)OpenLibrary("dos.library", 40);
/* Use the library ... */
/* Close — release reference: */
CloseLibrary((struct Library *)DOSBase);
```
Internally:
1. `exec` scans `LibList` for `ln_Name == "dos.library"`
2. If not found, searches resident list and `LIBS:` path
3. If found on disk: `LoadSeg` + call `InitLib`
4. Check `lib_Version >= requested_version`
5. Call library's `Open()` vector → `lib_OpenCnt++`
6. Return library base
---
## Library Flags
| Flag | Value | Meaning |
|---|---|---|
| `LIBF_SUMUSED` | 0x01 | Checksum is maintained |
| `LIBF_CHANGED` | 0x02 | Checksum needs recalculation |
| `LIBF_DELEXP` | 0x04 | Expunge deferred (opened while expunge pending) |
---
## Version Numbering Convention
`lib_Version.lib_Revision`:
- `40.1` = OS 3.1 release
- `40.x` = OS 3.1 (various revisions)
- `44.x` = OS 3.2
Increment rules:
- `lib_Revision` — minor bugfix, compatible
- `lib_Version` — API change or major update (requestors check this)
---
## Finding a Library Without Opening
```c
/* Read-only peek — no open count increment */
Forbid();
struct Library *lib = FindName(&SysBase->LibList, "graphics.library");
Permit();
if (lib) printf("Found v%d\n", lib->lib_Version);
```
> [!CAUTION]
> Using `FindName` without `Forbid()` is a race condition — the library could be expunged between finding it and using it.
---
## References
- NDK39: `exec/libraries.h`
- ADCD 2.1: `OpenLibrary`, `CloseLibrary`, `FindName`
- `04_linking_and_libraries/shared_libraries_runtime.md` — expunge lifecycle

View file

@ -0,0 +1,125 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Library Vectors — JMP Table, LVOs, MakeFunctions, SetFunction
## Overview
Every AmigaOS library exposes its functions via a **JMP table** at negative offsets from the library base. This document covers the structure of the table, how LVOs are assigned, and how to create or patch one programmatically.
---
## JMP Table Structure
```
Address Content Description
lib_base - N×6: 4EF9 XXXXXXXX JMP <absolute address> ← function N
...
lib_base - 24: 4EF9 XXXXXXXX JMP Reserved
lib_base - 18: 4EF9 XXXXXXXX JMP Expunge
lib_base - 12: 4EF9 XXXXXXXX JMP Close
lib_base - 6: 4EF9 XXXXXXXX JMP Open
lib_base + 0: struct Library ← pointer returned by OpenLibrary
```
Each slot is exactly **6 bytes**: opcode `$4EF9` (JMP abs.l) + 4-byte target address.
---
## LVO Formula
```
LVO = 6 × slot_index
where slot_index counts from 1 (Open) upward:
Open = slot 1 → LVO = 6
Close = slot 2 → LVO = 12
Expunge = slot 3 → LVO = 18
Reserved = slot 4 → LVO = 24
First user fn = slot 5 → LVO = 30
Second user fn = slot 6 → LVO = 36
...
```
The `.fd` file `##bias` value is the positive LVO: `bias 30` → LVO `30`.
---
## MakeFunctions — Building a JMP Table
`exec.library MakeFunctions()` fills in the JMP table from a function pointer array:
```c
ULONG MakeFunctions(APTR targetLib, APTR funcArray, APTR funcDispBase);
```
Typical usage in library `InitLib`:
```asm
; funcArray: table of function pointers, terminated by -1
_LibFuncTable:
dc.l _LibOpen
dc.l _LibClose
dc.l _LibExpunge
dc.l _LibNull ; Reserved — returns NULL
dc.l _MyFunc1
dc.l _MyFunc2
dc.l -1 ; terminator
LibInit:
LEA _LibFuncTable(PC), A0
MOVEA.L A6, A1 ; library base (passed in A6 by exec)
MOVEQ #0, D0 ; funcDispBase = 0 (absolute addresses)
MOVEA.L 4.W, A6
JSR (-420,A6) ; MakeFunctions(A1, A0, D0)
```
`MakeFunctions` writes `JMP <ptr>` for each entry, filling the table downward from `lib_base 6`.
---
## SetFunction — Patching a Single Slot
```c
APTR SetFunction(struct Library *library, LONG funcOffset, APTR newFunction);
```
- `funcOffset` is the negative LVO (e.g., `30` for the first user function)
- Returns the old function pointer
```c
/* Hook dos.library Write() */
old_write = SetFunction((struct Library *)DOSBase, -48, my_write_hook);
```
See `05_reversing/dynamic/setfunction_patching.md` for trampoline patterns.
---
## Checksum Maintenance
After `MakeFunctions` or `SetFunction`, exec updates `lib_Sum` via `SumLibrary`:
```c
SumLibrary((struct Library *)myLib);
```
If `LIBF_SUMUSED` is set, exec verifies the checksum at `CloseLibrary` time. Patching the JMP table without calling `SumLibrary` will trigger a checksum failure (alert box or guru).
---
## Viewing Vectors in IDA Pro
1. Navigate to `lib_base 6` (first standard vector)
2. Each 6-byte group: opcode `4EF9` + 4-byte address
3. Press `C` to disassemble if not auto-detected
4. The 4-byte value is the actual function address — press `G` (Go to) to navigate
5. Name each function with the `.fd` file as reference
---
## References
- NDK39: `exec/execbase.h`, `exec/libraries.h`
- ADCD 2.1: `MakeFunctions`, `MakeLibrary`, `SetFunction`, `SumLibrary`
- `05_reversing/static/library_jmp_table.md` — reconstruction workflow
- `04_linking_and_libraries/lvo_table.md` — complete LVO reference tables

158
06_exec_os/lists_nodes.md Normal file
View file

@ -0,0 +1,158 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Lists and Nodes — MinList, List, Node, MinNode
## Overview
AmigaOS uses **intrusive doubly-linked lists** throughout exec: the task list, library list, device list, memory list, port list, and more all use the same `List`/`Node` structures defined in `exec/lists.h`.
---
## Structures
```c
/* exec/nodes.h — NDK39 */
struct Node {
struct Node *ln_Succ; /* pointer to next node (NULL at tail sentinel) */
struct Node *ln_Pred; /* pointer to prev node (NULL at head sentinel) */
UBYTE ln_Type; /* node type — NT_TASK, NT_LIBRARY, NT_MEMORY... */
BYTE ln_Pri; /* scheduling priority (used by Enqueue) */
char *ln_Name; /* optional name string (NULL = anonymous) */
};
struct MinNode {
struct MinNode *mln_Succ;
struct MinNode *mln_Pred;
/* no type, priority, or name — minimal overhead */
};
```
```c
/* exec/lists.h — NDK39 */
struct List {
struct Node *lh_Head; /* first node (or tail sentinel if empty) */
struct Node *lh_Tail; /* always NULL — marks end of list */
struct Node *lh_TailPred; /* last node (or head sentinel if empty) */
UBYTE lh_Type; /* list type */
UBYTE lh_pad;
};
struct MinList {
struct MinNode *mlh_Head;
struct MinNode *mlh_Tail; /* always NULL */
struct MinNode *mlh_TailPred;
};
```
### Node Type Constants
```c
/* exec/nodes.h */
#define NT_UNKNOWN 0
#define NT_TASK 1 /* exec Task */
#define NT_INTERRUPT 2 /* Interrupt server */
#define NT_DEVICE 3 /* Device */
#define NT_MSGPORT 4 /* MsgPort */
#define NT_MESSAGE 5 /* Message */
#define NT_FREEMSG 6
#define NT_REPLYMSG 7
#define NT_RESOURCE 8
#define NT_LIBRARY 9 /* Library */
#define NT_MEMORY 10 /* MemHeader */
#define NT_SOFTINT 11
#define NT_FONT 12
#define NT_PROCESS 13 /* dos.library Process */
#define NT_SEMAPHORE 14
#define NT_SIGNALSEM 15 /* SignalSemaphore */
#define NT_BOOTNODE 16
#define NT_KICKMEM 17
#define NT_GRAPHICS 18
#define NT_DEATHMESSAGE 19
```
---
## Initialising a List
```c
/* Stack-allocated list: */
struct List myList;
NewList(&myList); /* sets up sentinel pointers — mandatory */
/* Or use NEWLIST() macro: */
NEWLIST(&myList);
```
---
## Adding and Removing Nodes
```c
/* Add at head (highest LRU position): */
AddHead(&myList, &myNode); /* LVO -240 */
/* Add at tail: */
AddTail(&myList, &myNode); /* LVO -246 */
/* Remove from wherever it is (no list pointer needed): */
Remove(&myNode); /* LVO -252 */
/* Priority-ordered insert (by ln_Pri, high first): */
Enqueue(&myList, &myNode); /* LVO -270 */
```
---
## Walking a List
```c
struct Node *node, *next;
for (node = myList.lh_Head; node->ln_Succ != NULL; node = node->ln_Succ) {
/* process node */
}
```
Safe removal while iterating (save next before removing):
```c
for (node = myList.lh_Head; (next = node->ln_Succ) != NULL; node = next) {
if (should_remove(node)) Remove(node);
}
```
---
## Finding a Node by Name
```c
struct Node *found = FindName(&SysBase->LibList, "dos.library");
/* Returns NULL if not found */
/* Always call under Forbid() if the list may change */
```
---
## How the Sentinel Works
The AmigaOS list design uses a **3-pointer layout** that avoids special-casing empty lists and end-of-list checks:
```
lh_Head ──→ [ Node A ]──→ [ Node B ]──→ [ tail sentinel ]
lh_Tail = NULL (always)
lh_TailPred ──────────────────────────→ [ Node B ]
Empty list:
lh_Head ──→ [ tail sentinel ]
lh_TailPred ──→ [ head sentinel ]
```
Walking stops when `ln_Succ == NULL` — that is the tail sentinel's `lh_Tail` field.
---
## References
- NDK39: `exec/nodes.h`, `exec/lists.h`
- ADCD 2.1: `AddHead`, `AddTail`, `Remove`, `Enqueue`, `FindName`, `NewList`
- *Amiga ROM Kernel Reference Manual: Exec* — lists chapter

View file

@ -0,0 +1,151 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Memory Management — AllocMem, FreeMem, MemHeader
## Overview
AmigaOS memory management is built directly into `exec.library`. There is no `malloc`/`free` in the OS itself — applications call `AllocMem` and `FreeMem` which operate on a linked list of `MemHeader` regions representing physical RAM.
---
## MemHeader — Memory Region Descriptor
```c
/* exec/memory.h — NDK39 */
struct MemHeader {
struct Node mh_Node; /* ln_Type=NT_MEMORY, ln_Pri=region priority */
/* ln_Name = e.g. "chip memory" */
UWORD mh_Attributes; /* MEMF_* flags describing this region */
struct MemChunk *mh_First; /* pointer to first free chunk in this region */
APTR mh_Lower; /* lowest byte address of region */
APTR mh_Upper; /* highest byte address + 1 */
ULONG mh_Free; /* total free bytes currently */
};
struct MemChunk {
struct MemChunk *mc_Next; /* next free chunk (NULL = end of list) */
ULONG mc_Bytes; /* size of this free chunk in bytes */
};
```
The OS maintains a doubly-linked list of `MemHeader` regions at `SysBase→MemList`. On a stock A1200:
- `"chip memory"` covering `$000000$1FFFFF` (2 MB Chip RAM)
- `"fast memory"` covering `$200000$9FFFFF` (up to 8 MB Fast RAM if fitted)
---
## MEMF_ Flag Constants
```c
/* exec/memory.h — NDK39 */
#define MEMF_ANY 0L /* no placement preference */
#define MEMF_PUBLIC (1L<<0) /* accessible by all hardware/software */
#define MEMF_CHIP (1L<<1) /* must be in Chip RAM (DMA-reachable) */
#define MEMF_FAST (1L<<2) /* prefer Fast RAM (CPU-only, faster) */
#define MEMF_CLEAR (1L<<16) /* zero-fill the allocation */
#define MEMF_LARGEST (1L<<17) /* return single largest free block */
#define MEMF_REVERSE (1L<<18) /* allocate from top of list */
#define MEMF_TOTAL (1L<<19) /* AvailMem: report total, not largest free */
```
**Chip RAM** is required for anything the custom chips DMA from — bitmaps, audio samples, Copper lists, blitter sources/destinations, sprite data. The custom chip DMA controllers cannot reach Fast RAM.
**Fast RAM** has no DMA contention with the custom chips, making it faster for pure CPU use.
---
## AllocMem / FreeMem
```c
/* exec/execbase.h — LVO -198 */
APTR AllocMem(ULONG byteSize, ULONG requirements);
/* Returns: pointer to allocated block, or NULL on failure */
/* LVO -210 */
void FreeMem(APTR memoryBlock, ULONG byteSize);
```
### Usage
```c
/* Allocate 512 bytes of Chip RAM, zero-filled: */
UBYTE *buf = AllocMem(512, MEMF_CHIP | MEMF_CLEAR);
if (!buf) { /* handle out-of-memory */ }
/* Free it: */
FreeMem(buf, 512);
```
> [!IMPORTANT]
> `FreeMem` requires the **exact same size** as `AllocMem`. The OS does not store the size internally — you must track it yourself.
---
## AllocVec / FreeVec (OS 2.0+)
```c
/* LVO -684 (exec.library 36+) */
APTR AllocVec(ULONG byteSize, ULONG requirements);
void FreeVec(APTR memoryBlock); /* LVO -690 */
```
`AllocVec` stores the size in the 4 bytes immediately before the returned pointer, allowing `FreeVec` to work without a size argument. Prefer this in new code.
---
## AvailMem — Query Free Memory
```c
/* LVO -216 */
ULONG AvailMem(ULONG requirements);
```
```c
ULONG chip_free = AvailMem(MEMF_CHIP);
ULONG fast_free = AvailMem(MEMF_FAST);
ULONG total_chip = AvailMem(MEMF_CHIP | MEMF_TOTAL);
```
---
## Pool Allocator (OS 3.0+)
For many small allocations, use the pool API which reduces fragmentation:
```c
/* LVO -696 */
APTR pool = CreatePool(MEMF_ANY, 4096, 1024);
/* puddleSize=4096, threshSize=1024 */
APTR p1 = AllocPooled(pool, 32); /* LVO -702 */
FreePooled(pool, p1, 32); /* LVO -708 */
DeletePool(pool); /* LVO -714 */
```
---
## Memory Map (A1200 Example)
| Range | Type | Used for |
|---|---|---|
| `$000000$000400` | Chip | 68k exception vectors |
| `$000400$000BFF` | Chip | exec library, SysBase |
| `$000C00$1FFFFF` | Chip | Application allocations, DMA buffers |
| `$200000$9FFFFF` | Fast | Fast RAM expansion (if present) |
| `$A00000$BFFFFF` | Slow/Ranger | A500 slow RAM (not on A1200) |
| `$BFD000$BFDFFF` | CIA | CIA-B registers |
| `$BFE001$BFEFFF` | CIA | CIA-A registers |
| `$C00000$D7FFFF` | Slow | A500 slow RAM expansion |
| `$D80000$DFFFFF` | Custom | Custom chip registers ($DFF000) |
| `$E00000$E7FFFF` | ROM mirror | (A500) |
| `$F80000$FFFFFF` | ROM | Kickstart 3.1 (512 KB) |
---
## References
- NDK39: `exec/memory.h`, `exec/execbase.h`
- ADCD 2.1: `AllocMem`, `FreeMem`, `AllocVec`, `FreeVec`, `CreatePool`
- `01_hardware/common/address_space.md` — full address map
- *Amiga ROM Kernel Reference Manual: Exec* — memory management chapter

142
06_exec_os/message_ports.md Normal file
View file

@ -0,0 +1,142 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Message Ports — MsgPort, Message, PutMsg, GetMsg, WaitPort
## Overview
AmigaOS inter-task communication uses a **message passing** system. Tasks send `Message` structures to `MsgPort` queues. The receiving task either polls (`GetMsg`) or blocks (`WaitPort`) for incoming messages. No shared memory is touched without the message handshake.
---
## Core Structures
```c
/* exec/ports.h — NDK39 */
struct MsgPort {
struct Node mp_Node; /* ln_Name = port name (for public ports) */
UBYTE mp_Flags; /* PA_SIGNAL, PA_SOFTINT, PA_IGNORE */
UBYTE mp_SigBit; /* signal bit used for PA_SIGNAL ports */
APTR mp_SigTask; /* task to signal on message arrival */
struct List mp_MsgList; /* queue of pending messages */
};
struct Message {
struct Node mn_Node; /* ln_Type = NT_MESSAGE */
struct MsgPort *mn_ReplyPort; /* port to send reply to (or NULL) */
UWORD mn_Length; /* total size of message including header */
};
```
`mp_Flags` values:
| Value | Constant | Meaning |
|---|---|---|
| 0 | `PA_SIGNAL` | Signal `mp_SigTask` when message arrives |
| 1 | `PA_SOFTINT` | Trigger software interrupt |
| 2 | `PA_IGNORE` | Do not wake the task (polling only) |
---
## Creating a Message Port
```c
struct MsgPort *port = CreateMsgPort(); /* exec.library LVO -732 (OS 2.0+) */
/* or manually for OS 1.x compatibility: */
struct MsgPort *port = AllocMem(sizeof(struct MsgPort), MEMF_PUBLIC|MEMF_CLEAR);
port->mp_Node.ln_Type = NT_MSGPORT;
port->mp_Flags = PA_SIGNAL;
port->mp_SigBit = AllocSignal(-1); /* any free signal bit */
port->mp_SigTask = FindTask(NULL); /* signal current task */
NewList(&port->mp_MsgList);
```
---
## Sending a Message
```c
/* PutMsg: add message to queue, signal receiver */
PutMsg(target_port, (struct Message *)my_msg);
/* Non-blocking — returns immediately */
```
PutMsg can be called from interrupt context.
---
## Receiving Messages
```c
/* Block until at least one message arrives: */
WaitPort(my_port);
/* Then drain the queue: */
struct MyMsg *msg;
while ((msg = (struct MyMsg *)GetMsg(my_port)) != NULL) {
/* process msg */
ReplyMsg((struct Message *)msg); /* send reply if mn_ReplyPort != NULL */
}
```
### GetMsg (non-blocking poll)
```c
struct Message *msg = GetMsg(my_port);
/* Returns NULL if queue is empty */
```
---
## Public Named Ports
```c
/* Register a port so others can find it by name: */
port->mp_Node.ln_Name = "myapp.port";
Forbid();
AddPort(port);
Permit();
/* From another task: */
Forbid();
struct MsgPort *remote = FindPort("myapp.port");
Permit();
if (remote) PutMsg(remote, my_msg);
/* Cleanup: */
Forbid();
RemPort(port);
Permit();
```
`Forbid()` is required around `FindPort`/`AddPort`/`RemPort` to prevent the task list from changing mid-operation.
---
## Reply Pattern
The standard request-reply idiom:
```c
/* Sender: */
my_msg->mn_ReplyPort = reply_port;
PutMsg(server_port, &my_msg->mn_Message);
WaitPort(reply_port);
struct MyMsg *reply = (struct MyMsg *)GetMsg(reply_port);
/* reply now contains the server's response */
/* Server: */
WaitPort(server_port);
struct MyMsg *req = (struct MyMsg *)GetMsg(server_port);
/* process req... */
req->result = 42;
ReplyMsg(&req->mn_Message); /* sends back to req->mn_ReplyPort */
```
---
## References
- NDK39: `exec/ports.h`, `exec/messages.h`
- ADCD 2.1: `CreateMsgPort`, `PutMsg`, `GetMsg`, `WaitPort`, `ReplyMsg`
- *Amiga ROM Kernel Reference Manual: Exec* — messages and ports chapter

View file

@ -0,0 +1,107 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Resident Modules — RomTag, RTF_AUTOINIT, FindResident
## Overview
AmigaOS ROM and disk-resident modules (libraries, devices, resources) identify themselves via a **RomTag** structure. At boot, exec scans the ROM and loaded segments for RomTags and initialises every module it finds.
---
## struct Resident (RomTag)
```c
/* exec/resident.h — NDK39 */
struct Resident {
UWORD rt_MatchWord; /* always $4AFC — magic identifier */
struct Resident *rt_MatchTag; /* pointer back to this struct (self-ref) */
APTR rt_EndSkip; /* pointer past end of this module's code */
UBYTE rt_Flags; /* RTF_* flags */
UBYTE rt_Version; /* module version number */
UBYTE rt_Type; /* NT_LIBRARY, NT_DEVICE, NT_RESOURCE, ... */
BYTE rt_Pri; /* initialisation priority (higher = earlier) */
char *rt_Name; /* module name string, e.g. "dos.library" */
char *rt_IdString; /* human-readable ID, e.g. "dos.library 40.1" */
APTR rt_Init; /* init function or InitTable pointer */
};
```
### Magic Word
`rt_MatchWord = $4AFC` is the 68k opcode for `ILLEGAL` — a deliberate trap instruction chosen so that an accidental execution of a RomTag causes an immediate CPU exception rather than silent corruption.
### RTF_ Flags
```c
#define RTF_AUTOINIT (1<<7) /* use rt_Init as pointer to InitTable */
#define RTF_SINGLETASK (1<<1) /* init runs in single-task context */
#define RTF_COLDSTART (1<<0) /* init on cold boot only */
```
---
## RTF_AUTOINIT — Automatic Initialisation
When `RTF_AUTOINIT` is set, `rt_Init` points to an **InitTable** rather than a bare function:
```c
struct InitTable {
ULONG it_DataSize; /* size of library instance struct */
APTR *it_FuncTable; /* pointer to function pointer table */
APTR it_DataTable; /* pointer to INITBYTE/INITWORD/INITLONG table */
APTR it_InitRoutine;/* pointer to actual LibInit() function */
};
```
exec uses `MakeLibrary()` to allocate the library, install the JMP table, and initialise the data, then calls `it_InitRoutine`. For most libraries, the author only needs to provide `it_FuncTable` and `it_DataTable` and `RTF_AUTOINIT` handles the rest automatically.
---
## Finding a Resident by Name
```c
struct Resident *res = FindResident("dos.library"); /* LVO -60 */
if (res) {
printf("Found: %s v%d\n", res->rt_Name, res->rt_Version);
}
```
`FindResident` scans `SysBase->ResModules` — the list of all RomTag pointers collected at boot.
---
## ROM Scan at Boot
During exec initialisation, the ROM scanner walks from `$F80000` (Kickstart base) upward looking for the `$4AFC` magic word. For each match it verifies `rt_MatchTag == &rt` (self-referential pointer), confirms `rt_EndSkip` is beyond the RomTag, and adds valid entries to `ResModules`.
The same scan is applied to any loaded segment when `AddResidentModule` is called.
---
## Writing a Minimal RomTag (Assembly)
```asm
; Minimal ROM tag for a library:
dc.w $4AFC ; rt_MatchWord
dc.l _RomTag ; rt_MatchTag (self-ref)
dc.l _EndTag ; rt_EndSkip
dc.b RTF_AUTOINIT ; rt_Flags
dc.b 1 ; rt_Version
dc.b NT_LIBRARY ; rt_Type
dc.b 0 ; rt_Pri
dc.l _Name ; rt_Name
dc.l _IdString ; rt_IdString
dc.l _InitTable ; rt_Init (InitTable when AUTOINIT)
_Name: dc.b "mylib.library", 0
_IdString: dc.b "mylib.library 1.0 (23.4.2026)", 13, 10, 0
even
_EndTag:
```
---
## References
- NDK39: `exec/resident.h`, `exec/execbase.h`
- ADCD 2.1: `FindResident`, `InitResident`, `AddResidentModule`
- *Amiga ROM Kernel Reference Manual: Exec* — resident modules chapter

113
06_exec_os/semaphores.md Normal file
View file

@ -0,0 +1,113 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Semaphores — SignalSemaphore, ObtainSemaphore, Shared/Exclusive
## Overview
Semaphores are the AmigaOS mechanism for **mutual exclusion and shared-read access** to resources. Unlike `Forbid()` (which blocks all scheduling), semaphores allow other tasks to run while waiting — the waiting task simply sleeps until the resource is available.
---
## struct SignalSemaphore
```c
/* exec/semaphores.h — NDK39 */
struct SignalSemaphore {
struct Node ss_Link; /* ln_Type = NT_SIGNALSEM */
/* ln_Name = semaphore name (public) */
WORD ss_NestCount; /* how many times THIS task has obtained it */
struct MinList ss_WaitQueue;/* tasks waiting for exclusive access */
struct SemaphoreRequest ss_MultipleLink; /* shared-reader slot */
struct Task *ss_Owner; /* task holding exclusive lock (or NULL) */
WORD ss_QueueCount; /* number of waiters */
};
```
---
## Initialising a Semaphore
```c
/* Stack or AllocMem — always initialise before use: */
struct SignalSemaphore sem;
InitSemaphore(&sem); /* LVO -558 */
/* Public (named) semaphore — so other tasks can find it: */
sem.ss_Link.ln_Name = "myapp.lock";
AddSemaphore(&sem); /* LVO -564 */
/* Later: */
RemSemaphore(&sem); /* LVO -570 */
```
---
## Exclusive (Write) Lock
```c
/* Block until this task holds the semaphore exclusively: */
ObtainSemaphore(&sem); /* LVO -534 */
/* --- critical section: only one task in here at a time --- */
ReleaseSemaphore(&sem); /* LVO -546 */
```
### Non-Blocking Try
```c
/* Returns TRUE if obtained, FALSE if someone else holds it: */
if (AttemptSemaphore(&sem)) { /* LVO -540 */
/* got it */
ReleaseSemaphore(&sem);
} else {
/* resource busy — do something else */
}
```
---
## Shared (Read) Lock
Multiple tasks may hold a shared lock simultaneously. An exclusive lock blocks until all shared holders release.
```c
ObtainSemaphoreShared(&sem); /* LVO -768 */
/* --- read-only access: multiple tasks may be here at once --- */
ReleaseSemaphore(&sem); /* same release for both modes */
```
---
## Nesting
Semaphores are **reentrant** — the same task can call `ObtainSemaphore` multiple times. The `ss_NestCount` tracks how many times the current owner has obtained it. `ReleaseSemaphore` must be called the same number of times.
```c
ObtainSemaphore(&sem); /* NestCount = 1 */
ObtainSemaphore(&sem); /* NestCount = 2 — safe, same task */
ReleaseSemaphore(&sem); /* NestCount = 1 */
ReleaseSemaphore(&sem); /* NestCount = 0 — fully released, waiters wake */
```
---
## Semaphore vs Forbid/Disable
| Mechanism | Blocks | Other tasks run while waiting? | Interrupt safe? |
|---|---|---|---|
| `Forbid()` | All task switching | ❌ No | ✅ (interrupts still run) |
| `Disable()` | All task switching + interrupts | ❌ No | ✅ |
| `ObtainSemaphore()` | Only contending tasks | ✅ Yes | ❌ Not from interrupt context |
Use semaphores for anything that may take more than a few microseconds. Use `Forbid()` only for very short list manipulations.
---
## References
- NDK39: `exec/semaphores.h`
- ADCD 2.1: `InitSemaphore`, `ObtainSemaphore`, `ObtainSemaphoreShared`, `ReleaseSemaphore`, `AttemptSemaphore`
- *Amiga ROM Kernel Reference Manual: Exec* — semaphores chapter

132
06_exec_os/signals.md Normal file
View file

@ -0,0 +1,132 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Signals — AllocSignal, SetSignal, Wait
## Overview
Signals are the lightest AmigaOS synchronization primitive. Each task has 32 signal bits (`tc_SigAlloc`). A task blocks on `Wait(mask)` until any of the specified bits are set by another task or interrupt handler calling `Signal()`.
---
## Signal Bit Constants
```c
/* exec/tasks.h — NDK39 */
/* Bits 015: application-allocated via AllocSignal() */
/* Bits 1631: reserved by exec */
#define SIGB_ABORT 0 /* bit 0: break signal */
#define SIGB_CHILD 1 /* bit 1: child task signal */
#define SIGB_BLIT 4 /* bit 4: blitter done (exec internal) */
#define SIGB_SINGLE 4 /* alias */
#define SIGB_INTUITION 5 /* bit 5: Intuition events (exec internal) */
#define SIGB_DOS 8 /* bit 8: DOS signal */
/* Workbench/DOS break signals (bits 1215): */
#define SIGBREAKB_CTRL_C 12
#define SIGBREAKB_CTRL_D 13
#define SIGBREAKB_CTRL_E 14
#define SIGBREAKB_CTRL_F 15
#define SIGBREAKF_CTRL_C (1L<<SIGBREAKB_CTRL_C) /* $1000 */
#define SIGBREAKF_CTRL_D (1L<<SIGBREAKB_CTRL_D) /* $2000 */
#define SIGBREAKF_CTRL_E (1L<<SIGBREAKB_CTRL_E) /* $4000 */
#define SIGBREAKF_CTRL_F (1L<<SIGBREAKB_CTRL_F) /* $8000 */
```
---
## Allocating and Freeing Signals
```c
/* Allocate an unused signal bit (-1 = any free bit): */
LONG sigBit = AllocSignal(-1); /* LVO -246 */
if (sigBit < 0) { /* all 16 user bits in use */ }
ULONG sigMask = (1L << sigBit);
/* Free when done: */
FreeSignal(sigBit); /* LVO -252 */
```
---
## Waiting for Signals
```c
/* Block until any of the listed signals arrive: */
ULONG received = Wait(sigMask | SIGBREAKF_CTRL_C); /* LVO -318 */
if (received & SIGBREAKF_CTRL_C) {
/* user pressed CTRL-C */
cleanup_and_exit();
}
if (received & sigMask) {
/* our custom event occurred */
}
```
`Wait()` returns only after at least one bit in the mask is set. It is equivalent to sleeping — the task is moved to `TaskWait` and no CPU is consumed.
---
## Sending Signals
```c
/* Signal a task from another task or interrupt handler: */
Signal(target_task, sigMask); /* LVO -324 */
```
`Signal()` is safe from interrupt context.
---
## SetSignal — Read and Clear
```c
/* Read and clear specific signal bits atomically: */
ULONG old = SetSignal(new_bits, change_mask); /* LVO -306 */
/* old = previous state of all 32 signal bits */
/* new value = (old & ~change_mask) | (new_bits & change_mask) */
/* Check CTRL-C without blocking: */
if (SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
/* CTRL-C was pending — now cleared */
}
```
---
## Typical Usage Pattern: Event Loop
```c
struct MsgPort *port = CreateMsgPort();
ULONG portSig = (1L << port->mp_SigBit);
ULONG waitMask = portSig | SIGBREAKF_CTRL_C;
BOOL running = TRUE;
while (running) {
ULONG sigs = Wait(waitMask);
if (sigs & SIGBREAKF_CTRL_C) {
running = FALSE;
}
if (sigs & portSig) {
struct Message *msg;
while ((msg = GetMsg(port)) != NULL) {
/* handle message */
ReplyMsg(msg);
}
}
}
DeleteMsgPort(port);
```
---
## References
- NDK39: `exec/tasks.h`, `exec/execbase.h`
- ADCD 2.1: `AllocSignal`, `FreeSignal`, `Signal`, `Wait`, `SetSignal`
- `06_exec_os/tasks_processes.md` — tc_SigAlloc, tc_SigRecvd fields
- *Amiga ROM Kernel Reference Manual: Exec* — signals chapter

View file

@ -0,0 +1,157 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Tasks and Processes — Structures, States, Scheduling
## Overview
AmigaOS uses **cooperative/preemptive** scheduling. Tasks are the fundamental unit of execution; Processes are Tasks with an additional DOS environment (message port, CLI, segment list). The scheduler runs at each quantum (50 Hz VBL interrupt) and after any `Signal()` or `Wait()` call.
---
## struct Task
```c
/* exec/tasks.h */
struct Task {
struct Node tc_Node; /* ln_Type=NT_TASK or NT_PROCESS */
/* ln_Pri = scheduling priority */
/* ln_Name = task name string */
UBYTE tc_Flags; /* TF_LAUNCH, TF_STRIKE, TF_EXCEPT */
UBYTE tc_State; /* TS_RUN, TS_READY, TS_WAIT, TS_EXCEPT */
BYTE tc_IDNestCnt; /* interrupt disable nesting */
BYTE tc_TDNestCnt; /* task disable (Forbid) nesting */
ULONG tc_SigAlloc; /* allocated signal bits mask */
ULONG tc_SigWait; /* signals task is waiting for */
ULONG tc_SigRecvd; /* signals received */
ULONG tc_SigExcept; /* exception signals */
/* ... stack bounds, context, exception handler ... */
APTR tc_SPLower; /* lowest valid stack address */
APTR tc_SPUpper; /* highest valid stack address + 2 */
APTR tc_SPReg; /* saved stack pointer (when not running) */
};
```
---
## struct Process (extends Task)
```c
/* dos/dosextens.h */
struct Process {
struct Task pr_Task; /* embedded Task */
struct MsgPort pr_MsgPort; /* I/O message port */
UWORD pr_Pad;
BPTR pr_SegList; /* BPTR to segment list */
LONG pr_StackSize;
APTR pr_GlobVec; /* BCPL global vector */
LONG pr_TaskNum; /* CLI task number */
BPTR pr_StackBase; /* base of stack (BPTR) */
LONG pr_Result2; /* secondary result */
BPTR pr_CurrentDir; /* current directory lock */
BPTR pr_CIS; /* current input stream */
BPTR pr_COS; /* current output stream */
APTR pr_ConsoleTask;
APTR pr_FileSystemTask;
BPTR pr_CLI; /* CLI structure (NULL if WB) */
...
struct MsgPort *pr_ReturnAddr; /* return address for CLI tasks */
APTR pr_PktWait;
struct SaveMsg pr_ExitData;
UBYTE *pr_Arguments; /* argument string */
struct MinList pr_LocalVars; /* local shell variables */
ULONG pr_ShellPrivate;
BPTR pr_CES; /* current error stream */
};
```
---
## Task States
| State | Value | Meaning |
|---|---|---|
| `TS_INVALID` | 0 | Not a valid task |
| `TS_ADDED` | 1 | Just added, not yet scheduled |
| `TS_RUN` | 2 | Currently running (only one task) |
| `TS_READY` | 3 | On the `TaskReady` list, waiting for CPU |
| `TS_WAIT` | 4 | Blocked on `Wait()` — on `TaskWait` list |
| `TS_EXCEPT` | 5 | Handling an exception |
| `TS_REMOVED` | 6 | Removed from scheduling |
---
## Scheduling: Priority-Based Round Robin
The scheduler (`exec.library` internal) picks the highest-priority task from `SysBase→TaskReady`. Among equal-priority tasks, they get equal time slices (round-robin).
- Default priority: 0
- Range: 128 to +127 (higher = more CPU)
- OS tasks run at priority 1020
- Input handler: priority 20
- Disk tasks: priority 10
```c
SetTaskPri(FindTask(NULL), 5); /* raise current task to priority 5 */
```
---
## Creating Tasks and Processes
```c
/* Simple task (exec level): */
struct Task *t = AllocMem(sizeof(struct Task), MEMF_PUBLIC|MEMF_CLEAR);
t->tc_Node.ln_Name = "MyTask";
t->tc_Node.ln_Pri = 0;
t->tc_SPLower = stack;
t->tc_SPUpper = stack + stacksize;
t->tc_SPReg = (APTR)((ULONG)stack + stacksize);
AddTask(t, myTaskFunc, NULL);
/* DOS Process (with message port, filesystem access): */
struct Process *p = CreateNewProcTags(
NP_Entry, myFunc,
NP_Name, "MyProcess",
NP_StackSize, 8192,
NP_Priority, 0,
TAG_DONE);
```
---
## Task State Machine
```mermaid
stateDiagram-v2
[*] --> READY : AddTask()
READY --> RUN : Scheduler picks task
RUN --> READY : Quantum expired or higher-priority task
RUN --> WAIT : Wait(signal_mask)
WAIT --> READY : Signal() delivers awaited signals
RUN --> EXCEPT : Exception signal received
EXCEPT --> RUN : Exception handler returns
RUN --> [*] : Task function returns / RemTask()
```
---
## FindTask and Task Identity
```c
struct Task *me = FindTask(NULL); /* NULL = current task */
printf("Running as: %s\n", me->tc_Node.ln_Name);
/* Check if we are a Process (vs plain Task): */
if (me->tc_Node.ln_Type == NT_PROCESS) {
struct Process *pr = (struct Process *)me;
/* access pr_CLI, pr_MsgPort, etc. */
}
```
---
## References
- NDK39: `exec/tasks.h`, `dos/dosextens.h`
- ADCD 2.1: `AddTask`, `RemTask`, `FindTask`, `SetTaskPri`, `CreateNewProc`
- *Amiga ROM Kernel Reference Manual: Exec* — tasks and scheduling chapter