Skip to content

feat(ax-cgroup): cgroup v2 L4 resource control + manager interfaces#1379

Draft
SongShiQ wants to merge 14 commits into
rcore-os:devfrom
SongShiQ:goal/cgroup-next-build
Draft

feat(ax-cgroup): cgroup v2 L4 resource control + manager interfaces#1379
SongShiQ wants to merge 14 commits into
rcore-os:devfrom
SongShiQ:goal/cgroup-next-build

Conversation

@SongShiQ

@SongShiQ SongShiQ commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

概述

本 PR 在 cgroup v2 统一框架(Controller 抽象 + ControllerRegistry 自动注册)之上,将 cgroup v2 子系统补齐到 L4 资源管控级别,并补全 systemd/docker 所需的管理器探测接口。

状态:Draft。 本 PR 依赖 cgroup 统一框架基线(见下文「与 #1234 的关系」),待该基线进入 dev 后转正式 PR。当前以草稿形式固定并展示本阶段成果。

#1234 的关系

  • feat(ax-cgroup): extract cgroup v2 subsystem into standalone crate #1234pr/cgroup-modularize)负责 将 cgroup v2 抽取为独立 crate / 模块化解耦,正在审核中。
  • 本 PR 是在 cgroup 统一框架基线之上继续做 L4 资源管控落地,是模块化之后的下一步能力建设。
  • 两条线基线不同(独立 rebase),因此本 PR 以 base=dev 独立提交,diff 中约 3 个继承自基线的提交属于框架本身;本阶段新增成果集中在 components/ax-cgroup/ 与 StarryOS cgroup 接线

本阶段成果(11 commits)

控制器 / 能力 内容 层级
测试地基 host MockProvider + 串行测试框架,接入 cargo xtask test 白名单
五控制器 round-trip pids/memory/cpu/cpuset/io 的 parse/format + 边界用例
io.max 每设备限额持久化(upsert / 多设备 / 读回对齐 Linux 空格式) L3.5
cpu.weight weight→CFS nice 映射(Linux sched_prio_to_weight 表,weight 100↔nice 0) L4(部分)
memory 沿 path-to-root 的层次化 charge / uncharge,migrate 整体搬迁、exit 精确归零 L4
pids.events + delegation pids.events 计数 + cgroup 委托写权限门控(can_delegate_write 闭环增强
cpuset effective 掩码层次求交(子 = 父 ∩ 自身)+ 子孙递归重算 L4(部分)
cpu.max 周期限流基础设施:上游 ax-task 可选 tick hook + throttled 任务态(默认关闭,零行为变化 L4
管理器接口(基础) /proc/<pid>/cgroup 真实路径、memory.stat(控制器属性) systemd/docker 可探测
cgroup.events / cgroup.stat 文件挂载 + inotify cgroup.events(populated)、cgroup.stat(nr_descendants) 真正挂载为 cgroupfs 只读文件;cgroup 内最后一个进程退出 / 迁出导致 populated 翻转 0/1 时,对 cgroup.events 发 inotify IN_MODIFY,供 systemd 在每个 unit 的 cgroup 上挂 watch、据此判定 unit 死亡(无需轮询) systemd 探测闭环

cgroup.events populated + inotify(本次重点补全)

cgroup v2 下 systemd 适配依赖一条边沿通知链路:systemd 在每个 service 的 cgroup 上挂 inotify watch,当最后一个进程退出、cgroup.eventspopulated 翻成 0 时收到 IN_MODIFY,才知道服务停了。本次将这条链路补全:

  • 文件挂载:此前内核 cgroupfs 只暴露 cgroup.controllers/procs/subtree_control/typecgroup.events/cgroup.stat 的文本生成函数虽已存在却未挂载为 VFS 文件。本次在 pseudofs/cgroup.rsCgroupFileKind 接线 Events/Stat(只读 0o444),ls 可见、cat 可读,populated 递归判定整棵子树是否有进程。
  • 翻转通知(依赖倒置):ax-cgroup 是 #![no_std] OS 无关组件,不直接调内核 inotify。新增 CgroupProvider::notify_populated_changed(path) 回调(默认空实现,向后兼容);ax-cgroup 在 exit_process / migrate_process 收口点快照前后 populated 状态,仅在翻转时回调;内核侧实现把它映射到挂载点下 cgroup.events 文件并发 IN_MODIFY/cgroup/sys/fs/cgroup 双挂载点均覆盖)。
  • host 单测:新增边沿语义测试——最后一个进程退出导致 leaf 及其祖先 populated 1→0 时,回调按预期对每个翻转的 cgroup 触发(用 MockProvider 记录通知路径,不依赖真实 inotify)。

验证

  • 42 个 host 单测全绿cargo test -p ax-cgroup
  • fmt / clippy 零警告cargo fmt -p ax-cgroup --checkcargo clippy -p ax-cgroup --all-features --tests -- -D warnings
  • 上游不破坏cargo check -p ax-task + cargo clippy -p ax-task -- -D warnings 通过(tick hook / throttled 默认关闭)
  • 整树 host 测试cargo xtask test(含 ax-cgroup)
  • StarryOS aarch64 整树构建通过cargo xtask starry build --arch aarch64(含 events/stat 文件挂载 + inotify 回调接线)
  • QEMU aarch64 启动序列全绿cargo xtask starry qemu --arch aarch64,正常启动、cgroup 子系统初始化成功、tick hook 不破坏调度

上游改动(最小侵入)

ax-task 仅新增默认关闭的能力,不改变 ArceOS/Axvisor 现有路径行为:

  • 可选 tick hook 注册(默认空指针 = 现有行为零变化)
  • Task.throttled: AtomicBool(默认 false = 不影响现有调度)

已知边界(诚实标注,待真机/QEMU 深度验证)

  • cgroup.events 的 inotify 端到端:文件挂载 + populated 计算 + 翻转回调 + 内核侧 IN_MODIFY 发射均已就位并有 host 单测;「真实 systemd 在伪文件上挂 watch 收到 IN_MODIFY」这一端到端链路待真机/QEMU 验证。
  • cpu.max 调度跳过:限流基础设施齐全(throttle flag + tick + 计费),但 scheduler pick_next 主动 skip throttled 任务留作后续验证(避免半成品 skip 引入 busy-spin)。
  • cpuset 亲和性接调度:effective 求交纯逻辑 + 单测齐全,「对指定任务设 cpumask」的上游入口暴露留作下一步。
  • io 真实限流:rdif-block 无队列层,令牌桶限流需新建 I/O 调度子系统,文档化为超范围;io.max 限额持久化(L3.5)已达成。
  • memory 记账:charge 走 alloc_frame 用户态页,内核 Box/Vec 不计(与 Linux 一致);RSS 精确化用 aspace VSS 作上界。

当前状态与下一步

SongShiQ and others added 13 commits June 20, 2026 03:08
将 StarryOS 内核中的 cgroup v2 实现抽取为独立的 `components/ax-cgroup`
crate,使 cgroup 核心逻辑与内核任务层解耦。

原 cgroup v2 实现直接内嵌在 `os/StarryOS/kernel/src/cgroup/` 下,核心层次
结构、控制器状态与进程成员管理都与内核 `crate::task::*` 紧耦合。这导致
cgroup 逻辑无法独立编译、复用或测试,且与内核构建强绑定,CI 编译范围偏大。

1. 新建 `ax-cgroup` crate(`no_std`),承载 cgroup v2 核心:
   - `core`:`CgroupNode`、全局根节点、id→node 注册表
   - `pids`:`PidsState`,基于 CAS 的进程计数计费(避免 SMP 下 TOCTOU)
   - `cpu`:`CpuState` / `BandwidthState`,`cpu.weight` / `cpu.max` 状态
   - `provider`:`CgroupProvider` trait + 注册单元
   - crate 根:membership、fork/migrate/exit 事务、属性解析
2. 引入 `CgroupProvider` trait 抽象内核任务原语(is_zombie /
   get_cgroup / set_cgroup)。cgroup 核心不再直接访问 `crate::task::*`,
   改为通过内核在启动时注册的 provider 调用。
3. 内核 `cgroup/mod.rs` 退化为薄集成层:re-export crate API、实现并注册
   `KernelCgroupProvider`、保留 `bandwidth_tick` 钩子(依赖 ax_task /
   ax_hal,故留在内核侧)。
4. 接入点适配:`task/mod.rs` 为 `ProcessData` 增加 `cgroup` 字段;
   `entry.rs` 将 init 进程 attach 到根 cgroup;`task/ops.rs` 在进程退出时
   调用 `exit_process` 解除计费;`clone.rs` 在 fork 路径使用 begin_fork
   事务并在成功后 commit。
5. cgroupfs 接入:`pseudofs/dir.rs` 支持 `create_dir`(mkdir 建 cgroup
   目录);`pseudofs/file.rs` 对伪文件采用全量覆盖写语义。
6. 新增 `cgroup-cpu` / `cgroup-pids` 测试用例及多架构 qemu 配置。
7. 为 crate 补充 README.md / README_CN.md,如实说明实际架构:借鉴 cgroup
   v2 语义与 Asterinas 的若干规则,但未照搬其 SysTree 架构,而是采用自管理
   的简化实现。

- 解耦边界放在 `CgroupProvider` trait:crate 只依赖最小任务原语,内核保留
  对 ax_task / ax_hal 的具体访问,符合 cgroup 逻辑与内核解耦的目标。
- fork 采用 begin_fork → commit 两阶段事务,失败时由 Drop 自动回滚 pids
  计费,保证 SMP 下的原子性。
- CPU 带宽 tick 钩子当前在内核侧留空(注释保留实现),因为 dev 分支上
  `ax_task::set_tick_hook` / `set_throttled` 尚不可用;带宽配额状态仍在
  ax-cgroup 维护,待任务 API 就绪后再启用强制限流。

cargo check 与 clippy 在 ax-cgroup 与 starry-kernel 上均通过(基于
origin/dev)。
…llers

- Add CgroupController trait + CgroupControllerFactory + global registry
- Implement memory, pids, cpu, cpuset, io controllers with consistent read/write model
- Add cgroup-cpu and cgroup-pids end-to-end test cases
- Adapt kernel integration layer and VFS dispatch to unified trait
- Update README with architecture documentation
Root cgroup pids fallback in create_child triggered clippy::collapsible_if
under -D warnings. Collapse the nested if-let into a single && let chain;
no behavior change.
- add MockProvider implementing CgroupProvider for host unit tests
- enable ax-kernel-guard host-test feature via dev-dependency to avoid
  privileged IRQ instructions segfaulting under cargo test
- wire cfg(test) mod tests into lib.rs, add extern crate std for tests
- add testdata/ with byte-faithful Linux cgroup v2 samples for M1
- whitelist ax-cgroup in scripts/test/std_crates.csv
- pids/memory/cpu/cpuset/io parse-format round-trip via public trait API
- boundary checks: EINVAL on out-of-range/negative/malformed input
- read-only attrs return EACCES (current/events/effective)
- 19 host tests, all green
- IoState gains limits: SpinNoIrq<Vec<IoDeviceLimit>>
- write io.max upserts by device, applying only specified fields
  (Linux semantics); malformed line rejects whole write, no partial apply
- read io.max renders one line per device, unset rates print 'max',
  empty when no limit (matches Linux, not 'default')
- tests: round-trip, same-device upsert, multi-device, reject-no-persist
- weight_to_nice(): scale cgroup weight (1..10000) to load weight, pick
  closest nice via Linux sched_prio_to_weight table; weight 100 -> nice 0,
  10000 -> -20, 1 -> 19; integer-only, no_std friendly, clamps out of range
- host unit tests: default, extremes, monotonicity, clamping, bounds
- MemoryState::try_charge: atomic CAS check+add under max; note_max_event
- CgroupNode gains memory: Option<Arc<MemoryState>> fast-path field,
  extracted at node creation (mirrors pids)
- try_charge_mem_path/uncharge_mem_path walk leaf-to-root, charge every
  memory-controlled node, roll back + bump events_max on overflow (ENOMEM)
- per-pid charged_mem map: migrate moves the whole footprint between
  cgroups, exit releases it exactly
- public API try_charge_memory/uncharge_memory(pid, bytes) for kernel wiring
- tests serialize global-tree mutation via test_guard() to avoid parallel
  heap corruption; cover symmetry, over-limit+events, migrate move, hierarchy
- PidsState gains events_max; pids.events (read-only) reports 'max N',
  bumped when a fork is denied by pids.max
- CgroupProvider gains current_uid() for delegation checks (MockProvider
  defaults to root 0)
- CgroupNode gains delegated_to; can_delegate_write(uid, deleg) pure rule:
  root always, else delegated uid only
- write_subtree_control gated by check_delegation; set_delegated_to() API
- tests: delegation rules, non-root subtree_control gating, pids.events
- KernelCgroupProvider returns the current thread's euid for cgroup
  delegation checks; kernel tasks (no thread) act as root (0)
- keeps the provider trait impl in sync with ax-cgroup (chk5)
- CpusetState::effective_intersect(parent, own) = parent & own (pure)
- writing cpuset.cpus/mems recomputes effective for the node and its whole
  subtree: effective = parent.effective intersect own; root uses all-ones
- replaces the prior 'effective = raw written value' behaviour
- tests: pure intersection (incl. disjoint=empty), hierarchical recompute,
  parent narrowing/widening ripples to child effective
- affinity scheduler wiring deferred to the upstream batch (work B)
ax-task (upstream, default-off):
- set_tick_hook(fn()): static AtomicUsize storing fn ptr, default 0;
  run_tick_hook() invoked from on_timer_irq after scheduler_timer_tick.
  No hook registered = zero behaviour change (invariant 9)
- Task gains throttled: AtomicBool (default false) + pub is_throttled /
  set_throttled; flag is exposed for cgroup to drive, scheduler skip is
  the QEMU-validated follow-up (Round 6: pending)

ax-cgroup:
- CgroupNode gains cpu: Option<Arc<CpuState>> fast-path field, extracted
  at node creation (mirrors pids/memory)
- BandwidthState consume/reset/is_throttled covered by host unit tests

StarryOS kernel:
- cgroup::cpu::bandwidth_tick reads current task -> cgroup -> charges
  monotonic elapsed time into bandwidth, rolls period when elapsed,
  sets/clears the task throttle flag accordingly
- registered via ax_task::set_tick_hook in cgroup::init()
- skip idle via name match (is_idle is pub(crate) in ax-task)

Verified: cargo test -p ax-cgroup (41 ok), cargo check -p ax-task aarch64,
cargo starry build --arch aarch64 (full kernel link OK)
ax-cgroup (work G — host-testable manager interfaces):
- BUILTIN_FILES gains cgroup.events, cgroup.stat (alongside existing
  cgroup.controllers, procs, subtree_control, type)
- events_text(): 'populated N\nfrozen 0' where populated is 1 iff the
  subtree (recursive) holds any pid; systemd inotify depends on this
- stat_text(): 'nr_descendants N\nnr_dying_descendants 0' counting
  non-root descendants of this node
- memory.stat: read-only attr printing the docker/runc-expected key set
  (anon, file, kernel, kernel_stack, slab, sock, shmem, file_mapped,
  file_dirty, file_writeback, anon_thp, inactive_anon, active_anon,
  inactive_file, active_file, unevictable). 'anon' and 'active_anon'
  reflect current; the rest are honest zeros pending RSS bookkeeping
- tests: events populated propagation, stat descendant count, memory.stat
  key presence + read-only

StarryOS kernel (work G1):
- /proc/<pid>/cgroup now returns '0::<path>\n' from the task's actual
  cgroup (replaces hardcoded '0::/'), enabling /proc/self/cgroup probes

io.rs (work F — scope documentation):
- Module-level doc records that io.max persistence (L3.5) is shipped but
  real throttling is out of scope: rdif-block has no queue layer to hook
  a token bucket against. Persisted IoState::limits is the ground truth
  an enforcement layer can read once a queue lands; no API change needed

@ZR233 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 PR 的方向和拆分关系在描述里已经写得比较清楚:它是在 #1234 的 cgroup 模块化基线之上继续做 L4 资源管控和 systemd/docker 探测接口。当前新增的 ax-cgroup host 测试、qemu-smp1/system 下的 cgroup C 子用例布局,以及 StarryOS 接线方向我做了初步检查;cgroup-cpu/cgroup-pids 的 CMake 安装路径符合 grouped system case 规则,会安装到 /usr/bin/starry-test-suit

不过当前 head 还不能进入可合并 review 状态,原因如下:

  1. PR 仍是 Draft,且正文明确说明依赖 #1234,等待 #1234 进入 dev 后再转正式 PR。现在提交列表也包含 feat(ax-cgroup): extract cgroup v2 subsystem into standalone crate 这一基线提交,说明当前 diff 仍混有 #1234 的基线内容,不能独立判断为可合入的 L4 增量。
  2. 当前 mergeStateStatus=DIRTY,与最新 dev 有冲突。我用 git merge-tree origin/dev HEAD 检查,至少 Cargo.toml 存在 content conflict。合并前需要先在 #1234 之后 rebase/merge 最新 dev,并重新生成 Cargo.lock
  3. 当前分支没有任何 reported checks:gh pr checks 1379 --repo rcore-os/tgoskits --watch=false 返回 no checks reported on the 'goal/cgroup-next-build' branch。PR 正文声明了 cargo test -p ax-cgroupcargo xtask testcargo xtask starry build --arch aarch64cargo xtask starry qemu --arch aarch64 等验证,但当前 GitHub head 没有 CI 证据;在 rebase 后需要重新跑并保留 current-head 证据。
  4. 这个 PR 新增/修改 Starry grouped system 测试和 cgroup 行为,按项目规则需要证明 runner 实际发现并执行新增子用例。rebase 后至少需要提供或通过 CI 覆盖 cargo xtask starry test qemu --arch x86_64 -c qemu-smp1/cgroup-pids / qemu-smp1/cgroup-cpu,或等价的 qemu-smp1/system 当前 head 运行结果。当前没有这部分可复用证据。

CI covered:无 current-head checks。
CI-missing validated:只做了只读检查,包括 PR 依赖关系、冲突状态、grouped system CMake 安装路径和相关 open PR 搜索;没有运行 QEMU,因为当前 PR 是 draft、依赖 #1234 且与 base 冲突。
CI-missing not validated:PR 声明的 host/unit/QEMU 验证,以及新增 cgroup grouped system 子用例的实际 guest 运行结果,需在 rebase 后补充。
重复/重叠分析:#1234 是明确前置依赖,不是可并行合入的独立重叠;#1156/#1243 已关闭,未发现另一个打开 PR 取代本 PR 的 L4 增量。

建议流程:先等 #1234 合入或把本 PR 重新基于 #1234 合入后的 dev 整理成只包含 L4 增量;解决冲突并重新生成 lockfile;然后跑 PR 正文列出的验证和新增 Starry grouped system 子用例,再转 Ready for review。

…flip

- pseudofs/cgroup.rs: mount cgroup.events and cgroup.stat as read-only
  VFS files (previously events_text/stat_text existed in ax-cgroup but
  were never wired into the kernel filesystem, so the files were absent)
- ax-cgroup: add CgroupProvider::notify_populated_changed callback
  (default no-op, dependency inversion keeps the no_std crate kernel-free)
- emit inotify IN_MODIFY on cgroup.events when a subtree's populated state
  flips (last process exits / first process enters), so systemd-style
  watchers detect unit death without polling
- fix order-dependent delegation test by making root +pids precondition
  explicit (was relying on another test polluting the global tree)
- strip CRLF from cgroup-cpu/cgroup-pids test sources (LF per repo convention)

All 42 host tests pass; aarch64 build and QEMU boot verified.

@ZR233 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本次复审基于当前 head fc80b86

新增的 cgroup.events / cgroup.stat 挂载与 populated 翻转通知方向本身看起来是对 systemd 探测链路的合理补齐;当前 diff hygiene 也比上一轮干净,本地 git diff --check origin/dev...HEAD 未再发现问题,且没有看到未解决的当前 review thread。

不过这个 PR 仍不能进入可合并状态,阻塞条件和上一轮一致,并且当前 head 仍可复现:

  1. PR 仍是 Draft,正文也继续说明它依赖 #1234。当前 #1234 仍是 open,reviewDecision=CHANGES_REQUESTEDmergeStateStatus=DIRTY,还没有合入 dev
  2. 当前分支仍包含 #1234 的基线提交(例如 feat(ax-cgroup): extract cgroup v2 subsystem into standalone crateimplement unified controller framework...),因此 diff 还不是纯 L4/resource-control 增量,不能作为独立合入对象判断。
  3. 当前 head 与最新 dev 仍有冲突。本地 git merge-tree origin/dev HEAD 报出 Cargo.tomlCargo.lock content conflict。按项目约定,后续解决非 lock 冲突后需要用 Cargo 重新生成 Cargo.lock,不要手工拼 lockfile。
  4. 当前 GitHub head 仍没有 reported checks:gh pr checks 1379 --repo rcore-os/tgoskits --watch=false 返回 no checks reported on the 'goal/cgroup-next-build' branch。PR 正文列出的 cargo test -p ax-cgroupcargo xtask test、Starry build/QEMU 结果需要在 rebase 后重新跑在 current head 上。
  5. 这个 PR 继续新增/修改 Starry grouped system 测试(cgroup-cpu / cgroup-pids),转 Ready for review 前还需要提供 runner 实际发现并执行这些用例的 current-head 证据,例如 cargo xtask starry test qemu --arch x86_64 -c qemu-smp1/cgroup-pidsqemu-smp1/cgroup-cpu,或等价的 grouped system 运行结果。

建议仍是:等 #1234 合入或先把本 PR rebase 到 #1234 合入后的 dev,把 diff 收窄为 L4 增量;解决 Cargo.toml 冲突并重新生成 lockfile;补齐 current-head CI/本地验证后再转 Ready for review。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants