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
|
|
@ -1,157 +1,379 @@
|
|||
[← Home](../README.md) · [Exec Kernel](README.md)
|
||||
|
||||
# Tasks and Processes — Structures, States, Scheduling
|
||||
# Tasks and Processes — Structures, States, Creation, 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.
|
||||
AmigaOS uses **preemptive priority-based scheduling** with round-robin time-sharing among equal-priority tasks. Tasks are the fundamental unit of execution; Processes are Tasks with an additional DOS environment (message port, CLI context, segment list, filesystem handles). The scheduler runs at each vertical blank interrupt (50/60 Hz) and after any `Signal()` or `Wait()` call.
|
||||
|
||||
For the full multitasking deep-dive — scheduler algorithm, context switch costs, IPC strategies, and memory safety — see [Multitasking](multitasking.md).
|
||||
|
||||
---
|
||||
|
||||
## Task vs Process
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "struct Process"
|
||||
subgraph "struct Task (embedded)"
|
||||
NODE["Node<br/>ln_Name, ln_Pri, ln_Type"]
|
||||
SIGS["Signal Fields<br/>tc_SigAlloc, tc_SigWait,<br/>tc_SigRecvd"]
|
||||
STACK["Stack Bounds<br/>tc_SPLower, tc_SPUpper,<br/>tc_SPReg"]
|
||||
EXCEPT["Exception<br/>tc_ExceptCode,<br/>tc_ExceptData"]
|
||||
end
|
||||
PORT["pr_MsgPort<br/>(built-in message port)"]
|
||||
DOS["DOS Context<br/>pr_CurrentDir, pr_CIS, pr_COS,<br/>pr_CLI, pr_Arguments"]
|
||||
SEG["pr_SegList<br/>(loaded code segments)"]
|
||||
end
|
||||
|
||||
style NODE fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style DOS fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
```
|
||||
|
||||
| Capability | Task | Process |
|
||||
|---|---|---|
|
||||
| Scheduling | ✅ | ✅ |
|
||||
| Signals | ✅ | ✅ |
|
||||
| Message Ports | Manual setup | ✅ Built-in `pr_MsgPort` |
|
||||
| DOS I/O (Open/Read/Write) | ❌ | ✅ |
|
||||
| CLI environment | ❌ | ✅ (if started from Shell) |
|
||||
| Current directory | ❌ | ✅ `pr_CurrentDir` |
|
||||
| stdin/stdout/stderr | ❌ | ✅ `pr_CIS`/`pr_COS`/`pr_CES` |
|
||||
| Local variables | ❌ | ✅ `pr_LocalVars` |
|
||||
|
||||
> **Rule of thumb**: Use `CreateNewProcTags()` for everything. Use raw `AddTask()` only for bare-metal interrupt-level code or when you explicitly don't need DOS.
|
||||
|
||||
---
|
||||
|
||||
## struct Task
|
||||
|
||||
```c
|
||||
/* exec/tasks.h */
|
||||
/* exec/tasks.h — NDK39 */
|
||||
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 Node tc_Node; /* ln_Type=NT_TASK or NT_PROCESS */
|
||||
/* ln_Pri = scheduling priority (-128 to +127) */
|
||||
/* ln_Name = task name string */
|
||||
UBYTE tc_Flags; /* TF_LAUNCH, TF_SWITCH, TF_EXCEPT */
|
||||
UBYTE tc_State; /* TS_RUN, TS_READY, TS_WAIT, TS_EXCEPT */
|
||||
BYTE tc_IDNestCnt; /* interrupt disable nesting (-1 = enabled) */
|
||||
BYTE tc_TDNestCnt; /* task disable (Forbid) nesting (-1 = enabled) */
|
||||
ULONG tc_SigAlloc; /* allocated signal bits mask */
|
||||
ULONG tc_SigWait; /* signals this task is waiting for */
|
||||
ULONG tc_SigRecvd; /* signals received but not yet consumed */
|
||||
ULONG tc_SigExcept; /* signals that trigger tc_ExceptCode */
|
||||
UWORD tc_TrapAlloc; /* allocated trap vectors */
|
||||
UWORD tc_TrapAble; /* enabled trap vectors */
|
||||
APTR tc_ExceptData; /* data pointer passed to exception handler */
|
||||
APTR tc_ExceptCode; /* exception handler function */
|
||||
APTR tc_TrapData; /* data pointer passed to trap handler */
|
||||
APTR tc_TrapCode; /* trap handler function */
|
||||
APTR tc_SPReg; /* saved stack pointer (when not running) */
|
||||
APTR tc_SPLower; /* lowest valid stack address */
|
||||
APTR tc_SPUpper; /* highest valid stack address + 2 */
|
||||
void (*tc_Switch)(); /* called when task is switched OUT */
|
||||
void (*tc_Launch)(); /* called when task is switched IN */
|
||||
struct List tc_MemEntry; /* list of memory entries to free on RemTask */
|
||||
APTR tc_UserData; /* application-private data pointer */
|
||||
};
|
||||
```
|
||||
|
||||
### Key Field Reference
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `tc_Node.ln_Pri` | Scheduling priority (−128 to +127). Higher = more CPU time |
|
||||
| `tc_State` | Current state: `TS_RUN`, `TS_READY`, `TS_WAIT`, `TS_EXCEPT`, `TS_REMOVED` |
|
||||
| `tc_IDNestCnt` | Interrupt disable nesting counter. −1 = interrupts enabled |
|
||||
| `tc_TDNestCnt` | Task disable nesting counter. −1 = task switching enabled |
|
||||
| `tc_SigAlloc` | Bitmask of allocated signal bits (1 = allocated) |
|
||||
| `tc_SigWait` | Bitmask of signals this task will wake for (set by `Wait()`) |
|
||||
| `tc_SigRecvd` | Bitmask of signals received (set by `Signal()`, cleared by `Wait()`) |
|
||||
| `tc_SPReg` | Saved SP when task is not running — points to saved context on stack |
|
||||
| `tc_SPLower` / `tc_SPUpper` | Stack bounds — exec fills these with guard patterns for stack overflow detection |
|
||||
| `tc_MemEntry` | List of `MemEntry` structures — automatically freed by `RemTask()` |
|
||||
| `tc_Switch` / `tc_Launch` | Optional callbacks on context switch — used by FPU context save/restore |
|
||||
|
||||
---
|
||||
|
||||
## struct Process (extends Task)
|
||||
|
||||
```c
|
||||
/* dos/dosextens.h */
|
||||
/* dos/dosextens.h — NDK39 */
|
||||
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 */
|
||||
struct Task pr_Task; /* embedded Task — MUST be first field */
|
||||
struct MsgPort pr_MsgPort; /* built-in message port for DOS packets */
|
||||
UWORD pr_Pad;
|
||||
BPTR pr_SegList; /* segment list (loaded code) */
|
||||
LONG pr_StackSize; /* stack size in bytes */
|
||||
APTR pr_GlobVec; /* BCPL global vector (legacy) */
|
||||
LONG pr_TaskNum; /* CLI task number (0 = Workbench) */
|
||||
BPTR pr_StackBase; /* base of stack (BPTR) */
|
||||
LONG pr_Result2; /* secondary result (IoErr()) */
|
||||
BPTR pr_CurrentDir; /* current directory lock */
|
||||
BPTR pr_CIS; /* current input stream (stdin) */
|
||||
BPTR pr_COS; /* current output stream (stdout) */
|
||||
APTR pr_ConsoleTask; /* console handler task */
|
||||
APTR pr_FileSystemTask; /* filesystem handler task */
|
||||
BPTR pr_CLI; /* pointer to CommandLineInterface */
|
||||
APTR pr_ReturnAddr; /* return address for exit */
|
||||
APTR pr_PktWait; /* custom packet wait function */
|
||||
APTR pr_WindowPtr; /* window for error requesters (or −1 to suppress) */
|
||||
BPTR pr_HomeDir; /* home directory of program */
|
||||
LONG pr_Flags; /* PR_FREESEGLIST, PR_FREEARGS, etc. */
|
||||
void (*pr_ExitCode)(); /* exit handler */
|
||||
LONG pr_ExitData; /* data for exit handler */
|
||||
UBYTE *pr_Arguments; /* argument string */
|
||||
struct MinList pr_LocalVars; /* local shell environment variables */
|
||||
ULONG pr_ShellPrivate; /* shell private data */
|
||||
BPTR pr_CES; /* current error stream (stderr) — V39+ */
|
||||
};
|
||||
```
|
||||
|
||||
### Important Process Fields
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `pr_MsgPort` | Built-in message port — used for DOS packet communication |
|
||||
| `pr_CurrentDir` | Lock on current directory — inherited from parent |
|
||||
| `pr_CIS` / `pr_COS` / `pr_CES` | File handles for stdin, stdout, stderr |
|
||||
| `pr_CLI` | Non-NULL if started from CLI/Shell, NULL if from Workbench |
|
||||
| `pr_WindowPtr` | Window for error requesters. Set to `−1` to suppress "Please insert volume" dialogs |
|
||||
| `pr_Arguments` | Raw argument string (not parsed — use `ReadArgs()`) |
|
||||
| `pr_Result2` | Secondary error code — retrieved by `IoErr()` |
|
||||
| `pr_ExitCode` | Called when process exits — cleanup handler |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
| State | Constant | Value | Meaning |
|
||||
|---|---|---|---|
|
||||
| Invalid | `TS_INVALID` | 0 | Not a valid task |
|
||||
| Added | `TS_ADDED` | 1 | Just added, not yet scheduled |
|
||||
| Running | `TS_RUN` | 2 | Currently executing (exactly one task) |
|
||||
| Ready | `TS_READY` | 3 | On `SysBase→TaskReady`, waiting for CPU |
|
||||
| Waiting | `TS_WAIT` | 4 | Blocked on `Wait()` — on `SysBase→TaskWait` |
|
||||
| Exception | `TS_EXCEPT` | 5 | Handling a task-level exception |
|
||||
| Removed | `TS_REMOVED` | 6 | Removed from scheduling |
|
||||
|
||||
### State Machine
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> ADDED : AddTask() / CreateNewProc()
|
||||
ADDED --> READY : Exec enqueues by priority
|
||||
READY --> RUN : Scheduler picks highest priority
|
||||
RUN --> READY : Quantum expired / higher-priority task wakes
|
||||
RUN --> WAIT : Wait(signal_mask)
|
||||
WAIT --> READY : Signal() delivers matching signal
|
||||
RUN --> EXCEPT : Exception signal received
|
||||
EXCEPT --> RUN : Exception handler returns
|
||||
RUN --> REMOVED : RemTask() / task function returns
|
||||
REMOVED --> [*]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
The scheduler (`exec.library` internal) picks the highest-priority task from `SysBase→TaskReady`:
|
||||
|
||||
- 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
|
||||
| Priority Range | Typical Use |
|
||||
|---|---|
|
||||
| +127 | (Unused — would starve everything) |
|
||||
| +20 | input.device handler |
|
||||
| +10 | trackdisk.device, filesystem handlers |
|
||||
| +5 | Real-time applications (audio players) |
|
||||
| 0 | **Normal applications** |
|
||||
| −1 | Background workers (file copy, indexing) |
|
||||
| −128 | Idle task |
|
||||
|
||||
```c
|
||||
SetTaskPri(FindTask(NULL), 5); /* raise current task to priority 5 */
|
||||
/* Change priority of current task */
|
||||
BYTE oldPri = SetTaskPri(FindTask(NULL), 5); /* LVO -300 */
|
||||
/* Returns old priority */
|
||||
```
|
||||
|
||||
> **Warning**: Priority > 20 will starve the input handler. Priority > 10 will starve filesystem tasks. Choose wisely.
|
||||
|
||||
---
|
||||
|
||||
## Creating Tasks
|
||||
|
||||
### Raw Task (exec level)
|
||||
|
||||
```c
|
||||
#define STACKSIZE 4096
|
||||
|
||||
/* Allocate task structure and stack together */
|
||||
struct Task *task = AllocMem(sizeof(struct Task), MEMF_PUBLIC | MEMF_CLEAR);
|
||||
APTR stack = AllocMem(STACKSIZE, MEMF_ANY);
|
||||
|
||||
task->tc_Node.ln_Type = NT_TASK;
|
||||
task->tc_Node.ln_Name = "MyTask";
|
||||
task->tc_Node.ln_Pri = 0;
|
||||
task->tc_SPLower = stack;
|
||||
task->tc_SPUpper = (APTR)((ULONG)stack + STACKSIZE);
|
||||
task->tc_SPReg = task->tc_SPUpper; /* Stack grows downward */
|
||||
|
||||
/* AddTask(task, initialPC, finalPC) */
|
||||
AddTask(task, MyTaskEntry, NULL); /* LVO -282 */
|
||||
/* NULL finalPC = exec's default task cleanup */
|
||||
```
|
||||
|
||||
> **Caution**: Raw `AddTask()` tasks cannot call DOS functions (Open, Read, Write, Printf). They have no `pr_MsgPort`, no current directory, no stdin/stdout. Use `CreateNewProcTags()` for anything that needs I/O.
|
||||
|
||||
### Process (DOS level — preferred)
|
||||
|
||||
```c
|
||||
struct Process *proc = CreateNewProcTags(
|
||||
NP_Entry, MyProcEntry, /* entry function */
|
||||
NP_Name, "MyProcess", /* process name */
|
||||
NP_StackSize, 8192, /* stack size in bytes */
|
||||
NP_Priority, 0, /* scheduling priority */
|
||||
NP_CurrentDir, DupLock(GetProgramDir()), /* inherit directory */
|
||||
NP_Input, NULL, /* or a file handle for stdin */
|
||||
NP_Output, NULL, /* or a file handle for stdout */
|
||||
NP_CloseInput, FALSE, /* don't close stdin on exit */
|
||||
NP_CloseOutput,FALSE, /* don't close stdout on exit */
|
||||
TAG_DONE
|
||||
);
|
||||
|
||||
if (!proc) { /* creation failed */ }
|
||||
```
|
||||
|
||||
### Task Cleanup: tc_MemEntry
|
||||
|
||||
Memory added to `tc_MemEntry` is automatically freed when the task is removed:
|
||||
|
||||
```c
|
||||
/* Add stack + task struct to auto-cleanup list */
|
||||
struct MemList *ml = AllocMem(sizeof(struct MemList) + sizeof(struct MemEntry),
|
||||
MEMF_PUBLIC | MEMF_CLEAR);
|
||||
ml->ml_NumEntries = 2;
|
||||
ml->ml_ME[0].me_Un.meu_Addr = task;
|
||||
ml->ml_ME[0].me_Length = sizeof(struct Task);
|
||||
ml->ml_ME[1].me_Un.meu_Addr = stack;
|
||||
ml->ml_ME[1].me_Length = STACKSIZE;
|
||||
AddHead(&task->tc_MemEntry, &ml->ml_Node);
|
||||
/* Now RemTask() will free both task struct and stack */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating Tasks and Processes
|
||||
## Task Identity
|
||||
|
||||
```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);
|
||||
/* Get current task */
|
||||
struct Task *me = FindTask(NULL); /* LVO -294 — NULL = current */
|
||||
Printf("Running as: %s (pri %ld)\n", me->tc_Node.ln_Name, me->tc_Node.ln_Pri);
|
||||
|
||||
/* 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);
|
||||
```
|
||||
/* Find another task by name */
|
||||
Forbid();
|
||||
struct Task *other = FindTask("TargetApp"); /* Returns NULL if not found */
|
||||
Permit();
|
||||
|
||||
---
|
||||
|
||||
## 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) {
|
||||
/* Check if current task is a Process */
|
||||
if (me->tc_Node.ln_Type == NT_PROCESS)
|
||||
{
|
||||
struct Process *pr = (struct Process *)me;
|
||||
/* access pr_CLI, pr_MsgPort, etc. */
|
||||
/* Safe to use pr_CLI, pr_MsgPort, pr_CurrentDir, etc. */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Removing Tasks
|
||||
|
||||
```c
|
||||
/* Remove current task (task commits suicide): */
|
||||
RemTask(NULL); /* LVO -288 — NULL = remove self */
|
||||
/* Never returns — task is destroyed */
|
||||
|
||||
/* Remove another task (dangerous!): */
|
||||
Forbid();
|
||||
struct Task *victim = FindTask("OtherTask");
|
||||
if (victim) RemTask(victim);
|
||||
Permit();
|
||||
```
|
||||
|
||||
> **Caution**: `RemTask()` on another task does NOT:
|
||||
> - Close its open files
|
||||
> - Free its message ports
|
||||
> - Reply to pending messages
|
||||
> - Close its libraries
|
||||
>
|
||||
> This leaks resources permanently. Use `Signal()` + cooperative shutdown instead.
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### 1. Calling DOS from a Raw Task
|
||||
|
||||
```c
|
||||
/* BUG — raw Task has no DOS environment */
|
||||
void __saveds MyTaskFunc(void)
|
||||
{
|
||||
BPTR fh = Open("RAM:test", MODE_NEWFILE); /* CRASH — no pr_FileSystemTask */
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Stack Overflow
|
||||
|
||||
```c
|
||||
/* BUG — recursive function exhausts 4 KB stack */
|
||||
void MyTask(void)
|
||||
{
|
||||
char buffer[2048]; /* Half the stack gone in one frame */
|
||||
ProcessData(buffer);
|
||||
MyTask(); /* Stack overflow → corrupts next task's memory */
|
||||
}
|
||||
```
|
||||
|
||||
The system does NOT catch stack overflows — memory just gets silently corrupted. Use `StackSwap()` for deep recursion.
|
||||
|
||||
### 3. RemTask Without Cleanup
|
||||
|
||||
```c
|
||||
/* BUG — resources leaked */
|
||||
void MyProcess(void)
|
||||
{
|
||||
struct Library *base = OpenLibrary("mylib.library", 0);
|
||||
struct MsgPort *port = CreateMsgPort();
|
||||
/* ... */
|
||||
RemTask(NULL); /* Library not closed, port not freed */
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Caching Task Pointers
|
||||
|
||||
```c
|
||||
/* BUG — task may have exited */
|
||||
struct Task *t = FindTask("Worker");
|
||||
/* ... some time passes ... */
|
||||
Signal(t, mask); /* t may be freed memory */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use `CreateNewProcTags()`** — raw `AddTask()` is for kernel-level code only
|
||||
2. **Use `tc_MemEntry`** for automatic cleanup of task-allocated memory
|
||||
3. **Always check `FindTask()` return** — tasks can exit at any time
|
||||
4. **Use cooperative shutdown** — send a signal, let the task clean up and exit itself
|
||||
5. **Set `pr_WindowPtr = -1`** to suppress "Please insert volume" dialogs in background tasks
|
||||
6. **Size stacks generously** — 8192+ bytes for processes, 4096+ for tasks
|
||||
7. **Use `StackSwap()`** if you need temporary deep stack for recursive algorithms
|
||||
8. **Never `RemTask()` another task** in production code — it leaks everything
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/tasks.h`, `dos/dosextens.h`
|
||||
- ADCD 2.1: `AddTask`, `RemTask`, `FindTask`, `SetTaskPri`, `CreateNewProc`
|
||||
- NDK39: `exec/tasks.h`, `dos/dosextens.h`, `dos/dostags.h`
|
||||
- ADCD 2.1: `AddTask`, `RemTask`, `FindTask`, `SetTaskPri`, `CreateNewProcTags`, `StackSwap`
|
||||
- See also: [Multitasking](multitasking.md) — scheduler algorithm, context switch, IPC strategies
|
||||
- See also: [Signals](signals.md) — tc_SigAlloc, tc_SigRecvd, tc_SigWait
|
||||
- *Amiga ROM Kernel Reference Manual: Exec* — tasks and scheduling chapter
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue