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,34 @@
|
|||
|
||||
## 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.
|
||||
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. They are the correct synchronization primitive for anything that takes more than a few microseconds.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> FREE : InitSemaphore()
|
||||
FREE --> EXCLUSIVE : ObtainSemaphore() (by Task A)
|
||||
FREE --> SHARED : ObtainSemaphoreShared() (by Task A)
|
||||
SHARED --> SHARED : ObtainSemaphoreShared() (by Task B — multiple OK)
|
||||
SHARED --> FREE : All shared holders release
|
||||
EXCLUSIVE --> NESTED : ObtainSemaphore() (by same Task A — reentrant)
|
||||
NESTED --> EXCLUSIVE : ReleaseSemaphore() (decrement nest count)
|
||||
EXCLUSIVE --> FREE : ReleaseSemaphore() (nest count reaches 0)
|
||||
EXCLUSIVE --> BLOCKED : ObtainSemaphore() by Task B → Task B sleeps
|
||||
BLOCKED --> EXCLUSIVE : Task A releases → Task B wakes
|
||||
```
|
||||
|
||||
### Shared vs Exclusive
|
||||
|
||||
| Mode | Multiple holders? | Use case |
|
||||
|---|---|---|
|
||||
| **Exclusive** (`ObtainSemaphore`) | No — only one task at a time | Writing/modifying shared data |
|
||||
| **Shared** (`ObtainSemaphoreShared`) | Yes — multiple readers allowed | Read-only access to shared data |
|
||||
|
||||
When a task requests exclusive access while shared holders exist, it blocks until ALL shared holders release. When a task requests shared access while an exclusive holder exists, it blocks until the exclusive holder releases.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -16,27 +43,41 @@ 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 MinList ss_WaitQueue;/* tasks waiting for access */
|
||||
struct SemaphoreRequest ss_MultipleLink; /* shared-reader management */
|
||||
struct Task *ss_Owner; /* task holding exclusive lock (or NULL) */
|
||||
WORD ss_QueueCount; /* number of waiters */
|
||||
WORD ss_QueueCount; /* internal waiter tracking */
|
||||
};
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `ss_Link.ln_Name` | Name string — set for public (findable) semaphores |
|
||||
| `ss_NestCount` | How many times the current owner has obtained it (reentrant) |
|
||||
| `ss_WaitQueue` | Queue of tasks waiting for access |
|
||||
| `ss_Owner` | Task holding exclusive lock, or NULL if free/shared-only |
|
||||
| `ss_QueueCount` | Internal — tracks waiting tasks and shared readers |
|
||||
|
||||
---
|
||||
|
||||
## Initialising a Semaphore
|
||||
|
||||
```c
|
||||
/* Stack or AllocMem — always initialise before use: */
|
||||
/* Stack or heap — always initialise before use: */
|
||||
struct SignalSemaphore sem;
|
||||
InitSemaphore(&sem); /* LVO -558 */
|
||||
|
||||
/* Public (named) semaphore — so other tasks can find it: */
|
||||
/* Public (named) semaphore — findable by other tasks: */
|
||||
sem.ss_Link.ln_Name = "myapp.lock";
|
||||
AddSemaphore(&sem); /* LVO -564 */
|
||||
sem.ss_Link.ln_Pri = 0;
|
||||
AddSemaphore(&sem); /* LVO -564 — adds to SysBase→SemaphoreList */
|
||||
|
||||
/* Later: */
|
||||
/* Find from another task: */
|
||||
Forbid();
|
||||
struct SignalSemaphore *found = FindSemaphore("myapp.lock"); /* LVO -576 */
|
||||
Permit();
|
||||
|
||||
/* Cleanup: */
|
||||
RemSemaphore(&sem); /* LVO -570 */
|
||||
```
|
||||
|
||||
|
|
@ -49,6 +90,7 @@ RemSemaphore(&sem); /* LVO -570 */
|
|||
ObtainSemaphore(&sem); /* LVO -534 */
|
||||
|
||||
/* --- critical section: only one task in here at a time --- */
|
||||
ModifySharedData();
|
||||
|
||||
ReleaseSemaphore(&sem); /* LVO -546 */
|
||||
```
|
||||
|
|
@ -57,11 +99,27 @@ ReleaseSemaphore(&sem); /* LVO -546 */
|
|||
|
||||
```c
|
||||
/* Returns TRUE if obtained, FALSE if someone else holds it: */
|
||||
if (AttemptSemaphore(&sem)) { /* LVO -540 */
|
||||
/* got it */
|
||||
if (AttemptSemaphore(&sem)) /* LVO -540 */
|
||||
{
|
||||
/* Got exclusive access */
|
||||
ModifySharedData();
|
||||
ReleaseSemaphore(&sem);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Resource busy — do something else or retry later */
|
||||
}
|
||||
```
|
||||
|
||||
### Shared-to-Exclusive Upgrade (OS 3.0+)
|
||||
|
||||
```c
|
||||
/* AttemptSemaphoreShared — try shared lock without blocking */
|
||||
if (AttemptSemaphoreShared(&sem)) /* LVO -774 */
|
||||
{
|
||||
/* Got shared access */
|
||||
ReadSharedData();
|
||||
ReleaseSemaphore(&sem);
|
||||
} else {
|
||||
/* resource busy — do something else */
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -69,45 +127,166 @@ if (AttemptSemaphore(&sem)) { /* LVO -540 */
|
|||
|
||||
## Shared (Read) Lock
|
||||
|
||||
Multiple tasks may hold a shared lock simultaneously. An exclusive lock blocks until all shared holders release.
|
||||
Multiple tasks may hold a shared lock simultaneously. An exclusive lock request blocks until all shared holders release.
|
||||
|
||||
```c
|
||||
ObtainSemaphoreShared(&sem); /* LVO -768 */
|
||||
|
||||
/* --- read-only access: multiple tasks may be here at once --- */
|
||||
result = ReadSharedData();
|
||||
|
||||
ReleaseSemaphore(&sem); /* same release for both modes */
|
||||
ReleaseSemaphore(&sem); /* Same release for both modes */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nesting
|
||||
## Nesting (Reentrancy)
|
||||
|
||||
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.
|
||||
Semaphores are **reentrant** — the same task can call `ObtainSemaphore` multiple times without deadlocking itself:
|
||||
|
||||
```c
|
||||
ObtainSemaphore(&sem); /* NestCount = 1 */
|
||||
ObtainSemaphore(&sem); /* NestCount = 1, Owner = thisTask */
|
||||
ObtainSemaphore(&sem); /* NestCount = 2 — safe, same task */
|
||||
ObtainSemaphore(&sem); /* NestCount = 3 */
|
||||
ReleaseSemaphore(&sem); /* NestCount = 2 */
|
||||
ReleaseSemaphore(&sem); /* NestCount = 1 */
|
||||
ReleaseSemaphore(&sem); /* NestCount = 0 — fully released, waiters wake */
|
||||
```
|
||||
|
||||
This is essential for recursive functions or library code that may be called from contexts that already hold the lock.
|
||||
|
||||
---
|
||||
|
||||
## Semaphore vs Forbid/Disable
|
||||
## Obtaining Multiple Semaphores
|
||||
|
||||
| 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 |
|
||||
To avoid deadlocks when you need multiple semaphores, use `ObtainSemaphoreList`:
|
||||
|
||||
Use semaphores for anything that may take more than a few microseconds. Use `Forbid()` only for very short list manipulations.
|
||||
```c
|
||||
/* Build a list of semaphores to obtain atomically: */
|
||||
struct SemaphoreRequest reqA, reqB;
|
||||
struct List semList;
|
||||
NewList(&semList);
|
||||
|
||||
reqA.sr_Semaphore = &semA;
|
||||
reqB.sr_Semaphore = &semB;
|
||||
AddTail(&semList, &reqA.sr_Link);
|
||||
AddTail(&semList, &reqB.sr_Link);
|
||||
|
||||
ObtainSemaphoreList(&semList); /* LVO -582 */
|
||||
/* Both semaphores held — no deadlock risk */
|
||||
|
||||
ReleaseSemaphore(&semA);
|
||||
ReleaseSemaphore(&semB);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Semaphore vs Forbid vs Disable
|
||||
|
||||
| Mechanism | Blocks | Other tasks run? | Interrupt safe? | Cost | Max duration |
|
||||
|---|---|---|---|---|---|
|
||||
| `Forbid()` | All task switching | ❌ No | ✅ Ints still run | Very low | ~100 ms |
|
||||
| `Disable()` | All ints + tasks | ❌ No | ✅ (is the lock) | Lowest | **~250 µs** |
|
||||
| `ObtainSemaphore()` | Only contending tasks | ✅ Yes | ❌ Not from IRQ | Medium | Unlimited |
|
||||
| `ObtainSemaphoreShared()` | Only if exclusive held | ✅ Yes | ❌ Not from IRQ | Medium | Unlimited |
|
||||
|
||||
### Decision Guide
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Q1{"In interrupt<br/>context?"} -->|Yes| DISABLE["Use Disable()"]
|
||||
Q1 -->|No| Q2{"Duration<br/>< 10 µs?"}
|
||||
Q2 -->|Yes| FORBID["Use Forbid()"]
|
||||
Q2 -->|No| Q3{"Multiple<br/>readers OK?"}
|
||||
Q3 -->|Yes| SHARED["ObtainSemaphoreShared()"]
|
||||
Q3 -->|No| EXCL["ObtainSemaphore()"]
|
||||
|
||||
style DISABLE fill:#ffcdd2,stroke:#e53935,color:#333
|
||||
style FORBID fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
style SHARED fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
style EXCL fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### 1. Deadlock (Lock Ordering)
|
||||
|
||||
```c
|
||||
/* Task A */ /* Task B */
|
||||
ObtainSemaphore(&semX); ObtainSemaphore(&semY);
|
||||
ObtainSemaphore(&semY); /*!*/ ObtainSemaphore(&semX); /*!*/
|
||||
/* Task A waits for Y Task B waits for X
|
||||
→ DEADLOCK — both tasks sleep forever */
|
||||
```
|
||||
|
||||
**Solution**: Always obtain semaphores in the same global order (alphabetical, by address, etc.).
|
||||
|
||||
### 2. Priority Inversion
|
||||
|
||||
```c
|
||||
/* Low-pri task holds semaphore, medium-pri task runs,
|
||||
high-pri task waits for semaphore → high-pri starves.
|
||||
AmigaOS has NO priority inheritance. */
|
||||
```
|
||||
|
||||
**Solution**: Keep critical sections short; don't hold semaphores across I/O.
|
||||
|
||||
### 3. ObtainSemaphore from Interrupt Context
|
||||
|
||||
```c
|
||||
/* CRASH — ObtainSemaphore may Wait(), which is illegal from interrupts */
|
||||
void __interrupt MyHandler(void)
|
||||
{
|
||||
ObtainSemaphore(&sem); /* DEADLOCK or crash */
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Use `Disable()`/`Enable()` for interrupt-level synchronization, or use `AttemptSemaphore()` (non-blocking) and skip if busy.
|
||||
|
||||
### 4. Forgetting to Release
|
||||
|
||||
```c
|
||||
/* BUG — semaphore held forever */
|
||||
ObtainSemaphore(&sem);
|
||||
if (error) return; /* Returns without releasing! */
|
||||
ReleaseSemaphore(&sem);
|
||||
|
||||
/* CORRECT — use cleanup pattern */
|
||||
ObtainSemaphore(&sem);
|
||||
if (error) goto cleanup;
|
||||
/* ... work ... */
|
||||
cleanup:
|
||||
ReleaseSemaphore(&sem);
|
||||
```
|
||||
|
||||
### 5. Shared/Exclusive Mismatch
|
||||
|
||||
```c
|
||||
/* Not a bug, but confusing — ReleaseSemaphore works for both modes */
|
||||
ObtainSemaphoreShared(&sem);
|
||||
ReleaseSemaphore(&sem); /* Correct — same function for both */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use semaphores** instead of `Forbid()` for anything > ~10 µs
|
||||
2. **Prefer shared locks** for read-only access — maximizes parallelism
|
||||
3. **Keep critical sections short** — don't do I/O while holding a semaphore
|
||||
4. **Use consistent lock ordering** to prevent deadlocks
|
||||
5. **Use `AttemptSemaphore()`** for non-blocking try-lock patterns
|
||||
6. **Always release** on every code path — use goto-cleanup pattern
|
||||
7. **Never call from interrupts** — use `Disable()` or `AttemptSemaphore()` instead
|
||||
8. **Use `ObtainSemaphoreList()`** when you need multiple semaphores atomically
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/semaphores.h`
|
||||
- ADCD 2.1: `InitSemaphore`, `ObtainSemaphore`, `ObtainSemaphoreShared`, `ReleaseSemaphore`, `AttemptSemaphore`
|
||||
- ADCD 2.1: `InitSemaphore`, `ObtainSemaphore`, `ObtainSemaphoreShared`, `ReleaseSemaphore`, `AttemptSemaphore`, `ObtainSemaphoreList`
|
||||
- See also: [Multitasking](multitasking.md) — priority inversion and synchronization strategies
|
||||
- *Amiga ROM Kernel Reference Manual: Exec* — semaphores chapter
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue