// 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 #include // 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