amiga-bootcamp/06_exec_os/tasks_processes.md
Ilia Sharin 59929047d4 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
2026-04-23 17:55:31 -04:00

14 KiB
Raw Permalink Blame History

← Home · Exec Kernel

Tasks and Processes — Structures, States, Creation, Scheduling

Overview

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.


Task vs Process

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

/* exec/tasks.h — NDK39 */
struct Task {
    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)

/* dos/dosextens.h — NDK39 */
struct Process {
    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 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

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:

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
/* 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)

#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)

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:

/* 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 */

Task Identity

/* 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);

/* Find another task by name */
Forbid();
struct Task *other = FindTask("TargetApp");  /* Returns NULL if not found */
Permit();

/* Check if current task is a Process */
if (me->tc_Node.ln_Type == NT_PROCESS)
{
    struct Process *pr = (struct Process *)me;
    /* Safe to use pr_CLI, pr_MsgPort, pr_CurrentDir, etc. */
}

Removing Tasks

/* 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

/* 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

/* 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

/* 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

/* 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, dos/dostags.h
  • ADCD 2.1: AddTask, RemTask, FindTask, SetTaskPri, CreateNewProcTags, StackSwap
  • See also: Multitasking — scheduler algorithm, context switch, IPC strategies
  • See also: Signals — tc_SigAlloc, tc_SigRecvd, tc_SigWait
  • Amiga ROM Kernel Reference Manual: Exec — tasks and scheduling chapter