Programs
How LainDOS moves from a shell command to a child process and back: EXEC, PSP layout, environments, COM and MZ EXE handoff, overlays, return codes, and termination cleanup.
The loader path
LainDOS is single-tasking, but DOS still has process state. A child gets its own PSP, MCB owner, JFT view, DTA, environment block, command tail, and terminate vectors; the parent is suspended by a saved real-mode stack frame until the child exits.
0102030405EXEC captures the parent frame
AH=4Bh is the bridge from a shell command to a child process.The parent supplies the path in DS:DX and an EXEC parameter block in ES:BX. LainDOS saves the parent's registers, PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer., DTADTAThe DOS buffer where FindFirst/FindNext and some FCB calls return directory search results., and stack frame before resolving the child. That saved frame is what makes returning from a child look like a normal DOS call to the parent.
Only AL=00h load-and-run and AL=03h overlay load are implemented. Other EXEC variants fail explicitly instead of becoming silent compatibility stubs.
Tests that pin this
scripts/test_shell.pyscripts/test_execparam.pyscripts/test_spawn.pyResolve, size, and classify the image
The first sector decides whether the child is COM or MZ EXE.load_exec_program turns the path into a directory entry, records the first cluster and size, reads sector zero into SEC_BUF, and then checks for the MZMZThe DOS EXE header signature. LainDOS uses it to distinguish EXE files from flat COM programs. signature. COM programs get a flat allocation plus slack; EXE programs compute a paragraph requirement from the header, minalloc, maxalloc, and file image size.
After allocation the image is loaded immediately above the PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. at prog_seg + 10h. COM images get their PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. and command tail before control transfers; EXE images wait until relocation setup has validated the header.
Tests that pin this
scripts/test_badreloc.pyscripts/test_memrelease.pyscripts/test_shell.pyEnvironment blocks and executable path tail
Each child receives an environment block owned by its PSP.If the EXEC parameter block names a custom environment, LainDOS copies it. Otherwise it writes the default variables, including COMSPEC, PATH, PROMPT, and BLASTER. It then appends the DOS convention tail: a word count followed by the fully normalized executable path.
The environment MCBMCBA 16-byte DOS memory header that describes the allocated or free block immediately after it. starts with a temporary owner while loading. Once the PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. is committed, assign_exec_environment_owner stamps that MCBMCBA 16-byte DOS memory header that describes the allocated or free block immediately after it. with the child PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. so termination cleanup can release it with the rest of the process.
Tests that pin this
scripts/test_execenv.pyscripts/test_envmcb.pyscripts/test_envpath.pyThe Program Segment Prefix
The PSP is the child process contract DOS programs expect at DS=ES.LainDOS clears the 256-byte PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer., writes the CD 20 terminate instruction, records the top-of-memory word, copies INT 22h/23h/24h vectors, builds the Job File Table, links the parent PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer., and stores the environment pointer at PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.:2Ch.
The command tail lives at PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.:80h. Default FCBs from the EXEC parameter block are copied to PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.:5Ch and PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.:6Ch so older startup code and C runtimes see the DOS layout they expect.
Tests that pin this
scripts/test_shell.pyscripts/test_execparam.pyscripts/test_jft.pyCOM and EXE handoff
COM is flat; MZ EXE is relocated before CS:IP and SS:SP are loaded.COM handoff is simple: DS, ES, and SS all point at the PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer., SP is placed near the top of the allocated block, and a far return lands at offset 0100h. EXE handoff is stricter: relocation table bounds are validated, relocation entries are applied against exe_load_seg, the image is slid down past the MZMZThe DOS EXE header signature. LainDOS uses it to distinguish EXE files from flat COM programs. header, and then the header's CS:IP and SS:SP are used.
Both paths reset the keyboard buffer and FPU before entering the child so old startup code sees a predictable machine state.
Tests that pin this
scripts/test_badreloc.pyscripts/test_overlay.pyscripts/test_regpres.pyOverlays load without becoming processes
EXEC AL=03h copies code into a caller-supplied segment.Overlay loads use the same path resolver but no PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. switch. The caller supplies a load segment and relocation segment in the overlay parameter block. MZMZThe DOS EXE header signature. LainDOS uses it to distinguish EXE files from flat COM programs. overlays skip the header, copy the image to the requested segment, and apply relocation entries using the supplied relocation base.
Because no child process starts, success returns directly to the caller with carry clear; failures return DOS-style errors without changing process context.
Tests that pin this
scripts/test_overlay.pytests/programs/ovltest.asmtests/programs/overlay.asmTermination returns to the parent
Cleanup releases child-owned state and restores the saved parent stack.Normal termination clears transient hardware state, releases inherited handles, closes child-owned handles, frees child-owned MCBs, coalesces the arena, restores the parent PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. from PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.:16h, and jumps back through the saved EXEC frame.
TSR termination is different: it keeps the requested part of the PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. block resident, frees the rest of the child's allocations, records return type 3, and then restores the parent just like a normal return.
Tests that pin this
scripts/test_retcode.pyscripts/test_termflush.pyscripts/test_tsr.py