Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a7ec0ee
feat: add the run queue bitmap and update the run queue data structur…
LuernOutOfOrder Feb 10, 2026
41b97ce
feat: improve run and block queue type to handle multi core arch for …
LuernOutOfOrder Feb 10, 2026
3571e5e
feat(primitives): add basic bitmap datastructure
LuernOutOfOrder Feb 10, 2026
2330c85
feat(scheduler & task): update run_queue bitmap to use new Bitmap typ…
LuernOutOfOrder Feb 10, 2026
4abb5c4
feat(primitives & scheduler): add is_bitmap_zero to bitmap data struc…
LuernOutOfOrder Feb 12, 2026
f3c7273
feat(primitives & scheduler): update the bitmap and ring buffer primi…
LuernOutOfOrder Feb 12, 2026
4da9773
refactor: rename the old dispatch function to scheduler and update co…
LuernOutOfOrder Feb 12, 2026
b12dd8f
feat(primitives): working on the push to delta list, hard to do, wip
LuernOutOfOrder Feb 12, 2026
f7f2bc1
feat(primitives & tests): start adding DeltaList test, easier to test…
LuernOutOfOrder Feb 12, 2026
2c39d50
refactor: update the delta list to index_linked_list, delta was too m…
LuernOutOfOrder Feb 13, 2026
b3aa4f7
feat(primitives & tests): make the indexed linked list work
LuernOutOfOrder Feb 13, 2026
d219321
feat(primitives & tests): add more test for the indexed linked list push
LuernOutOfOrder Feb 13, 2026
8a4a38c
feat(primitives & tests): add pop and get_head_node methods with test…
LuernOutOfOrder Feb 13, 2026
613c47e
docs(kernel): update the data structure doc to include the new Indexe…
LuernOutOfOrder Feb 13, 2026
b5165f6
feat(primitives & tests): add a duplication check
LuernOutOfOrder Feb 13, 2026
fb8bf47
docs: update the docs for IndexedLinkedList in data structure
LuernOutOfOrder Feb 13, 2026
1a7cd0d
feat(scheduler & task primitives): update the scheduler to use new bl…
LuernOutOfOrder Feb 16, 2026
4169852
docs(scheduler): improve code comment on the need_resched flags in th…
LuernOutOfOrder Feb 16, 2026
e95789e
feat(scheduler & task primitives): update the sleep routine to correc…
LuernOutOfOrder Feb 16, 2026
90c0bb6
fix: the scheduler blocked queue not working with task primitives
LuernOutOfOrder Feb 16, 2026
077e77a
feat(primitives): update the indexed linked list to correctly pop
LuernOutOfOrder Feb 16, 2026
7cfe8a1
fix(arch riscv32): no more crash when trigger a reschedule, the runti…
LuernOutOfOrder Feb 17, 2026
42f2f80
feat(arch riscv32): remove the save of mstatus in save_context, and u…
LuernOutOfOrder Feb 17, 2026
f3c7490
fix(arch riscv32): trap entry to use mepc to return to task execution…
LuernOutOfOrder Feb 17, 2026
db707bc
feat(arch riscv32): improve mstatus handling on save and restore context
LuernOutOfOrder Feb 17, 2026
7825b87
feat: preemptif scheduler work
LuernOutOfOrder Feb 17, 2026
7bc3147
feat(arch riscv32): remove the hardcoded mstatus value, prefer handli…
LuernOutOfOrder Feb 17, 2026
8b15c37
docs(kernel): add basic scheduler doc
LuernOutOfOrder Feb 17, 2026
ea5eafb
docs(kernel): update the scheduler doc, add invariants and toc
LuernOutOfOrder Feb 19, 2026
537a3f5
docs(kernel): add the scheduling model section
LuernOutOfOrder Feb 19, 2026
10d2bc1
docs(kernel): update the scheduler invariants with temp invariant
LuernOutOfOrder Feb 19, 2026
afbaeb9
feat(tests): add the indexed linked list test suite
LuernOutOfOrder Feb 19, 2026
05db047
feat(tests): update riscv32 task_context test to use new scheduler qu…
LuernOutOfOrder Feb 19, 2026
a6ed074
feat(tests): update task_context and task primitives tests
LuernOutOfOrder Feb 19, 2026
c5a3934
feat(scheduler & tests): update the scheduler info header and add emp…
LuernOutOfOrder Feb 19, 2026
410bfed
chore: remove all warnings
LuernOutOfOrder Feb 19, 2026
3bf1f34
chore: remove unused import and clean unused variable
LuernOutOfOrder Feb 19, 2026
196cd04
feat(tests primitives): add missing IndexedLinkedList pop test
LuernOutOfOrder Feb 19, 2026
4e59f26
chore: format file and added scheduler test suite empty fn
LuernOutOfOrder Feb 19, 2026
e624955
chore: refactor codebase for make check, improve codebase, format fil…
LuernOutOfOrder Feb 19, 2026
78a7969
feat(tests): add the scheduler_test_suite to test_suites fn
LuernOutOfOrder Feb 19, 2026
31ec292
feat(linkers): update test_mode linker, smelly I know
LuernOutOfOrder Feb 19, 2026
2ff0dcb
chore: bump kernel version to 0.4.5
LuernOutOfOrder Feb 19, 2026
5ca34ec
chore: remove Debug impl in task_context structure and indexed_linked…
LuernOutOfOrder Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lrnrtos"
version = "0.4.4"
version = "0.4.5"
edition = "2024"

[[bin]]
Expand Down
18 changes: 18 additions & 0 deletions Documentation/kernel/data_structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- [Invariants](#invariants)
- [AlignedStack16](#alignedstack16)
- [Invariants](#invariants-1)
- [IndexedLinkedList](#indexedlinkedlist)
- [Invariants](#invariants-2)
<!--toc:end-->

## Description
Expand Down Expand Up @@ -37,3 +39,19 @@ This type is used when you need a stack on a buffer, and the `sp` must be aligne
- The backing storage is always 16-byte aligned.
- Any stack pointer derived from this type must remain 16-byte aligned at all call boundaries.
- This type provides a memory-layout guarantee only; it does not validate stack usage correctness.

### IndexedLinkedList

A linked list but store in an array, so each node is accessed from it's index in the array.
This is better to use this data structure as a side storage, like we use it to store blocked task.
Task are already store in a TaskList, so in the IndexedLinkedList used for blocked task we only store task id, and task awake tick for now.

#### Invariants

- All node should be accessible from the head node.
- The list is sorted naturally from the `value` field of a node.
- The `count` field should reflect the number of accessible node in the list.
- The list is empty when `count`, `head` and `tail` are equal to 0.
- If the `next_node` of a node is some, this `next_node` is valid.
- If the `next_node` of a node is none, then this node is the `tail`.
- The node `id` is unique, you can't add the same `id` in the list.
57 changes: 57 additions & 0 deletions Documentation/kernel/scheduler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Kernel scheduler

<!--toc:start-->
- [Kernel scheduler](#kernel-scheduler)
- [Description](#description)
- [Queues](#queues)
- [Run queue](#run-queue)
- [Blocked queue](#blocked-queue)
- [Preemption](#preemption)
- [Scheduling model](#scheduling-model)
- [Invariants](#invariants)
<!--toc:end-->

## Description

The kernel scheduler is in charge of managing which task to run, updating the run queue and the blocked queue and updating the task state.

## Queues

There's 2 queues used in the scheduler. The `run queue` and the `blocked queue`. Each queue is specific for a `CPU core`. The `CPU core 1` has a different `run queue` than the `CPU core 2`.

### Run queue

The `run queue` contains all the task ready to be run. In the `ready` task state.

The `run queue` work using a `FIFO` queue, there's a queue for each `priority`, up to `32 queues`.
To find which queue to use to execute a task, instead of iterating over all queues, we use a `bitmap`.
There's only `32 priorities`, so we use a `u32 bitmap`, each bit representing a `run queue`, if the bit is set, there's at least 1 task to run.
Else, the queue is empty.

### Blocked queue

The `blocked queue` contains all the task currently blocked. With different block reasons.
But currently the `blocked queue` contains uniquely the task blocked using the `sleep` task primitive.

The `blocked queue` work using an `indexed linked list`, the list is sorted from the shortest awake tick to the largest awake tick.
The list is manage using `head` and `tail`, a bit like a `ring buffer` data structure.
So when we need to check the next task to awake, we just check the `head` of the `blocked queue`.
If it can be awake, it will update the `need_resched` flag, then it'll trigger a reschedule, the scheduler will be able to awake the task, and move it from the `blocked queue` to the `run queue`.

## Preemption

The scheduler is preemptive, meaning that if a task as a higher priority than the current task, it will run this higher priority task.
The current task having a lowest priority, will be saved, and re-execute when there's no higher priority task to run.

## Scheduling model

The current scheduling model is a `cooperative priority based`. Meaning that if there's `2 task` with the same `priority`, if they don't call `yield`, one task will `always run`.
Making the other task `starving`.
So if you want to switch from a `task` to another one, you need to use `cooperative` functions, like `yield` or `sleep`.

## Invariants

- The scheduler need at least one task in the `run queue`, if the `run queue` is empty, it will try to run the idle task. Make sure that there's always at least one task in the `run queue`, or enable the `idle task` feature.
- The scheduler assume that the `CpusState` is initialized to access the `CPU core scheduler state`.
- The scheduler can be called from the `trap epilogue`, if so, a `trap frame`, should be available and accessible for the scheduler to run on.
- For now the scheduler assume that there's always a `current running task`, if not, and the scheduler is triggered, this could lead to UB.
4 changes: 2 additions & 2 deletions linkers/linker_test_mode.ld
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
ENTRY(kstart)

MEMORY {
RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 128K
RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 256K
/* Not real ROM or flash from qemu virt machine, just use another RAM reg for now */
ROM (rx) : ORIGIN = 0x80000000, LENGTH = 210K
ROM (rx) : ORIGIN = 0x80000000, LENGTH = 256K
}

SECTIONS {
Expand Down
5 changes: 2 additions & 3 deletions src/arch/riscv32/asm/context_offset.S
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
.set OFFSET_SP, 140
# Return address
.set OFFSET_RA, 144
.set OFFSET_MSTATUS, 148
# Optionnal flags, will be use later maybe
.set OFFSET_FLAGS, 152
.set OFFSET_INSTRUCTION_REG, 155
.set OFFSET_FLAGS, 148
.set OFFSET_INSTRUCTION_REG, 151
30 changes: 27 additions & 3 deletions src/arch/riscv32/asm/restore_context.S
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,38 @@ restore_context:
# Restore sp from current task context structure
lw t0, OFFSET_SP(t6)
mv sp, t0
## Update mstatus
lw t0, OFFSET_MSTATUS(t6)
csrw mstatus, t0
# Restore current task context using GNU macro
# GNU macros from `src/arch/riscv32/asm/gnu_macro.S`
.set i, 1
.rept 31
load_gp_context %i
.set i, i+1
.endr
# Update mstatus
li t0, ((3 << 11) | (1 << 3))
csrrs x0, mstatus, t0
ret

.global trap_restore_context
.type trap_restore_context, @function
trap_restore_context:
# Move current task context struct save in caller in a0 reg to t6
mv t6, a0
# Update sp
# Restore sp from current task context structure
lw t0, OFFSET_SP(t6)
mv sp, t0
# Update mepc
lw t0, OFFSET_PC(t6)
csrw mepc, t0
# Restore current task context using GNU macro
# GNU macros from `src/arch/riscv32/asm/gnu_macro.S`
.set i, 1
.rept 31
load_gp_context %i
.set i, i+1
.endr
# Update mstatus
li t0, ((3 << 11) | (1 << 7))
csrrs x0, mstatus, t0
mret
28 changes: 25 additions & 3 deletions src/arch/riscv32/asm/save_context.S
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,37 @@
.global save_context
.type save_context, @function
save_context:
# Clear mstatus.MPP and mstatus.mie to avoid interruption while saving and switching context
li t0, ((3 << 11) | (1 << 3))
csrrc x0, mstatus, t0
# Move current task context struct save in caller in a0 reg to t6
mv t6, a0
# Store ra in task context structure
sw a1, OFFSET_RA(t6)
# Store word from t0(sp) in context structure
sw a2, OFFSET_SP(t6)
# Save mstatus
csrr t0, mstatus
sw t0, OFFSET_MSTATUS(t6)
sw a3, OFFSET_PC(t6)
# Save current task context using GNU macro
# GNU macros from `src/arch/riscv32/asm/gnu_macro.S`
.set i, 1
.rept 31
save_gp_context %i
.set i, i+1
.endr
ret

.global trap_save_context
.type trap_save_context, @function
trap_save_context:
# Clear mstatus.MPP and mstatus.mie to avoid interruption while saving and switching context
li t0, ((3 << 11) | (1 << 3))
csrrc x0, mstatus, t0
# Move current task context struct save in caller in a0 reg to t6
mv t6, a0
# Store ra in task context structure
sw a1, OFFSET_PC(t6)
# Store word from t0(sp) in context structure
sw a2, OFFSET_SP(t6)
# Save current task context using GNU macro
# GNU macros from `src/arch/riscv32/asm/gnu_macro.S`
.set i, 1
Expand Down
45 changes: 19 additions & 26 deletions src/arch/riscv32/asm/trap_entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,19 @@
.global trap_entry
.type trap_entry, @function
trap_entry:
# Read mscratch into t6
# Save task context
# Get current task ptr
la t0, TASK_HANDLER # Address in RAM
lw t1, 0(t0) # Get the value behind the ref
mv a0, t1
# Save current pc
csrr a1, mepc
# Save current sp
mv a2, sp
# Call the save context function
call trap_save_context
# Read mscratch into t7
csrr t6, mscratch
# Save all GP registers using GNU macro
# Just a loop
.set i, 1
.rept 31
save_gp %i, t6
.set i, i+1
.endr
# Read CSR and store word in correct offset of trap frame structure
csrr t0, satp
sw t0, OFFSET_SATP(t6) # store satp
csrr t0, mhartid
sw t0, OFFSET_HARTID(t6) # store hartid (optional, redundant)
sw sp, (OFFSET_TRAP_STACK - 4)(t6) # store original sp just before trap_stack slot
# Load content of trap stack offset into t1
lw t1, OFFSET_TRAP_STACK(t6) # t1 = trap_stack pointer
# Branch instruction to check if t1 = 0
beqz t1, trap_no_trapstack
Expand All @@ -42,22 +39,18 @@ trap_entry:
mv a5, t6
# Call trap_handler rust function
call trap_handler
# Load all register back to previous state
csrr t6, mscratch
# Restore all GP registers
.set i, 1
.rept 31
load_gp %i
.set i, i+1
.endr

# Check if a re-schedule is needed or not.
call read_need_reschedule
# If a0 != 0, goto 1f, else mret
bnez a0, 1f
mret
# If there's no need to a reschedule, restore the task context
la t0, TASK_HANDLER # Address in RAM
lw t1, 0(t0) # Get the value behind the ref
mv a0, t1
call trap_restore_context
1:
call dispatch
mret
call scheduler

# Function used when the trap strack = 0, just infinite loop for debuging purpose
trap_no_trapstack:
Expand Down
2 changes: 1 addition & 1 deletion src/arch/riscv32/asm/yield.S
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ yield:
# Call the save context function
call save_context
# Once save_context return, call the scheduler
call dispatch
call scheduler
6 changes: 2 additions & 4 deletions src/arch/riscv32/task/task_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ pub struct TaskContext {
pub pc: u32, // Offset 136
pub sp: u32, // Offset 140
pub ra: u32, // Offset 144
pub mstatus: u32, // Offset 148
pub flags: [u8; 3], // Offset 152 (first index 152; second index 153, third index 154)
pub instruction_register: u8, // Offset 155
pub flags: [u8; 3], // Offset 148 (first index 148; second index 149, third index 150)
pub instruction_register: u8, // Offset 151
}

impl TaskContext {
Expand All @@ -39,7 +38,6 @@ impl TaskContext {
sp: size[0] as u32,
ra: func as usize as u32,
// Set mstatus to 8 by default to enable mie
mstatus: 8,
flags: [0u8; 3],
instruction_register: 0,
}
Expand Down
8 changes: 6 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ pub static LOG_LEVEL: LogLevel = LogLevel::Debug;

// Define the uart address to use in kprint
pub static KPRINT_ADDRESS: usize = 0x1000_0000;

// ————————————————————————————————————————————————————————————
// ——————— Define the max priority available for a task ———————
// ————————————————————————————————————————————————————————————
pub static TASK_MAX_PRIORITY: usize = 32;
// ————————————————————————————————————————————————————————————
// ———————— Define the max size of devices sub-systems ————————
// ————————————————————————————————————————————————————————————
Expand All @@ -36,10 +39,11 @@ pub static FDT_MAX_PROPS: usize = 128;
// ————————————————————————————————————————————————————————————
pub static TASK_LIST_MAX_SIZE: usize = 4;
// ————————————————————————————————————————————————————————————
// ————————————— Define the max size of the Run queue —————————
// ———— Define the max size of the task run/blocked queue —————
// ————————————————————————————————————————————————————————————
// The run queue is len - 1, if the size is 4, it will only use 3 slot in the queue.
pub static RUN_QUEUE_MAX_SIZE: usize = 3;
pub static BLOCK_QUEUE_MAX_SIZE: usize = 3;
// ————————————————————————————————————————————————————————————
// ————————————— Define the number of CPU core ————————————————
// ————————————————————————————————————————————————————————————
Expand Down
2 changes: 1 addition & 1 deletion src/info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// The kernel exposes build-time version information at runtime.
// This information is immutable and reflects the exact binary currently running.
// It is intended for debugging, logging, and diagnostic purposes.
pub static KERNEL_VERSION: &str = "0.4.4";
pub static KERNEL_VERSION: &str = "0.4.5";
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#![deny(clippy::expect_used)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
#![feature(stmt_expr_attributes)]

// Config module
pub mod config;
Expand Down
44 changes: 44 additions & 0 deletions src/primitives/bitmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#[derive(Copy, Clone)]
pub struct Bitmap {
pub map: u32,
}

impl Bitmap {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Bitmap { map: 0 }
}

/// Set the given bit to 1.
pub fn set_bit(&mut self, bit: usize) {
let mask = 1 << bit;
self.map |= mask;
}

/// Clear the given bit. Set it to 0.
pub fn clear_bit(&mut self, bit: usize) {
self.map &= !(1 << bit);
}

/// Iterate over the bitmap and return the heavier bit.
/// Example: bitmap set to u8 -> map: 01001010. The function will return 6, because the first
/// bit set to 1, from the highest bit, is the bit 6.
/// Arguments:
/// &mut self: call the map initialized. Must be mutable.
pub fn find_leading_bit(&mut self) -> usize {
let bits = core::mem::size_of::<usize>() * 8;
let mut value: usize = 0;
for i in (0..bits).rev() {
let bit = (self.map >> i) & 1;
if bit == 1 {
value = i;
break;
}
}
value
}

pub fn is_bitmap_zero(&self) -> bool {
self.map == 0
}
}
Loading