mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
exec_os: enrich all stubs to bootcamp-quality reference articles
Complete rewrite of 14 exec_os articles from stubs to comprehensive deep-dive technical references with architecture diagrams, pitfalls, and best practices. New: multitasking.md (scheduler, IPC, memory safety, real-world scenarios) Enriched: exec_base, tasks_processes, library_system, library_vectors, interrupts, exceptions_traps, memory_management, message_ports, signals, semaphores, io_requests, lists_nodes, resident_modules Updated indexes: 06_exec_os/README.md, root README.md
This commit is contained in:
parent
4d136b0672
commit
59929047d4
16 changed files with 4463 additions and 678 deletions
|
|
@ -4,7 +4,59 @@
|
|||
|
||||
## 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`.
|
||||
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`. Understanding this data structure is prerequisite to understanding anything else in the kernel — every system object is a node on some list.
|
||||
|
||||
The term "intrusive" means the link pointers are embedded directly inside the data structure, not in a separate container. This eliminates dynamic allocation overhead but means each object can only be on one list at a time.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "struct List"
|
||||
HEAD["lh_Head"]
|
||||
TAIL["lh_Tail<br/>(always NULL)"]
|
||||
TAILP["lh_TailPred"]
|
||||
end
|
||||
|
||||
subgraph "Nodes"
|
||||
A["Node A<br/>ln_Pri=10"]
|
||||
B["Node B<br/>ln_Pri=5"]
|
||||
C["Node C<br/>ln_Pri=0"]
|
||||
end
|
||||
|
||||
HEAD --> A
|
||||
A -->|ln_Succ| B
|
||||
B -->|ln_Succ| C
|
||||
C -->|ln_Succ| TAIL
|
||||
C -.->|ln_Pred| B
|
||||
B -.->|ln_Pred| A
|
||||
TAILP --> C
|
||||
|
||||
style HEAD fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style TAIL fill:#ffcdd2,stroke:#e53935,color:#333
|
||||
```
|
||||
|
||||
### The Sentinel Design
|
||||
|
||||
The AmigaOS list uses a clever **3-pointer sentinel** layout that eliminates special-casing for empty lists:
|
||||
|
||||
```
|
||||
Full list:
|
||||
|
||||
lh_Head ──→ [Node A] ──→ [Node B] ──→ [Node C] ──→ NULL (lh_Tail)
|
||||
↑
|
||||
lh_TailPred ─────────────────────────────┘
|
||||
|
||||
Empty list:
|
||||
|
||||
lh_Head ──→ NULL (lh_Tail) ← lh_Head points to lh_Tail
|
||||
↑
|
||||
lh_TailPred ────┘ ← lh_TailPred points to lh_Head
|
||||
```
|
||||
|
||||
The `lh_Tail` field is always NULL and acts as a "dummy tail node". Walking the list stops when `node->ln_Succ == NULL` — no need to compare against a separate end marker.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -24,7 +76,7 @@ struct Node {
|
|||
struct MinNode {
|
||||
struct MinNode *mln_Succ;
|
||||
struct MinNode *mln_Pred;
|
||||
/* no type, priority, or name — minimal overhead */
|
||||
/* no type, priority, or name — saves 6 bytes per node */
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -32,9 +84,9 @@ struct MinNode {
|
|||
/* exec/lists.h — NDK39 */
|
||||
|
||||
struct List {
|
||||
struct Node *lh_Head; /* first node (or tail sentinel if empty) */
|
||||
struct Node *lh_Head; /* first node (or &lh_Tail if empty) */
|
||||
struct Node *lh_Tail; /* always NULL — marks end of list */
|
||||
struct Node *lh_TailPred; /* last node (or head sentinel if empty) */
|
||||
struct Node *lh_TailPred; /* last node (or &lh_Head if empty) */
|
||||
UBYTE lh_Type; /* list type */
|
||||
UBYTE lh_pad;
|
||||
};
|
||||
|
|
@ -46,30 +98,41 @@ struct MinList {
|
|||
};
|
||||
```
|
||||
|
||||
### Node Type Constants
|
||||
### Size Comparison
|
||||
|
||||
| Structure | Size | Use Case |
|
||||
|---|---|---|
|
||||
| `MinNode` | 8 bytes | Private lists, message queues, semaphore wait queues |
|
||||
| `Node` | 14 bytes | Named/typed nodes — tasks, libraries, ports |
|
||||
| `MinList` | 12 bytes | Lightweight list header |
|
||||
| `List` | 14 bytes | Full list header with type |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
/* exec/nodes.h — NDK39 */
|
||||
#define NT_UNKNOWN 0 /* Unknown or uninitialized */
|
||||
#define NT_TASK 1 /* exec Task */
|
||||
#define NT_INTERRUPT 2 /* Interrupt server */
|
||||
#define NT_DEVICE 3 /* Device driver */
|
||||
#define NT_MSGPORT 4 /* Message port */
|
||||
#define NT_MESSAGE 5 /* In-transit message */
|
||||
#define NT_FREEMSG 6 /* Free message */
|
||||
#define NT_REPLYMSG 7 /* Replied message */
|
||||
#define NT_RESOURCE 8 /* System resource */
|
||||
#define NT_LIBRARY 9 /* Shared library */
|
||||
#define NT_MEMORY 10 /* Memory region (MemHeader) */
|
||||
#define NT_SOFTINT 11 /* Software interrupt */
|
||||
#define NT_FONT 12 /* Font */
|
||||
#define NT_PROCESS 13 /* DOS Process (extends Task) */
|
||||
#define NT_SEMAPHORE 14 /* Old-style semaphore */
|
||||
#define NT_SIGNALSEM 15 /* SignalSemaphore */
|
||||
#define NT_BOOTNODE 16 /* Boot node */
|
||||
#define NT_KICKMEM 17 /* Kick memory */
|
||||
#define NT_GRAPHICS 18 /* Graphics resource */
|
||||
#define NT_DEATHMESSAGE 19 /* Task death notification */
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -77,82 +140,196 @@ struct MinList {
|
|||
## Initialising a List
|
||||
|
||||
```c
|
||||
/* Stack-allocated list: */
|
||||
struct List myList;
|
||||
NewList(&myList); /* sets up sentinel pointers — mandatory */
|
||||
NewList(&myList); /* MANDATORY — sets up sentinel pointers */
|
||||
|
||||
/* Or use NEWLIST() macro: */
|
||||
NEWLIST(&myList);
|
||||
/* Expands to: */
|
||||
myList.lh_Head = (struct Node *)&myList.lh_Tail;
|
||||
myList.lh_Tail = NULL;
|
||||
myList.lh_TailPred = (struct Node *)&myList.lh_Head;
|
||||
```
|
||||
|
||||
> **Critical**: An uninitialized list has garbage pointers. Calling `AddHead`/`AddTail` on an uninitialized list corrupts random memory.
|
||||
|
||||
---
|
||||
|
||||
## Adding and Removing Nodes
|
||||
## Adding Nodes
|
||||
|
||||
```c
|
||||
/* Add at head (highest LRU position): */
|
||||
AddHead(&myList, &myNode); /* LVO -240 */
|
||||
/* Add at head (newest first — stack semantics): */
|
||||
AddHead(&myList, &myNode); /* LVO -240 */
|
||||
|
||||
/* Add at tail: */
|
||||
AddTail(&myList, &myNode); /* LVO -246 */
|
||||
/* Add at tail (oldest first — queue semantics): */
|
||||
AddTail(&myList, &myNode); /* LVO -246 */
|
||||
|
||||
/* Remove from wherever it is (no list pointer needed): */
|
||||
Remove(&myNode); /* LVO -252 */
|
||||
/* Priority-ordered insert (highest ln_Pri first): */
|
||||
Enqueue(&myList, &myNode); /* LVO -270 */
|
||||
/* Scans from head, inserts before first node with lower priority
|
||||
Equal priority: inserts AFTER existing nodes of same priority (FIFO) */
|
||||
```
|
||||
|
||||
/* Priority-ordered insert (by ln_Pri, high first): */
|
||||
Enqueue(&myList, &myNode); /* LVO -270 */
|
||||
### How Enqueue Works
|
||||
|
||||
```
|
||||
Before: [Pri 10] → [Pri 5] → [Pri 0] → NULL
|
||||
Insert node with Pri 7:
|
||||
After: [Pri 10] → [Pri 7] → [Pri 5] → [Pri 0] → NULL
|
||||
```
|
||||
|
||||
This is how `SysBase->TaskReady` stays sorted — `Enqueue` ensures the highest-priority task is always at the head.
|
||||
|
||||
---
|
||||
|
||||
## Removing Nodes
|
||||
|
||||
```c
|
||||
/* Remove a specific node (no list pointer needed): */
|
||||
Remove(&myNode); /* LVO -252 */
|
||||
/* Patches prev->ln_Succ and next->ln_Pred to skip this node */
|
||||
|
||||
/* Remove and return the first node: */
|
||||
struct Node *first = RemHead(&myList); /* LVO -258 */
|
||||
/* Returns NULL if list is empty */
|
||||
|
||||
/* Remove and return the last node: */
|
||||
struct Node *last = RemTail(&myList); /* LVO -264 */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Walking a List
|
||||
|
||||
### Standard Traversal
|
||||
|
||||
```c
|
||||
struct Node *node;
|
||||
for (node = myList.lh_Head;
|
||||
node->ln_Succ != NULL; /* Stop at tail sentinel */
|
||||
node = node->ln_Succ)
|
||||
{
|
||||
Printf("Node: %s (pri %ld)\n", node->ln_Name, node->ln_Pri);
|
||||
}
|
||||
```
|
||||
|
||||
### Safe Removal While Iterating
|
||||
|
||||
```c
|
||||
/* Save next BEFORE removing, because Remove() corrupts ln_Succ */
|
||||
struct Node *node, *next;
|
||||
for (node = myList.lh_Head; node->ln_Succ != NULL; node = node->ln_Succ) {
|
||||
/* process node */
|
||||
for (node = myList.lh_Head;
|
||||
(next = node->ln_Succ) != NULL;
|
||||
node = next)
|
||||
{
|
||||
if (ShouldRemove(node))
|
||||
{
|
||||
Remove(node);
|
||||
FreeNode(node);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Safe removal while iterating (save next before removing):
|
||||
### Checking if a List is Empty
|
||||
|
||||
```c
|
||||
for (node = myList.lh_Head; (next = node->ln_Succ) != NULL; node = next) {
|
||||
if (should_remove(node)) Remove(node);
|
||||
}
|
||||
/* The canonical empty check: */
|
||||
if (IsListEmpty(&myList)) { /* empty */ }
|
||||
|
||||
/* Expands to: */
|
||||
if (myList.lh_TailPred == (struct Node *)&myList) { /* empty */ }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Finding a Node by Name
|
||||
## Finding Nodes
|
||||
|
||||
```c
|
||||
struct Node *found = FindName(&SysBase->LibList, "dos.library");
|
||||
/* Find by name (case-sensitive): */
|
||||
struct Node *found = FindName(&myList, "dos.library"); /* LVO -276 */
|
||||
/* Returns NULL if not found */
|
||||
/* Always call under Forbid() if the list may change */
|
||||
/* Scans from head — returns first match */
|
||||
|
||||
/* Find next occurrence (continue scanning): */
|
||||
struct Node *next = FindName(found, "dos.library");
|
||||
/* Starts searching from 'found' node forward */
|
||||
```
|
||||
|
||||
> **Warning**: `FindName` on a system list (`LibList`, `PortList`) must be done under `Forbid()` — the list can change between `FindName` and your use of the returned node.
|
||||
|
||||
---
|
||||
|
||||
## Where Lists Are Used
|
||||
|
||||
| System List | Location | Node Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `SysBase->LibList` | ExecBase | `NT_LIBRARY` | All loaded libraries |
|
||||
| `SysBase->DeviceList` | ExecBase | `NT_DEVICE` | All loaded devices |
|
||||
| `SysBase->ResourceList` | ExecBase | `NT_RESOURCE` | System resources |
|
||||
| `SysBase->PortList` | ExecBase | `NT_MSGPORT` | Public message ports |
|
||||
| `SysBase->TaskReady` | ExecBase | `NT_TASK` | Tasks ready to run |
|
||||
| `SysBase->TaskWait` | ExecBase | `NT_TASK` | Tasks blocked on Wait() |
|
||||
| `SysBase->MemList` | ExecBase | `NT_MEMORY` | Memory regions |
|
||||
| `SysBase->SemaphoreList` | ExecBase | `NT_SIGNALSEM` | Public semaphores |
|
||||
| `SysBase->IntrList` | ExecBase | `NT_INTERRUPT` | Interrupt servers |
|
||||
| `MsgPort->mp_MsgList` | Per-port | `NT_MESSAGE` | Pending messages |
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### 1. Forgetting NewList
|
||||
|
||||
```c
|
||||
/* BUG — uninitialized list */
|
||||
struct List myList; /* Contains garbage */
|
||||
AddTail(&myList, &node); /* Writes to garbage address → Guru */
|
||||
```
|
||||
|
||||
### 2. Node on Two Lists
|
||||
|
||||
```c
|
||||
/* BUG — intrusive list = one list per node */
|
||||
AddTail(&listA, &node);
|
||||
AddTail(&listB, &node); /* Corrupts listA — node's prev/next now point into listB */
|
||||
```
|
||||
|
||||
### 3. Walking Without Forbid
|
||||
|
||||
```c
|
||||
/* RACE — another task modifies the list */
|
||||
struct Node *n;
|
||||
for (n = SysBase->LibList.lh_Head; n->ln_Succ; n = n->ln_Succ)
|
||||
{
|
||||
/* Context switch here — another task calls CloseLibrary → node removed */
|
||||
Printf("%s\n", n->ln_Name); /* n may be freed memory */
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Using Freed Node's Links
|
||||
|
||||
```c
|
||||
Remove(&node);
|
||||
FreeMem(node, ...);
|
||||
/* node->ln_Succ is now garbage — never dereference after Remove+Free */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How the Sentinel Works
|
||||
## Best Practices
|
||||
|
||||
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.
|
||||
1. **Always call `NewList()`** before using a list — no exceptions
|
||||
2. **Use `Forbid()`** when walking system lists
|
||||
3. **Use safe iteration** (save `ln_Succ` before `Remove()`) when modifying during traversal
|
||||
4. **Use `Enqueue()`** for priority-sorted insertion — don't manually scan
|
||||
5. **Use `MinNode`/`MinList`** for private lists to save memory
|
||||
6. **Set `ln_Type`** when adding to typed lists — debugging tools rely on it
|
||||
7. **Never put a node on two lists** — use a wrapper struct with two embedded nodes if needed
|
||||
8. **Check `IsListEmpty()`** before `RemHead()`/`RemTail()` if NULL return is unacceptable
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/nodes.h`, `exec/lists.h`
|
||||
- ADCD 2.1: `AddHead`, `AddTail`, `Remove`, `Enqueue`, `FindName`, `NewList`
|
||||
- ADCD 2.1: `AddHead`, `AddTail`, `Remove`, `RemHead`, `RemTail`, `Enqueue`, `FindName`, `NewList`, `IsListEmpty`
|
||||
- See also: [ExecBase](exec_base.md) — system lists stored in ExecBase
|
||||
- *Amiga ROM Kernel Reference Manual: Exec* — lists chapter
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue