Skip to content

Support SMP scheduling and synchronization #6

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
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

visitorckw
Copy link
Collaborator

Enable SMP support by adding multi-core QEMU simulation and adapting
RISC-V boot for per-hart stacks and timers. It replaces interrupt
masking with spinlocks in core kernel subsystems for safe concurrency,
updates the kernel control block to track per-hart current tasks, and
spawns idle tasks at boot to prevent panics. Spinlock-based
synchronization replaces hart parking during boot, and printf output is
protected against interleaving. A spinlock implementation using RV32A
atomics is also included. These changes enable stable multi-core
operation.

Introduce a simple spinlock implementation based on test-and-set using
RV32A atomic instructions. The spinlock API includes basic locking,
IRQ-safe variants, and versions that save and restore interrupt state.

To support atomic instructions, the Makefile is updated to enable the
'A' extension by changing the -march flag.

This is the first step toward enabling multi-core task scheduling
support on RISC-V SMP systems.
The original malloc/free implementation used CRITICAL_ENTER() and
CRITICAL_LEAVE() to protect critical sections by simply disabling
interrupts, which is sufficient on single-core systems.

To support SMP, we replace these with a proper spinlock that uses RV32A
atomic instructions. This ensures correctness when multiple harts
access the allocator concurrently.

This change allows future task scheduling across multiple harts without
risking race conditions in the memory allocator.
The original message queue implementation used CRITICAL_ENTER() and
CRITICAL_LEAVE() to protect critical sections by disabling interrupts.
This was sufficient for single-core systems, where only one hart could
execute tasks.

To support SMP, we replace these macros with a proper spinlock using
RV32A atomic instructions. This ensures safe access to the internal
queue structures when multiple harts concurrently interact with message
queues.

This change eliminates potential race conditions in message queue
operations as we move toward multi-hart scheduling.
…pport

The original task management code used CRITICAL_ENTER() /
CRITICAL_LEAVE() and NOSCHED_ENTER() / NOSCHED_LEAVE() to protect
critical sections by disabling interrupts, which was sufficient for
single-core systems.

To support SMP, these macros are replaced with a spinlock based on
RV32A atomic instructions. This ensures that multiple harts can safely
access and modify shared task data such as ready queues, priority
values, and task control blocks.

This change is essential for enabling multi-hart task scheduling
without introducing race conditions in the kernel task subsystem.
The original pipe implementation used CRITICAL_ENTER() and
CRITICAL_LEAVE() to protect critical sections by disabling interrupts,
which was acceptable for single-core systems.

To support SMP, these macros are replaced with a proper spinlock based
on RV32A atomic instructions. This ensures safe concurrent access to
the circular buffer used by the pipe, even when multiple harts are
performing read or write operations simultaneously.

This change is necessary to avoid race conditions and ensure correct
pipe behavior under multi-hart task scheduling.
The original semaphore implementation used NOSCHED_ENTER() and
NOSCHED_LEAVE() to protect critical sections by disabling interrupts,
which was sufficient in single-core environments.

To support SMP, we replace these macros with a spinlock based on RV32A
atomic instructions. This ensures safe access to shared semaphore
state, including the count and wait queue, when multiple harts operate
concurrently.

This change is necessary to avoid race conditions during mo_sem_wait(),
mo_sem_signal(), and other semaphore operations under multi-hart
scheduling.
The timer subsystem originally used NOSCHED_ENTER() and NOSCHED_LEAVE()
to disable interrupts when accessing shared timer state, which sufficed
on single-core systems.

To support SMP, we now replace these macros with a spinlock based on
RV32A atomic instructions. This ensures safe concurrent access to
global timer state such as timer_initialized, the timer list, and ID
management.

This change prepares the timer subsystem for correct operation when
multiple harts simultaneously create, start, or cancel timers.
The mutex and condition variable implementation previously relied on
NOSCHED_ENTER() and NOSCHED_LEAVE() to protect critical sections by
disabling interrupts. This works in single-core environments but breaks
down under SMP due to race conditions between harts.

This patch replaces those macros with a spinlock built using RV32A
atomic instructions. The spinlock protects access to shared state
including mutex ownership, waiter lists, and condition wait queues.

This change ensures correct mutual exclusion and atomicity when
multiple harts concurrently lock/unlock mutexes or signal condition
variables.
On SMP systems, concurrent calls to printf() from multiple harts can
cause interleaved and unreadable output due to racing writes to the
shared output buffer.

Add a spinlock to serialize access to printf(), ensuring that only one
hart writes at a time.

This change improves the readability of debug messages and prevents
garbled output when multiple harts are active.
All calls to NOSCHED_ENTER(), NOSCHED_LEAVE(), CRITICAL_ENTER(), and
CRITICAL_LEAVE() have been replaced with spinlock-based synchronization
primitives throughout the kernel.

As a result, these macros are no longer used and have been removed from
include/sys/task.h to clean up the codebase and avoid confusion.
To support SMP, allocate separate stack memory regions for each hart
during boot.

This patch modifies the assembly entry code in arch/riscv/boot.c to
compute the initial stack pointer based on the hart ID, ensuring each
hart uses a distinct stack area of fixed size (STACK_SIZE_PER_HART).

This enables multiple harts to safely run concurrently without stack
collisions during early boot stages.
Remove the old logic that parks all secondary harts in WFI, which
caused them to hang indefinitely. Instead, all harts proceed with boot.

To ensure proper initialization sequence, hart 0 performs hardware
setup, heap initialization, and task creation. Other harts spin-wait on
a spinlock-protected flag until hart 0 finishes initialization before
starting task dispatch.
The task_lock spinlock was primarily used to protect access to the
Kernel Control Block (kcb) and its internal data structures. Move the
spinlock into the kcb_t struct as kcb_lock, consolidating related
state and synchronization primitives together.

All uses of the standalone task_lock spinlock are replaced by
kcb->kcb_lock accesses, improving code clarity and encapsulation of the
kernel's core control block.
To prevent kernel panic during startup when some harts may not have any
runnable tasks assigned, add an idle task for each hart. The idle task
runs an infinite loop calling mo_task_wfi(), ensuring the hart remains
in a low-power wait state instead of causing a panic due to lack of
tasks.

This guarantees that every hart has at least one task to execute
immediately after boot, improving system robustness and stability on
SMP setups.
Previously, only a single global pointer tracked the current running
task, which worked for single-core systems. To support SMP, change the
Kernel Control Block (KCB) to maintain an array of current task
pointers, one per hart.

Added get_task_current() and set_task_current() helper functions to
retrieve and update the current task for the executing hart.

Modify kernel and HAL code to use these new functions instead of the
single global current task pointer, ensuring correct task tracking on
each hart.
Since kcb->ticks is shared and updated by all cores, add a spinlock to
protect its increment operation in the dispatcher, ensuring atomicity
and preventing race conditions in SMP environments.
Previously, mtimecmp was accessed at a fixed MMIO address assuming a
single core. Each hart has its own mtimecmp register at distinct
offsets, so update mtimecmp read and write functions to index based on
the current hart ID, ensuring correct timer compare handling in SMP
systems.
Enable running the kernel on 4 simulated cores by passing the -smp 4
parameter to qemu-system-riscv32, facilitating SMP testing and
development.
@visitorckw
Copy link
Collaborator Author

Marking this PR as a draft due to test failures in several applications, but I still appreciate any reviews or suggestions.

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.

1 participant