Skip to main content

starnix_core/task/
cgroup.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This file implements control group hierarchy.
6//!
7//! There is no support for actual resource constraints, or any operations outside of adding tasks
8//! to a control group (for the duration of their lifetime).
9
10use crate::signals::{SignalInfo, send_freeze_signal};
11use crate::task::{Kernel, Pid, Task, ThreadGroup, WaitQueue, Waiter};
12use crate::vfs::{FsStr, FsString, PathBuilder};
13use starnix_logging::{CATEGORY_STARNIX, log_warn, track_stub};
14use starnix_sync::{
15    CgroupChildrenLock, CgroupPidTableLock, CgroupStateLock, CgroupV1Level, LockDepGuard,
16    LockDepMutex, allow_subclass,
17};
18use starnix_uapi::errors::Errno;
19use starnix_uapi::signals::SIGKILL;
20use starnix_uapi::{errno, error, pid_t};
21use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map, hash_map};
22use std::ops::{Deref, DerefMut};
23use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
24use std::sync::{Arc, Weak};
25
26use crate::signals::KernelSignal;
27use zx;
28
29/// Hierarchy ID reserved for the unified cgroup v2 hierarchy.
30const CGROUP2_HIERARCHY_ID: u32 = 0;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub enum ControllerType {
34    Cpu,
35    Cpuacct,
36    Cpuset,
37    Memory,
38    Freezer,
39    Blkio,
40}
41
42impl ControllerType {
43    pub const ALL: [Self; 6] =
44        [Self::Cpu, Self::Cpuacct, Self::Cpuset, Self::Memory, Self::Freezer, Self::Blkio];
45
46    pub fn as_str(&self) -> &'static str {
47        match self {
48            Self::Cpu => "cpu",
49            Self::Cpuacct => "cpuacct",
50            Self::Cpuset => "cpuset",
51            Self::Memory => "memory",
52            Self::Freezer => "freezer",
53            Self::Blkio => "blkio",
54        }
55    }
56}
57
58impl std::str::FromStr for ControllerType {
59    type Err = ();
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        match s {
62            "cpu" => Ok(Self::Cpu),
63            "cpuacct" => Ok(Self::Cpuacct),
64            "cpuset" => Ok(Self::Cpuset),
65            "memory" => Ok(Self::Memory),
66            "freezer" => Ok(Self::Freezer),
67            "blkio" => Ok(Self::Blkio),
68            _ => Err(()),
69        }
70    }
71}
72
73impl std::fmt::Display for ControllerType {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self.as_str())
76    }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub struct CgroupV1Key {
81    pub controllers: BTreeSet<ControllerType>,
82    pub name: Option<String>,
83}
84
85#[derive(Default, Debug)]
86pub struct CgroupV1State {
87    // TODO(https://fxbug.dev/401298305): Support removing cgroup hierarchies when they are
88    // unmounted and no longer have any tasks or child cgroups. Currently, they are kept alive
89    // indefinitely by these Arc references.
90    /// Maps controller to its active hierarchy.
91    pub controllers: HashMap<ControllerType, Arc<CgroupRoot>>,
92    /// Maps named hierarchies.
93    pub named: HashMap<String, Arc<CgroupRoot>>,
94    /// List of all unique hierarchies, naturally sorted.
95    pub hierarchies: BTreeMap<CgroupV1Key, Arc<CgroupRoot>>,
96    /// The next ID to assign to a cgroup v1 hierarchy.
97    next_hierarchy_id: u32,
98}
99
100/// All cgroups of the kernel. There is a single cgroup v2 hierarchy, and one-or-more cgroup v1
101/// hierarchies.
102#[derive(Debug)]
103pub struct KernelCgroups {
104    /// The single cgroup v2 hierarchy.
105    pub cgroup2: Arc<CgroupRoot>,
106    /// The cgroup v1 hierarchies state, protected by a lockdep mutex.
107    pub cgroup1: LockDepMutex<CgroupV1State, CgroupV1Level>,
108}
109
110impl KernelCgroups {
111    /// Returns a locked `CgroupPidTable`, which guarantees that processes would not move in this
112    /// cgroup hierarchy until the lock is freed.
113    ///
114    /// Note: Mutex dependency graph:
115    ///
116    /// `PidTableLock` -> `CgroupPidTableLock` -> `CgroupStateLock` -> `ThreadGroupMutableStateLock`
117    pub fn lock_cgroup2_pid_table(&self) -> LockDepGuard<'_, CgroupPidTable> {
118        self.cgroup2.pid_table.lock()
119    }
120
121    pub fn get_or_create_cgroup1(
122        &self,
123        controllers: &BTreeSet<ControllerType>,
124        name: Option<&str>,
125    ) -> Result<Arc<CgroupRoot>, Errno> {
126        let mut cgroup1 = self.cgroup1.lock();
127
128        let key = CgroupV1Key { controllers: controllers.clone(), name: name.map(String::from) };
129
130        if let Some(root) = cgroup1.hierarchies.get(&key) {
131            return Ok(root.clone());
132        }
133
134        for c in controllers {
135            if cgroup1.controllers.contains_key(c) {
136                return error!(EBUSY);
137            }
138        }
139
140        if let Some(n) = name {
141            if cgroup1.named.contains_key(n) {
142                return error!(EBUSY);
143            }
144        }
145
146        cgroup1.next_hierarchy_id += 1;
147        let hierarchy_id = cgroup1.next_hierarchy_id;
148        let root = CgroupRoot::new(hierarchy_id, controllers.clone());
149        cgroup1.hierarchies.insert(key, root.clone());
150        for c in controllers {
151            cgroup1.controllers.insert(*c, root.clone());
152        }
153        if controllers.contains(&ControllerType::Cpuset) {
154            self.cgroup2.v1_cpuset_mounted.store(true, Ordering::Relaxed);
155        }
156        if let Some(n) = name {
157            cgroup1.named.insert(n.to_string(), root.clone());
158        }
159
160        Ok(root)
161    }
162
163    pub fn get_cgroup1(&self, controller: ControllerType, pid: &Pid) -> Option<Weak<Cgroup>> {
164        let cgroup1 = self.cgroup1.lock();
165        let root = cgroup1.controllers.get(&controller)?;
166        root.get_cgroup(pid)
167    }
168}
169
170impl Default for KernelCgroups {
171    fn default() -> Self {
172        Self {
173            cgroup2: CgroupRoot::new(
174                CGROUP2_HIERARCHY_ID,
175                ControllerType::ALL.iter().copied().collect(),
176            ),
177            cgroup1: Default::default(),
178        }
179    }
180}
181
182#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
183pub enum FreezerState {
184    Thawed,
185    Frozen,
186}
187
188impl Default for FreezerState {
189    fn default() -> Self {
190        FreezerState::Thawed
191    }
192}
193
194impl std::fmt::Display for FreezerState {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        match self {
197            FreezerState::Frozen => write!(f, "1"),
198            FreezerState::Thawed => write!(f, "0"),
199        }
200    }
201}
202
203#[derive(Default)]
204pub struct CgroupFreezerState {
205    /// Cgroups's own freezer state as set by the `cgroup.freeze` file.
206    pub self_freezer_state: FreezerState,
207    /// Considers both the cgroup's self freezer state as set by the `cgroup.freeze` file and
208    /// the freezer state of its ancestors. A cgroup is considered frozen if either itself or any
209    /// of its ancestors is frozen.
210    pub effective_freezer_state: FreezerState,
211}
212
213#[derive(Debug, Default, Clone, PartialEq, Eq)]
214pub struct CpusetControllerState {
215    pub cpus: Option<Vec<u32>>,
216}
217
218#[derive(Debug, Default, Clone, PartialEq, Eq)]
219pub struct FreezerControllerState {
220    pub self_freezer_state: FreezerState,
221    pub inherited_freezer_state: FreezerState,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub enum ControllerState {
226    Cpuset(CpusetControllerState),
227    Freezer(FreezerControllerState),
228}
229
230pub trait FreezerOps: Send + Sync + 'static {
231    /// Get the freezer `self state` and `effective state`.
232    fn get_freezer_state(&self) -> CgroupFreezerState;
233
234    /// Freeze all tasks in the cgroup.
235    fn freeze(&self);
236
237    /// Thaw all tasks in the cgroup.
238    fn thaw(&self);
239}
240
241pub trait CpusetOps: Send + Sync + 'static {
242    /// Returns the cpuset cpus of the cgroup.
243    fn cpuset_cpus(&self) -> Vec<u32>;
244
245    /// Sets the cpuset cpus of the cgroup.
246    fn set_cpuset_cpus(&self, cpus: Vec<u32>);
247}
248
249/// Common operations of all cgroups.
250pub trait CgroupOps: Send + Sync + 'static {
251    /// Returns the unique ID of the cgroup. ID of root cgroup is 0.
252    fn id(&self) -> u64;
253
254    /// Add a process to a cgroup. Errors if the cgroup has been deleted.
255    fn add_process(&self, thread_group: &ThreadGroup) -> Result<(), Errno>;
256
257    /// Add a single task (thread) to a cgroup via the cgroup v1 `tasks` interface.
258    ///
259    /// Current semantics and limitations (see https://fxbug.dev/401298305):
260    /// - Cgroup membership (`CgroupPidTable` and `CgroupState::processes`) is currently tracked per
261    ///   `ThreadGroup` (keyed by the thread-group leader) rather than per thread.
262    /// - In hierarchies with active `cpuset` tracking, `add_task` updates `Task::cpuset_path` and
263    ///   the scheduler role only for `task` (preserving per-thread `cpuset_path` placements of
264    ///   other threads in the group). If `task` is the thread-group leader, it also updates the
265    ///   thread group's membership entry in the hierarchy.
266    /// - In hierarchies without `cpuset` tracking (e.g. freezer or named hierarchies), `add_task`
267    ///   falls back to `add_process(task.thread_group())`, migrating the entire thread group.
268    fn add_task(&self, task: &Task) -> Result<(), Errno> {
269        self.add_process(task.thread_group())
270    }
271
272    /// Create a new sub-cgroup as a child of this cgroup. Errors if the cgroup is deleted, or a
273    /// child with `name` already exists.
274    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
275
276    /// Gets all children of this cgroup.
277    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno>;
278
279    /// Gets the child with `name`, errors if not found.
280    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
281
282    /// Remove a child from this cgroup and return it, if found. Errors if cgroup is deleted, or a
283    /// child with `name` is not found.
284    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
285
286    /// Return all pids that belong to this cgroup.
287    fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t>;
288
289    /// Kills all processes in the cgroup and its descendants.
290    fn kill(&self);
291
292    /// Whether the cgroup or any of its descendants have any processes.
293    fn is_populated(&self) -> bool;
294
295    /// Returns freezer ops if freezer controller is supported.
296    fn freezer(&self) -> Option<&dyn FreezerOps>;
297
298    /// Returns cpuset ops if cpuset controller is supported.
299    fn cpuset(&self) -> Option<&dyn CpusetOps>;
300}
301
302/// `CgroupPidTable` contains the mapping of `ThreadGroup` (by pid) to non-root cgroup.
303/// If `pid` is valid but does not exist in the mapping, then it is assumed to be in the root cgroup.
304#[derive(Debug, Default)]
305pub struct CgroupPidTable(HashMap<Pid, Weak<Cgroup>>);
306impl Deref for CgroupPidTable {
307    type Target = HashMap<Pid, Weak<Cgroup>>;
308
309    fn deref(&self) -> &Self::Target {
310        &self.0
311    }
312}
313impl DerefMut for CgroupPidTable {
314    fn deref_mut(&mut self) -> &mut Self::Target {
315        &mut self.0
316    }
317}
318
319impl CgroupPidTable {
320    /// Add a newly created `ThreadGroup` to the same cgroup as its parent. Assumes that
321    /// `ThreadGroup` does not have any `Task` associated with it.
322    pub fn inherit_cgroup(&mut self, parent: &ThreadGroup, child: &ThreadGroup) {
323        assert!(child.read().tasks_count() == 0, "threadgroup must be newly created");
324        if let Some(weak_cgroup) = self.0.get(&parent.leader).cloned() {
325            let Some(cgroup) = weak_cgroup.upgrade() else {
326                log_warn!("ignored attempt to inherit a non-existant cgroup");
327                return;
328            };
329            assert!(
330                self.0.insert(child.leader.clone(), weak_cgroup).is_none(),
331                "child pid should not exist when inheriting"
332            );
333            // Skip freezer propagation because the `ThreadGroup` is newly created and has no tasks.
334            cgroup.state.lock().processes.insert(child.leader.clone());
335        }
336    }
337
338    /// Creates a new `KernelSignal` for a new `Task`, if that `Task` is added to a frozen cgroup.
339    pub fn maybe_create_freeze_signal(&self, pid: &Pid) -> Option<KernelSignal> {
340        let Some(weak_cgroup) = self.0.get(pid) else {
341            return None;
342        };
343        let Some(cgroup) = weak_cgroup.upgrade() else {
344            return None;
345        };
346        let state = cgroup.state.lock();
347        if state.get_effective_freezer_state() != FreezerState::Frozen {
348            return None;
349        }
350        Some(KernelSignal::Freeze(state.create_freeze_waiter()))
351    }
352
353    /// Remove a `ThreadGroup` from the root cgroup pid table and from the cgroup it is in.
354    pub fn remove_process(&mut self, pid: &Pid) {
355        if let Some(entry) = self.remove(pid) {
356            if let Some(cgroup) = entry.upgrade() {
357                cgroup.state.lock().processes.remove(pid);
358            }
359        }
360    }
361}
362
363/// Updates `cpuset_path` on `task` and returns `true` if `cpuset_path` changed.
364fn set_task_cpuset_path(task: &Task, cpuset_path: &str) -> bool {
365    let mut state = task.write();
366    let changed = state.cpuset_path != cpuset_path;
367    if changed {
368        state.cpuset_path = cpuset_path.to_string();
369    }
370    changed
371}
372
373fn sync_task_scheduler_role(task: &Task) {
374    if let Err(e) = task.sync_scheduler_state_to_role() {
375        log_warn!("Failed to set thread role for task {}: {:?}", task.tid, e);
376    }
377}
378
379/// `CgroupRoot` is the root of the cgroup hierarchy. The root cgroup is different from the rest of
380/// the cgroups in a cgroup hierarchy (sub-cgroups of the root) in a few ways:
381///
382/// - The root contains all known processes on cgroup creation, and all new processes as they are
383/// spawned. As such, the root cgroup reports processes belonging to it differently than its
384/// sub-cgroups.
385///
386/// - The root does not contain resource controller interface files, as otherwise they would apply
387/// to the whole system.
388///
389/// - The root does not own a `FsNode` as it is created and owned by the `FileSystem` instead.
390#[derive(Debug)]
391pub struct CgroupRoot {
392    /// The ID of this hierarchy. `CGROUP2_HIERARCHY_ID` (0) is reserved for cgroup v2.
393    pub hierarchy_id: u32,
394
395    /// Controllers supported by this hierarchy.
396    pub controllers: BTreeSet<ControllerType>,
397
398    /// Set on the cgroup v2 root once a cgroup v1 `cpuset` hierarchy is mounted.
399    v1_cpuset_mounted: AtomicBool,
400
401    /// Look up cgroup by pid. Must be locked before child states.
402    pid_table: LockDepMutex<CgroupPidTable, CgroupPidTableLock>,
403
404    /// Sub-cgroups of this cgroup.
405    children: LockDepMutex<CgroupChildren, CgroupChildrenLock>,
406
407    /// Weak reference to self, used when creating child cgroups.
408    weak_self: Weak<CgroupRoot>,
409
410    /// Used to generate IDs for descendent Cgroups.
411    next_id: AtomicU64,
412}
413
414impl CgroupRoot {
415    pub fn new(hierarchy_id: u32, controllers: BTreeSet<ControllerType>) -> Arc<CgroupRoot> {
416        Arc::new_cyclic(|weak_self| Self {
417            hierarchy_id,
418            controllers,
419            v1_cpuset_mounted: AtomicBool::new(false),
420            pid_table: Default::default(),
421            children: Default::default(),
422            weak_self: weak_self.clone(),
423            next_id: AtomicU64::new(1),
424        })
425    }
426
427    pub fn is_cgroup2(&self) -> bool {
428        self.hierarchy_id == CGROUP2_HIERARCHY_ID
429    }
430
431    pub fn has_controller(&self, controller: ControllerType) -> bool {
432        self.controllers.contains(&controller)
433    }
434
435    /// Whether this hierarchy tracks `Task::cpuset_path` for scheduler role overrides.
436    ///
437    /// If a cgroup v1 `cpuset` hierarchy is mounted, it takes precedence over the cgroup v2
438    /// unified hierarchy so that cgroup v2 process management does not overwrite v1 cpuset
439    /// placements.
440    fn tracks_cpuset(&self) -> bool {
441        self.has_controller(ControllerType::Cpuset)
442            && !(self.is_cgroup2() && self.v1_cpuset_mounted.load(Ordering::Relaxed))
443    }
444
445    fn remove_process_from_child(
446        &self,
447        thread_group: &ThreadGroup,
448    ) -> Result<LockDepGuard<'_, CgroupPidTable>, Errno> {
449        let mut pid_table = self.pid_table.lock();
450        // If the process is currently in a child cgroup, we must remove it from that cgroup's
451        // tracking. If it's not in the pid table, it is already implicitly in the root cgroup,
452        // so adding it to root is a no-op.
453        if let Some(entry) = pid_table.remove(&thread_group.leader) {
454            if let Some(cgroup) = entry.upgrade() {
455                cgroup.state.lock().remove_process(thread_group)?;
456            }
457        }
458        Ok(pid_table)
459    }
460
461    fn get_next_id(&self) -> u64 {
462        self.next_id.fetch_add(1, Ordering::Relaxed)
463    }
464
465    pub fn get_cgroup(&self, pid: &Pid) -> Option<Weak<Cgroup>> {
466        self.pid_table.lock().get(pid).cloned()
467    }
468
469    pub fn get_cgroup_inspect(&self) -> fuchsia_inspect::Inspector {
470        let inspector = fuchsia_inspect::Inspector::default();
471        let cgroups = inspector.root();
472        cgroups.record_uint("pids", self.pid_table.lock().len() as u64);
473        cgroups.record_uint("count", self.children.lock().count_descendants());
474        inspector
475    }
476}
477
478impl CgroupOps for CgroupRoot {
479    fn id(&self) -> u64 {
480        0
481    }
482
483    fn add_process(&self, thread_group: &ThreadGroup) -> Result<(), Errno> {
484        // Update `pid_table` and each task's `cpuset_path` under the `pid_table` lock so
485        // concurrent migrations cannot leave `pid_table` and `cpuset_path` out of sync, then
486        // drop `pid_table` before making synchronous FIDL calls in `sync_task_scheduler_role`.
487        let pid_table = self.remove_process_from_child(thread_group)?;
488
489        if self.tracks_cpuset() {
490            let tasks = thread_group.read().tasks();
491            let tasks_to_sync: Vec<_> =
492                tasks.into_iter().filter(|task| set_task_cpuset_path(task, "/")).collect();
493            drop(pid_table);
494            for task in tasks_to_sync {
495                sync_task_scheduler_role(&task);
496            }
497        }
498
499        Ok(())
500    }
501
502    fn add_task(&self, task: &Task) -> Result<(), Errno> {
503        // TODO(https://fxbug.dev/401298305): Track cgroup membership per thread rather than per
504        // thread group.
505        if self.tracks_cpuset() {
506            let changed = if task.is_leader() {
507                let _pid_table = self.remove_process_from_child(task.thread_group())?;
508                set_task_cpuset_path(task, "/")
509            } else {
510                set_task_cpuset_path(task, "/")
511            };
512            if changed {
513                sync_task_scheduler_role(task);
514            }
515            return Ok(());
516        }
517        self.add_process(task.thread_group())
518    }
519
520    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
521        let id = self.get_next_id();
522        let new_child = Cgroup::new(id, name, &self.weak_self, None);
523        let mut children = self.children.lock();
524        children.insert_child(name.into(), new_child)
525    }
526
527    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
528        let children = self.children.lock();
529        children.get_child(name).ok_or_else(|| errno!(ENOENT))
530    }
531
532    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
533        let mut children = self.children.lock();
534        children.remove_child(name)
535    }
536
537    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> {
538        let children = self.children.lock();
539        Ok(children.get_children())
540    }
541
542    fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t> {
543        let controlled_pids: HashSet<pid_t> = self
544            .pid_table
545            .lock()
546            .keys()
547            .filter_map(|v| v.get_thread_group().ok().map(|tg| tg.leader.id))
548            .collect();
549        let kernel_pids = kernel.pids.process_ids();
550        kernel_pids.into_iter().filter(|pid| !controlled_pids.contains(pid)).collect()
551    }
552
553    fn kill(&self) {
554        unreachable!("Root cgroup cannot kill its processes.");
555    }
556
557    fn is_populated(&self) -> bool {
558        false
559    }
560
561    fn freezer(&self) -> Option<&dyn FreezerOps> {
562        None
563    }
564
565    fn cpuset(&self) -> Option<&dyn CpusetOps> {
566        if self.controllers.contains(&ControllerType::Cpuset) { Some(self) } else { None }
567    }
568}
569
570impl CpusetOps for CgroupRoot {
571    fn cpuset_cpus(&self) -> Vec<u32> {
572        (0..zx::system_get_num_cpus()).collect()
573    }
574
575    fn set_cpuset_cpus(&self, _cpus: Vec<u32>) {}
576}
577
578#[derive(Debug, Default)]
579struct CgroupChildren(BTreeMap<FsString, CgroupHandle>);
580impl CgroupChildren {
581    fn insert_child(&mut self, name: FsString, child: CgroupHandle) -> Result<CgroupHandle, Errno> {
582        let btree_map::Entry::Vacant(child_entry) = self.0.entry(name) else {
583            return error!(EEXIST);
584        };
585        Ok(child_entry.insert(child).clone())
586    }
587
588    fn remove_child(&mut self, name: &FsStr) -> Result<CgroupHandle, Errno> {
589        let btree_map::Entry::Occupied(child_entry) = self.0.entry(name.into()) else {
590            return error!(ENOENT);
591        };
592        let child = child_entry.get();
593
594        // This allow_subclass is safe because the lock is being acquired
595        // in a strictly top-down traversal of the Cgroup tree (from parent
596        // to child), so no lock ordering cycles can be formed.
597        let _token = allow_subclass();
598        let mut child_state = child.state.lock();
599        assert!(!child_state.deleted, "child cannot be deleted");
600
601        child_state.update_processes();
602        if !child_state.processes.is_empty() {
603            return error!(EBUSY);
604        }
605        if !child_state.children.is_empty() {
606            return error!(EBUSY);
607        }
608
609        child_state.deleted = true;
610        drop(child_state);
611
612        Ok(child_entry.remove())
613    }
614
615    fn get_child(&self, name: &FsStr) -> Option<CgroupHandle> {
616        self.0.get(name).cloned()
617    }
618
619    fn get_children(&self) -> Vec<CgroupHandle> {
620        self.0.values().cloned().collect()
621    }
622
623    fn count_descendants(&self) -> u64 {
624        self.0
625            .values()
626            .map(|child| {
627                1 + {
628                    // This allow_subclass is safe because the lock is being acquired
629                    // in a strictly top-down traversal of the Cgroup tree (from parent
630                    // to child), so no lock ordering cycles can be formed.
631                    let _token = allow_subclass();
632                    child.count_descendants()
633                }
634            })
635            .sum()
636    }
637}
638
639impl Deref for CgroupChildren {
640    type Target = BTreeMap<FsString, CgroupHandle>;
641
642    fn deref(&self) -> &Self::Target {
643        &self.0
644    }
645}
646
647#[derive(Debug, Default)]
648struct CgroupState {
649    /// Subgroups of this control group.
650    children: CgroupChildren,
651
652    /// The tasks that are part of this control group.
653    processes: HashSet<Pid>,
654
655    /// If true, can no longer add children or tasks.
656    deleted: bool,
657
658    /// Wait queue to thaw all blocked tasks in this cgroup.
659    wait_queue: WaitQueue,
660
661    /// Controller-specific state.
662    controllers: HashMap<ControllerType, ControllerState>,
663}
664
665impl CgroupState {
666    fn freezer(&self) -> Option<&FreezerControllerState> {
667        match self.controllers.get(&ControllerType::Freezer) {
668            Some(ControllerState::Freezer(state)) => Some(state),
669            _ => None,
670        }
671    }
672
673    fn freezer_mut(&mut self) -> Option<&mut FreezerControllerState> {
674        match self.controllers.get_mut(&ControllerType::Freezer) {
675            Some(ControllerState::Freezer(state)) => Some(state),
676            _ => None,
677        }
678    }
679
680    fn cpuset(&self) -> Option<&CpusetControllerState> {
681        match self.controllers.get(&ControllerType::Cpuset) {
682            Some(ControllerState::Cpuset(state)) => Some(state),
683            _ => None,
684        }
685    }
686
687    fn cpuset_mut(&mut self) -> Option<&mut CpusetControllerState> {
688        match self.controllers.get_mut(&ControllerType::Cpuset) {
689            Some(ControllerState::Cpuset(state)) => Some(state),
690            _ => None,
691        }
692    }
693
694    /// Creates a new Waiter that subscribes to the Cgroup's freezer WaitQueue. This `Waiter` can be
695    /// sent as a part of a `KernelSignal::Freeze` to freeze a `Task`.
696    fn create_freeze_waiter(&self) -> Waiter {
697        let waiter = Waiter::new();
698        self.wait_queue.wait_async(&waiter);
699        waiter
700    }
701
702    // Goes through `processes` and remove processes that are no longer alive.
703    fn update_processes(&mut self) {
704        self.processes.retain(|thread_group| {
705            let Ok(thread_group) = thread_group.get_thread_group() else {
706                return false;
707            };
708            let running = thread_group.read().is_running();
709            running
710        });
711    }
712
713    fn freeze_thread_group(&self, thread_group: &ThreadGroup) {
714        let tasks = thread_group.read().tasks();
715        for task in tasks {
716            send_freeze_signal(&task, self.create_freeze_waiter())
717                .expect("sending freeze signal should not fail");
718        }
719    }
720
721    fn thaw_thread_group(&self, thread_group: &ThreadGroup) {
722        let tasks = thread_group.read().tasks();
723        for task in tasks {
724            task.write().thaw();
725            task.interrupt();
726        }
727    }
728
729    fn get_effective_freezer_state(&self) -> FreezerState {
730        if let Some(freezer) = self.freezer() {
731            std::cmp::max(freezer.self_freezer_state, freezer.inherited_freezer_state)
732        } else {
733            FreezerState::Thawed
734        }
735    }
736
737    fn add_process(&mut self, thread_group: &ThreadGroup) -> Result<(), Errno> {
738        if self.deleted {
739            return error!(ENOENT);
740        }
741        self.processes.insert(thread_group.leader.clone());
742
743        if self.get_effective_freezer_state() == FreezerState::Frozen {
744            self.freeze_thread_group(&thread_group);
745        }
746        Ok(())
747    }
748
749    fn remove_process(&mut self, thread_group: &ThreadGroup) -> Result<(), Errno> {
750        if self.deleted {
751            return error!(ENOENT);
752        }
753        self.processes.remove(&thread_group.leader);
754
755        if self.get_effective_freezer_state() == FreezerState::Frozen {
756            self.thaw_thread_group(thread_group);
757        }
758        Ok(())
759    }
760
761    fn propagate_freeze(&mut self, inherited_freezer_state: FreezerState) {
762        let prev_effective_freezer_state = self.get_effective_freezer_state();
763        if let Some(freezer) = self.freezer_mut() {
764            freezer.inherited_freezer_state = inherited_freezer_state;
765        }
766        if prev_effective_freezer_state == FreezerState::Frozen {
767            return;
768        }
769
770        for pid in self.processes.iter() {
771            let Ok(thread_group) = pid.get_thread_group() else {
772                continue;
773            };
774            self.freeze_thread_group(&thread_group);
775        }
776
777        // Freeze all children cgroups while holding self state lock
778        for child in self.children.get_children() {
779            // This allow_subclass is safe because the lock is being acquired
780            // in a strictly top-down traversal of the Cgroup tree (from parent
781            // to child), so no lock ordering cycles can be formed.
782            let _token = allow_subclass();
783            child.state.lock().propagate_freeze(FreezerState::Frozen);
784        }
785    }
786
787    fn propagate_thaw(&mut self, inherited_freezer_state: FreezerState) {
788        if let Some(freezer) = self.freezer_mut() {
789            freezer.inherited_freezer_state = inherited_freezer_state;
790        }
791        if self.get_effective_freezer_state() == FreezerState::Thawed {
792            self.wait_queue.notify_all();
793            for child in self.children.get_children() {
794                // This allow_subclass is safe because the lock is being acquired
795                // in a strictly top-down traversal of the Cgroup tree (from parent
796                // to child), so no lock ordering cycles can be formed.
797                let _token = allow_subclass();
798                child.state.lock().propagate_thaw(FreezerState::Thawed);
799            }
800        }
801    }
802
803    fn propagate_kill(&self) {
804        for pid in self.processes.iter() {
805            let Ok(thread_group) = pid.get_thread_group() else {
806                continue;
807            };
808            thread_group.write().send_signal(SignalInfo::kernel(SIGKILL));
809        }
810
811        // Recursively lock and kill children cgroups' processes.
812        for child in self.children.get_children() {
813            // This allow_subclass is safe because the lock is being acquired
814            // in a strictly top-down traversal of the Cgroup tree (from parent
815            // to child), so no lock ordering cycles can be formed.
816            let _token = allow_subclass();
817            child.state.lock().propagate_kill();
818        }
819    }
820}
821
822/// `Cgroup` is a non-root cgroup in a cgroup hierarchy, and can have other `Cgroup`s as children.
823#[derive(Debug)]
824pub struct Cgroup {
825    root: Weak<CgroupRoot>,
826
827    /// ID of the cgroup.
828    id: u64,
829
830    /// Name of the cgroup.
831    name: FsString,
832
833    /// Weak reference to its parent cgroup, `None` if direct descendent of the root cgroup.
834    /// This field is useful in implementing features that only apply to non-root cgroups.
835    parent: Option<Weak<Cgroup>>,
836
837    /// Internal state of the Cgroup.
838    state: LockDepMutex<CgroupState, CgroupStateLock>,
839
840    weak_self: Weak<Cgroup>,
841}
842pub type CgroupHandle = Arc<Cgroup>;
843
844/// Returns the path from the root to this `cgroup`.
845pub fn path_from_root(weak_cgroup: Option<Weak<Cgroup>>) -> Result<FsString, Errno> {
846    let cgroup = match weak_cgroup {
847        Some(weak_cgroup) => Weak::upgrade(&weak_cgroup).ok_or_else(|| errno!(ENODEV))?,
848        None => return Ok("/".into()),
849    };
850    let mut path = PathBuilder::new();
851    let mut current = Some(cgroup);
852    while let Some(cgroup) = current {
853        path.prepend_element(cgroup.name());
854        current = cgroup.parent()?;
855    }
856    Ok(path.build_absolute())
857}
858
859impl Cgroup {
860    pub fn new(
861        id: u64,
862        name: &FsStr,
863        root: &Weak<CgroupRoot>,
864        parent: Option<Weak<Cgroup>>,
865    ) -> CgroupHandle {
866        let root_shared = root.upgrade().expect("root must exist");
867        let mut controllers = HashMap::new();
868        for controller in &root_shared.controllers {
869            match controller {
870                ControllerType::Cpuset => {
871                    controllers.insert(
872                        *controller,
873                        ControllerState::Cpuset(CpusetControllerState::default()),
874                    );
875                }
876                ControllerType::Freezer => {
877                    controllers.insert(
878                        *controller,
879                        ControllerState::Freezer(FreezerControllerState::default()),
880                    );
881                }
882                _ => {}
883            }
884        }
885
886        Arc::new_cyclic(|weak| Self {
887            id,
888            root: root.clone(),
889            name: name.to_owned(),
890            parent,
891            state: LockDepMutex::new(CgroupState {
892                children: Default::default(),
893                processes: Default::default(),
894                deleted: false,
895                wait_queue: Default::default(),
896                controllers,
897            }),
898            weak_self: weak.clone(),
899        })
900    }
901
902    pub fn name(&self) -> &FsStr {
903        self.name.as_ref()
904    }
905
906    fn root(&self) -> Result<Arc<CgroupRoot>, Errno> {
907        self.root.upgrade().ok_or_else(|| errno!(ENODEV))
908    }
909
910    /// Returns the upgraded parent cgroup, or `Ok(None)` if cgroup is a direct desendent of root.
911    /// Errors if parent node is no longer around.
912    fn parent(&self) -> Result<Option<CgroupHandle>, Errno> {
913        self.parent.as_ref().map(|weak| weak.upgrade().ok_or_else(|| errno!(ENODEV))).transpose()
914    }
915
916    fn count_descendants(&self) -> u64 {
917        self.state.lock().children.count_descendants()
918    }
919
920    fn is_controller_supported(&self, controller: ControllerType) -> bool {
921        self.root().map(|r| r.controllers.contains(&controller)).unwrap_or(false)
922    }
923
924    fn cpuset_path(&self, root: &CgroupRoot) -> Option<String> {
925        if !root.tracks_cpuset() {
926            return None;
927        }
928        let bytes = path_from_root(Some(self.weak_self.clone())).ok()?;
929        std::str::from_utf8(&bytes).ok().map(|s| s.to_string())
930    }
931
932    fn move_process_into_cgroup<'a>(
933        &self,
934        root: &'a CgroupRoot,
935        thread_group: &ThreadGroup,
936    ) -> Result<LockDepGuard<'a, CgroupPidTable>, Errno> {
937        let mut pid_table = root.pid_table.lock();
938        match pid_table.entry(thread_group.leader.clone()) {
939            hash_map::Entry::Occupied(mut entry) => {
940                // Check if thread_group is already in the current cgroup. Linux does not return an
941                // error if it already exists.
942                if !std::ptr::eq(self, entry.get().as_ptr()) {
943                    // If thread_group is in another cgroup, we need to remove it first.
944                    track_stub!(TODO("https://fxbug.dev/383374687"), "check permissions");
945                    if let Some(other_cgroup) = entry.get().upgrade() {
946                        other_cgroup.state.lock().remove_process(thread_group)?;
947                    }
948
949                    self.state.lock().add_process(thread_group)?;
950                    entry.insert(self.weak_self.clone());
951                }
952            }
953            hash_map::Entry::Vacant(entry) => {
954                self.state.lock().add_process(thread_group)?;
955                entry.insert(self.weak_self.clone());
956            }
957        }
958        Ok(pid_table)
959    }
960}
961
962impl CgroupOps for Cgroup {
963    fn id(&self) -> u64 {
964        self.id
965    }
966
967    fn add_process(&self, thread_group: &ThreadGroup) -> Result<(), Errno> {
968        let root = self.root()?;
969        // Update `pid_table` and each task's `cpuset_path` under the `pid_table` lock so
970        // concurrent migrations cannot leave `pid_table` and `cpuset_path` out of sync, then
971        // drop `pid_table` before making synchronous FIDL calls in `sync_task_scheduler_role`.
972        let pid_table = self.move_process_into_cgroup(&root, thread_group)?;
973
974        if let Some(cpuset_path) = self.cpuset_path(&root) {
975            let tasks = thread_group.read().tasks();
976            let tasks_to_sync: Vec<_> =
977                tasks.into_iter().filter(|task| set_task_cpuset_path(task, &cpuset_path)).collect();
978            drop(pid_table);
979            for task in tasks_to_sync {
980                sync_task_scheduler_role(&task);
981            }
982        }
983
984        Ok(())
985    }
986
987    fn add_task(&self, task: &Task) -> Result<(), Errno> {
988        // TODO(https://fxbug.dev/401298305): Track cgroup membership and freezer state per thread
989        // rather than per thread group.
990        let root = self.root()?;
991        if let Some(cpuset_path) = self.cpuset_path(&root) {
992            let changed = if task.is_leader() {
993                let _pid_table = self.move_process_into_cgroup(&root, task.thread_group())?;
994                set_task_cpuset_path(task, &cpuset_path)
995            } else {
996                if self.state.lock().deleted {
997                    return error!(ENOENT);
998                }
999                set_task_cpuset_path(task, &cpuset_path)
1000            };
1001            if changed {
1002                sync_task_scheduler_role(task);
1003            }
1004            return Ok(());
1005        }
1006        self.add_process(task.thread_group())
1007    }
1008
1009    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
1010        let id = self.root()?.get_next_id();
1011        let new_child = Cgroup::new(id, name, &self.root, Some(self.weak_self.clone()));
1012        let mut state = self.state.lock();
1013        if state.deleted {
1014            return error!(ENOENT);
1015        }
1016        // New child should inherit the effective freezer state of the current cgroup.
1017        // This allow_subclass is safe because the lock is being acquired
1018        // in a strictly top-down traversal of the Cgroup tree (from parent
1019        // to child), so no lock ordering cycles can be formed.
1020        let effective_freezer = state.get_effective_freezer_state();
1021        let _token = allow_subclass();
1022        if let Some(freezer) = new_child.state.lock().freezer_mut() {
1023            freezer.inherited_freezer_state = effective_freezer;
1024        }
1025        state.children.insert_child(name.into(), new_child)
1026    }
1027
1028    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
1029        let state = self.state.lock();
1030        state.children.get_child(name).ok_or_else(|| errno!(ENOENT))
1031    }
1032
1033    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
1034        let mut state = self.state.lock();
1035        if state.deleted {
1036            return error!(ENOENT);
1037        }
1038        state.children.remove_child(name)
1039    }
1040
1041    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> {
1042        let state = self.state.lock();
1043        if state.deleted {
1044            return error!(ENOENT);
1045        }
1046        Ok(state.children.get_children())
1047    }
1048
1049    fn get_pids(&self, _kernel: &Kernel) -> Vec<pid_t> {
1050        let mut state = self.state.lock();
1051        state.update_processes();
1052        state
1053            .processes
1054            .iter()
1055            .filter_map(|v| v.get_thread_group().ok().map(|tg| tg.leader.id))
1056            .collect()
1057    }
1058
1059    fn kill(&self) {
1060        fuchsia_trace::duration!(CATEGORY_STARNIX, "CgroupKill");
1061        let state = self.state.lock();
1062        state.propagate_kill();
1063    }
1064
1065    fn is_populated(&self) -> bool {
1066        let mut state = self.state.lock();
1067        if state.deleted {
1068            return false;
1069        }
1070        state.update_processes();
1071        if !state.processes.is_empty() {
1072            return true;
1073        }
1074
1075        state.children.get_children().into_iter().any(|child| {
1076            // This allow_subclass is safe because the lock is being acquired
1077            // in a strictly top-down traversal of the Cgroup tree (from parent
1078            // to child), so no lock ordering cycles can be formed.
1079            let _token = allow_subclass();
1080            child.is_populated()
1081        })
1082    }
1083
1084    fn freezer(&self) -> Option<&dyn FreezerOps> {
1085        if self.is_controller_supported(ControllerType::Freezer) { Some(self) } else { None }
1086    }
1087
1088    fn cpuset(&self) -> Option<&dyn CpusetOps> {
1089        if self.is_controller_supported(ControllerType::Cpuset) { Some(self) } else { None }
1090    }
1091}
1092
1093impl FreezerOps for Cgroup {
1094    fn get_freezer_state(&self) -> CgroupFreezerState {
1095        let state = self.state.lock();
1096        let self_freezer_state = state.freezer().map(|f| f.self_freezer_state).unwrap_or_default();
1097        CgroupFreezerState {
1098            self_freezer_state,
1099            effective_freezer_state: state.get_effective_freezer_state(),
1100        }
1101    }
1102
1103    fn freeze(&self) {
1104        fuchsia_trace::duration!(CATEGORY_STARNIX, "CgroupFreeze");
1105        let mut state = self.state.lock();
1106        let inherited_freezer_state =
1107            state.freezer().map(|f| f.inherited_freezer_state).unwrap_or_default();
1108        state.propagate_freeze(inherited_freezer_state);
1109        if let Some(freezer) = state.freezer_mut() {
1110            freezer.self_freezer_state = FreezerState::Frozen;
1111        }
1112    }
1113
1114    fn thaw(&self) {
1115        fuchsia_trace::duration!(CATEGORY_STARNIX, "CgroupThaw");
1116        let mut state = self.state.lock();
1117        if let Some(freezer) = state.freezer_mut() {
1118            freezer.self_freezer_state = FreezerState::Thawed;
1119        }
1120        let inherited_freezer_state =
1121            state.freezer().map(|f| f.inherited_freezer_state).unwrap_or_default();
1122        state.propagate_thaw(inherited_freezer_state);
1123    }
1124}
1125
1126impl CpusetOps for Cgroup {
1127    fn cpuset_cpus(&self) -> Vec<u32> {
1128        self.state
1129            .lock()
1130            .cpuset()
1131            .and_then(|c| c.cpus.clone())
1132            .unwrap_or_else(|| (0..zx::system_get_num_cpus()).collect())
1133    }
1134
1135    fn set_cpuset_cpus(&self, cpus: Vec<u32>) {
1136        // TODO(b/322255433): Translate the cpuset logic into Zircon
1137        let mut state = self.state.lock();
1138        if let Some(cpuset) = state.cpuset_mut() {
1139            cpuset.cpus = Some(cpus);
1140        }
1141    }
1142}
1143
1144#[cfg(test)]
1145mod test {
1146    use super::*;
1147    use crate::testing::spawn_kernel_and_run;
1148    use assert_matches::assert_matches;
1149    use starnix_uapi::signals::SIGCHLD;
1150    use starnix_uapi::{CLONE_SIGHAND, CLONE_THREAD, CLONE_VM};
1151
1152    #[::fuchsia::test]
1153    async fn cgroup_path_from_root() {
1154        spawn_kernel_and_run(async |_| {
1155            let root = CgroupRoot::new(0, BTreeSet::new());
1156
1157            let test_cgroup =
1158                root.new_child("test".into()).expect("new_child on root cgroup succeeds");
1159            let child_cgroup = test_cgroup
1160                .new_child("child".into())
1161                .expect("new_child on non-root cgroup succeeds");
1162
1163            assert_eq!(path_from_root(Some(Arc::downgrade(&test_cgroup))), Ok("/test".into()));
1164            assert_eq!(
1165                path_from_root(Some(Arc::downgrade(&child_cgroup))),
1166                Ok("/test/child".into())
1167            );
1168        })
1169        .await;
1170    }
1171
1172    #[::fuchsia::test]
1173    async fn cgroup_clone_task_in_frozen_cgroup() {
1174        spawn_kernel_and_run(async |current_task| {
1175            let kernel = current_task.kernel();
1176            let root = &kernel.cgroups.cgroup2;
1177            let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds");
1178
1179            let process = current_task.clone_task_for_test(0, Some(SIGCHLD));
1180            cgroup.add_process(process.thread_group()).expect("add process to cgroup");
1181            cgroup.freeze();
1182            assert_eq!(cgroup.get_pids(&kernel).first(), Some(process.get_pid()).as_ref());
1183            assert_eq!(root.get_cgroup(&process.pid).unwrap().as_ptr(), Arc::as_ptr(&cgroup));
1184
1185            let thread = process.clone_task_for_test(
1186                (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM) as u64,
1187                Some(SIGCHLD),
1188            );
1189
1190            let thread_state = thread.read();
1191            let kernel_signals = thread_state.kernel_signals_for_test();
1192            assert_matches!(kernel_signals.front(), Some(KernelSignal::Freeze(_)));
1193        })
1194        .await;
1195    }
1196
1197    #[::fuchsia::test]
1198    async fn cgroup_tg_release_removes_pid() {
1199        spawn_kernel_and_run(async |current_task| {
1200            let kernel = current_task.kernel();
1201            let root = &kernel.cgroups.cgroup2;
1202            let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds");
1203
1204            let process = current_task.clone_task_for_test(0, Some(SIGCHLD));
1205            cgroup.add_process(process.thread_group()).expect("add process to cgroup");
1206
1207            assert_eq!(root.get_cgroup(&process.pid).unwrap().as_ptr(), Arc::as_ptr(&cgroup));
1208
1209            // Drop the process to release it.
1210            drop(process);
1211
1212            // Verify that the process is removed from the cgroup pid table.
1213            assert!(root.pid_table.lock().is_empty());
1214        })
1215        .await;
1216    }
1217
1218    #[::fuchsia::test]
1219    async fn cgroup_add_task_per_thread_cpuset_and_deleted_check() {
1220        spawn_kernel_and_run(async |current_task| {
1221            let kernel = current_task.kernel();
1222            let controllers = BTreeSet::from([ControllerType::Cpuset]);
1223            let v1_root = kernel
1224                .cgroups
1225                .get_or_create_cgroup1(&controllers, None)
1226                .expect("create cgroup1 cpuset");
1227            let top_app = v1_root.new_child("top-app".into()).expect("new_child top-app");
1228            let background = v1_root.new_child("background".into()).expect("new_child background");
1229
1230            let leader = current_task.clone_task_for_test(0, Some(SIGCHLD));
1231            let worker = leader.clone_task_for_test(
1232                (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM) as u64,
1233                Some(SIGCHLD),
1234            );
1235
1236            // Move the worker thread to /background via `add_task`, then move the leader thread
1237            // to /top-app via `add_task`. The worker must retain `/background`.
1238            background.add_task(&worker).expect("add worker to background");
1239            assert_eq!(worker.read().cpuset_path, "/background");
1240            assert_eq!(leader.read().cpuset_path, "/");
1241
1242            top_app.add_task(&leader).expect("add leader to top-app");
1243            assert_eq!(leader.read().cpuset_path, "/top-app");
1244            assert_eq!(worker.read().cpuset_path, "/background");
1245            assert_eq!(v1_root.get_cgroup(&leader.pid).unwrap().as_ptr(), Arc::as_ptr(&top_app));
1246
1247            // With a v1 cpuset hierarchy mounted, cgroup v2 operations must not overwrite the v1
1248            // cpuset_path.
1249            let v2_child = kernel.cgroups.cgroup2.new_child("uid_1000".into()).expect("v2 child");
1250            v2_child.add_process(leader.thread_group()).expect("add to v2 child");
1251            assert_eq!(leader.read().cpuset_path, "/top-app");
1252            assert_eq!(worker.read().cpuset_path, "/background");
1253
1254            // Deleting an empty cgroup makes `add_task` fail with ENOENT even for non-leader TIDs.
1255            v1_root.remove_child("background".into()).expect("remove background cgroup");
1256            assert_eq!(background.add_task(&worker), error!(ENOENT));
1257        })
1258        .await;
1259    }
1260}