feat(ax-cgroup): cgroup v2 L4 resource control + manager interfaces#1379
feat(ax-cgroup): cgroup v2 L4 resource control + manager interfaces#1379SongShiQ wants to merge 14 commits into
Conversation
将 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
left a comment
There was a problem hiding this comment.
这个 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 状态,原因如下:
- PR 仍是 Draft,且正文明确说明依赖 #1234,等待 #1234 进入
dev后再转正式 PR。现在提交列表也包含feat(ax-cgroup): extract cgroup v2 subsystem into standalone crate这一基线提交,说明当前 diff 仍混有 #1234 的基线内容,不能独立判断为可合入的 L4 增量。 - 当前
mergeStateStatus=DIRTY,与最新dev有冲突。我用git merge-tree origin/dev HEAD检查,至少Cargo.toml存在 content conflict。合并前需要先在 #1234 之后 rebase/merge 最新dev,并重新生成Cargo.lock。 - 当前分支没有任何 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-cgroup、cargo xtask test、cargo xtask starry build --arch aarch64、cargo xtask starry qemu --arch aarch64等验证,但当前 GitHub head 没有 CI 证据;在 rebase 后需要重新跑并保留 current-head 证据。 - 这个 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
left a comment
There was a problem hiding this comment.
本次复审基于当前 head fc80b86。
新增的 cgroup.events / cgroup.stat 挂载与 populated 翻转通知方向本身看起来是对 systemd 探测链路的合理补齐;当前 diff hygiene 也比上一轮干净,本地 git diff --check origin/dev...HEAD 未再发现问题,且没有看到未解决的当前 review thread。
不过这个 PR 仍不能进入可合并状态,阻塞条件和上一轮一致,并且当前 head 仍可复现:
- PR 仍是 Draft,正文也继续说明它依赖 #1234。当前 #1234 仍是 open,
reviewDecision=CHANGES_REQUESTED,mergeStateStatus=DIRTY,还没有合入dev。 - 当前分支仍包含 #1234 的基线提交(例如
feat(ax-cgroup): extract cgroup v2 subsystem into standalone crate、implement unified controller framework...),因此 diff 还不是纯 L4/resource-control 增量,不能作为独立合入对象判断。 - 当前 head 与最新
dev仍有冲突。本地git merge-tree origin/dev HEAD报出Cargo.toml和Cargo.lockcontent conflict。按项目约定,后续解决非 lock 冲突后需要用 Cargo 重新生成Cargo.lock,不要手工拼 lockfile。 - 当前 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-cgroup、cargo xtask test、Starry build/QEMU 结果需要在 rebase 后重新跑在 current head 上。 - 这个 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-pids、qemu-smp1/cgroup-cpu,或等价的 groupedsystem运行结果。
建议仍是:等 #1234 合入或先把本 PR rebase 到 #1234 合入后的 dev,把 diff 收窄为 L4 增量;解决 Cargo.toml 冲突并重新生成 lockfile;补齐 current-head CI/本地验证后再转 Ready for review。
概述
本 PR 在 cgroup v2 统一框架(Controller 抽象 + ControllerRegistry 自动注册)之上,将 cgroup v2 子系统补齐到 L4 资源管控级别,并补全 systemd/docker 所需的管理器探测接口。
与 #1234 的关系
pr/cgroup-modularize)负责 将 cgroup v2 抽取为独立 crate / 模块化解耦,正在审核中。base=dev独立提交,diff 中约 3 个继承自基线的提交属于框架本身;本阶段新增成果集中在components/ax-cgroup/与 StarryOS cgroup 接线。本阶段成果(11 commits)
cargo xtask test白名单sched_prio_to_weight表,weight 100↔nice 0)pids.events计数 + cgroup 委托写权限门控(can_delegate_write)throttled任务态(默认关闭,零行为变化)/proc/<pid>/cgroup真实路径、memory.stat(控制器属性)cgroup.events/cgroup.stat文件挂载 + inotifycgroup.events(populated)、cgroup.stat(nr_descendants) 真正挂载为 cgroupfs 只读文件;cgroup 内最后一个进程退出 / 迁出导致populated翻转 0/1 时,对cgroup.events发 inotifyIN_MODIFY,供 systemd 在每个 unit 的 cgroup 上挂 watch、据此判定 unit 死亡(无需轮询)cgroup.eventspopulated + inotify(本次重点补全)cgroup v2 下 systemd 适配依赖一条边沿通知链路:systemd 在每个 service 的 cgroup 上挂 inotify watch,当最后一个进程退出、
cgroup.events的populated翻成 0 时收到IN_MODIFY,才知道服务停了。本次将这条链路补全:cgroup.controllers/procs/subtree_control/type,cgroup.events/cgroup.stat的文本生成函数虽已存在却未挂载为 VFS 文件。本次在pseudofs/cgroup.rs的CgroupFileKind接线Events/Stat(只读 0o444),ls可见、cat可读,populated递归判定整棵子树是否有进程。#![no_std]OS 无关组件,不直接调内核 inotify。新增CgroupProvider::notify_populated_changed(path)回调(默认空实现,向后兼容);ax-cgroup 在exit_process/migrate_process收口点快照前后populated状态,仅在翻转时回调;内核侧实现把它映射到挂载点下cgroup.events文件并发IN_MODIFY(/cgroup与/sys/fs/cgroup双挂载点均覆盖)。populated1→0 时,回调按预期对每个翻转的 cgroup 触发(用 MockProvider 记录通知路径,不依赖真实 inotify)。验证
cargo test -p ax-cgroupcargo fmt -p ax-cgroup --check、cargo clippy -p ax-cgroup --all-features --tests -- -D warningscargo check -p ax-task+cargo clippy -p ax-task -- -D warnings通过(tick hook / throttled 默认关闭)cargo xtask test(含 ax-cgroup)cargo xtask starry build --arch aarch64(含 events/stat 文件挂载 + inotify 回调接线)cargo xtask starry qemu --arch aarch64,正常启动、cgroup 子系统初始化成功、tick hook 不破坏调度上游改动(最小侵入)
ax-task 仅新增默认关闭的能力,不改变 ArceOS/Axvisor 现有路径行为:
Task.throttled: AtomicBool(默认 false = 不影响现有调度)已知边界(诚实标注,待真机/QEMU 深度验证)
cgroup.events的 inotify 端到端:文件挂载 +populated计算 + 翻转回调 + 内核侧IN_MODIFY发射均已就位并有 host 单测;「真实 systemd 在伪文件上挂 watch 收到IN_MODIFY」这一端到端链路待真机/QEMU 验证。pick_next主动 skip throttled 任务留作后续验证(避免半成品 skip 引入 busy-spin)。当前状态与下一步
dev;goal/cgroup-next-buildrebase 到最新dev,diff 收窄到纯本阶段增量;cgroup.eventsinotify 真机端到端验证作为后续工作线推进。