-
Notifications
You must be signed in to change notification settings - Fork 28
O(1) scheduler: Complete implementation #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
vicLin8712
wants to merge
13
commits into
sysprog21:main
Choose a base branch
from
vicLin8712:o1-sched-lauch
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Contributor
|
Do not include numbers in pull-request titles. |
b18ebac to
0d8c856
Compare
This commit extends the core scheduler data structures to support the new O(1) scheduler design. Adds in tcb_t: - rq_node: embedded list node for ready-queue membership used during task state transitions. This avoids redundant malloc/free for per-enqueue/dequeue nodes by tying the node's lifetime to the task control block. Adds in kcb_t: - ready_bitmap: 8-bit bitmap tracking which priority levels have runnable tasks. - ready_queues[]: per-priority ready queues for O(1) task selection. - rr_cursors[]: round-robin cursor per priority level to support fair selection within the same priority. These additions are structural only and prepare the scheduler for O(1) ready-queue operations; they do not change behavior yet.
Previously, list_pushback() and list_remove() were the only list APIs available for data insertion into and removal from the list by malloc a new and free target linkage node. After the new data structure, rq_node, is added as the linkage node for ready queue operation purpose, there is no need to malloc and free each time. This commit adds the insertion and removal list operations without malloc and free on the linkage node. - list_pushback_node(): append an existing node to the end of the list in O(n) time without allocating memory. - list_remove_node(): remove a node from the list without freeing it. Both helper functions are operated in O(n) by linearly searching method and will be applied in the upcoming task dequeuing/enqueuing from/into the ready queue operations.
Previously, `sched_enqueue_task()` only marked task state as TASK_READY to represent the task has been enqueued due to the original scheduler selects the next task based on the global list and all tasks are kept in it. After new data structure, ready_queue[], is added for keeping runnable tasks, the enqueuing task API should push the embedded linkage list node, rq_node, into the corresponding ready_queue. This commit uses list_pushback_node() helper to enqueue the embeded list node of tcb into ready queue and sets up cursor and bitmap of the corresponding priority queue.
Previously, sched_dequeue_task() was a no-op stub, which was sufficient when the scheduler selected tasks from the global list. Since new data structure, ready_queue, is added for keeping all runnable tasks, a dequeue path is required to remove tasks from ready queue to ensure it always holds runnable tasks. This commit adds the dequeue path to sched_dequeue_task(), using list_remove_node() helper to remove the existing linkage node from the corresponding ready queue and update the RR cursor and priority bitmap accordingly.
Previously, task operation APIs such as sched_wakeup_task() only updated
the task state, which was sufficient when scheduling relied on the global
task list. With the scheduler now selecting runnable tasks from
ready_queue[] per priority level, state changes alone are insufficient.
To support the new scheduler and to prevent selection of tasks that have
already left the runnable set, explicit enqueue and dequeue paths are
required when task state transitions cross the runnable boundary:
In ready-queue set: {TASK_RUNNING, TASK_READY}
Not in ready-queue set: {all other states}
This commit updates task operation APIs to include queue insertion and
removal logic according to their semantics. In general, queue operations
are performed by invoking existing helper functions mo_enqueue_task()
and mo_dequeue_task().
The modified APIs include:
- sched_wakeup_task(): avoid enqueueing a task that is already running
by treating TASK_RUNNING as part of the runnable set complement.
- mo_task_cancel(): dequeue TASK_READY tasks from ready_queue[] before
cancelling, ensuring removed tasks are not scheduled again.
- mo_task_delay(): runnable boundary transition only ("TASK_RUNNING →
TASK_BLOCKED"), no queue insertion for non-runnable tasks.
- mo_task_suspend(): supports both TASK_RUNNING and TASK_READY
("TASK_RUNNING/TASK_READY → TASK_SUSPENDED"), dequeue before suspend
when necessary.
- mo_task_resume(): only for suspended tasks ("TASK_SUSPENDED →
TASK_READY"), enqueue into ready_queue[] on resume.
- _sched_block(): runnable boundary transition only ("TASK_RUNNING →
TASK_BLOCKED"), dequeue without memory free.
Currently, mo_mutex_lock() will call mutex_block_atomic() to mark the running task as TASK_BLOCKED so that it won't be selected by the old scheduler. To support the ready queue consistency that always keeps runnable tasks, the dequeuing path should be included when mutex_block_atomic() is called. This commit adds _sched_blocked_dequeue() helper and will be applied in mutex_block_atomic() in the following commit.
Previously, mutex_block_atomic() only marked the running task as TASK_BLOCKED, which was sufficient when scheduling selected tasks by scanning the global task list. Since the new scheduler is designed to select only runnable tasks from ready_queue[], mutex blocking now also requires removing the task’s rq_node from the corresponding ready queue, preventing the scheduler from selecting a blocked (non-runnable/dequeued) task again.
Currently, there is no enqueueing API that can be invoked from other files, especially in mutex and semaphore operations which include task state transition from TASK_BLOCKED to TASK_READY when a held resource is released. This change introduces the _sched_blocked_enqueue() helper, which will be used by mutex/semaphore unblocking paths to insert the task’s existing linkage node into the corresponding per-priority ready queue, keeping scheduler visibility and ready-queue consistency.
This commit replaces unblocking state transitions (TASK_BLOCKED->TASK_READY) in mutex and semaphore paths with the _sched_block_enqueue() helper to ensure scheduler visibility and preserve ready-queue invariants.
Previously, mo_task_priority() only updated the task’s time slice and priority level. With the new scheduler design, tasks are kept in per-priority ready queues, so mo_task_priority() must also handle migrating tasks between these queues. This commit adds dequeue/enqueue logic for tasks in TASK_RUNNING or TASK_READY state, as such tasks must reside in a ready queue and a priority change implies ready-queue migration. The priority fields are still updated as part of the migration path: sched_dequeue_task() relies on the current priority, while the enqueue operation needs the new priority. Therefore, the priority update is performed between the dequeue and enqueue steps. If the priority change happens while the task is running, it must yield immediately to preserve the scheduler’s strict task-ordering policy.
This commit refactors mo_task_spawn() to align with the new O(1) scheduler design. The task control block (tcb_t) embeds its list node during task creation. The enqueue operation is moved inside a critical section to guarantee consistent enqueuing process during task creation. The “first task assignment” logic is removed because first task has been assigned to system idle task as previous commit mentioned.
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.
7e130fd to
164ad8d
Compare
164ad8d to
77bb7ae
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
O(1) scheduler: Complete implementation
This PR provides the complete O(1) scheduler implementation and serves as the final part of the 3-part stacked PR series.
It integrates all components introduced in the earlier patches and replaces the legacy O(n) linear scheduler with the new ready-queue–based, RR-cursor-based, bitmap-assisted O(1) design.
Feature of O(1) scheduler
Priority-indexed ready queues
Each priority level maintains an independent ready queue.
Bitmap + De Bruijn–based highest-priority lookup
The scheduler locates the next runnable task in constant time using priority bitmaps and De Bruijn table lookup.
RR cursor for fair round-robin scheduling
Each priority queue maintains a cursor to provide O(1) fair scheduling among tasks of the same priority.
Full integration into the scheduler execution path
The legacy O(n) priority scanning algorithm is completely replaced by the new O(1) logic; the iteration limit
IMAX=500is removed.Idle task fully integrated into new design
System execution starts in the idle task, which serves as the initial execution context.
Whenever the idle task yields, control deterministically transitions to the highest-priority runnable task.
Unit tests
Commit: Add unit test suite for RR-cursor scheduler
Approach
TASK_PRIO_CRITto orchestrate the entire test process and enforce deterministic sequencing.Task types
TASK_BLOCKEDthroughmo_task_delay().Used to verify dequeue behavior and correct clearing of priority bits when a task leaves the schedulable state set.
Serves as the primary subject for testing state transitions and enqueue/dequeue correctness.
Verified state points
The following state transitions are validated by checking both ready-queue task counts and bitmap updates after each operation:
Normal task state transitions
TASK_READY) – initial enqueue and priority bit set.TASK_READY→TASK_SUSPEND) – dequeued from the ready queue and priority bit cleared.TASK_SUSPEND→TASK_READY) – re-enqueued with correct priority placement.TASK_READY→TASK_CANCELLED) – removed from ready queues and all bitmap bits fully cleared.Blocked task behavior (
TASK_RUNNING→TASK_BLOCKED)TASK_READY).TASK_BLOCKED.Results
Note
TASK_CANCELLEDin this document is used only for explanation. It is not an actual state in the task state machine, but represents the condition where a task has been removed from all scheduling structures and no longer exists in the system.(TASK_READY)) refer to the state of the test tasks being created or manipulated, not the state of the controller task.Benchmark
Commit: Add benchmarking files
Approach
N=500normal tasks to populate the scheduling domain.All tasks begin in the
TASK_READYstate, ensuring the ready queues and bitmap are fully populated.For each benchmark scenario, suspend a portion of tasks to reach the desired active-ratio load:
Benchmark execution
To compare the legacy O(n) scheduler with the new O(1) scheduler, a compile-time flag OLD is passed to select which scheduling algorithm is active.
The original linear-search scheduler is preserved in task.c for baseline measurement.
For each benchmark scenario, the scheduler is executed 20 times to obtain stable timing data.
The average and maximum scheduling latencies are collected, and the performance improvement is computed as the ratio between the old and new scheduler times (e.g., 1.5× faster).
Metrics collected
The benchmark collects the following metrics for each scenario:
Mean improvement
Average speedup factor computed as (old_latency / new_latency) across 20 runs.
Standard deviation of improvement
Measures the variability of speedup across repeated runs.
Minimum / maximum improvement
Best and worst observed speedup factors among the 20 runs.
95% confidence interval (CI)
Statistical confidence bounds for the mean improvement.
Mean scheduling latency (old / new)
Average schedule-selection time for both the legacy O(n) scheduler and the new O(1) scheduler.
Maximum scheduling latency (old / new)
Worst-case schedule-selection time observed for each scheduler.
Results
Reference
#23 - Draft discussion
#36 - Infrustrue
#37 - Task state transitions APIs
ae35c84 - unit test test suite
11e9ee6 - benchmark
Summary by cubic
Complete O(1) scheduler with priority queues, bitmap selection, and RR cursors, replacing the legacy O(n) scan. Adds an idle task and updates task lifecycle to use ready queues; up to ~2.7x faster under light load.
New Features
Refactors
Written for commit 77bb7ae. Summary will update automatically on new commits.