]> 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 e308dbff4b38917d7f65d3177028bf98729f3480..304c5219cf11e9e38671bbc0871a88e6e812b8b1 100644 (file)
@@ -24,7 +24,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..0adfc15
--- /dev/null
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2018, Tulio A M Mendes <[email protected]>
+ * All rights reserved.
+ * See LICENSE for details.
+ *
+ * Source: https://github.com/tadryanom/AdrOS
+ */
+
+#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 a3598a358d8a6e60cdc0b67848defd999c8d0143..bb278c366d3a180cf1a3269648cccaa552b1ff1d 100644 (file)
@@ -164,6 +164,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..0688f3c
--- /dev/null
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2018, Tulio A M Mendes <[email protected]>
+ * All rights reserved.
+ * See LICENSE for details.
+ *
+ * Source: https://github.com/tadryanom/AdrOS
+ */
+
+#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 4c8b02047074ead9b6b76f76bc7e9e88c0980566..acf3776eced5baea41562532984d9579c3d9cda9 100644 (file)
@@ -19,6 +19,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;
@@ -220,6 +221,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);
 }
@@ -903,6 +905,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) {