-
Notifications
You must be signed in to change notification settings - Fork 28
Implement EDF scheduler with ecall-based context switch #51
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 issues found across 8 files
Prompt for AI agents (all 3 issues)
Understand the root cause of the following 3 issues and fix them.
<file name="app/rtsched.c">
<violation number="1" location="app/rtsched.c:238">
Expected job counts use floor division, so RT statistics under-report by one when the test duration is not an exact multiple of the task period. Use a ceiling calculation to reflect the actual number of releases.</violation>
</file>
<file name="kernel/task.c">
<violation number="1" location="kernel/task.c:478">
Blocked tasks are force-selected as placeholders and immediately reclassified as RUNNING, so `mo_task_delay()` returns before the requested ticks elapse and the delay counter never reaches zero. Tasks can no longer sleep when every ready task is blocked.</violation>
</file>
<file name="arch/riscv/hal.c">
<violation number="1" location="arch/riscv/hal.c:313">
`__builtin_frame_address(0)` does not point to the ISR trap frame, so MEPC is never updated and the ecall keeps retriggering instead of yielding.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| list_node_t *any_node = list_next(kcb->tasks->head); | ||
| while (any_node && any_node != kcb->tasks->tail) { | ||
| if (any_node->data) { | ||
| kcb->task_current = any_node; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocked tasks are force-selected as placeholders and immediately reclassified as RUNNING, so mo_task_delay() returns before the requested ticks elapse and the delay counter never reaches zero. Tasks can no longer sleep when every ready task is blocked.
Prompt for AI agents
Address the following comment on kernel/task.c at line 478:
<comment>Blocked tasks are force-selected as placeholders and immediately reclassified as RUNNING, so `mo_task_delay()` returns before the requested ticks elapse and the delay counter never reaches zero. Tasks can no longer sleep when every ready task is blocked.</comment>
<file context>
@@ -443,7 +463,29 @@ uint16_t sched_select_next_task(void)
+ list_node_t *any_node = list_next(kcb->tasks->head);
+ while (any_node && any_node != kcb->tasks->tail) {
+ if (any_node->data) {
+ kcb->task_current = any_node;
+ tcb_t *any_task = any_node->data;
+ return any_task->id;
</file context>
6c67a2d to
489485c
Compare
This commit introduces a preemptive Earliest Deadline First (EDF) scheduler that uses RISC-V ecall instructions for voluntary context switches while preserving the existing cooperative scheduling mode. The preemptive scheduler required several architectural changes. Tasks now maintain separate stack pointer (sp) fields for ISR-based context switching, distinct from the jmp_buf context used in cooperative mode. The dispatcher accepts a from_timer parameter to distinguish timer-driven preemption from voluntary yields, ensuring tick counters only increment on actual timer interrupts. Context switching in preemptive mode builds ISR stack frames with mepc pointing to task entry points, allowing mret to resume execution. The ecall handler invokes the dispatcher directly, enabling tasks to yield without relying on setjmp/longjmp which are incompatible with interrupt contexts. The cooperative mode preserves its setjmp/longjmp semantics. The dispatcher always calls hal_context_restore() even when the same task continues, because the longjmp completes the save/restore cycle initiated by hal_context_save(). The hal_interrupt_tick() function enables interrupts on a task's first run by detecting when the entry point still resides in the context's return address slot. Real-time scheduling support includes EDF with deadline-based priority calculation, configurable through mo_task_rt_priority(). The RT scheduler hook in KCB allows custom scheduling policies. Delay handling was enhanced with batch updates to minimize critical section duration. The logger subsystem gained a direct_mode flag for ISR-safe output, and printf was made flush-aware to support synchronous output when needed. Exception handling uses trap_puts() to avoid printf deadlock in trap context. Close #26
Linmo CI Test ResultsOverall Status: ✅ passed Toolchain Results
Application Tests
Functional Test Details
Report generated from |
The preemptive EDF (Earliest Deadline First) scheduler requires a mechanism for tasks to voluntarily yield CPU time while blocked on delays. In RISC-V M-mode, the ecall instruction provides a clean way to trigger a synchronous trap that invokes the scheduler without relying on timer interrupts.
The dispatcher function now accepts a from_timer parameter to distinguish between timer-driven preemption and ecall-driven yields. Timer interrupts increment the system tick counter and process time slices, while ecall-based yields skip tick advancement to prevent time drift.
When handling ecall from M-mode, the trap handler advances mepc past the 4-byte ecall instruction to prevent re-execution upon return. Critically, the ISR stack frame must also be updated because the ISR epilogue restores mepc from the saved frame rather than the CSR directly. Without this fix, mret would jump back to the ecall instruction causing an infinite trap loop.
The logger subsystem gains a flush mechanism with a direct_mode flag that bypasses the async queue. This ensures multi-line output like statistics reports prints in order, as printf normally enqueues to the ring buffer which the logger task drains asynchronously.
The rtsched test application validates the EDF implementation by running periodic RT tasks with different periods and deadlines, measuring execution counts, deadline misses, response times, and jitter to verify correct scheduler behavior.
Close #26
Summary by cubic
Introduce a preemptive EDF scheduler with ecall-based context switching on RISC‑V and a flushable logger for ordered output. Includes a test app that validates deadlines, response times, jitter, fairness, and non‑RT starvation.
New Features
Migration
Written for commit 424616f. Summary will update automatically on new commits.