asterinas/kernel/src/fs/cgroupfs/controller/mod.rs

414 lines
14 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use alloc::{collections::vec_deque::VecDeque, sync::Arc};
use core::{fmt::Display, str::FromStr};
use aster_systree::{Error, Result, SysAttrSetBuilder, SysBranchNode, SysObj};
use bitflags::bitflags;
use ostd::{
mm::{VmReader, VmWriter},
sync::{Mutex, MutexGuard, Rcu},
};
use crate::fs::cgroupfs::{
controller::{cpuset::CpuSetController, memory::MemoryController, pids::PidsController},
systree_node::CgroupSysNode,
CgroupNode,
};
mod cpuset;
mod memory;
mod pids;
/// A trait to abstract all individual cgroup sub-controllers.
trait SubControl {
fn read_attr_at(&self, name: &str, offset: usize, writer: &mut VmWriter) -> Result<usize>;
fn write_attr(&self, name: &str, reader: &mut VmReader) -> Result<usize>;
}
/// Defines the static properties and behaviors of a specific cgroup sub-controller.
trait SubControlStatic: SubControl + Sized + 'static {
/// Creates a new instance of the sub-controller.
fn new(is_root: bool) -> Self;
/// Returns the `SubCtrlType` enum variant corresponding to this sub-controller.
fn type_() -> SubCtrlType;
/// Reads and clones the `Arc` of this sub-controller in the given `Controller`.
fn read_from(controller: &Controller) -> Arc<SubController<Self>>;
}
/// The type of a sub-controller in the cgroup subsystem.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum SubCtrlType {
Memory,
CpuSet,
Pids,
}
impl SubCtrlType {
const ALL: [Self; 3] = [Self::Memory, Self::CpuSet, Self::Pids];
}
impl FromStr for SubCtrlType {
type Err = aster_systree::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"memory" => Ok(SubCtrlType::Memory),
"cpuset" => Ok(SubCtrlType::CpuSet),
"pids" => Ok(SubCtrlType::Pids),
_ => Err(Error::NotFound),
}
}
}
bitflags! {
/// A set of sub-controller types, represented as bitflags.
pub(super) struct SubCtrlSet: u8 {
const MEMORY = 1 << 0;
const CPUSET = 1 << 1;
const PIDS = 1 << 2;
}
}
impl SubCtrlSet {
/// Checks whether a sub-control is active in the current set.
pub(super) fn contains_type(&self, ctrl_type: SubCtrlType) -> bool {
self.contains(ctrl_type.into())
}
/// Adds a sub-control type to the current set.
pub(super) fn add_type(&mut self, ctrl_type: SubCtrlType) {
*self |= ctrl_type.into()
}
/// Removes a sub-control type from the current set.
pub(super) fn remove_type(&mut self, ctrl_type: SubCtrlType) {
*self -= ctrl_type.into()
}
/// Returns an iterator over the sub-controller types in the current set.
pub(super) fn iter_types(&self) -> impl Iterator<Item = SubCtrlType> + '_ {
SubCtrlType::ALL
.into_iter()
.filter(|&ctrl_type| self.contains_type(ctrl_type))
}
}
impl Display for SubCtrlSet {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.contains(Self::MEMORY) {
write!(f, "memory ")?;
}
if self.contains(Self::CPUSET) {
write!(f, "cpuset ")?;
}
if self.contains(Self::PIDS) {
write!(f, "pids")?;
}
Ok(())
}
}
impl From<SubCtrlType> for SubCtrlSet {
fn from(ctrl_type: SubCtrlType) -> Self {
match ctrl_type {
SubCtrlType::Memory => Self::MEMORY,
SubCtrlType::CpuSet => Self::CPUSET,
SubCtrlType::Pids => Self::PIDS,
}
}
}
/// The sub-controller for a specific cgroup controller type.
///
/// If the sub-controller is inactive, the `inner` field will be `None`.
struct SubController<T: SubControlStatic> {
inner: Option<T>,
/// The parent sub-controller in the hierarchy.
///
/// This field is used to traverse the controller hierarchy.
#[expect(dead_code)]
parent: Option<Arc<SubController<T>>>,
}
impl<T: SubControlStatic> SubController<T> {
fn new(parent_controller: Option<&LockedController>) -> Arc<Self> {
let is_active = if let Some(parent) = parent_controller {
parent.active_set.contains_type(T::type_())
} else {
true
};
let inner = if is_active {
Some(T::new(parent_controller.is_none()))
} else {
None
};
let parent = parent_controller.map(|controller| T::read_from(controller.controller));
Arc::new(Self { inner, parent })
}
}
trait TryGetSubControl {
fn try_get(&self) -> Option<&dyn SubControl>;
}
impl<T: SubControlStatic> TryGetSubControl for SubController<T> {
fn try_get(&self) -> Option<&dyn SubControl> {
self.inner.as_ref().map(|sub_ctrl| sub_ctrl as _)
}
}
/// The controller for a single cgroup.
///
/// This struct can manage the activation state of each sub-control, and dispatches read/write
/// operations to the appropriate sub-controllers.
///
/// The following is an explanation of the activation for sub-controls and sub-controllers. When a
/// cgroup activates a specific sub-control (e.g., memory, io), it means this control capability is
/// being delegated to its children. Consequently, the corresponding sub-controller within the
/// child nodes will be activated.
///
/// The root node serves as the origin for all these control capabilities, so the sub-controllers
/// it possesses are always active. For any other node, only if its parent node first enables a
/// sub-control, its corresponding sub-controller will be activated.
pub(super) struct Controller {
/// A set of types of active sub-controllers.
active_set: Mutex<SubCtrlSet>,
memory: Rcu<Arc<SubController<MemoryController>>>,
cpuset: Rcu<Arc<SubController<CpuSetController>>>,
pids: Rcu<Arc<SubController<PidsController>>>,
}
impl Controller {
/// Creates a new controller manager for a cgroup.
pub(super) fn new(locked_parent_controller: Option<&LockedController>) -> Self {
let memory_controller = SubController::new(locked_parent_controller);
let cpuset_controller = SubController::new(locked_parent_controller);
let pids_controller = SubController::new(locked_parent_controller);
Self {
active_set: Mutex::new(SubCtrlSet::empty()),
memory: Rcu::new(memory_controller),
cpuset: Rcu::new(cpuset_controller),
pids: Rcu::new(pids_controller),
}
}
pub(super) fn init_attr_set(builder: &mut SysAttrSetBuilder, is_root: bool) {
MemoryController::init_attr_set(builder, is_root);
CpuSetController::init_attr_set(builder, is_root);
PidsController::init_attr_set(builder, is_root);
}
pub(super) fn lock(&self) -> LockedController {
LockedController {
active_set: self.active_set.lock(),
controller: self,
}
}
fn read_sub(&self, ctrl_type: SubCtrlType) -> Arc<dyn TryGetSubControl> {
match ctrl_type {
SubCtrlType::Memory => MemoryController::read_from(self),
SubCtrlType::CpuSet => CpuSetController::read_from(self),
SubCtrlType::Pids => PidsController::read_from(self),
}
}
/// Returns whether the attribute with the given name is absent in this controller.
pub(super) fn is_attr_absent(&self, name: &str) -> bool {
let Some((subsys, _)) = name.split_once('.') else {
return false;
};
let Ok(ctrl_type) = SubCtrlType::from_str(subsys) else {
return false;
};
let sub_controller = self.read_sub(ctrl_type);
if sub_controller.try_get().is_none() {
// If the sub-controller is not active, all its attributes are considered absent.
true
} else {
false
}
}
pub(super) fn read_attr_at(
&self,
name: &str,
offset: usize,
writer: &mut VmWriter,
) -> Result<usize> {
let Some((subsys, _)) = name.split_once('.') else {
return Err(Error::NotFound);
};
let ctrl_type = SubCtrlType::from_str(subsys)?;
let sub_controller = self.read_sub(ctrl_type);
let Some(controller) = sub_controller.try_get() else {
return Err(Error::IsDead);
};
controller.read_attr_at(name, offset, writer)
}
pub(super) fn write_attr(&self, name: &str, reader: &mut VmReader) -> Result<usize> {
let Some((subsys, _)) = name.split_once('.') else {
return Err(Error::NotFound);
};
let ctrl_type = SubCtrlType::from_str(subsys)?;
let sub_controller = self.read_sub(ctrl_type);
let Some(controller) = sub_controller.try_get() else {
return Err(Error::IsDead);
};
controller.write_attr(name, reader)
}
}
/// A locked controller for a cgroup.
///
/// Holding this lock indicates exclusive access to modify the sub-control state.
pub(super) struct LockedController<'a> {
active_set: MutexGuard<'a, SubCtrlSet>,
controller: &'a Controller,
}
impl LockedController<'_> {
/// Activates a sub-control of the specified type.
pub(super) fn activate(
&mut self,
ctrl_type: SubCtrlType,
current_node: &dyn CgroupSysNode,
parent_controller: Option<&LockedController>,
) -> Result<()> {
if self.active_set.contains_type(ctrl_type) {
return Ok(());
}
// A cgroup can activate the sub-control only if this
// sub-control has been activated in its parent cgroup.
if parent_controller
.is_some_and(|controller| !controller.active_set.contains_type(ctrl_type))
{
return Err(Error::NotFound);
}
self.active_set.add_type(ctrl_type);
self.update_sub_controllers_for_descents(ctrl_type, current_node);
Ok(())
}
/// Deactivates a sub-control of the specified type.
pub(super) fn deactivate(
&mut self,
ctrl_type: SubCtrlType,
current_node: &dyn CgroupSysNode,
) -> Result<()> {
if !self.active_set.contains_type(ctrl_type) {
return Ok(());
}
// If any child node has activated this sub-control,
// the deactivation operation will be rejected.
for child in current_node.children() {
let cgroup_child = child.as_any().downcast_ref::<CgroupNode>().unwrap();
let child_controller = cgroup_child.controller().lock();
// This is race-free because if a child wants to activate a sub-controller, it should
// first acquire the lock of the parent controller, which is held here.
if child_controller.active_set().contains_type(ctrl_type) {
return Err(Error::InvalidOperation);
}
}
self.active_set.remove_type(ctrl_type);
self.update_sub_controllers_for_descents(ctrl_type, current_node);
Ok(())
}
fn update_sub_controllers_for_descents(
&self,
ctrl_type: SubCtrlType,
current_node: &dyn CgroupSysNode,
) {
fn update_sub_controller_for_one_child(
child: &Arc<dyn SysObj>,
ctrl_type: SubCtrlType,
parent_controller: &LockedController,
) {
let child_node = child.as_any().downcast_ref::<CgroupNode>().unwrap();
match ctrl_type {
SubCtrlType::Memory => {
let new_controller = SubController::new(Some(parent_controller));
child_node.controller().memory.update(new_controller);
}
SubCtrlType::CpuSet => {
let new_controller = SubController::new(Some(parent_controller));
child_node.controller().cpuset.update(new_controller);
}
SubCtrlType::Pids => {
let new_controller = SubController::new(Some(parent_controller));
child_node.controller().pids.update(new_controller);
}
}
}
let mut descents = VecDeque::new();
// The following update logic is race-free due to the following reasons:
//
// 1. **No Concurrent Controller Activation/Deactivation**:
// At this point, we hold the controller lock for the current node and we know that the
// sub-controllers for the direct children are inactive. Then, no sub-controllers for
// any of the descendants can be activated before we release the lock.
//
// 2. **Concurrent Child Addition/Deletion is Fine**:
// We do need to consider that children may be added or removed concurrently. However,
// this is handled correctly:
// - If a child is added, it will attempt to hold its parent's controller lock, which is
// synchronized with the code below. If this happens after us, the up-to-date
// sub-controllers will be seen. If it happens before us, we will update the
// sub-controllers for it; due to race conditions, the sub-controllers may already be
// up to date, but updating them twice is harmless since they must not be activated.
// - If a child is removed, we may update a sub-controller that's about to be destroyed,
// which is harmless.
// Update the direct children first.
current_node.visit_children_with(0, &mut |child_node| {
descents.push_back(child_node.clone());
update_sub_controller_for_one_child(child_node, ctrl_type, self);
Some(())
});
// Then update all the other descendent nodes.
while let Some(node) = descents.pop_front() {
let current_node = node.as_any().downcast_ref::<CgroupNode>().unwrap();
// For descendent nodes, the sub-control must be inactive. But taking the controller
// lock is necessary for synchronization purposes (see the explanation above).
let locked_controller = current_node.controller().lock();
current_node.visit_children_with(0, &mut |child_node| {
descents.push_back(child_node.clone());
update_sub_controller_for_one_child(child_node, ctrl_type, &locked_controller);
Some(())
});
}
}
pub(super) fn active_set(&self) -> SubCtrlSet {
*self.active_set
}
}