Process Creation and Structure¶
An AmigaDOS process is an exec task extended with what dos.library needs: a message port for talking to handlers, a current directory, standard input/output/error streams, and the secondary result code returned by IoErr(). Most dos.library functions must be called from a process because they read and update these fields. This article covers struct Process field by field, every NP_* tag of CreateNewProc() with its real default and ownership rules, the legacy CreateProc(), process termination, and the accessor functions (several new in V47).
The rule of thumb throughout: use an accessor function whenever one exists (Input(), Cli(), GetCurrentDir(), IoErr() …). Only pr_WindowPtr and pr_TaskNum have no accessor and are meant to be touched directly.
struct Process¶
From dos/dosextens.h:
struct Process {
struct Task pr_Task;
struct MsgPort pr_MsgPort;
WORD pr_Pad;
BPTR pr_SegList;
LONG pr_StackSize;
APTR pr_GlobVec;
LONG pr_TaskNum;
BPTR pr_StackBase;
LONG pr_Result2;
BPTR pr_CurrentDir;
BPTR pr_CIS;
BPTR pr_COS;
APTR pr_ConsoleTask;
APTR pr_FileSystemTask;
BPTR pr_CLI;
APTR pr_ReturnAddr;
APTR pr_PktWait;
APTR pr_WindowPtr;
/* V36 and later */
BPTR pr_HomeDir;
LONG pr_Flags;
void (*pr_ExitCode)();
LONG pr_ExitData;
STRPTR pr_Arguments;
struct MinList pr_LocalVars;
ULONG pr_ShellPrivate;
BPTR pr_CES;
};
| Field | Meaning | Access |
|---|---|---|
pr_Task |
exec task; tc_Node.ln_Type is NT_PROCESS (not NT_TASK) |
— |
pr_MsgPort |
Port used by dos.library to exchange packets with handlers |
— |
pr_Pad |
Alignment only | — |
pr_SegList |
BCPL segment array (not a seglist), see below | leave alone |
pr_StackSize |
Stack size in bytes (multiple of 4) — of the shell, not of a command it runs | use tc_SPUpper - tc_SPLower |
pr_GlobVec |
BCPL Global Vector; for C/assembler processes the shared system vector | leave alone |
pr_TaskNum |
Shell task number as printed by Status; 0 for Workbench programs, handlers and processes made by CreateNewProc()/CreateProc() |
read directly; FindCliProc() |
pr_StackBase |
BPTR to the low end of the stack (start of the BCPL stack); initialized but unused, and wrong for commands | leave alone |
pr_Result2 |
Secondary result code | IoErr(), SetIoErr() |
pr_CurrentDir |
Current directory lock; ZERO = root of pr_FileSystemTask |
GetCurrentDir(), CurrentDir() |
pr_CIS |
Standard input; may be ZERO |
Input(), SelectInput() |
pr_COS |
Standard output; may be ZERO |
Output(), SelectOutput() |
pr_ConsoleTask |
MsgPort of the console handler for * and CONSOLE: |
GetConsoleTask(), SetConsoleTask() |
pr_FileSystemTask |
MsgPort of the file system used for the ZERO lock (boot volume by default) |
GetFileSysTask(), SetFileSysTask() |
pr_CLI |
BPTR to struct CommandLineInterface, ZERO if not a shell |
Cli() (no setter: do not modify) |
pr_ReturnAddr |
BCPL stack frame used by Exit() |
leave alone |
pr_PktWait |
Optional function called to wait for a packet reply | see below |
pr_WindowPtr |
Window for error requesters; NULL = default public screen, -1 = no requesters |
read/write directly |
pr_HomeDir |
Lock on the directory the program was loaded from (PROGDIR:) |
GetProgramDir(), SetProgramDir() |
pr_Flags |
PRF_* cleanup flags, system use only |
do not touch |
pr_ExitCode |
Function called first during process shutdown | set via NP_ExitCode |
pr_ExitData |
Argument for pr_ExitCode |
set via NP_ExitData |
pr_Arguments |
Command-line arguments (without command name), or NULL |
GetArgStr(), SetArgStr() |
pr_LocalVars |
Local shell variables and aliases; no semaphore, owner process only | GetVar()/SetVar() family |
pr_ShellPrivate |
Reserved for the Shell | do not touch |
pr_CES |
Standard error stream; ZERO means "use pr_COS". Present since V36 but only used from V47 |
ErrorOutput(), SelectError() |
Notes on individual fields¶
- Stack size on the stack. Before starting the process,
dos.librarypushes the stack size, so((ULONG *)pr_Task.tc_SPUpper)[-1]holds the stack size in bytes. Some binaries, notably those built with Aztec (Manx) C, depend on this. - Trap handler.
dos.libraryinstalls its owntc_TrapCode, which shows the "Software Failure" requester (suspend =Wait(0)forever; otherwise exec'sAlert()). Up to V45 it misread 68K exception stack frames and left garbage on the stack before entering the ROM debugger. - Real stack bounds.
pr_StackSizeandpr_StackBasedescribe the shell's stack when a command runs in a shell.tc_SPLower/tc_SPUpperare always correct:StackSwap()andRunCommand()update them. ZEROstreams are notNIL:. Workbench-started programs getpr_CIS,pr_COSandpr_CESallZERO, and no console task. Reading from or writing to aZEROhandle crashes. Older documentation claiming they receiveNIL:handles is wrong.ZEROhome directory. Ifpr_HomeDirisZERO(resident or built-in commands), resolvingPROGDIR:makesGetDeviceProc()ask for a volume calledPROGDIR— most likely a defect.GetProgramDir()returningZEROmeans "no home directory", not "boot volume root". Never unlockpr_HomeDir;dos.library(or whoever loaded the program) does.pr_WindowPtrin shell commands. Commands share their process with the shell and every other command run in it. A command that changespr_WindowPtrmust save the old value and restore it before exiting, otherwise the next error requester may reference a closed window and crash. Requesters raised by the file system itself (e.g. an unreadable block) are not redirected by this field.pr_Argumentsvs.ReadArgs(). The arguments are also in registera0(length ind0) at startup and in the buffer of thepr_CIShandle.ReadArgs()reads the input buffer, notpr_Arguments, soSetArgStr()never changes whatReadArgs()sees.
/* Shell command that suppresses DOS requesters while it runs */
struct Process *me = (struct Process *)FindTask(NULL);
APTR oldwin = me->pr_WindowPtr;
me->pr_WindowPtr = (APTR)-1L;
/* ... work that might otherwise show "Please insert volume" ... */
me->pr_WindowPtr = oldwin; /* always restore before returning */
/* Stack size of the running program, correct under Shell and Workbench */
struct Task *me = FindTask(NULL);
ULONG stack = (ULONG)me->tc_SPUpper - (ULONG)me->tc_SPLower;
pr_PktWait¶
If non-NULL, dos.library calls this function instead of waiting on pr_MsgPort whenever it waits for a packet reply:
struct Message *(*pr_PktWait)(void); /* result in d0 */
It receives no arguments (find the process with FindTask(NULL)), must block until a message arrives, and must never return NULL. dos.library extracts the packet from the returned message. See DOS Packets.
Process flags (pr_Flags)¶
System use only — listed here to explain the cleanup logic:
| Flag | Value | Cleanup action at exit | Set by |
|---|---|---|---|
PRF_FREESEGLIST 2.0+ |
1 | UnLoadSeg() of pr_SegList[3] |
NP_FreeSeglist |
PRF_FREECURRDIR 2.0+ |
2 | UnLock() pr_CurrentDir |
any non-ZERO initial current directory |
PRF_FREECLI 2.0+ |
4 | Free the CLI structure and everything it references | NP_Cli |
PRF_CLOSEINPUT 2.0+ |
8 | Close() pr_CIS |
NP_CloseInput |
PRF_CLOSEOUTPUT 2.0+ |
16 | Close() pr_COS |
NP_CloseOutput |
PRF_FREEARGS 2.0+ |
32 | Free pr_Arguments |
NP_Arguments |
PRF_CLOSEERROR 3.2+ |
64 | Close() pr_CES (V47) |
NP_CloseError |
The only way to avoid PRF_FREECURRDIR is to pass a ZERO lock in NP_CurrentDir.
pr_ExitCode¶
Called as the first step of process shutdown:
/* SAS/C register syntax */
LONG __asm ExitFunc(register __d0 LONG rc, /* value in d0 at final RTS */
register __d1 LONG exitdata); /* pr_ExitData */
The return value is meant to replace the process result, but it is currently useless: the shutdown code goes straight into exec RemTask(), which discards all registers.
The BCPL legacy fields¶
pr_SegList points to an array whose entry 0 is a count and whose other entries are BPTRs to segment lists: entries 1 and 2 hold the BCPL klib and blib system segments, entry 3 the program's own seglist, and entry 4 the shell's seglist when the process is a shell (the shell moves itself from 3 to 4 to make room for commands). The count is 3 for BCPL programs (handlers with GLOBVEC 0 or -3) and 2 for C/assembler programs, so the binder ignores entry 3 for them. pr_GlobVec and pr_ReturnAddr belong to the same mechanism. See BCPL Runtime Binder; this layout reflects V47 and may change.
Creating a process: CreateNewProc()¶
struct Process *CreateNewProc(CONST struct TagItem *tags); /* V36 */
struct Process *CreateNewProcTagList(CONST struct TagItem *tags); /* same entry */
struct Process *CreateNewProcTags(ULONG tag1type, ...); /* varargs stub */
Callable from a plain task as well. On success the process is already running (depending on its priority). On failure it returns NULL and does not set IoErr() consistently.
Tags (dos/dostags.h)¶
| Tag | Type | Default | Ownership / notes |
|---|---|---|---|
NP_Seglist 2.0+ |
BPTR | — | Seglist to run, entered at the first byte of the first segment. One of NP_Seglist/NP_Entry is required |
NP_FreeSeglist 2.0+ |
BOOL | FALSE | UnLoadSeg() the seglist at exit. Never freed if creation fails. Must be absent or FALSE with NP_Entry |
NP_Entry 2.0+ |
APTR | — | Absolute entry address; mutually exclusive with NP_Seglist |
NP_Input 2.0+ |
BPTR | Open("NIL:") |
Becomes pr_CIS. On failure the caller still owns a handle passed here; the default NIL: handle is cleaned up |
NP_CloseInput 2.0+ |
BOOL | TRUE | Closes whatever is in pr_CIS at exit |
NP_Output 2.0+ |
BPTR | Open("NIL:") |
Becomes pr_COS; same ownership rule as NP_Input |
NP_CloseOutput 2.0+ |
BOOL | TRUE | Closes whatever is in pr_COS at exit |
NP_Error 2.0+ |
BPTR | ZERO |
Becomes pr_CES (ZERO = fall back to pr_COS). Not implemented before V47 |
NP_CloseError 2.0+ |
BOOL | FALSE | V47; closes whatever is in pr_CES at exit. Different default for compatibility |
NP_CurrentDir 2.0+ |
BPTR | DupLock() of caller's directory (ZERO from a task) |
Absorbed: released at exit, and also released if creation fails |
NP_StackSize 2.0+ |
LONG | 4000 | Bytes |
NP_Name 2.0+ |
STRPTR | "New Process" |
Copied; the caller keeps the original |
NP_Priority 2.0+ |
LONG | 0 | −128…127; useful range 0…19 |
NP_ConsoleTask 2.0+ |
struct MsgPort * |
caller's console (NULL from a task) | Handler for * and CONSOLE: |
NP_WindowPtr 2.0+ |
struct Window * |
see below | Becomes pr_WindowPtr |
NP_HomeDir 2.0+ |
BPTR | DupLock() of caller's pr_HomeDir (ZERO from a task) |
Absorbed: released at exit even if it is the caller's own lock (pass a DupLock()), and released if creation fails |
NP_CopyVars 2.0+ |
BOOL | TRUE (per dostags.h) |
Deep copy of the caller's local variables and aliases; nothing is copied from a task |
NP_Cli 2.0+ |
BOOL | FALSE | Create a CommandLineInterface for the new process (pr_CLI) |
NP_Path 2.0+ |
BPTR | copy of caller's path (if caller has a CLI) | Only with NP_Cli. Referenced, not copied: owned by the new process on success, still the caller's on failure |
NP_CommandName 2.0+ |
STRPTR | caller's command name, else empty | Only with NP_Cli; copied |
NP_Arguments 2.0+ |
STRPTR | none | Copied into pr_Arguments, a0/d0, and the pr_CIS buffer. NP_Input must then not be ZERO |
NP_ExitCode 2.0+ |
function | none | Installed in pr_ExitCode |
NP_ExitData 2.0+ |
LONG | — | Passed to NP_ExitCode in d1 |
NP_NotifyOnDeath, NP_Synchronous |
— | — | Defined but ignored |
Note: the NDK 3.2 autodoc and
dos/dostags.hstate thatNP_PriorityandNP_WindowPtrdefault to the same value as the parent.
NP_WindowPtr default. Without the tag, the caller's window pointer is copied only if it is NULL or -1; any real window pointer becomes NULL in the child (and it is NULL from a task). This keeps the child from outliving the parent's window. To share the caller's window, pass the tag explicitly — and keep the window open as long as the child runs.
Not a tag: pr_FileSystemTask is copied from the caller, or taken from dos.library's default file system when the caller is a task.
NP_Cli details. Prompt, path and command name are copied from the caller's CLI if there is one, otherwise defaults are used. cli_CurrentInput and cli_StandardInput are set to the new pr_CIS, cli_StandardOutput to pr_COS. Startup code should leave these streams alone: some commands (e.g. Execute) compare the process streams with those in the CLI structure to detect redirection. To run shell commands, System() is usually the better tool.
Note: the NDK 3.2
dos/dostags.hstates thatNP_Argumentswas not operational until V37 (random registers in V36).
What is released on failure¶
CreateNewProc() is inconsistent here. If it returns NULL:
- Released by dos:
NP_CurrentDirandNP_HomeDirlocks (and any defaultNIL:handles, the copied name/arguments and an implicitly copied path). - Still yours: the seglist (regardless of
NP_FreeSeglist), handles passed inNP_Input/NP_Output/NP_Error, and an explicitNP_Pathlist.
Note: the NDK 3.2 autodoc states that on a NULL return you must free items passed via tags, such as
NP_CurrentDir. Doing so would unlock the lock twice.
Example: run a loaded program as a background process¶
#include <exec/types.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <proto/dos.h>
BOOL LaunchBackground(STRPTR file)
{
struct Process *pr;
BPTR seg, out;
if (!(seg = LoadSeg(file)))
return FALSE;
out = Open("CON:0/0/480/120/Child/AUTO/CLOSE/WAIT", MODE_NEWFILE);
if (!out) {
UnLoadSeg(seg);
return FALSE;
}
pr = CreateNewProcTags(NP_Seglist, (ULONG)seg,
NP_FreeSeglist, TRUE,
NP_Output, (ULONG)out,
NP_CloseOutput, TRUE,
NP_StackSize, 8192,
NP_Name, (ULONG)"Child",
TAG_DONE);
if (!pr) {
/* seglist and NP_Output stay ours on failure; the default
current dir / home dir locks were already released by dos */
Close(out);
UnLoadSeg(seg);
return FALSE;
}
return TRUE; /* child frees seg and closes out when it exits */
}
On V47, ScanStackToken(seg, 8192) gives the stack size honouring the program's $STACK: cookie.
Starting Workbench or Shell programs¶
CreateNewProc() alone does not provide the environment such programs expect:
- Workbench programs also need a
WBStartupmessage (workbench/startup.h) sent to the new process's port. It can be built and sent by hand; theWBLoadprogram added with AmigaOS version 45 does exactly that. - Shell commands should be run with
RunCommand()(overlays the calling process, which must already have a shell environment) orSystem()(creates a shell and runs the command line in it).
CreateProc() (legacy)¶
struct MsgPort *CreateProc(CONST_STRPTR name, LONG pri, BPTR segList, LONG stackSize);
- Returns the new process's
pr_MsgPort, not astruct Process *. Subtractsizeof(struct Task)to get the process. - Name is copied;
pr_ConsoleTask,pr_WindowPtrandpr_FileSystemTaskare copied from the caller (NULL/0 anddos.library's default file system from a task). - Input, output and error handles are
ZERO; current and home directory areZERO; no CLI, no arguments, no variables. - The seglist is never freed, and there is no protocol telling you when it is safe to free it. Prefer
CreateNewProc()withNP_FreeSeglistunless V34 compatibility is required. - Callable from a task since V36; in V34 and below that crashes.
- Returns
NULLon failure without settingIoErr()reliably.
A seglist that will never be passed to UnLoadSeg() can be faked in assembler:
CNOP 0,4 ; long-word align
DC.L 16 ; segment "length" (faked)
DC.L 0 ; BPTR to next segment: none
; ... start of code ...
Pass MKBADDR() of the address of the DC.L 0 link word as the seglist.
Terminating a process¶
There is no function to kill another process. A process ends when its code returns from main() or executes its final RTS; the AmigaDOS shutdown code then calls pr_ExitCode, releases what the PRF_* flags say, and removes the task.
| Method | Verdict |
|---|---|
return from main() / final RTS |
Correct |
C library exit() |
Correct for C programs: releases ANSI-C resources too |
Exit(rc) |
Avoid. BCPL legacy: unwinds the BCPL stack frame in pr_ReturnAddr and returns to the process shutdown code or to the shell (RunCommand() cleanup). Releases only what AmigaDOS allocated at startup, nothing of the program's or its C startup code. rc is ignored for processes |
exec RemTask() |
Never: skips even the AmigaDOS cleanup |
VOID Exit(LONG returnCode);
atexit() in ANSI C or NP_ExitCode are the clean ways to hook shutdown.
Stack size¶
NP_StackSize defaults to 4000 bytes. V34 and below reserved 1500 bytes at the bottom of the 68K stack for the BCPL environment of every called function, so DOS-using programs needed a larger stack, while programs not using dos.library could get away with about 2000 bytes. The default stack can only be raised by the program's $STACK: cookie (see Hunk Version and Stack Cookies).
Accessor functions¶
These act on the calling process and do not touch IoErr() unless stated.
| Function | Since | Field | Notes |
|---|---|---|---|
BPTR Input(void) |
all | pr_CIS |
ZERO if none; do not Close() it |
BPTR SelectInput(BPTR fh) |
V36 | pr_CIS |
Returns old handle; restore before exit |
BPTR Output(void) |
all | pr_COS |
ZERO if none |
BPTR SelectOutput(BPTR fh) |
V36 | pr_COS |
Restore before exit |
ErrorOutput(void) |
V47 | pr_CES |
Returns pr_CES, else pr_COS, else ZERO — the stderr equivalent |
SelectError(BPTR fh) |
V47 | pr_CES |
Restore before exit |
BPTR GetCurrentDir(void) |
V47 | pr_CurrentDir |
Does not change the directory |
BPTR CurrentDir(BPTR lock) |
all | pr_CurrentDir |
The new lock now belongs to the process; restore the original lock (not a copy) before a shell command exits |
LONG IoErr(void) / LONG SetIoErr(LONG) |
all / V36 | pr_Result2 |
SetIoErr(0) before calls that do not always set it |
struct MsgPort *SetConsoleTask(struct MsgPort *) |
V36 | pr_ConsoleTask |
Takes a port, not a process; revert before returning to the shell |
struct MsgPort *SetFileSysTask(struct MsgPort *) |
V36 | pr_FileSystemTask |
Should match SYS:; changing it makes path resolution inconsistent between processes |
BPTR GetProgramDir(void) |
V36 | pr_HomeDir |
ZERO = no home directory; never unlock |
BPTR SetProgramDir(BPTR lock) |
V36 | pr_HomeDir |
Restore the original before exit |
STRPTR GetArgStr(void) |
V36 | pr_Arguments |
NULL for Workbench programs; excludes the command name |
STRPTR SetArgStr(STRPTR) |
V36 | pr_Arguments |
Does not affect ReadArgs() |
struct CommandLineInterface *Cli(void) |
V36 | pr_CLI |
No setter |
Note: the NDK 3.2
clib/dos_protos.hdeclaresErrorOutput()andSelectError()as returningLONG.
DoPkt() also works from a plain task, but then cannot set IoErr(); CreateNewProc() and (since V36) CreateProc() are task-callable too. AddPart(), FilePart() and PathPart() only manipulate strings and need no process.
See Also¶
- dos.library Reference
- LoadSeg and Segment Lists
- BCPL Runtime Binder
- DOS Packets
- AmigaDOS Shell
- DOS Error Reporting
- BPTR, BSTR, and BCPL Legacy
Sources: Thomas Richter, Amiga ROM Kernel Reference Manual: AmigaDOS (2024, v1.27), ch. 10 (pp. 140–161); NDK 3.2 R4 dos/dosextens.h, dos/dostags.h, clib/dos_protos.h, Autodocs/dos.doc Updated: 2026-11-13