133 lines
5.5 KiB
C
133 lines
5.5 KiB
C
// taskswitch.h -- Cooperative task switching library for DJGPP
|
|
//
|
|
// Cooperative (non-preemptive) multitasking for DOS protected mode (DJGPP/DPMI).
|
|
//
|
|
// Why cooperative instead of preemptive:
|
|
// 1. DOS is single-threaded — there is no kernel scheduler to preempt us.
|
|
// 2. DPMI provides no thread or timer-based preemption primitives.
|
|
// 3. The DVX GUI event model is inherently single-threaded: one compositor,
|
|
// one input queue, one window stack. Preemption would require locking
|
|
// around every GUI call, adding latency and complexity for no benefit.
|
|
// 4. Cooperative switching lets each task yield at safe points, avoiding
|
|
// the need for synchronization primitives entirely.
|
|
//
|
|
// Credit-based cooperative multitasking with task pausing.
|
|
// 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. Higher-priority
|
|
// tasks run proportionally more often but never starve lower ones.
|
|
//
|
|
// This is a weighted fair-share scheduler: a priority-10 task gets 11
|
|
// turns per round while a priority-0 task gets 1, but the low-priority
|
|
// task is guaranteed to run eventually — no starvation is possible.
|
|
|
|
#ifndef TASKSWITCH_H
|
|
#define TASKSWITCH_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
// Error codes
|
|
#define TS_OK 0
|
|
#define TS_ERR_INIT (-1)
|
|
#define TS_ERR_PARAM (-2)
|
|
#define TS_ERR_FULL (-3)
|
|
#define TS_ERR_NOMEM (-4)
|
|
#define TS_ERR_STATE (-5)
|
|
|
|
// 32KB default stack — generous for callback-style app tasks that spend
|
|
// most of their time in the shell's GUI code. Apps needing deeper recursion
|
|
// or large stack allocations can request more via AppDescriptorT.stackSize.
|
|
#define TS_DEFAULT_STACK_SIZE 32768
|
|
#define TS_NAME_MAX 32
|
|
|
|
// Priority levels — the credit count is (priority + 1), so LOW gets 1 turn
|
|
// per round, NORMAL gets 6, HIGH gets 11. The shell's main task runs at
|
|
// HIGH to keep UI responsive; app tasks default to NORMAL.
|
|
#define TS_PRIORITY_LOW 0
|
|
#define TS_PRIORITY_NORMAL 5
|
|
#define TS_PRIORITY_HIGH 10
|
|
|
|
// Task states — only Ready tasks participate in scheduling. The Running
|
|
// state is cosmetic (marks the currently executing task). Paused tasks
|
|
// are skipped until explicitly resumed. Terminated slots are recycled.
|
|
typedef enum {
|
|
TaskStateReady = 0,
|
|
TaskStateRunning = 1,
|
|
TaskStatePaused = 2,
|
|
TaskStateTerminated = 3
|
|
} TaskStateE;
|
|
|
|
// Task entry function signature — the void* arg lets the caller pass
|
|
// arbitrary context (e.g., a ShellAppT* for the DVX shell's app wrapper)
|
|
typedef void (*TaskEntryT)(void *arg);
|
|
|
|
// Initialize the task system. The calling context becomes task 0 (main).
|
|
// Task 0 is special: it cannot be killed or paused, and the crash recovery
|
|
// path (tsRecoverToMain) always returns control here. No separate stack is
|
|
// allocated for task 0 — it uses the process stack directly.
|
|
int32_t tsInit(void);
|
|
|
|
// Shut down the task system and free all resources.
|
|
void tsShutdown(void);
|
|
|
|
// Create a new task. Pass 0 for stackSize to use TS_DEFAULT_STACK_SIZE.
|
|
// Returns the task ID (>= 0) on success, or a negative error code.
|
|
// Terminated task slots are recycled to avoid unbounded array growth.
|
|
int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);
|
|
|
|
// Yield CPU to the next eligible ready task (credit-based round-robin).
|
|
// This is the sole mechanism for task switching — every app must call
|
|
// this (or a GUI function that calls it) periodically, or it will
|
|
// monopolize the CPU.
|
|
void tsYield(void);
|
|
|
|
// Pause a task. Cannot pause the main task (id 0).
|
|
// If a task pauses itself, an implicit yield occurs.
|
|
int32_t tsPause(uint32_t taskId);
|
|
|
|
// Resume a paused task. Credits are refilled so the task gets a fair
|
|
// share of CPU time immediately rather than waiting for the next round.
|
|
int32_t tsResume(uint32_t taskId);
|
|
|
|
// Set a task's scheduling priority. Also refills credits so the change
|
|
// takes effect immediately rather than at the next credit refill epoch.
|
|
int32_t tsSetPriority(uint32_t taskId, int32_t priority);
|
|
|
|
// Get a task's priority, or TS_ERR_PARAM on invalid id.
|
|
int32_t tsGetPriority(uint32_t taskId);
|
|
|
|
// Get a task's current state.
|
|
TaskStateE tsGetState(uint32_t taskId);
|
|
|
|
// Get the currently running task's ID.
|
|
uint32_t tsCurrentId(void);
|
|
|
|
// Get a task's name, or NULL on invalid id.
|
|
const char *tsGetName(uint32_t taskId);
|
|
|
|
// Terminate the calling task. Must not be called from the main task.
|
|
// The stack is freed immediately and the slot is marked for reuse.
|
|
// Internally performs a context switch to the next ready task — this
|
|
// function never returns to the caller.
|
|
void tsExit(void);
|
|
|
|
// Forcibly terminate another task. Cannot kill main task (id 0) or self.
|
|
// Safe to call only because we're cooperative: the target task is
|
|
// guaranteed to be suspended at a yield point, so its stack can be freed
|
|
// without worry about it executing concurrently. In a preemptive system
|
|
// this would be undefined behavior.
|
|
int32_t tsKill(uint32_t taskId);
|
|
|
|
// Crash recovery: force scheduler back to main task (id 0).
|
|
// Call after longjmp from a signal handler that fired in a non-main task.
|
|
// The crashed task is NOT cleaned up — call tsKill() afterward.
|
|
// This exists because longjmp unwinds the crashed task's stack but the
|
|
// scheduler's currentIdx still points to it. We must fix the bookkeeping
|
|
// before doing anything else.
|
|
void tsRecoverToMain(void);
|
|
|
|
// Get the number of non-terminated tasks.
|
|
uint32_t tsActiveCount(void);
|
|
|
|
#endif // TASKSWITCH_H
|