Skip to content

Commit fc2a929

Browse files
committed
Refactor scheduler to RR cursor-based O(1) design
Previously, the scheduler performed an O(N) scan of the global task list (kcb->tasks) to locate the next TASK_READY task. This resulted in non-deterministic selection latency and unstable round-robin rotation under heavy load or frequent task state transitions. This change introduces a strict O(1) scheduler based on per-priority ready queues and round-robin (RR) cursors. Each priority level maintains its own ready queue and cursor, enabling constant-time selection of the next runnable task while preserving fairness within the same priority. Additionally, when all tasks are non-runnable, the scheduler now switches directly to the system idle task after bitmap lookup, ensuring consistent control flow and eliminating unnecessary scanning paths.
1 parent a96606a commit fc2a929

File tree

2 files changed

+33
-52
lines changed

2 files changed

+33
-52
lines changed

include/sys/task.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,6 @@ typedef struct {
130130
extern kcb_t *kcb;
131131

132132
/* System Configuration Constants */
133-
#define SCHED_IMAX \
134-
500 /* Safety limit for scheduler iterations to prevent livelock */
135133
#define MIN_TASK_STACK_SIZE \
136134
256 /* Minimum stack size to prevent stack overflow */
137135
#define TASK_CACHE_SIZE \

kernel/task.c

Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -525,20 +525,20 @@ static inline tcb_t *sched_switch_to_idle(void)
525525
return idle;
526526
}
527527

528-
/* Efficient Round-Robin Task Selection with O(n) Complexity
528+
/* Efficient Round-Robin Task Selection (Cursor-Based, O(1) Complexity)
529529
*
530-
* Selects the next ready task using circular traversal of the master task list.
530+
* Selects the next ready task by advancing the per-priority round-robin
531+
* cursor (rr_cursor) circularly using list API list_cnext().
531532
*
532-
* Complexity: O(n) where n = number of tasks
533-
* - Best case: O(1) when next task in sequence is ready
534-
* - Worst case: O(n) when only one task is ready and it's the last checked
535-
* - Typical case: O(k) where k << n (number of non-ready tasks to skip)
533+
* Complexity: O(1)
534+
* - Always constant-time selection, regardless of total task count.
535+
* - No need to traverse the task list.
536536
*
537537
* Performance characteristics:
538-
* - Excellent for small-to-medium task counts (< 50 tasks)
539-
* - Simple and reliable implementation
540-
* - Good cache locality due to sequential list traversal
541-
* - Priority-aware time slice allocation
538+
* - Ideal for systems with frequent context switches or many tasks.
539+
* - Excellent cache locality: only touches nodes in the active ready queue.
540+
* - Priority-aware: highest non-empty ready queue is chosen via bitmap lookup.
541+
* - Each priority level maintains its own rr_cursor to ensure fair rotation.
542542
*/
543543
uint16_t sched_select_next_task(void)
544544
{
@@ -551,53 +551,36 @@ uint16_t sched_select_next_task(void)
551551
if (current_task->state == TASK_RUNNING)
552552
current_task->state = TASK_READY;
553553

554-
/* Round-robin search: find next ready task in the master task list */
555-
list_node_t *start_node = kcb->task_current;
556-
list_node_t *node = start_node;
557-
int iterations = 0; /* Safety counter to prevent infinite loops */
554+
/* Check out bitmap */
555+
uint32_t bitmap = kcb->ready_bitmap;
558556

559-
do {
560-
/* Move to next task (circular) */
561-
node = list_cnext(kcb->tasks, node);
562-
if (!node || !node->data)
563-
continue;
557+
/* If no available ready queue found in bitmap - all tasks blocked, the
558+
* scheduler will switch to system idle task and wait for next timer
559+
* interrupt*/
560+
if (unlikely(!bitmap))
561+
return sched_switch_to_idle()->id;
564562

565-
tcb_t *task = node->data;
563+
/* Find top priority ready queue */
564+
uint8_t top_prio_level = find_highest_ready_priority(bitmap);
566565

567-
/* Skip non-ready tasks */
568-
if (task->state != TASK_READY)
569-
continue;
566+
list_node_t **cursor = &kcb->rr_cursors[top_prio_level];
567+
list_t *rq = kcb->ready_queues[top_prio_level];
568+
if (unlikely(!rq || !*cursor))
569+
panic(ERR_NO_TASKS);
570570

571-
/* Found a ready task */
572-
kcb->task_current = node;
573-
task->state = TASK_RUNNING;
574-
task->time_slice = get_priority_timeslice(task->prio_level);
571+
/* Update next task with top priority cursor */
572+
kcb->task_current = *cursor;
575573

576-
return task->id;
574+
/* Advance top priority cursor to next task node */
575+
*cursor = list_cnext(rq, *cursor);
577576

578-
} while (node != start_node && ++iterations < SCHED_IMAX);
577+
/* Update new task properties */
578+
tcb_t *new_task = kcb->task_current->data;
579+
new_task->time_slice = get_priority_timeslice(new_task->prio_level);
580+
new_task->state = TASK_RUNNING;
579581

580-
/* No ready tasks found in preemptive mode - all tasks are blocked.
581-
* This is normal for periodic RT tasks waiting for their next period.
582-
* We CANNOT return a BLOCKED task as that would cause it to run.
583-
* Instead, find ANY task (even blocked) as a placeholder, then wait for
584-
* interrupt.
585-
*/
586-
if (kcb->preemptive) {
587-
/* Select any task as placeholder (dispatcher won't actually switch to
588-
* it if blocked) */
589-
list_node_t *any_node = list_next(kcb->tasks->head);
590-
while (any_node && any_node != kcb->tasks->tail) {
591-
if (any_node->data) {
592-
kcb->task_current = any_node;
593-
tcb_t *any_task = any_node->data;
594-
return any_task->id;
595-
}
596-
any_node = list_next(any_node);
597-
}
598-
/* No tasks at all - this is a real error */
599-
panic(ERR_NO_TASKS);
600-
}
582+
if (kcb->task_current)
583+
return new_task->id;
601584

602585
/* In cooperative mode, having no ready tasks is an error */
603586
panic(ERR_NO_TASKS);

0 commit comments

Comments
 (0)