]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: per-CPU scheduler runqueue infrastructure with load tracking
authorTulio A M Mendes <[email protected]>
Sun, 15 Feb 2026 02:47:29 +0000 (23:47 -0300)
committerTulio A M Mendes <[email protected]>
Sun, 15 Feb 2026 02:47:29 +0000 (23:47 -0300)
- Add rq_load field to percpu_data struct (offset 20, struct stays 32 bytes)
- New sched_pcpu module: per-CPU load counters with atomic operations
  - sched_pcpu_init(): initialize for N CPUs after SMP enumeration
  - sched_pcpu_inc_load/dec_load(): lock-free load tracking
  - sched_pcpu_least_loaded(): find CPU with fewest ready processes
  - sched_pcpu_get_load(): query per-CPU load
- Integrate load tracking into scheduler enqueue/dequeue paths
- Wire up sched_pcpu_init() in arch_platform_setup after percpu_setup_gs
- All 35/35 smoke tests pass, 16/16 battery, cppcheck clean

include/arch/x86/percpu.h
include/sched_pcpu.h [new file with mode: 0644]
src/arch/x86/arch_platform.c
src/kernel/sched_pcpu.c [new file with mode: 0644]
src/kernel/scheduler.c

index 2a1a59143ef6a834123b54d05cdcdff7b7e71411..993098ff80ce296003281353f995ef80fbe2f10d 100644 (file)
@@ -15,7 +15,8 @@ struct percpu_data {
     struct process*  current_process; /* Currently running process on this CPU */
     uintptr_t        kernel_stack;    /* Top of this CPU's kernel stack */
     uint32_t         nested_irq;      /* IRQ nesting depth */
-    uint32_t         reserved[3];     /* Padding to 32 bytes */
+    uint32_t         rq_load;         /* Number of READY processes on this CPU */
+    uint32_t         reserved[2];     /* Padding to 32 bytes */
 };
 
 /* Initialize per-CPU data for all CPUs. Called once from BSP after SMP init. */
diff --git a/include/sched_pcpu.h b/include/sched_pcpu.h
new file mode 100644 (file)
index 0000000..466c403
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef SCHED_PCPU_H
+#define SCHED_PCPU_H
+
+#include <stdint.h>
+
+/*
+ * Per-CPU scheduler runqueue infrastructure.
+ *
+ * Provides per-CPU runqueue data structures and load-balancing helpers.
+ * The BSP currently runs the global scheduler; these structures prepare
+ * the foundation for future AP scheduling.
+ *
+ * Usage:
+ *   sched_pcpu_init()           — called once after SMP init
+ *   sched_pcpu_get_load(cpu)    — query load on a CPU
+ *   sched_pcpu_least_loaded()   — find CPU with fewest ready processes
+ *   sched_pcpu_inc_load(cpu)    — increment load counter
+ *   sched_pcpu_dec_load(cpu)    — decrement load counter
+ */
+
+#define SCHED_PCPU_MAX 16
+
+void     sched_pcpu_init(uint32_t ncpus);
+uint32_t sched_pcpu_get_load(uint32_t cpu);
+uint32_t sched_pcpu_least_loaded(void);
+void     sched_pcpu_inc_load(uint32_t cpu);
+void     sched_pcpu_dec_load(uint32_t cpu);
+uint32_t sched_pcpu_count(void);
+
+#endif
index 450f5a49da693bcc56f2e6d500632142dfaf0ea3..f3a8c5361274189adff480ad36dacf88ef2df677 100644 (file)
@@ -155,6 +155,9 @@ int arch_platform_setup(const struct boot_info* bi) {
         percpu_init();
         percpu_setup_gs(0);
 
+        extern void sched_pcpu_init(uint32_t);
+        sched_pcpu_init(smp_get_cpu_count());
+
         /* Phase 2: Send INIT-SIPI-SIPI to wake APs */
         smp_start_aps();
     }
diff --git a/src/kernel/sched_pcpu.c b/src/kernel/sched_pcpu.c
new file mode 100644 (file)
index 0000000..2c7d8c5
--- /dev/null
@@ -0,0 +1,48 @@
+#include "sched_pcpu.h"
+#include "console.h"
+
+static uint32_t pcpu_load[SCHED_PCPU_MAX];
+static uint32_t pcpu_count;
+
+void sched_pcpu_init(uint32_t ncpus) {
+    if (ncpus > SCHED_PCPU_MAX) ncpus = SCHED_PCPU_MAX;
+    pcpu_count = ncpus;
+    for (uint32_t i = 0; i < SCHED_PCPU_MAX; i++)
+        pcpu_load[i] = 0;
+    kprintf("[SCHED] Per-CPU runqueues initialized for %u CPU(s).\n",
+            (unsigned)ncpus);
+}
+
+uint32_t sched_pcpu_count(void) {
+    return pcpu_count;
+}
+
+uint32_t sched_pcpu_get_load(uint32_t cpu) {
+    if (cpu >= pcpu_count) return 0;
+    return __atomic_load_n(&pcpu_load[cpu], __ATOMIC_RELAXED);
+}
+
+uint32_t sched_pcpu_least_loaded(void) {
+    uint32_t best = 0;
+    uint32_t best_load = __atomic_load_n(&pcpu_load[0], __ATOMIC_RELAXED);
+    for (uint32_t i = 1; i < pcpu_count; i++) {
+        uint32_t l = __atomic_load_n(&pcpu_load[i], __ATOMIC_RELAXED);
+        if (l < best_load) {
+            best_load = l;
+            best = i;
+        }
+    }
+    return best;
+}
+
+void sched_pcpu_inc_load(uint32_t cpu) {
+    if (cpu >= pcpu_count) return;
+    __atomic_add_fetch(&pcpu_load[cpu], 1, __ATOMIC_RELAXED);
+}
+
+void sched_pcpu_dec_load(uint32_t cpu) {
+    if (cpu >= pcpu_count) return;
+    uint32_t old = __atomic_load_n(&pcpu_load[cpu], __ATOMIC_RELAXED);
+    if (old > 0)
+        __atomic_sub_fetch(&pcpu_load[cpu], 1, __ATOMIC_RELAXED);
+}
index 07f7c05d3273bda9b9f34bbac1670df3f245d0a9..4a4fa977a0dd476b8e1be8fcfedb3e46d3d15cee 100644 (file)
@@ -10,6 +10,7 @@
 #include "hal/cpu.h"
 #include "hal/usermode.h"
 #include "arch_process.h"
+#include "sched_pcpu.h"
 #include <stddef.h>
 
 struct process* current_process = NULL;
@@ -211,6 +212,7 @@ void sched_enqueue_ready(struct process* p) {
     sleep_queue_remove(p);
     if (p->state == PROCESS_READY) {
         rq_enqueue(rq_active, p);
+        sched_pcpu_inc_load(0);
     }
     spin_unlock_irqrestore(&sched_lock, flags);
 }
@@ -894,6 +896,7 @@ void schedule(void) {
     if (next) {
         // next came from rq_active — safe to dequeue.
         rq_dequeue(rq_active, next);
+        sched_pcpu_dec_load(0);
     } else {
         // Nothing in runqueues.
         if (prev->state == PROCESS_READY) {