Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
56 changes: 47 additions & 9 deletions components/axsched/src/cfs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use alloc::{collections::BTreeMap, sync::Arc};
Comment thread
SongShiQ marked this conversation as resolved.
use core::{
ops::Deref,
sync::atomic::{AtomicIsize, Ordering},
sync::atomic::{AtomicBool, AtomicIsize, Ordering},
};

use crate::BaseScheduler;
Expand All @@ -13,6 +13,12 @@ pub struct CFSTask<T> {
delta: AtomicIsize,
nice: AtomicIsize,
id: AtomicIsize,
/// cgroup cpu.weight (1..10000, default 100). Multiplied with the
/// nice-derived weight to produce the effective scheduling weight.
cgroup_weight: AtomicIsize,
/// When true the task is throttled by cgroup cpu.max and must not be
/// scheduled until the next bandwidth period.
throttled: AtomicBool,
}

// https://elixir.bootlin.com/linux/latest/source/include/linux/sched/prio.h
Expand All @@ -39,6 +45,8 @@ impl<T> CFSTask<T> {
delta: AtomicIsize::new(0_isize),
nice: AtomicIsize::new(0_isize),
id: AtomicIsize::new(0_isize),
cgroup_weight: AtomicIsize::new(100_isize),
throttled: AtomicBool::new(false),
}
}

Expand All @@ -56,11 +64,17 @@ impl<T> CFSTask<T> {
}

fn get_vruntime(&self) -> isize {
if self.nice.load(Ordering::Acquire) == 0 {
self.init_vruntime.load(Ordering::Acquire) + self.delta.load(Ordering::Acquire)
let nice_weight = self.get_weight();
let cgroup_w = self.cgroup_weight.load(Ordering::Acquire);
// Effective weight: nice_weight * cgroup_weight / 100
// vruntime increment: delta * 1024 / effective_weight
let effective_weight = nice_weight * cgroup_w / 100;
if effective_weight == 0 {
// Avoid division by zero; treat as very high weight (low priority)
self.init_vruntime.load(Ordering::Acquire) + self.delta.load(Ordering::Acquire) * 1024
} else {
self.init_vruntime.load(Ordering::Acquire)
+ self.delta.load(Ordering::Acquire) * 1024 / self.get_weight()
+ self.delta.load(Ordering::Acquire) * 1024 / effective_weight
}
}

Expand All @@ -82,6 +96,23 @@ impl<T> CFSTask<T> {
self.id.store(id, Ordering::Release);
}

/// Set the cgroup cpu.weight for this task.
pub fn set_cgroup_weight(&self, weight: isize) {
// Clamp to valid range [1, 10000]
let clamped = weight.clamp(1, 10000);
self.cgroup_weight.store(clamped, Ordering::Release);
}

/// Returns true if this task is throttled by cgroup cpu.max.
pub fn is_throttled(&self) -> bool {
self.throttled.load(Ordering::Acquire)
}

/// Set the throttled state.
pub fn set_throttled(&self, throttled: bool) {
self.throttled.store(throttled, Ordering::Release);
}

fn task_tick(&self) {
self.delta.fetch_add(1, Ordering::Release);
}
Expand Down Expand Up @@ -161,11 +192,14 @@ impl<T> BaseScheduler for CFScheduler<T> {
}

fn pick_next_task(&mut self) -> Option<Self::SchedItem> {
if let Some((_, v)) = self.ready_queue.pop_first() {
Some(v)
} else {
None
}
// Find the first non-throttled task without allocating a temporary Vec.
// Use iter() to find the key, then remove it directly.
let key_to_take = self
.ready_queue
.iter()
.find(|(_, task)| !task.is_throttled())
.map(|(k, _)| *k);
key_to_take.and_then(|key| self.ready_queue.remove(&key))
}

fn put_prev_task(&mut self, prev: Self::SchedItem, _preempt: bool) {
Expand All @@ -177,6 +211,10 @@ impl<T> BaseScheduler for CFScheduler<T> {

fn task_tick(&mut self, current: &Self::SchedItem) -> bool {
current.task_tick();
// Throttled tasks must be rescheduled immediately
if current.is_throttled() {
return true;
}
if self.ready_queue.is_empty() {
return false;
}
Expand Down
89 changes: 89 additions & 0 deletions os/StarryOS/kernel/src/cgroup/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! cgroup v2 core data structures.

use alloc::{
collections::BTreeMap,
format,
string::{String, ToString},
sync::{Arc, Weak},
vec::Vec,
};

use ax_kspin::SpinNoIrq;
use ax_lazyinit::LazyInit;
use axfs_ng_vfs::{VfsError, VfsResult};

use super::{cpu::CpuState, pids::PidsState};

/// A cgroup node in the hierarchy.
#[allow(dead_code)]
pub struct CgroupNode {
/// Directory name (e.g. "my-cgroup").
pub name: String,
/// Full path from root (e.g. "/my-cgroup").
pub path: String,
/// Child cgroups.
pub children: SpinNoIrq<BTreeMap<String, Arc<CgroupNode>>>,
/// PIDs in this cgroup.
pub procs: SpinNoIrq<Vec<u32>>,
/// Registered controller names (e.g. "pids", "cpu").
pub controllers: Vec<String>,
/// Parent (None for root).
pub parent: Option<Weak<CgroupNode>>,
/// Pids controller state.
pub pids: Arc<PidsState>,
pub cpu: Arc<CpuState>,
}

impl CgroupNode {
pub fn new_root() -> Arc<Self> {
Arc::new(Self {
name: String::new(),
path: "/".to_string(),
children: SpinNoIrq::new(BTreeMap::new()),
procs: SpinNoIrq::new(Vec::new()),
controllers: Vec::new(),
parent: None,
pids: Arc::new(PidsState::new()),
cpu: Arc::new(CpuState::new()),
})
}

/// Create a child cgroup under this node.
pub fn create_child(self: &Arc<Self>, name: &str) -> VfsResult<Arc<CgroupNode>> {
let mut children = self.children.lock();
if children.contains_key(name) {
return Err(VfsError::AlreadyExists);
}
let child_path = if self.path == "/" {
format!("/{}", name)
} else {
format!("{}/{}", self.path, name)
};
let child = Arc::new(CgroupNode {
name: name.to_string(),
path: child_path,
children: SpinNoIrq::new(BTreeMap::new()),
procs: SpinNoIrq::new(Vec::new()),
controllers: Vec::new(),
parent: Some(Arc::downgrade(self)),
pids: Arc::new(PidsState::new()),
cpu: Arc::new(CpuState::new()),
});
children.insert(name.to_string(), child);
Ok(children.get(name).unwrap().clone())
}

/// List controller names.
pub fn controller_list(&self) -> String {
let mut list = alloc::vec!["pids".to_string(), "cpu".to_string()];
list.extend(self.controllers.iter().cloned());
list.join(" ")
}
}

/// Global cgroup v2 root.
pub static GLOBAL_CGROUP_ROOT: LazyInit<Arc<CgroupNode>> = LazyInit::new();

pub fn init() {
GLOBAL_CGROUP_ROOT.init_once(CgroupNode::new_root());
}
58 changes: 58 additions & 0 deletions os/StarryOS/kernel/src/cgroup/cpu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! cgroup v2 cpu controller.
Comment thread
SongShiQ marked this conversation as resolved.
//!
//! Provides file interfaces for cpu.weight and cpu.max.

use core::sync::atomic::{AtomicI64, AtomicU64};

/// Per-cgroup cpu.max bandwidth state.
/// This is the single source of truth for quota/period.
pub struct BandwidthState {
pub quota: AtomicI64,
pub period: AtomicI64,
pub consumed: AtomicI64,
pub nr_periods: AtomicU64,
pub nr_throttled: AtomicU64,
pub throttled_usec: AtomicU64,
pub period_start: AtomicU64,
}

impl BandwidthState {
pub fn new() -> Self {
Self {
quota: AtomicI64::new(-1),
period: AtomicI64::new(100_000),
consumed: AtomicI64::new(0),
nr_periods: AtomicU64::new(0),
nr_throttled: AtomicU64::new(0),
throttled_usec: AtomicU64::new(0),
period_start: AtomicU64::new(0),
}
}
}

/// Per-cgroup cpu controller state.
///
/// `weight` controls relative CPU share (1-10000, default 100).
/// `bandwidth` holds the cpu.max quota/period and runtime stats.
pub struct CpuState {
pub weight: AtomicI64,
pub bandwidth: BandwidthState,
}

impl CpuState {
pub fn new() -> Self {
Self {
weight: AtomicI64::new(100),
bandwidth: BandwidthState::new(),
}
}
}

/// Called on every scheduler timer tick to consume quota and throttle.
/// Currently deferred — requires ax_task tick hook API.
/// TODO: Re-implement when ax_task::set_tick_hook is available.
#[allow(dead_code)]
pub fn bandwidth_tick() {
// Placeholder: actual implementation requires ax_task tick hook
// which is deferred in this PR.
}
Loading
Loading