# taskswitch -- Cooperative Task Switching Library for DJGPP A lightweight cooperative multitasking library targeting DJGPP (i386 protected mode DOS). Tasks voluntarily yield the CPU with `tsYield()`. A credit-based weighted round-robin scheduler ensures every task runs while giving higher-priority tasks proportionally more CPU time. ## Files | File | Description | |----------------|------------------------------------------| | `taskswitch.h` | Public API -- types, constants, functions | | `taskswitch.c` | Implementation | | `demo.c` | Example program exercising every feature | | `Makefile` | Build rules for native DJGPP or cross | ## Building Native DJGPP (from a DOS prompt with DJGPP in the PATH): ``` make ``` Cross-compiling from Linux (adjust the prefix to match your toolchain): ``` make CC=i586-pc-msdosdjgpp-gcc ``` Debug build with no optimisation and symbols: ``` make debug ``` Clean: ``` make clean ``` ## Quick Start ```c #include #include "taskswitch.h" void myTask(void *arg) { const char *name = (const char *)arg; for (int i = 0; i < 3; i++) { printf("[%s] working...\n", name); tsYield(); } } int main(void) { tsInit(4); tsCreate("alpha", myTask, "alpha", 0, TS_PRIORITY_NORMAL); tsCreate("beta", myTask, "beta", 0, TS_PRIORITY_HIGH); for (int i = 0; i < 10; i++) { tsYield(); } tsShutdown(); return 0; } ``` ## Lifecycle 1. **`tsInit(maxTasks)`** -- Allocate the task table. The calling context (typically `main`) becomes task 0 with `TS_PRIORITY_NORMAL`. `maxTasks` is the total capacity including this main task slot. 2. **`tsCreate(...)`** -- Create tasks. Each gets a name, entry function, argument pointer, stack size (0 for the 8 KB default), and a priority. 3. **`tsYield()`** -- Call from any task (including main) to hand the CPU to the next eligible task. 4. **`tsShutdown()`** -- Free all task stacks and the task table. Tasks terminate by returning from their entry function or by calling `tsExit()`. The main task (id 0) must never call `tsExit()`. ## API Reference ### Initialisation and Teardown | Function | Signature | Description | |----------------|------------------------------|--------------------------------------------------------------------------------------| | `tsInit` | `int32_t tsInit(uint32_t)` | Initialise the library. Returns `TS_OK` or a negative error code. | | `tsShutdown` | `void tsShutdown(void)` | Free all resources. Safe to call even if `tsInit` was never called. | ### Task Creation and Termination | Function | Signature | Description | |------------|----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| | `tsCreate` | `int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t ss, int32_t pri)` | Create a ready task. Returns the task ID (>= 0) or a negative error code. Pass 0 for `ss` to use `TS_DEFAULT_STACK_SIZE` (8 KB). | | `tsExit` | `void tsExit(void)` | Terminate the calling task. Must not be called from the main task. | ### Scheduling | Function | Signature | Description | |-----------|-----------------------|--------------------------------------------------------------------------| | `tsYield` | `void tsYield(void)` | Voluntarily relinquish the CPU to the next eligible ready task. | ### Pausing and Resuming | Function | Signature | Description | |------------|-----------------------------------|--------------------------------------------------------------------------------------------------------------------| | `tsPause` | `int32_t tsPause(uint32_t id)` | Pause a task. The main task (id 0) cannot be paused. If a task pauses itself, an implicit yield occurs. | | `tsResume` | `int32_t tsResume(uint32_t id)` | Resume a paused task. Its credits are refilled to `priority + 1` so it is not penalised for having been paused. | ### Priority | Function | Signature | Description | |-----------------|--------------------------------------------------|---------------------------------------------------------------------------------------------------| | `tsSetPriority` | `int32_t tsSetPriority(uint32_t id, int32_t pri)`| Change a task's priority. Credits are reset to `pri + 1` so the change takes effect immediately. | | `tsGetPriority` | `int32_t tsGetPriority(uint32_t id)` | Return the task's priority, or `TS_ERR_PARAM` on an invalid ID. | ### Query | Function | Signature | Description | |-----------------|-----------------------------------------|---------------------------------------------------------------| | `tsGetState` | `TaskStateE tsGetState(uint32_t id)` | Return the task's state enum value. | | `tsCurrentId` | `uint32_t tsCurrentId(void)` | Return the ID of the currently running task. | | `tsGetName` | `const char *tsGetName(uint32_t id)` | Return the task's name string, or `NULL` on invalid ID. | | `tsActiveCount` | `uint32_t tsActiveCount(void)` | Return the number of non-terminated tasks. | ## Constants ### Error Codes | Name | Value | Meaning | |----------------|-------|--------------------------------------| | `TS_OK` | 0 | Success | | `TS_ERR_INIT` | -1 | Library not initialised | | `TS_ERR_PARAM` | -2 | Invalid parameter | | `TS_ERR_FULL` | -3 | Task table full | | `TS_ERR_NOMEM` | -4 | Memory allocation failed | | `TS_ERR_STATE` | -5 | Invalid state transition | ### Priority Presets | Name | Value | Credits per Round | |----------------------|-------|-------------------| | `TS_PRIORITY_LOW` | 0 | 1 | | `TS_PRIORITY_NORMAL` | 5 | 6 | | `TS_PRIORITY_HIGH` | 10 | 11 | Any non-negative `int32_t` may be used as a priority. The presets are provided for convenience. ### Defaults | Name | Value | Description | |-------------------------|-------|-------------------------| | `TS_DEFAULT_STACK_SIZE` | 8192 | Default stack per task | | `TS_NAME_MAX` | 32 | Max task name length | ## Types ### `TaskStateE` ```c typedef enum { TaskStateReady = 0, // Eligible for scheduling TaskStateRunning = 1, // Currently executing TaskStatePaused = 2, // Suspended until tsResume() TaskStateTerminated = 3 // Finished; slot cannot be reused } TaskStateE; ``` ### `TaskEntryT` ```c typedef void (*TaskEntryT)(void *arg); ``` The signature every task entry function must follow. `arg` is the pointer passed to `tsCreate`. ## Scheduler Details The scheduler is a **credit-based weighted round-robin**. 1. Every ready task holds a credit counter initialised to `priority + 1`. 2. When `tsYield()` is called, the scheduler scans tasks starting one past the current task (wrapping around) looking for a ready task with credits > 0. When found, that task's credits are decremented and it becomes the running task. 3. When **no** ready task has credits remaining, every ready task is refilled to `priority + 1` and the scan repeats. This means a priority-10 task receives 11 turns for every 1 turn a priority-0 task receives, but the low-priority task still runs -- it is never starved. Credits are also refilled when: - A task is **created** (`tsCreate`) -- starts with `priority + 1`. - A task is **resumed** (`tsResume`) -- refilled so it is not penalised. - A task's **priority changes** (`tsSetPriority`) -- reset to `new + 1`. ## Context Switch Internals Context switching is performed entirely in i386 inline assembly. Six values are saved and restored per switch: | Register | Offset | Purpose | |----------|--------|-----------------------------------------| | EBX | 0 | Callee-saved general purpose | | ESI | 4 | Callee-saved general purpose | | EDI | 8 | Callee-saved general purpose | | EBP | 12 | Frame pointer | | ESP | 16 | Stack pointer | | EIP | 20 | Resume address (captured as local label)| The save and restore pointers are passed into the assembly block via GCC register constraints (`%eax` and `%edx`). Segment registers are not saved because DJGPP runs in a flat 32-bit protected-mode environment where CS, DS, ES, and SS share the same base. New tasks have their initial ESP pointed at a 16-byte-aligned region at the top of a `malloc`'d stack, with EIP set to an internal trampoline that calls the user's entry function and then `tsExit()`. ## Limitations - **Cooperative only** -- tasks must call `tsYield()` (or `tsPause`/`tsExit`) to allow other tasks to run. A task that never yields blocks everything. - **No task slot reuse** -- once a task terminates, its slot in the task table cannot be reclaimed. The `maxTasks` value passed to `tsInit` is a lifetime limit. - **Not interrupt-safe** -- the library uses no locking or `volatile` module state. Do not call library functions from interrupt handlers. - **Single-threaded** -- designed for one CPU under DOS protected mode. - **Stack overflow is not detected** -- size the stack appropriately for each task's needs. ## Demo `demo.c` exercises four phases: 1. **Priority scheduling** -- creates tasks at low, normal, and high priority. All tasks run, but the high-priority task gets significantly more turns. 2. **Pause** -- pauses one task mid-run and shows it stops being scheduled. 3. **Resume** -- resumes the paused task and shows it picks up where it left off. 4. **Priority boost** -- raises the low-priority task above all others and shows it immediately gets more turns. Build and run: ``` make demo ```