Skip to content

Conversation

@vicLin8712
Copy link
Collaborator

@vicLin8712 vicLin8712 commented Nov 18, 2025

Summary

This PR introduces new data structures and a new O(1) scheduler to enhance the current O(n) RR scheduler, make tasks order deterministic, and execute in strictly priority order. At the same time, make context switch in constant time performance.

The new scheduler selects the next task in strict priority order, based on the per-priority RR cursor, which always points to the next task in each priority ready queue. The new data structure list node rq_node is introduced into tcb to support task state transition and accompanies with task during the whole task lifecycle, which benefits without further malloc/free operation cost.

Modified APIs and passed the unit test list:

  • task.c
  • mo_task_spawn()
  • mo_task_priority()
  • mo_task_cancel()
  • mo_task_suspend()
  • sched_wakup_task(): Inner API but never used.
  • kernel/semaphore.c
  • mo_sem_signal()
  • mo_sem_wait()
  • kernel/mutex.c
  • mo_mutex_lock()
  • mo_mutex_unlock()
  • mo_mutex_timedlock()
  • mutex_cond_wait()
  • mutex_cond_signal()
  • mutex_cond_broadcast()
  • mutex_cond_timedwait()

Refer: #13

Changes

Section 1. New data structure for the new scheduler

  • rq_node: Embedded list node without malloc/free when task state transitions.
  • ready_bitmap: 8-bit bitmap tracking which priority levels have runnable tasks.
  • ready_queues[]: Per-priority ready queue, keeps all tasks with state TASK_RUNNING and TASK_READY.
  • rr_cursors[]: Round-Robin cursor per priority level to support fair selection within the same priority, always points to the next task of the ready queue.

Section 2. Ready-queue maintenance in task state transition APIs

Task state transition is now classified into two categories:

  1. Runnable queue members: TASK_RUNNING / TASK_READY (must be kept inside a ready queue)
  2. Non-queue members: all other states (BLOCKED, SUSPENDED, etc.)

State-transition APIs now invoke enqueue/dequeue only when crossing between these two groups. The modification of the APIs follows the state transition diagram below.
未命名绘图 drawio (1)

Section 3. Refactor new scheduler logic

The new scheduler logic is refactored as:

  1. Scan the bitmap to figure out the highest priority ready queue
  2. Update kcb->task_current to RR cursor of the scanned ready queue.
  3. Advance RR cursor circularly, points to the next task in the ready queue.

Unit tests

The unit tests cover the following APIs and new data structures and can be divided into the following scopes:

1. Inner queue operation APIs

Check sched_dequeue_task() and sched_enqueue_task() helpers operate properly on the ready queue and bitmap.
Verification by calling the following APIs that include the above helpers.

  • New task creation: mo_task_spawn() , check bit set up.
  • Task priority change: 1mo_task_priority()`, check bit clean, and set up in the new priority ready queue.
  • Task cancelled: 1mo_task_cancel()`, check bit clean

2. Ready queue cursor

Check that the per-priority cursor behaves consistently across the whole system lifecycle with the following rules:

  • Cursor invariants for a ready queue
    • When ready-queue task count = 0 → RR cursor must point to NULL
    • When ready-queue task count = 1 → RR cursor must point to the only task node
    • When ready-queue task count > 1 → RR cursor must point to a runnable task node that is not the currently running task
  • Covered scenarios
  • Running task creates and then cancels a same-priority task
未命名绘图 drawio (2)
  • Running task creates and then cancels tasks in a different priority ready queue
未命名绘图 drawio (3)

3. Task transition APIs (Refer to section 2. state transition diagram)

Validate general task state transition APIs, including suspend, resume, spawn, and delay with three paths.
Each state transition verifies that the task is in the ready queue or not, and the task count matches the rule of section 2.

  • Ready task state transition path
未命名绘图 drawio (4)
  • Running task state transition path - suspend task
未命名绘图 drawio (5)
  • Running task state transition path - block task
未命名绘图 drawio (6)

4. Semaphore

Validate the task blocking path when using the semaphore API.

未命名绘图 drawio (7)

The task count in the ready queue and the waiting list are confirmed in points 4 and 9 in the following diagram:

Task count pair def: (in the ready queue, in the semaphore waiting list)

  • 4: Sem_task dequeue and block itself (1,1).
  • 6: Sem_task enqueue by controller signal (2,0).
sequenceDiagram
    autonumber

    participant ControllerTask
    participant SemTask

    ControllerTask ->> ControllerTask: Take semaphore
    ControllerTask ->> SemTask: Yield CPU to sem_task
    SemTask ->> SemTask: Try to take the semaphore
    SemTask -->> ControllerTask: Fail and block itself (RUNNING->BLOCKED) 

    ControllerTask ->> ControllerTask: Release semaphore
    ControllerTask ->> ControllerTask: Wakeup sem_task(BLOCKED->RUNNING) and check metadata
Loading
  • The checking list of modified APIs in this patch included in this unit test:
  • mo_sem_signal()
  • mo_sem_wait()

5. Mutex

Validate the mutex lock operations APIs, separated into timeout and normal cases:

未命名绘图 drawio (8)

Task count pair def: (in the ready queue, in the mutex waiter list)

  • Mutex
    • 3: Mutex task tries to lock the mutex and fails, becoming BLOCKED (1,1).
    • 5: Controller task unlocks mutex, waking up one waiter (2,0).

The following diagram illustrates the verified transition path:

sequenceDiagram
    autonumber

    participant ControllerTask
    participant MutexTask

    ControllerTask ->> ControllerTask: Lock mutex
    ControllerTask ->> MutexTask: Yield CPU
    MutexTask -->> ControllerTask: Lock fali: Running->Blocked

    ControllerTask ->> ControllerTask: Scheduler selects Controller
    ControllerTask ->> ControllerTask: Unlock mutex: Wake up mutex task TASK_BLOCK -> TASK_READY
Loading
  • Timed mutex
    • 3: Timed mutex task lock itself (1,1)
    • 7: Timed mutex task is woken up due to timeout, enqueue and check at the controller task (2,0)
sequenceDiagram
    autonumber

    participant ControllerTask
    participant MutexTask

    ControllerTask ->> MutexTask: Yield CPU
    MutexTask->>ControllerTask: Timed mutex lock 10 ticks (RUNNING->BLOCKED), yield to the controller
    ControllerTask->>ControllerTask: Check state
    ControllerTask -->> MutexTask: Controller suspends itself
    MutexTask ->> MutexTask: Timeout and wakeup itself (BLOCKED->READY)
    MutexTask ->> ControllerTask: Yield to the controller
    ControllerTask->>ControllerTask: Check state
Loading
  • The checking list of modified APIs in this patch included in this unit test:
  • mo_mutex_lock()
  • mo_mutex_unlock()
  • mo_mutex_timedlock()

6. Condition (used with Mutex)

Validate the condition mutex lock operations APIs, separated into timeout and normal cases:

Task count pair def: (in the ready queue, in the mutex condition waiter list)

  • Condition wait
    • 10: Three tasks wait for a condition signal (1,3).
    • 12: Signal (2,2).
    • 14: Broadcast (4,0)
sequenceDiagram
    autonumber

    participant ControllerTask
    participant cond1

    ControllerTask ->> ControllerTask: Create 3 mutex_cond tasks
    ControllerTask ->> condtasks: Controller yield to mutex_cond tasks

     condtasks->>condtasks: Mutex lock task 1
     condtasks->>condtasks: Task 1 wait for condition variable (release mutex)
     condtasks->>condtasks: Mutex lock task 2
     condtasks->>condtasks: Task 2 wait for condition variable (release mutex)
     condtasks->>condtasks: Mutex lock task 3
     condtasks->>condtasks: Task 3 wait for condition variable (release mutex)
    
    condtasks->>ControllerTask: All cond_task are in the condition signal waiting list
    ControllerTask->>ControllerTask: Check state
    ControllerTask->>ControllerTask: Signal  a condition variable
    ControllerTask->>ControllerTask: Check state
    ControllerTask->>ControllerTask: Broadcast
    ControllerTask->>ControllerTask: Check state
Loading
  • Timed condition wait
    • 4: Timed condition mutex lock (1,1)
    • 8: Timed out condition mutex lock (2,0)
sequenceDiagram
    autonumber

    participant ControllerTask
    participant timed_cond

    ControllerTask->>timed_cond: Yield to timed_cond task
    timed_cond->>timed_cond: timed_cond block(RUNNING->BLOCKED)
    timed_cond->>ControllerTask: Yield to the controller task
    ControllerTask->>ControllerTask: Check state
    ControllerTask-->>timed_cond: Suspend self and wait for timed_cond wakeup.
    timed_cond->>timed_cond: timed_cond timeout and wakeup (BLOCKED->READY)
    timed_cond->>ControllerTask: yield to the controller for the state check
   ControllerTask->>ControllerTask: Check state
    
Loading
  • The checking list of modified APIs in this patch included in this unit test:
  • mutex_cond_wait()
  • mutex_cond_signal()
  • mutex_cond_broadcast()
  • mutex_cond_timedwait()

Benchmark

Approach

  • Test scenarios
#define DURATION 40000 /* Execution duration(ms) */
static const struct {
    const char *name;
    uint32_t task_count;
    int task_active_ratio;
} perf_tests[] = {
    {"Minimal Active", 500, 2}, /* 2% tasks available */
    {"Moderate Active", 500, 4}, {"Heavy Active", 500, 20},
    {"Stress Test", 500, 50},    {"Full Load Test", 500, 100},
};
  • Timer
    Start recording max schedule time after 3000ms, waking up end task(highest priority) after 40000ms (40s).
 /* Default task */
void task_normal(void)
{
    while (1) {
        if (mo_uptime() - test_start_time > DURATION) {
            /* Wake up end task */
            mo_task_resume(end_task_id);
        }

        /* Start record max schedule time after 3000ms(3s) */
        if (mo_uptime() - test_start_time > 3000)
            max_schedule_time = (each_schedule_time > max_schedule_time)
                                    ? each_schedule_time
                                    : max_schedule_time;

        mo_task_wfi();
    }
}

/* Timer insertion */
sched_select_next_task(){
uint64_t u = _read_us();   /* Start timer */
...
uint32_t exetime = _read_us() - u; /* Record each task selectiono time */
}

Result

Static data

Per-scenario statistics (improvement vs OLD):

Scenario 'Minimal Active':
  mean improvement        = 4.94x faster
  std dev of improvement  = 2.04x
  min / max improvement   = 3.14x  /  11.66x
  95% CI of improvement   = [4.05x, 5.83x]
  mean old sched time     = 5317.85 us 
  mean new sched time     = 1077.2 us 
  max  old sched time     = 56.0 us 
  max  new sched time     = 21.0 us 

Scenario 'Moderate Active':
  mean improvement        = 2.67x faster
  std dev of improvement  = 0.47x
  min / max improvement   = 2.10x  /  3.93x
  95% CI of improvement   = [2.46x, 2.88x]
  mean old sched time     = 2925.75 us 
  mean new sched time     = 1098.65 us 
  max  old sched time     = 40.0 us 
  max  new sched time     = 18.0 us 

Scenario 'Heavy Active':
  mean improvement        = 1.36x faster
  std dev of improvement  = 0.28x
  min / max improvement   = 0.87x  /  2.05x
  95% CI of improvement   = [1.23x, 1.48x]
  mean old sched time     = 1530.95 us 
  mean new sched time     = 1155.7 us 
  max  old sched time     = 22.0 us 
  max  new sched time     = 17.0 us 

Scenario 'Stress Test':
  mean improvement        = 1.20x faster
  std dev of improvement  = 0.08x
  min / max improvement   = 1.04x  /  1.39x
  95% CI of improvement   = [1.16x, 1.23x]
  mean old sched time     = 1240.9 us 
  mean new sched time     = 1039.0 us 
  max  old sched time     = 31.0 us 
  max  new sched time     = 18.0 us 

Scenario 'Full Load Test':
  mean improvement        = 1.29x faster
  std dev of improvement  = 0.10x
  min / max improvement   = 1.03x  /  1.46x
  95% CI of improvement   = [1.25x, 1.34x]
  mean old sched time     = 1382.9 us 
  mean new sched time     = 1074.15 us 
  max  old sched time     = 31.0 us 
  max  new sched time     = 23.0 us 

Comparison of new and original scheduler
image

New scheduler performance
image

Reference

Benchmark - 45406d1
#13

@vicLin8712 vicLin8712 changed the title Add foundational data structures and enqueue/dequeue API updates for the O(1) scheduler [1/4] O(1) scheduler: Introduce infrastructure Nov 18, 2025
@vicLin8712 vicLin8712 changed the title [1/4] O(1) scheduler: Introduce infrastructure [1/5] O(1) scheduler: Introduce infrastructure Nov 19, 2025
@vicLin8712 vicLin8712 changed the title [1/5] O(1) scheduler: Introduce infrastructure [1/3] O(1) scheduler: Introduce infrastructure Nov 19, 2025
@jserv jserv changed the title [1/3] O(1) scheduler: Introduce infrastructure O(1) scheduler: Introduce infrastructure Nov 19, 2025
@jserv
Copy link
Contributor

jserv commented Nov 19, 2025

Do not include numbers in pull-request subjects.

Copy link
Contributor

@jserv jserv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase the latest 'main' branch to resolve unintended CI/CD regressions.

/* Hart-Specific Data */
uint8_t hart_id; /* RISC-V hart identifier */

} sched_t;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, I’ve asked several questions about per-hart data structures before, but I didn't get a definitive answer at the time. If we want to split out per-hart data structures to facilitate future SMP integration, how do we distinguish which queues should go into the per-hart structure and which should remain in the global one in a multi-hart scenario? You retained the sched_t design, but I'm still unclear if this is a reasonable approach.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, the description for Patch 1 doesn't seem quite right to me. It states that sched_t is introduced to support the O(1) scheduler. However, IIUC, we could achieve the exact same behavior by placing everything in kcb_t if we disregard SMP support for now. Therefore, the existence of sched_t seems to be more about preparing for future SMP support rather than enabling the O(1) scheduler itself. Did I miss something? Is there actually a direct connection between the two?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your point. I originally separated the scheduler-related data structures for readability, but it’s not essential for this PR.

I will embed the separated section back into kcb_t to keep this patch focused on the functional changes.

Thanks for your suggestions.

@visitorckw
Copy link
Collaborator

I'm also a bit confused as to why the original PR was closed and split into three separate ones. It seems all three are related to O(1) scheduler support and are interdependent.

This actually makes the review process more difficult because the subsequent PRs include commits and changes from the previous ones. It also makes it much harder for me to track down previous discussions.

@vicLin8712
Copy link
Collaborator Author

I'm also a bit confused as to why the original PR was closed and split into three separate ones. It seems all three are related to O(1) scheduler support and are interdependent.

This actually makes the review process more difficult because the subsequent PRs include commits and changes from the previous ones. It also makes it much harder for me to track down previous discussions.

Thanks for the feedback, and I understand the confusion.

The original PR contained too many changes at once — new data structures, new APIs, logic refactoring, and the introduction of the O(1) scheduler. I split the work into three PRs (data structures, task state transitions, and final scheduler activation) so the series would be easier to review. Each PR remains compilable and all applications in apps/ continue to run with the old scheduler.

However, I understand that the current split causes later PRs to include commits from earlier ones, which complicates the review process.

Could you share your thoughts on what approach would be most appropriate for this project? For example, would you prefer a single PR with a clean commit history, or a small series of self-contained PRs?

I’d be happy to reorganize the work to match the project’s preferred workflow.

@vicLin8712 vicLin8712 force-pushed the o1-sched-basic branch 5 times, most recently from 24c5367 to eaf8b0c Compare November 21, 2025 13:36
@vicLin8712 vicLin8712 changed the title O(1) scheduler: Introduce infrastructure O(1) scheduler Nov 21, 2025
@vicLin8712 vicLin8712 force-pushed the o1-sched-basic branch 2 times, most recently from e995be1 to 6f37b93 Compare November 21, 2025 14:23
@vicLin8712
Copy link
Collaborator Author

@jserv ,

The following atomic operation in mutex.c includes task state transition.

/* Atomic block operation with enhanced error checking */
static void mutex_block_atomic(list_t *waiters)
{
...
    /* Block and yield atomically */
    self->state = TASK_BLOCKED;
    _yield(); /* This releases NOSCHED when we context switch */
}

In this commit series, the dequeuing path has been added in _sched_block() so that it can handle the correct RUNNING/READY → BLOCKED transition and corresponding bitmap operation. Still, its queue_t parameter doesn’t match the list_t used by mutex_block_atomic(), and the mutex doesn’t need the timer work either.
Should I extend _sched_block() to support list-based waiters, or create a small other helper for the mutex path?

@vicLin8712 vicLin8712 force-pushed the o1-sched-basic branch 3 times, most recently from 1d9d7a3 to fc2a929 Compare November 23, 2025 05:52
@vicLin8712 vicLin8712 marked this pull request as ready for review November 23, 2025 06:05
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (all 2 issues)

Understand the root cause of the following 2 issues and fix them.


<file name="kernel/task.c">

<violation number="1" location="kernel/task.c:914">
First task creation no longer initializes `kcb-&gt;task_current`, so the new scheduler immediately panics on startup because `sched_select_next_task` still requires a non-null current-task pointer.</violation>
</file>

<file name="include/lib/list.h">

<violation number="1" location="include/lib/list.h:106">
list_pushback_node dereferences target-&gt;next even though new rq_node instances never initialise that field, so the guard invokes undefined behaviour and can prevent the first enqueue.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

tcb->rq_node.data = tcb;

/* Push node to ready queue */
sched_enqueue_task(tcb);
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First task creation no longer initializes kcb->task_current, so the new scheduler immediately panics on startup because sched_select_next_task still requires a non-null current-task pointer.

Prompt for AI agents
Address the following comment on kernel/task.c at line 914:

<comment>First task creation no longer initializes `kcb-&gt;task_current`, so the new scheduler immediately panics on startup because `sched_select_next_task` still requires a non-null current-task pointer.</comment>

<file context>
@@ -770,8 +907,11 @@ int32_t mo_task_spawn(void *task_entry, uint16_t stack_size_req)
+    tcb-&gt;rq_node.data = tcb;
+
+    /* Push node to ready queue */
+    sched_enqueue_task(tcb);
 
     CRITICAL_LEAVE();
</file context>
Fix with Cubic

/* Pushback list node into list */
static inline void list_pushback_node(list_t *list, list_node_t *target)
{
if (unlikely(!list || !target || target->next))
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list_pushback_node dereferences target->next even though new rq_node instances never initialise that field, so the guard invokes undefined behaviour and can prevent the first enqueue.

Prompt for AI agents
Address the following comment on include/lib/list.h at line 106:

<comment>list_pushback_node dereferences target-&gt;next even though new rq_node instances never initialise that field, so the guard invokes undefined behaviour and can prevent the first enqueue.</comment>

<file context>
@@ -100,6 +100,24 @@ static inline list_node_t *list_pushback(list_t *list, void *data)
+/* Pushback list node into list */
+static inline void list_pushback_node(list_t *list, list_node_t *target)
+{
+    if (unlikely(!list || !target || target-&gt;next))
+        return;
+
</file context>
Fix with Cubic

@vicLin8712 vicLin8712 marked this pull request as draft November 25, 2025 09:46
@vicLin8712 vicLin8712 force-pushed the o1-sched-basic branch 3 times, most recently from f2a35b3 to 846be25 Compare December 3, 2025 12:22
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.
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.

Add the insertion and removal list helpers without malloc and free
on the linkage node. Both APIs will be applied in the dequeue and
enqueue paths.
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 change aligns with the new task selection based on the ready 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 change aligns with the non-runnable task should not in the ready
queue.
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 change 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 sched_enqueue_task()
and sched_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.

This change keeps task state transition consistent to the ready queue
semantic.
Previously, task operations lacked public enqueue/dequeue helpers usable
from outside the task core, which made it impossible to keep ready queue
synchronized when tasks crossed the runnable boundary.

This change introduces two helpers to be used by mutex and semaphore
resource-wait and wakeup paths, ensuring their state transitions update
ready queue explicitly and remain aligned with ready-queue semantics.

Both helpers are prefixed with _sched_block to emphasize their role in
TASK_BLOCKED-related transitions.

This keeps mutex and semaphore APIs consistent with ready-queue semantics.
Invoke _sched_block_enqueue() and _sched_block_dequeue() helpers
for all transitions into or out of TASK_BLOCKED state.

This change keeps the scheduler ready queue and mutex/semaphore
semantics aligned and consistent.
Once a task wakes up from a timed mutex lock, its state is already
in TASK_RUNNING instead of TASK_BLOCKED.

To determine whether the wakeup was triggered by the mutex or by a
timeout, check whether the woken task is still present in the mutex
waiter list.

This change removes the incorrect TASK_BLOCKED-based condition check
and replaces it with a waiter list check for timed mutex lock.
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 change supports task migration from original ready queue to the new
priority ready queue.
Move sched_enqueue_task() into a critical section to protect ready_queue[]
integrity, as the API modifies shared scheduler resources.

Initialize the embedded rq_node when a task spawns and set its next pointer
to NULL to ensure deterministic linkage for ready-queue insertion.

Bind the initial task slot using rq_node instead of the global task list,
matching the new ready-queue selection model.

This aligns task-spawn behavior with rq_node-based scheduling semantics.
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.
This unit test cover task.c, mutex.c, and semaphore.c APIs that related
to task state transition.
@vicLin8712 vicLin8712 marked this pull request as ready for review December 4, 2025 05:56
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files

Prompt for AI agents (all 1 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="kernel/task.c">

<violation number="1" location="kernel/task.c:506">
P1: Array out-of-bounds access when `ready_bitmap` is 0. The loop exits with `top_prio_level = 8`, but arrays are sized 0-7. Add a bounds check or early panic when bitmap is empty.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

/* Skip non-ready tasks */
if (task->state != TASK_READY)
continue;
list_node_t **cursor = &kcb->rr_cursors[top_prio_level];
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Array out-of-bounds access when ready_bitmap is 0. The loop exits with top_prio_level = 8, but arrays are sized 0-7. Add a bounds check or early panic when bitmap is empty.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kernel/task.c, line 506:

<comment>Array out-of-bounds access when `ready_bitmap` is 0. The loop exits with `top_prio_level = 8`, but arrays are sized 0-7. Add a bounds check or early panic when bitmap is empty.</comment>

<file context>
@@ -443,53 +492,35 @@ uint16_t sched_select_next_task(void)
-        /* Skip non-ready tasks */
-        if (task-&gt;state != TASK_READY)
-            continue;
+    list_node_t **cursor = &amp;kcb-&gt;rr_cursors[top_prio_level];
+    list_t *rq = kcb-&gt;ready_queues[top_prio_level];
+    if (unlikely(!rq || !*cursor))
</file context>
Suggested change
list_node_t **cursor = &kcb->rr_cursors[top_prio_level];
if (top_prio_level >= TASK_PRIORITY_LEVELS)
panic(ERR_NO_TASKS);
list_node_t **cursor = &kcb->rr_cursors[top_prio_level];
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants