mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
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:
parent
f07a368bf1
commit
21751c0025
172 changed files with 19701 additions and 0 deletions
20
06_exec_os/README.md
Normal file
20
06_exec_os/README.md
Normal 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 |
|
||||
106
06_exec_os/exceptions_traps.md
Normal file
106
06_exec_os/exceptions_traps.md
Normal 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 |
|
||||
| 12–14 | `$030–$038` | Reserved | — |
|
||||
| 15 | `$03C` | Uninitialised Interrupt | Alert |
|
||||
| 24 | `$060` | Spurious Interrupt | — |
|
||||
| 25–31 | `$064–$07C` | Auto-vector interrupts 1–7 | Exec interrupt dispatcher |
|
||||
| 32–47 | `$080–$0BC` | TRAP #0–#15 | User-installable traps |
|
||||
| 48–63 | `$0C0–$0FC` | Reserved (FPU) | 68881/68882 exception handlers |
|
||||
| 64–255 | `$100–$3FC` | User-defined vectors | User |
|
||||
|
||||
---
|
||||
|
||||
## TRAP Instructions — Software Interrupts
|
||||
|
||||
`TRAP #n` (n = 0–15) 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
110
06_exec_os/exec_base.md
Normal 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
126
06_exec_os/interrupts.md
Normal 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 IPL0–IPL6) 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 | AUD0–AUD3 | 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()**
|
||||
- D0–D1, A0–A1 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
153
06_exec_os/io_requests.md
Normal 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
|
||||
97
06_exec_os/library_system.md
Normal file
97
06_exec_os/library_system.md
Normal 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
|
||||
125
06_exec_os/library_vectors.md
Normal file
125
06_exec_os/library_vectors.md
Normal 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
158
06_exec_os/lists_nodes.md
Normal 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
|
||||
151
06_exec_os/memory_management.md
Normal file
151
06_exec_os/memory_management.md
Normal 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
142
06_exec_os/message_ports.md
Normal 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
|
||||
107
06_exec_os/resident_modules.md
Normal file
107
06_exec_os/resident_modules.md
Normal 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
113
06_exec_os/semaphores.md
Normal 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
132
06_exec_os/signals.md
Normal 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 0–15: application-allocated via AllocSignal() */
|
||||
/* Bits 16–31: 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 12–15): */
|
||||
#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
|
||||
157
06_exec_os/tasks_processes.md
Normal file
157
06_exec_os/tasks_processes.md
Normal 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 10–20
|
||||
- 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue