# taskswitch -- Cooperative Task Switching Library Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS protected mode). Built as `libtasks.lib` -- a DXE3 module loaded by the DVX loader. ## Why Cooperative DOS is single-threaded with no kernel scheduler. DPMI provides no thread or timer-based preemption. The DVX GUI event model is inherently single-threaded (one compositor, one input queue, one window stack). Cooperative switching lets each task yield at safe points, avoiding the need for synchronization primitives entirely. ## Scheduling Algorithm Credit-based weighted fair-share, round-robin within a credit epoch. Each task receives `(priority + 1)` credits per scheduling round. Tasks run round-robin, consuming one credit per turn. When all credits are spent, every ready task is refilled. | Priority | Constant | Credits/Round | Relative Share | |----------|----------|---------------|----------------| | 0 | `TS_PRIORITY_LOW` | 1 | ~4% | | 5 | `TS_PRIORITY_NORMAL` | 6 | ~22% | | 10 | `TS_PRIORITY_HIGH` | 11 | ~41% | Higher-priority tasks run proportionally more often but never starve lower ones. A priority-10 task gets 11 turns per round while a priority-0 task gets 1. ## Task States | State | Description | |-------|-------------| | `TaskStateReady` | Eligible for scheduling | | `TaskStateRunning` | Currently executing (cosmetic marker) | | `TaskStatePaused` | Skipped until resumed | | `TaskStateTerminated` | Slot available for reuse | ## API Reference | Function | Description | |----------|-------------| | `tsInit()` | Initialize task system; calling context becomes task 0 (main) | | `tsShutdown()` | Shut down and free all resources | | `tsCreate(name, entry, arg, stackSize, priority)` | Create a new task; returns task ID | | `tsYield()` | Yield CPU to next eligible task (credit-based round-robin) | | `tsPause(taskId)` | Pause a task (implicit yield if self) | | `tsResume(taskId)` | Resume a paused task (refills credits) | | `tsSetPriority(taskId, priority)` | Set priority (refills credits immediately) | | `tsGetPriority(taskId)` | Get task priority | | `tsGetState(taskId)` | Get task state | | `tsCurrentId()` | Get currently running task's ID | | `tsGetName(taskId)` | Get task name | | `tsExit()` | Terminate calling task (never returns) | | `tsKill(taskId)` | Forcibly terminate another task | | `tsRecoverToMain()` | Force scheduler back to task 0 after crash longjmp | | `tsActiveCount()` | Count of non-terminated tasks | ### Error Codes | Constant | Value | Description | |----------|-------|-------------| | `TS_OK` | 0 | Success | | `TS_ERR_INIT` | -1 | Initialization failure | | `TS_ERR_PARAM` | -2 | Invalid parameter | | `TS_ERR_FULL` | -3 | No available task slots | | `TS_ERR_NOMEM` | -4 | Stack allocation failed | | `TS_ERR_STATE` | -5 | Invalid state transition | ### Constants | Constant | Value | Description | |----------|-------|-------------| | `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack size (32KB) | | `TS_NAME_MAX` | 32 | Maximum task name length | ## Task 0 (Main Task) Task 0 is special: * Cannot be killed or paused * Uses the process stack directly (no separate allocation) * `tsRecoverToMain()` always returns control here after a crash * The DVX shell runs as task 0 with `TS_PRIORITY_HIGH` ## Crash Recovery `tsRecoverToMain()` is called after `longjmp` from a signal handler that fired in a non-main task. It fixes the scheduler's `currentIdx` to point back to task 0. The crashed task is NOT cleaned up by this call -- `tsKill()` must be called separately afterward. ## Files | File | Description | |------|-------------| | `taskswitch.h` | Public API header | | `taskswitch.c` | Complete implementation (context switch, scheduler, stack management) | | `Makefile` | Builds `bin/libs/libtasks.lib` | ## Build ``` make # builds bin/libs/libtasks.lib make clean # removes objects and library ``` No dependencies. Exports all symbols matching `_ts*`.