Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| baadbfc539 | |||
| d41d0aa728 | |||
| d37b421cb3 | |||
| 6a5582783f | |||
| 4789d546e2 | |||
| e8ec916158 | |||
| 327c1502d1 | |||
| 7fc8bbf057 | |||
| f7652fc26a | |||
| cbf051e6d8 | |||
| 5fb42fcaa1 | |||
| ed3f0e1e64 | |||
| 9bc1fbfe46 | |||
| 475f96ecab | |||
| 1be659be05 | |||
| 6b98c64663 | |||
| 01ef6f5c5d | |||
| 3f2f3bacc5 | |||
| 9f6a4288b5 | |||
| f8308866e0 | |||
| 8d9f9e552f | |||
| 75c7618313 | |||
| 24fd0a083d | |||
| a8042049ce | |||
| 19010ce174 | |||
| 7f7095be1c | |||
| 8cd4f69108 | |||
| 4f2a0436eb | |||
| 4cb9d80396 | |||
| 82feefbaee |
@@ -1,5 +0,0 @@
|
||||
[build]
|
||||
rustflags = [
|
||||
# Kernel should preserve floating-point registers
|
||||
"-Ctarget-feature=-sse,-sse2",
|
||||
]
|
||||
Generated
+21
-21
@@ -40,15 +40,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.12.1"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.63"
|
||||
version = "1.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -88,9 +88,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
@@ -99,16 +99,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
"hashbrown 0.17.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel"
|
||||
version = "0.5.12+rb0.3.0"
|
||||
version = "0.5.12"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitfield",
|
||||
"bitflags 2.12.1",
|
||||
"bitflags 2.11.1",
|
||||
"cc",
|
||||
"fdt",
|
||||
"hashbrown 0.14.5",
|
||||
@@ -147,9 +147,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
@@ -201,16 +201,16 @@ checksum = "64072665120942deff5fd5425d6c1811b854f4939e7f1c01ce755f64432bbea7"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.9.0+rb0.3.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"bitflags 2.12.1",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.12.1",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -280,9 +280,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -400,18 +400,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.50"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -420,4 +420,4 @@ dependencies = [
|
||||
|
||||
[[patch.unused]]
|
||||
name = "libredox"
|
||||
version = "0.1.18+rb0.2.5"
|
||||
version = "0.1.18"
|
||||
|
||||
+15
-4
@@ -4,8 +4,7 @@ members = [".", "rmm"]
|
||||
|
||||
[package]
|
||||
name = "kernel"
|
||||
version = "0.5.12+rb0.3.0"
|
||||
authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"]
|
||||
version = "0.5.12"
|
||||
build = "build.rs"
|
||||
edition = "2024"
|
||||
|
||||
@@ -21,7 +20,7 @@ fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa
|
||||
hashbrown = { version = "0.14.3", default-features = false, features = ["ahash", "inline-more"] }
|
||||
linked_list_allocator = "0.9.0"
|
||||
redox-path = "0.2.0"
|
||||
redox_syscall = { path = "../syscall", default-features = false }
|
||||
redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall.git", default-features = false }
|
||||
rmm = { path = "rmm", default-features = false }
|
||||
slab = { version = "0.4", default-features = false }
|
||||
smallvec = { version = "1.15.1", default-features = false }
|
||||
@@ -132,6 +131,18 @@ debug = "full"
|
||||
# same type, so `?` conversions in scheme-utils / daemon
|
||||
# compile cleanly.
|
||||
[patch.crates-io]
|
||||
# Local fork dependency rule: every crate with a local fork MUST resolve through it.
|
||||
# Phase J: override libredox 0.1.17 to use the local
|
||||
# fork at ../libredox/ (which itself uses the local syscall
|
||||
# fork). This breaks the libredox::error::Error <->
|
||||
# syscall::Error type-identity barrier that previously
|
||||
# caused E0277 errors in scheme-utils and daemon.
|
||||
libredox = { path = "../libredox" }
|
||||
# Phase J: the kernel's redox_syscall dep is a git URL
|
||||
# (not crates.io), so [patch.crates-io] doesn't apply.
|
||||
# Use a [patch."<URL>"] section to match the dep source.
|
||||
# The local fork at ../syscall adds the EnterS2Idle /
|
||||
# ExitS2Idle AcpiVerb variants — the kernel's direct use
|
||||
# of AcpiVerb in src/scheme/acpi.rs's kcall handler
|
||||
# needs the fork to see these variants.
|
||||
[patch."https://gitlab.redox-os.org/redox-os/syscall.git"]
|
||||
redox_syscall = { path = "../syscall" }
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2026-05-24"
|
||||
channel = "nightly-2025-10-03"
|
||||
components = ["rust-src"]
|
||||
|
||||
+7
-5
@@ -53,10 +53,12 @@ mod offsets {
|
||||
/// Structure address. ACPI 2.0+.
|
||||
pub const X_FIRMWARE_CTRL_64: usize = 140;
|
||||
/// FADT minimum size for ACPI 2.0+ (i.e., enough to
|
||||
/// include `x_firmware_ctrl` at offset 140).
|
||||
pub const FADT_MIN_SIZE_ACPI_2_0: usize = 148;
|
||||
/// FADT minimum size for ACPI 1.0.
|
||||
pub const FADT_MIN_SIZE_ACPI_1_0: usize = 76;
|
||||
/// include `x_firmware_ctrl` at offset 140). Stored as
|
||||
/// `u32` to match `Sdt::length()` (which is `u32` per
|
||||
/// ACPI spec) and avoid a usize/u32 comparison on x86_64.
|
||||
pub const FADT_MIN_SIZE_ACPI_2_0: u32 = 148;
|
||||
/// FADT minimum size for ACPI 1.0. Same rationale.
|
||||
pub const FADT_MIN_SIZE_ACPI_1_0: u32 = 76;
|
||||
}
|
||||
|
||||
/// Parse the FADT from the given SDT bytes and extract the
|
||||
@@ -107,7 +109,7 @@ pub fn init(sdt: &Sdt) {
|
||||
// FADT to be at least 148 bytes to have this field
|
||||
// (the field is at offset 140, which is 8 bytes for the
|
||||
// u64, so the minimum FADT size is 148 bytes).
|
||||
if sdt.length() >= offsets::FADT_MIN_SIZE_ACPI_2_0 as u32 {
|
||||
if sdt.length() >= offsets::FADT_MIN_SIZE_ACPI_2_0 {
|
||||
let x_firmware_ctrl = core::ptr::read_unaligned(
|
||||
data.add(offsets::X_FIRMWARE_CTRL_64) as *const u64,
|
||||
);
|
||||
|
||||
+107
-1
@@ -18,7 +18,8 @@ use crate::{
|
||||
cpu_stats,
|
||||
ipi::{ipi, IpiKind, IpiTarget},
|
||||
memory::{
|
||||
allocate_p2frame, deallocate_p2frame, Enomem, Frame, RaiiFrame, RmmA, RmmArch, PAGE_SIZE,
|
||||
allocate_p2frame, deallocate_p2frame, Enomem, Frame, PhysicalAddress, RaiiFrame, RmmA,
|
||||
RmmArch, PAGE_SIZE,
|
||||
},
|
||||
percpu::PercpuBlock,
|
||||
scheme::{CallerCtx, FileHandle, SchemeId},
|
||||
@@ -62,6 +63,38 @@ impl Status {
|
||||
}
|
||||
}
|
||||
|
||||
pub const SCHED_PRIORITY_LEVELS: usize = 40;
|
||||
pub const DEFAULT_SCHED_OTHER_PRIORITY: usize = 20;
|
||||
pub const DEFAULT_SCHED_RR_QUANTUM: u128 = 100_000_000;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SchedPolicy {
|
||||
Fifo = 0,
|
||||
RoundRobin = 1,
|
||||
Other = 2,
|
||||
}
|
||||
|
||||
impl SchedPolicy {
|
||||
pub fn try_from_raw(raw: u8) -> Option<Self> {
|
||||
match raw {
|
||||
0 => Some(Self::Fifo),
|
||||
1 => Some(Self::RoundRobin),
|
||||
2 => Some(Self::Other),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rt_priority_to_kernel_prio(rt_priority: u8) -> usize {
|
||||
(SCHED_PRIORITY_LEVELS - 1)
|
||||
.saturating_sub((usize::from(rt_priority.min(99)) * (SCHED_PRIORITY_LEVELS - 1)) / 99)
|
||||
}
|
||||
|
||||
fn clamp_sched_other_prio(prio: usize) -> usize {
|
||||
prio.min(SCHED_PRIORITY_LEVELS - 1)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HardBlockedReason {
|
||||
/// "SIGSTOP", only procmgr is allowed to switch contexts this state
|
||||
@@ -96,6 +129,7 @@ pub struct Context {
|
||||
pub running: bool,
|
||||
/// Current CPU ID
|
||||
pub cpu_id: Option<LogicalCpuId>,
|
||||
pub last_cpu: Option<LogicalCpuId>,
|
||||
/// Time this context was switched to
|
||||
pub switch_time: u128,
|
||||
/// Amount of CPU time used
|
||||
@@ -140,6 +174,20 @@ pub struct Context {
|
||||
pub fmap_ret: Option<Frame>,
|
||||
/// Priority
|
||||
pub prio: usize,
|
||||
pub sched_policy: SchedPolicy,
|
||||
pub sched_rt_priority: u8,
|
||||
pub sched_rr_ticks_consumed: u32,
|
||||
pub sched_static_prio: usize,
|
||||
pub sched_rr_quantum: u128,
|
||||
/// Virtual runtime for SCHED_OTHER fair scheduling.
|
||||
/// CPU-bound threads accumulate vruntime faster; I/O-bound stay lower.
|
||||
pub vruntime: u128,
|
||||
#[allow(dead_code)]
|
||||
pub futex_pi_boost: bool,
|
||||
#[allow(dead_code)]
|
||||
pub futex_pi_original_prio: usize,
|
||||
#[allow(dead_code)]
|
||||
pub futex_pi_waiters: Vec<PhysicalAddress>,
|
||||
|
||||
// TODO: id can reappear after wraparound?
|
||||
pub owner_proc_id: Option<NonZeroUsize>,
|
||||
@@ -184,6 +232,7 @@ impl Context {
|
||||
status_reason: "",
|
||||
running: false,
|
||||
cpu_id: None,
|
||||
last_cpu: None,
|
||||
switch_time: 0,
|
||||
cpu_time: 0,
|
||||
sched_affinity: LogicalCpuSet::all(),
|
||||
@@ -200,6 +249,15 @@ impl Context {
|
||||
userspace: false,
|
||||
fmap_ret: None,
|
||||
prio: 20,
|
||||
sched_policy: SchedPolicy::Other,
|
||||
sched_rt_priority: 0,
|
||||
sched_rr_ticks_consumed: 0,
|
||||
sched_static_prio: 20,
|
||||
sched_rr_quantum: DEFAULT_SCHED_RR_QUANTUM,
|
||||
vruntime: 0,
|
||||
futex_pi_boost: false,
|
||||
futex_pi_original_prio: 0,
|
||||
futex_pi_waiters: Vec::new(),
|
||||
being_sigkilled: false,
|
||||
owner_proc_id,
|
||||
|
||||
@@ -435,6 +493,54 @@ impl Context {
|
||||
core::mem::replace(&mut self.addr_space, addr_space)
|
||||
}
|
||||
|
||||
/// Set the scheduling policy and RT priority for this context.
|
||||
///
|
||||
/// `policy` selects SCHED_FIFO / SCHED_RR / SCHED_OTHER. `rt_priority` is
|
||||
/// POSIX 0..99 for FIFO/RR (clamped); ignored for SCHED_OTHER (which uses
|
||||
/// `sched_static_prio` set via [`Self::set_sched_other_prio`]).
|
||||
///
|
||||
/// The mapping from POSIX `rt_priority` to the kernel's
|
||||
/// `SCHED_PRIORITY_LEVELS` (0..=39, higher = lower priority) is the
|
||||
/// standard inverse weighting: rt_priority 99 maps to level 0 (highest
|
||||
/// priority), rt_priority 0 maps to level 39 (lowest). The actual
|
||||
/// kernel priority level is computed by [`rt_priority_to_kernel_prio`].
|
||||
///
|
||||
/// `sched_rr_ticks_consumed` is reset to 0 so that a fresh RR quantum
|
||||
/// begins on the next scheduler pass.
|
||||
pub fn set_sched_policy(&mut self, policy: SchedPolicy, rt_priority: u8) {
|
||||
self.sched_policy = policy;
|
||||
self.sched_rt_priority = match policy {
|
||||
SchedPolicy::Other => 0,
|
||||
SchedPolicy::Fifo | SchedPolicy::RoundRobin => rt_priority.min(99),
|
||||
};
|
||||
self.sched_rr_ticks_consumed = 0;
|
||||
if matches!(policy, SchedPolicy::Fifo | SchedPolicy::RoundRobin) {
|
||||
self.sched_static_prio = rt_priority_to_kernel_prio(self.sched_rt_priority);
|
||||
self.prio = self.sched_static_prio;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the SCHED_OTHER priority (nice level) for this context.
|
||||
///
|
||||
/// `prio` is the kernel priority level 0..=39, where 0 is the highest
|
||||
/// weight (`SCHED_PRIO_TO_WEIGHT[0] = 88761`, most CPU time, "nice
|
||||
/// -20") and 39 is the lowest weight
|
||||
/// (`SCHED_PRIO_TO_WEIGHT[39] = 15`, least CPU time, "nice +19").
|
||||
/// The clamping and conversion from POSIX nice is the responsibility
|
||||
/// of the caller
|
||||
/// (see `kernel_prio_to_nice` / `nice_to_kernel_prio` in
|
||||
/// `scheme/proc.rs`).
|
||||
///
|
||||
/// SCHED_OTHER contexts accumulate vruntime faster at higher kernel
|
||||
/// priority (lower nice) — see the DWRR weight table in
|
||||
/// `context/switch.rs:30-34`. Setting this does NOT change the
|
||||
/// scheduling class; use [`Self::set_sched_policy`] for that.
|
||||
pub fn set_sched_other_prio(&mut self, prio: usize) {
|
||||
let clamped = clamp_sched_other_prio(prio);
|
||||
self.sched_static_prio = clamped;
|
||||
self.prio = clamped;
|
||||
}
|
||||
|
||||
fn can_access_regs(&self) -> bool {
|
||||
self.userspace
|
||||
}
|
||||
|
||||
+38
-3
@@ -10,9 +10,9 @@ use core::{num::NonZeroUsize, ops::Deref};
|
||||
|
||||
use crate::{
|
||||
context::memory::AddrSpaceWrapper,
|
||||
cpu_set::LogicalCpuSet,
|
||||
cpu_set::{LogicalCpuId, LogicalCpuSet},
|
||||
memory::{RmmA, RmmArch, TableKind},
|
||||
percpu::PercpuBlock,
|
||||
percpu::{get_percpu_block, PercpuBlock},
|
||||
sync::{
|
||||
ArcRwLockWriteGuard, CleanLockToken, LockToken, Mutex, MutexGuard, RwLock, RwLockReadGuard,
|
||||
RwLockWriteGuard, L0, L1, L2, L4,
|
||||
@@ -22,13 +22,21 @@ use crate::{
|
||||
|
||||
use self::context::Kstack;
|
||||
pub use self::{
|
||||
context::{BorrowedHtBuf, Context, Status},
|
||||
context::{BorrowedHtBuf, Context, SchedPolicy, Status},
|
||||
switch::switch,
|
||||
};
|
||||
|
||||
pub type ContextLock = RwLock<L4, Context>;
|
||||
pub type ArcContextLockWriteGuard = ArcRwLockWriteGuard<L4, Context>;
|
||||
|
||||
/// Number of priority queues in the per-CPU run queue (DWRR priority
|
||||
/// count). Kept at 40 to match the historical global `RunContextData.set`
|
||||
/// length and the existing `SCHED_PRIO_TO_WEIGHT` table in
|
||||
/// `context/switch.rs`. Each `PerCpuSched.run_queues` entry is one
|
||||
/// DWRR priority level. Consumers: `crate::percpu::PerCpuSched`,
|
||||
/// `crate::context::switch`.
|
||||
pub const RUN_QUEUE_COUNT: usize = 40;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[path = "arch/aarch64.rs"]
|
||||
mod arch;
|
||||
@@ -117,6 +125,30 @@ pub fn run_contexts(token: LockToken<'_, L0>) -> MutexGuard<'_, L1, RunContextDa
|
||||
RUN_CONTEXTS.lock(token)
|
||||
}
|
||||
|
||||
fn least_loaded_cpu() -> LogicalCpuId {
|
||||
let current_cpu = crate::cpu_id();
|
||||
let mut best_cpu = current_cpu;
|
||||
let mut best_depth = usize::MAX;
|
||||
|
||||
for raw_id in 0..crate::cpu_count() {
|
||||
let cpu_id = LogicalCpuId::new(raw_id);
|
||||
let Some(percpu) = get_percpu_block(cpu_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
percpu.sched.take_lock();
|
||||
let depth = unsafe { percpu.sched.queues().iter().map(|queue| queue.len()).sum() };
|
||||
percpu.sched.release_lock();
|
||||
|
||||
if depth < best_depth {
|
||||
best_depth = depth;
|
||||
best_cpu = cpu_id;
|
||||
}
|
||||
}
|
||||
|
||||
best_cpu
|
||||
}
|
||||
|
||||
pub fn init(token: &mut CleanLockToken) {
|
||||
let owner = None; // kmain not owned by any fd
|
||||
let mut context = Context::new(owner).expect("failed to create kmain context");
|
||||
@@ -231,6 +263,9 @@ pub fn spawn(
|
||||
|
||||
context.kstack = Some(stack);
|
||||
context.userspace = userspace_allowed;
|
||||
let target_cpu = least_loaded_cpu();
|
||||
context.sched_affinity = LogicalCpuSet::empty();
|
||||
context.sched_affinity.atomic_set(target_cpu);
|
||||
|
||||
let context_lock = Arc::new(ContextLock::new(context));
|
||||
let context_ref = ContextRef(Arc::clone(&context_lock));
|
||||
|
||||
+767
-167
File diff suppressed because it is too large
Load Diff
+62
@@ -0,0 +1,62 @@
|
||||
/// NUMA topology hints for the kernel scheduler.
|
||||
/// NUMA discovery (SRAT/SLIT parsing) is performed by a userspace daemon
|
||||
/// (numad) via /scheme/acpi/, then pushed to the kernel via scheme:numa.
|
||||
/// The kernel stores a lightweight copy for O(1) scheduling lookups.
|
||||
use crate::cpu_set::{LogicalCpuId, LogicalCpuSet};
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
const MAX_NUMA_NODES: usize = 8;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NumaHint {
|
||||
pub node_id: u8,
|
||||
pub cpus: LogicalCpuSet,
|
||||
}
|
||||
|
||||
pub struct NumaTopology {
|
||||
pub nodes: [Option<NumaHint>; MAX_NUMA_NODES],
|
||||
pub initialized: AtomicBool,
|
||||
}
|
||||
|
||||
impl NumaTopology {
|
||||
pub const fn new() -> Self {
|
||||
const NONE: Option<NumaHint> = None;
|
||||
Self {
|
||||
nodes: [NONE; MAX_NUMA_NODES],
|
||||
initialized: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_for_cpu(&self, cpu: LogicalCpuId) -> Option<u8> {
|
||||
for node in self.nodes.iter().flatten() {
|
||||
if node.cpus.contains(cpu) {
|
||||
return Some(node.node_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn same_node(&self, cpu1: LogicalCpuId, cpu2: LogicalCpuId) -> bool {
|
||||
self.node_for_cpu(cpu1) == self.node_for_cpu(cpu2)
|
||||
}
|
||||
}
|
||||
|
||||
static mut NUMA_TOPOLOGY: NumaTopology = NumaTopology::new();
|
||||
|
||||
pub fn topology() -> &'static NumaTopology {
|
||||
unsafe { &NUMA_TOPOLOGY }
|
||||
}
|
||||
|
||||
pub fn init_default() {
|
||||
let topo = topology();
|
||||
if topo.initialized.swap(true, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let topo_mut = &mut *core::ptr::addr_of_mut!(NUMA_TOPOLOGY);
|
||||
topo_mut.nodes[0] = Some(NumaHint {
|
||||
node_id: 0,
|
||||
cpus: LogicalCpuSet::all(),
|
||||
});
|
||||
}
|
||||
}
|
||||
+69
-6
@@ -1,9 +1,10 @@
|
||||
use alloc::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Weak},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{
|
||||
cell::{Cell, RefCell},
|
||||
cell::{Cell, RefCell, SyncUnsafeCell},
|
||||
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
|
||||
};
|
||||
|
||||
@@ -12,7 +13,10 @@ use syscall::PtraceFlags;
|
||||
|
||||
use crate::{
|
||||
arch::device::ArchPercpuMisc,
|
||||
context::{empty_cr3, memory::AddrSpaceWrapper, switch::ContextSwitchPercpu},
|
||||
context::{
|
||||
empty_cr3, memory::AddrSpaceWrapper, switch::ContextSwitchPercpu, WeakContextRef,
|
||||
RUN_QUEUE_COUNT,
|
||||
},
|
||||
cpu_set::{LogicalCpuId, MAX_CPU_COUNT},
|
||||
cpu_stats::{CpuStats, CpuStatsData},
|
||||
ptrace::Session,
|
||||
@@ -20,6 +24,58 @@ use crate::{
|
||||
syscall::debug::SyscallDebugInfo,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PerCpuSched {
|
||||
pub run_queues: SyncUnsafeCell<[VecDeque<WeakContextRef>; RUN_QUEUE_COUNT]>,
|
||||
pub run_queues_lock: AtomicBool,
|
||||
pub balance: Cell<[usize; RUN_QUEUE_COUNT]>,
|
||||
pub last_queue: Cell<usize>,
|
||||
pub last_balance_time: Cell<u128>,
|
||||
}
|
||||
|
||||
impl PerCpuSched {
|
||||
pub const fn new() -> Self {
|
||||
const EMPTY: VecDeque<WeakContextRef> = VecDeque::new();
|
||||
Self {
|
||||
run_queues: SyncUnsafeCell::new([EMPTY; RUN_QUEUE_COUNT]),
|
||||
run_queues_lock: AtomicBool::new(false),
|
||||
balance: Cell::new([0; RUN_QUEUE_COUNT]),
|
||||
last_queue: Cell::new(0),
|
||||
last_balance_time: Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_lock(&self) {
|
||||
while self
|
||||
.run_queues_lock
|
||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
while self.run_queues_lock.load(Ordering::Relaxed) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_lock(&self) {
|
||||
self.run_queues_lock.store(false, Ordering::Release);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must hold `run_queues_lock` while accessing the returned reference.
|
||||
pub unsafe fn queues(&self) -> &[VecDeque<WeakContextRef>; RUN_QUEUE_COUNT] {
|
||||
unsafe { &*self.run_queues.get() }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must hold `run_queues_lock` while accessing the returned reference.
|
||||
pub unsafe fn queues_mut(&self) -> &mut [VecDeque<WeakContextRef>; RUN_QUEUE_COUNT] {
|
||||
unsafe { &mut *self.run_queues.get() }
|
||||
}
|
||||
}
|
||||
|
||||
/// The percpu block, that stored all percpu variables.
|
||||
pub struct PercpuBlock {
|
||||
/// A unique immutable number that identifies the current CPU - used for scheduling
|
||||
@@ -31,8 +87,8 @@ pub struct PercpuBlock {
|
||||
pub current_addrsp: RefCell<Option<Arc<AddrSpaceWrapper>>>,
|
||||
pub new_addrsp_tmp: Cell<Option<Arc<AddrSpaceWrapper>>>,
|
||||
pub wants_tlb_shootdown: AtomicBool,
|
||||
pub balance: Cell<[usize; 40]>,
|
||||
pub last_queue: Cell<usize>,
|
||||
|
||||
pub sched: PerCpuSched,
|
||||
|
||||
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
|
||||
// first to avoid cache invalidation.
|
||||
@@ -57,6 +113,14 @@ pub unsafe fn init_tlb_shootdown(id: LogicalCpuId, block: *mut PercpuBlock) {
|
||||
ALL_PERCPU_BLOCKS[id.get() as usize].store(block, Ordering::Release)
|
||||
}
|
||||
|
||||
pub fn get_percpu_block(id: LogicalCpuId) -> Option<&'static PercpuBlock> {
|
||||
unsafe {
|
||||
ALL_PERCPU_BLOCKS[id.get() as usize]
|
||||
.load(Ordering::Acquire)
|
||||
.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_stats() -> Vec<(LogicalCpuId, CpuStatsData)> {
|
||||
let mut res = ALL_PERCPU_BLOCKS
|
||||
.iter()
|
||||
@@ -187,8 +251,7 @@ impl PercpuBlock {
|
||||
current_addrsp: RefCell::new(None),
|
||||
new_addrsp_tmp: Cell::new(None),
|
||||
wants_tlb_shootdown: AtomicBool::new(false),
|
||||
balance: Cell::new([0; 40]),
|
||||
last_queue: Cell::new(39),
|
||||
sched: PerCpuSched::new(),
|
||||
ptrace_flags: Cell::new(PtraceFlags::empty()),
|
||||
ptrace_session: RefCell::new(None),
|
||||
inside_syscall: Cell::new(false),
|
||||
|
||||
+143
-28
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
context::{
|
||||
self,
|
||||
context::{HardBlockedReason, LockedFdTbl, SignalState},
|
||||
context::{HardBlockedReason, LockedFdTbl, SchedPolicy, SignalState},
|
||||
file::InternalFlags,
|
||||
memory::{handle_notify_files, AddrSpace, AddrSpaceWrapper, Grant, PageSpan},
|
||||
Context, ContextLock, Status,
|
||||
@@ -146,6 +146,9 @@ enum ContextHandle {
|
||||
// directory.
|
||||
OpenViaDup,
|
||||
SchedAffinity,
|
||||
SchedPolicy,
|
||||
Name,
|
||||
Priority,
|
||||
|
||||
MmapMinAddr(Arc<AddrSpaceWrapper>),
|
||||
}
|
||||
@@ -250,6 +253,9 @@ impl ProcScheme {
|
||||
false,
|
||||
),
|
||||
"sched-affinity" => (ContextHandle::SchedAffinity, true),
|
||||
"sched-policy" => (ContextHandle::SchedPolicy, true),
|
||||
"name" => (ContextHandle::Name, true),
|
||||
"priority" => (ContextHandle::Priority, true),
|
||||
"status" => (ContextHandle::Status { privileged: false }, false),
|
||||
_ if path.starts_with("auth-") => {
|
||||
let nonprefix = &path["auth-".len()..];
|
||||
@@ -303,27 +309,65 @@ impl ProcScheme {
|
||||
}
|
||||
OpenTy::Auth => {
|
||||
extern "C" fn ret() {}
|
||||
let context = match operation_str.ok_or(Error::new(ENOENT))? {
|
||||
"new-context" => {
|
||||
let id = NonZeroUsize::new(NEXT_ID.fetch_add(1, Ordering::Relaxed))
|
||||
.ok_or(Error::new(EMFILE))?;
|
||||
let context = context::spawn(true, Some(id), ret, token)?;
|
||||
{
|
||||
let parent_groups =
|
||||
context::current().read(token.token()).groups.clone();
|
||||
context.write(token.token()).groups = parent_groups;
|
||||
}
|
||||
HANDLES.write(token.token()).insert(
|
||||
id.get(),
|
||||
Handle {
|
||||
context,
|
||||
kind: ContextHandle::OpenViaDup,
|
||||
},
|
||||
);
|
||||
return Ok((id.get(), InternalFlags::empty()));
|
||||
let operation_str = operation_str.ok_or(Error::new(ENOENT))?;
|
||||
// The relibc fork's pthread_setname_np / getname_np /
|
||||
// setaffinity_np / getaffinity_np all use the path
|
||||
// format "proc:{thread_fd}/<sub-handle>" via a single
|
||||
// Sys::open() call. To support this, we need to parse
|
||||
// the leading numeric prefix as a context id and the
|
||||
// remaining suffix as the sub-handle path. The
|
||||
// existing "new-context" and "cur-context" arms are
|
||||
// kept for the "literal" paths that don't have a
|
||||
// numeric prefix.
|
||||
let context = if operation_str == "new-context" {
|
||||
let id = NonZeroUsize::new(NEXT_ID.fetch_add(1, Ordering::Relaxed))
|
||||
.ok_or(Error::new(EMFILE))?;
|
||||
let context = context::spawn(true, Some(id), ret, token)?;
|
||||
{
|
||||
let parent_groups =
|
||||
context::current().read(token.token()).groups.clone();
|
||||
context.write(token.token()).groups = parent_groups;
|
||||
}
|
||||
"cur-context" => context::current(),
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
HANDLES.write(token.token()).insert(
|
||||
id.get(),
|
||||
Handle {
|
||||
context: context.clone(),
|
||||
kind: ContextHandle::OpenViaDup,
|
||||
},
|
||||
);
|
||||
return Ok((id.get(), InternalFlags::empty()));
|
||||
} else if operation_str == "cur-context" {
|
||||
context::current()
|
||||
} else if let Some(slash) = operation_str.find('/') {
|
||||
// Path format "<thread_fd>/<sub-handle>" used by
|
||||
// relibc's pthread_setname_np / getname_np /
|
||||
// setaffinity_np / getaffinity_np. Split on the
|
||||
// first '/' to get the numeric context id and
|
||||
// the sub-handle name. Look up the context by id
|
||||
// in the HANDLES map.
|
||||
let id_str = &operation_str[..slash];
|
||||
let sub_path = &operation_str[slash + 1..];
|
||||
let id = id_str.parse::<usize>().map_err(|_| Error::new(EINVAL))?;
|
||||
let context = {
|
||||
let handles = HANDLES.read(token.token());
|
||||
let handle = handles.get(&id).ok_or(Error::new(ENOENT))?;
|
||||
if !matches!(
|
||||
handle.kind,
|
||||
ContextHandle::OpenViaDup | ContextHandle::Authority
|
||||
) {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
handle.context.clone()
|
||||
};
|
||||
// Re-call openat_context with the sub-path. This
|
||||
// is a recursive open_inner which works because
|
||||
// openat_context doesn't depend on the open
|
||||
// path's Authority state.
|
||||
return self
|
||||
.open_inner(OpenTy::Ctxt(context), Some(sub_path), _flags, token)
|
||||
.map(|(id, flags)| (id, flags));
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
};
|
||||
|
||||
(
|
||||
@@ -432,6 +476,7 @@ impl KernelScheme for ProcScheme {
|
||||
}
|
||||
|
||||
fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> {
|
||||
let mut inner_token = unsafe { CleanLockToken::new() };
|
||||
let handle = HANDLES
|
||||
.write(token.token())
|
||||
.remove(&id)
|
||||
@@ -459,9 +504,7 @@ impl KernelScheme for ProcScheme {
|
||||
))]
|
||||
regs.set_arg1(arg1);
|
||||
|
||||
// TODO: Lock ordering violation
|
||||
let mut token = unsafe { CleanLockToken::new() };
|
||||
Ok(context.set_addr_space(Some(new), token.downgrade()))
|
||||
Ok(context.set_addr_space(Some(new), inner_token.downgrade()))
|
||||
})?;
|
||||
if let Some(old_ctx) = old_ctx
|
||||
&& let Some(addrspace) = Arc::into_inner(old_ctx)
|
||||
@@ -500,6 +543,7 @@ impl KernelScheme for ProcScheme {
|
||||
consume: bool,
|
||||
token: &mut CleanLockToken,
|
||||
) -> Result<usize> {
|
||||
let mut inner_token = unsafe { CleanLockToken::new() };
|
||||
let handle = HANDLES
|
||||
.read(token.token())
|
||||
.get(&id)
|
||||
@@ -590,9 +634,7 @@ impl KernelScheme for ProcScheme {
|
||||
};
|
||||
// TODO: Allocated or AllocatedShared?
|
||||
let addrsp = AddrSpace::current()?;
|
||||
// TODO: Lock ordering violation
|
||||
let mut token = unsafe { CleanLockToken::new() };
|
||||
let page = addrsp.acquire_write(token.downgrade()).mmap_anywhere(
|
||||
let page = addrsp.acquire_write(inner_token.downgrade()).mmap_anywhere(
|
||||
&addrsp,
|
||||
NonZeroUsize::new(1).unwrap(),
|
||||
MapFlags::PROT_READ | MapFlags::PROT_WRITE,
|
||||
@@ -1174,6 +1216,56 @@ impl ContextHandle {
|
||||
|
||||
Ok(size_of_val(&mask))
|
||||
}
|
||||
Self::SchedPolicy => {
|
||||
if buf.len() != 2 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let [policy, rt_priority] = unsafe { buf.read_exact::<[u8; 2]>()? };
|
||||
let sched_policy = SchedPolicy::try_from_raw(policy).ok_or(Error::new(EINVAL))?;
|
||||
|
||||
context
|
||||
.write(token.token())
|
||||
.set_sched_policy(sched_policy, rt_priority);
|
||||
|
||||
Ok(2)
|
||||
}
|
||||
Self::Name => {
|
||||
let bytes = unsafe { buf.read_exact::<[u8; 32]>()? };
|
||||
let len = bytes
|
||||
.iter()
|
||||
.position(|&b| b == 0)
|
||||
.unwrap_or(bytes.len());
|
||||
let name = core::str::from_utf8(&bytes[..len]).map_err(|_| Error::new(EINVAL))?;
|
||||
context.write(token.token()).name.clear();
|
||||
context.write(token.token()).name.push_str(name);
|
||||
Ok(bytes.len())
|
||||
}
|
||||
Self::Priority => {
|
||||
if buf.len() != size_of::<i32>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let nice = unsafe { buf.read_exact::<i32>()? };
|
||||
if !(-20..=19).contains(&nice) {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
// POSIX nice ∈ [-20, +19] maps to kernel_prio ∈ [0, 39]
|
||||
// where kernel_prio 0 = highest weight (SCHED_PRIO_TO_WEIGHT[0]
|
||||
// = 88761) = "most CPU time" and kernel_prio 39 = lowest
|
||||
// weight (SCHED_PRIO_TO_WEIGHT[39] = 15) = "least CPU time".
|
||||
// nice -20 (most favorable) -> kernel_prio 0 (highest weight)
|
||||
// nice 0 (default) -> kernel_prio 20
|
||||
// nice +19 (least favorable) -> kernel_prio 39 (lowest weight)
|
||||
// The old formula `(20 - nice)` was inverted: nice -20
|
||||
// produced kernel_prio 40 (clamped to 39 = lowest weight),
|
||||
// so processes that set nice to the most favorable value
|
||||
// actually got the LEAST CPU time.
|
||||
let kernel_prio = (nice + 20) as usize;
|
||||
context
|
||||
.write(token.token())
|
||||
.set_sched_other_prio(kernel_prio);
|
||||
Ok(size_of::<i32>())
|
||||
}
|
||||
ContextHandle::Status { privileged } => {
|
||||
let mut args = buf.usizes();
|
||||
|
||||
@@ -1277,7 +1369,7 @@ impl ContextHandle {
|
||||
guard.pid = info.pid as usize;
|
||||
guard.euid = info.euid;
|
||||
guard.egid = info.egid;
|
||||
guard.prio = (info.prio as usize).min(39);
|
||||
guard.set_sched_other_prio(info.prio as usize);
|
||||
Ok(size_of::<ProcSchemeAttrs>())
|
||||
}
|
||||
Self::Groups => {
|
||||
@@ -1469,6 +1561,29 @@ impl ContextHandle {
|
||||
|
||||
buf.copy_exactly(crate::cpu_set::mask_as_bytes(&mask))?;
|
||||
Ok(size_of_val(&mask))
|
||||
}
|
||||
ContextHandle::SchedPolicy => {
|
||||
let context = context.read(token.token());
|
||||
let data = [context.sched_policy as u8, context.sched_rt_priority];
|
||||
buf.copy_common_bytes_from_slice(&data)
|
||||
}
|
||||
ContextHandle::Name => {
|
||||
let context = context.read(token.token());
|
||||
let bytes = context.name.as_bytes();
|
||||
let max = buf.len().min(bytes.len());
|
||||
buf.copy_common_bytes_from_slice(&bytes[..max])
|
||||
}
|
||||
ContextHandle::Priority => {
|
||||
let context = context.read(token.token());
|
||||
// Inverse of the write-side mapping: kernel_prio
|
||||
// = nice + 20, so nice = kernel_prio - 20.
|
||||
// Clamped to [-20, 19] for consistency with the
|
||||
// POSIX range even when kernel_prio is at the
|
||||
// boundary values 0 or 39 (the mapping is exact
|
||||
// for those values).
|
||||
let nice = (context.prio as i32 - 20).clamp(-20, 19);
|
||||
let data = nice.to_ne_bytes();
|
||||
buf.copy_common_bytes_from_slice(&data)
|
||||
} // TODO: Replace write() with SYS_SENDFD?
|
||||
ContextHandle::Status { .. } => {
|
||||
let status = {
|
||||
|
||||
+10
-4
@@ -49,8 +49,13 @@ pub struct FutexEntry {
|
||||
// implement that fully in userspace. Although futex is probably the best API for process-shared
|
||||
// POSIX synchronization primitives, a local hash table and wait-for-thread kernel APIs (e.g.
|
||||
// lwp_park/lwp_unpark from NetBSD) could be a simpler replacement.
|
||||
static FUTEXES: Mutex<L1, FutexList> =
|
||||
Mutex::new(FutexList::with_hasher(DefaultHashBuilder::new()));
|
||||
const FUTEX_SHARDS: usize = 64;
|
||||
|
||||
fn futex_shard(phys: PhysicalAddress) -> usize {
|
||||
(phys.data() as usize >> 12) % FUTEX_SHARDS
|
||||
}
|
||||
|
||||
static FUTEXES: [Mutex<L1, FutexList>; FUTEX_SHARDS] = [const { Mutex::new(FutexList::with_hasher(DefaultHashBuilder::new())) }; FUTEX_SHARDS];
|
||||
|
||||
fn validate_and_translate_virt(space: &AddrSpace, addr: VirtualAddress) -> Option<PhysicalAddress> {
|
||||
// TODO: Move this elsewhere!
|
||||
@@ -97,7 +102,7 @@ pub fn futex(
|
||||
{
|
||||
// TODO: Lock ordering violation
|
||||
let mut token = unsafe { CleanLockToken::new() };
|
||||
let mut futexes = FUTEXES.lock(token.token());
|
||||
let mut futexes = FUTEXES[futex_shard(target_physaddr)].lock(token.token());
|
||||
let (futexes, mut token) = futexes.token_split();
|
||||
|
||||
let (fetched, expected) = if op == FUTEX_WAIT {
|
||||
@@ -181,10 +186,11 @@ pub fn futex(
|
||||
}
|
||||
FUTEX_WAKE => {
|
||||
let mut woken = 0;
|
||||
let shard = futex_shard(target_physaddr);
|
||||
|
||||
{
|
||||
drop(addr_space_guard);
|
||||
let mut futexes_map = FUTEXES.lock(token.token());
|
||||
let mut futexes_map = FUTEXES[shard].lock(token.token());
|
||||
let (futexes_map, mut token) = futexes_map.token_split();
|
||||
|
||||
let is_empty = if let Some(futexes) = futexes_map.get_mut(&target_physaddr) {
|
||||
|
||||
Reference in New Issue
Block a user