Skip to content

Commit d1b2a0a

Browse files
committed
Add task termination infrastructure
Implement deferred cleanup mechanism to safely terminate tasks that encounter unrecoverable faults. Tasks cannot immediately free their own resources since they execute on the stack being freed. The solution uses a two-phase approach: when termination is requested, the task is marked as suspended with a zombie flag indicating pending cleanup, then yields to another task. The scheduler detects zombie tasks during task selection and safely frees their resources from a different task's context. This infrastructure enables graceful handling of PMP violations and other unrecoverable task faults without panicking the entire system.
1 parent e9ac8f1 commit d1b2a0a

File tree

2 files changed

+110
-5
lines changed

2 files changed

+110
-5
lines changed

include/sys/task.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ enum task_states {
4444
TASK_SUSPENDED /* Task paused/excluded from scheduling until resumed */
4545
};
4646

47+
/* Task Flags */
48+
#define TASK_FLAG_ZOMBIE 0x01 /* Task terminated, awaiting cleanup */
49+
4750
/* Priority Level Constants for Priority-Aware Time Slicing */
4851
#define TASK_PRIORITY_LEVELS 8 /* Number of priority levels (0-7) */
4952
#define TASK_HIGHEST_PRIORITY 0 /* Highest priority level */
@@ -83,7 +86,7 @@ typedef struct tcb {
8386
uint16_t delay; /* Ticks remaining for task in TASK_BLOCKED state */
8487
uint16_t id; /* Unique task ID, assigned by kernel upon creation */
8588
uint8_t state; /* Current lifecycle state (e.g., TASK_READY) */
86-
uint8_t flags; /* Task flags for future extensions (reserved) */
89+
uint8_t flags; /* Task flags (TASK_FLAG_ZOMBIE for deferred cleanup) */
8790

8891
/* Real-time Scheduling Support */
8992
void *rt_prio; /* Opaque pointer for custom real-time scheduler hook */
@@ -284,6 +287,16 @@ uint64_t mo_uptime(void);
284287
*/
285288
void _sched_block(queue_t *wait_q);
286289

290+
/* Terminates the currently running task due to unrecoverable fault.
291+
*
292+
* Marks the current task as suspended and sets the zombie flag for deferred
293+
* cleanup. Forces an immediate context switch to another task. The marked
294+
* task's resources will be freed by the scheduler after the switch completes.
295+
*
296+
* This function does not return - execution continues in another task.
297+
*/
298+
void task_terminate_current(void) __attribute__((noreturn));
299+
287300
/* Application Entry Point */
288301

289302
/* The main entry point for the user application.

kernel/task.c

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,48 @@ void sched_wakeup_task(tcb_t *task)
392392
}
393393
}
394394

395+
/* Helper to clean up zombie task resources from safe context */
396+
static void cleanup_zombie_task(tcb_t *zombie)
397+
{
398+
if (!zombie || !(zombie->flags & TASK_FLAG_ZOMBIE))
399+
return;
400+
401+
/* Find and remove task node from list */
402+
CRITICAL_ENTER();
403+
404+
list_node_t *node = NULL;
405+
list_node_t *iter = kcb->tasks->head;
406+
while (iter) {
407+
if (iter->data == zombie) {
408+
node = iter;
409+
break;
410+
}
411+
iter = iter->next;
412+
}
413+
414+
if (node) {
415+
list_remove(kcb->tasks, node);
416+
kcb->task_count--;
417+
418+
/* Clear from cache */
419+
for (int i = 0; i < TASK_CACHE_SIZE; i++) {
420+
if (task_cache[i].task == zombie) {
421+
task_cache[i].id = 0;
422+
task_cache[i].task = NULL;
423+
}
424+
}
425+
}
426+
427+
CRITICAL_LEAVE();
428+
429+
/* Free resources outside critical section */
430+
if (zombie->mspace)
431+
mo_memspace_destroy(zombie->mspace);
432+
433+
free(zombie->stack);
434+
free(zombie);
435+
}
436+
395437
/* Efficient Round-Robin Task Selection with O(n) Complexity
396438
*
397439
* Selects the next ready task using circular traversal of the master task list.
@@ -414,6 +456,16 @@ uint16_t sched_select_next_task(void)
414456

415457
tcb_t *current_task = kcb->task_current->data;
416458

459+
/* Clean up current task if it's a zombie before proceeding */
460+
if (current_task->flags & TASK_FLAG_ZOMBIE) {
461+
cleanup_zombie_task(current_task);
462+
/* After cleanup, move to first real task to start fresh search */
463+
kcb->task_current = kcb->tasks->head->next;
464+
if (!kcb->task_current || !kcb->task_current->data)
465+
panic(ERR_NO_TASKS);
466+
current_task = kcb->task_current->data;
467+
}
468+
417469
/* Mark current task as ready if it was running */
418470
if (current_task->state == TASK_RUNNING)
419471
current_task->state = TASK_READY;
@@ -431,6 +483,16 @@ uint16_t sched_select_next_task(void)
431483

432484
tcb_t *task = node->data;
433485

486+
/* Clean up zombie tasks during scheduling */
487+
if (task->flags & TASK_FLAG_ZOMBIE) {
488+
list_node_t *next_node = list_cnext(kcb->tasks, node);
489+
cleanup_zombie_task(task);
490+
node = next_node ? next_node : kcb->tasks->head;
491+
if (!node || !node->data)
492+
continue;
493+
task = node->data;
494+
}
495+
434496
/* Skip non-ready tasks */
435497
if (task->state != TASK_READY)
436498
continue;
@@ -455,6 +517,7 @@ static int32_t noop_rtsched(void)
455517
return -1;
456518
}
457519

520+
458521
/* The main entry point from the system tick interrupt. */
459522
void dispatcher(void)
460523
{
@@ -493,8 +556,9 @@ void dispatch(void)
493556
uint32_t ready_count = 0;
494557
list_foreach(kcb->tasks, delay_update_batch, &ready_count);
495558

496-
/* Save old task before scheduler modifies task_current */
497-
memspace_t *old_mspace = ((tcb_t *) kcb->task_current->data)->mspace;
559+
/* Save old task's memory space for PMP context switching */
560+
tcb_t *old_task = (tcb_t *)kcb->task_current->data;
561+
memspace_t *old_mspace = old_task->mspace;
498562

499563
/* Hook for real-time scheduler - if it selects a task, use it */
500564
if (kcb->rt_sched() < 0)
@@ -531,8 +595,9 @@ void yield(void)
531595
if (!kcb->preemptive)
532596
list_foreach(kcb->tasks, delay_update, NULL);
533597

534-
/* Save old task before scheduler modifies task_current */
535-
memspace_t *old_mspace = ((tcb_t *) kcb->task_current->data)->mspace;
598+
/* Save old task's memory space for PMP context switching */
599+
tcb_t *old_task = (tcb_t *)kcb->task_current->data;
600+
memspace_t *old_mspace = old_task->mspace;
536601

537602
sched_select_next_task(); /* Use O(1) priority scheduler */
538603

@@ -716,6 +781,33 @@ int32_t mo_task_cancel(uint16_t id)
716781
return ERR_OK;
717782
}
718783

784+
void task_terminate_current(void)
785+
{
786+
NOSCHED_ENTER();
787+
788+
/* Verify we have a current task */
789+
if (unlikely(!kcb || !kcb->task_current || !kcb->task_current->data)) {
790+
NOSCHED_LEAVE();
791+
panic(ERR_NO_TASKS);
792+
}
793+
794+
tcb_t *self = kcb->task_current->data;
795+
796+
/* Mark as suspended to prevent re-scheduling */
797+
self->state = TASK_SUSPENDED;
798+
799+
/* Set zombie flag for deferred cleanup */
800+
self->flags |= TASK_FLAG_ZOMBIE;
801+
802+
NOSCHED_LEAVE();
803+
804+
/* Force immediate context switch - never returns */
805+
_dispatch();
806+
807+
/* Unreachable */
808+
__builtin_unreachable();
809+
}
810+
719811
void mo_task_yield(void)
720812
{
721813
_yield();

0 commit comments

Comments
 (0)