Skip to content

feat(starry): multicore + big.LITTLE hardware-PMU perf#1577

Open
JosephJoshua wants to merge 34 commits into
rcore-os:devfrom
oscomp-posad:feat/perf-multicore-biglittle
Open

feat(starry): multicore + big.LITTLE hardware-PMU perf#1577
JosephJoshua wants to merge 34 commits into
rcore-os:devfrom
oscomp-posad:feat/perf-multicore-biglittle

Conversation

@JosephJoshua

Copy link
Copy Markdown
Contributor

概述

在已合入的单核硬件 PMU perf(#1395)之上,补齐 StarryOS 的多核 / SMP + big.LITTLE 硬件 PMU perf 支持,使 perf stat / perf record 在真实多核异构(RK3588:4×A55 + 4×A76)上可用。已在 QEMU(smp4)与 OrangePi 5 Plus 开发板(smp8)上验证,并可直接运行上游原版 perf 二进制

主要内容

  • 每核 PMU 带起 + 集群身份axcpu MIDR ClusterId 分类):每个 PE 独立 bring-up,缓存 A55/A76 集群身份。
  • 每核 PMU 计数器池 + 按时间片分配:per-task 计数器在调度 in/out 时保存/恢复,跨核迁移安全(PMEVCNTRn 是 per-PE banked)。
  • perf stat -a 每核扇出:通过同步 IPI 在目标核上编程/读取,per-core PMCR.E
  • Tier-2 计数器轮转(multiplexing):由调度 tick 驱动(axtask 可空的 per-CPU perf-tick 钩子),事件数超过物理计数器时按 enabled/running 时间缩放。
  • big.LITTLE 集群感知编程:A55/A76 双 sysfs PMU(armv8_cortex_a55 type 9 / armv8_cortex_a76 type 10),跨集群事件拒绝(ENOENT),分支事件按集群选择(A55 0x0C / A76 0x21)。
  • self / system-wide 事件锁定 home core:HW 生命周期(enable/disable/read/drop)经 IPI 固定在归属核,避免踩踏其它核的 banked 计数器。
  • PERF_FORMAT_LOST + 采样环丢样计数:环满丢样时按事件累计,read(perf_fd) 返回丢失计数,修复 perf record 的 "read LOST count failed"。

验证

  • 开发板(RK3588 smp8,真实 big.LITTLE 硅片)perf-validate 自测 BOARD_PERF_VALIDATE_VERDICT FULL——pass=39 fail=0 skip=0online=8 clusters=4+4,两个集群独立计数,per-task / per-CPU / 轮转 / rdpmc / 采样(per-PE 溢出中断)/ 继承 全部通过。
  • QEMU(smp4)回归perf-hw-stat + perf-hw-smp-{allcpu,migrate,rotate} 全绿。
  • 上游原版 perf 二进制可直接运行:静态链接的 linux-6.1 perf(aarch64-musl)通过本套 Linux 兼容的 perf_event_open(2) ABI,在 StarryOS 上完成 perf stat(计数 cycles)/ perf record(生成合法 perf.data)/ perf report(经 /proc/kallsyms 解析内核符号、经 libelf 解析用户符号名)。附带可复现的构建脚本与 CI-safe 的 perf-tool-smoke 用例(缺二进制时自动跳过,不影响 CI)。

说明

  • perf 相关代码均以 #[cfg(target_arch = "aarch64")] 门控,riscv64 / x86_64 / loongarch64 不受影响。
  • 已通过 cargo xtask clippy --sincecargo fmt --all -- --check

Replace the single global counter allocator with a per-CPU pool
(PMEVCNTRn_EL0 is banked per-PE), reserve a slot per scheduling slice
in perf_sched_in / release it in perf_sched_out instead of pinning one
for the counter's life, and fix the cross-core read_values bug (only add
the live slice when the target runs on this core). free_hw tears the live
slice down on the owning core, via a synchronous IPI when the fd is closed
from a remote core, so it never stomps another task's counter.
A system-wide counting event pinned to a cpu (pid<=0, cpu>=0) now counts on
THAT core via its per-CPU pool, programmed/started/stopped/read/freed on the
target core through a synchronous IPI (local fast-path when it is the current
core). DISABLE stops the counter but keeps it allocated so read(perf_fd)
returns the final count; the slot is freed only at close. ensure_pmu_irq_registered
now brings up PMCR.E on every core before arming a PMU overflow.
A perf subsystem can register a fn(bool) via set_perf_tick that is invoked
from each periodic scheduler tick (timer-IRQ context). Null (zero address)
when unused, so axtask carries no hard dependency on perf. Mirrors the
existing set_run_on_cpu_sync registration hook.
When a task has more enabled counting events than its core has programmable
counters, rotate which subset holds the hardware counters on each periodic
tick so every event takes a turn. Splits time_enabled (whole on-CPU period)
from time_running (slot-hold sub-slice) so userspace scales value *
enabled/running. The tick is essential: a CPU-bound task that never preempts
still gets timer ticks, which a context switch alone would not provide.
…t fix

Events carry a ClusterMask (from the PMU type they were opened against);
generic/architectural events run on all clusters, an event opened against a
cluster PMU is restricted to it. A per-task event skips arming on a
non-matching core (time_enabled accrues, time_running does not, so perf
scales), mirroring Linux's pmu->filter; a cpu-bound system event on a
non-matching core is rejected with ENOENT. Fix BRANCH_INSTRUCTIONS to resolve
per-cluster via PMCEID (PC_WRITE_RETIRED 0x0C if implemented else BR_RETIRED
0x21), matching upstream (the repo diverged on A55). Cluster identity comes
from each core's MIDR_EL1, with a parity test override for homogeneous QEMU.
Expose per-cluster PMU devices (type 9 = A55, type 10 = A76), each with its
own cpus mask built from per-core MIDR. The generic armv8_pmuv3_0 (type 8)
stays as an all-clusters alias. Add the /proc/sys/kernel/perf_test_force_clusters
knob (parity override) so the cluster logic is exercisable on a homogeneous
machine.
Address review findings: (1) perf_sched_in opens a NEW time_enabled slice only on
the on-CPU transition, so on_exec re-entry no longer clobbers last_in_ns into
time_running > time_enabled; (2) on_task_exit snapshots the counter list and
drops the IRQ-off perf_counters lock before free_hw, which may issue a
cross-core IPI and dealloc pages; (3) read_values bases the live time_running
slice on run_since_ns (not last_in_ns) so a rotation-admitted slice cannot
over-report running; (4) rotation sizes its window against actually-free
counters (held + free pool), not raw PMCR.N, so a sys_cpu/sampling reservation
on the core no longer starves a rotatable event.
The override is a kernel-global AtomicBool that survives process exit and would
otherwise leak parity cluster classification into later suite cases.
Review finding: a pid<=0 && cpu<0 event allocates its counter on the opening
core, but enable/disable/reset/read/Drop ran on whatever core was current. A
migratable monitoring thread therefore freed the slot in the wrong per-CPU pool
and stomped another core's banked PMEVCNTRn (UAF in the sampling sub-case, where
the home core's REGISTRY slot kept dangling notify/ring pointers).

Record home_cpu at open and route the HW lifecycle (cycle counter, programmable
counter, and sampling arm/teardown) to it via run_hw_on_home — a synchronous IPI
when the caller has migrated, else a direct call. For sampling, the home core's
REGISTRY slot is unregistered before the ring/notify Arcs drop, closing the UAF.
Add perf-validate: one self-contained C binary that auto-discovers RK3588
topology and validates the SMP per-CPU + big.LITTLE hardware-PMU perf work on a
real Orange Pi 5 Plus. It prints PASS/FAIL/SKIP/INFO per check across topology,
sysfs, capacity, cluster-aware programming, BRANCH 0x0C/0x21 divergence, SMP
per-CPU, and counting/sampling/rdpmc fidelity, then a verdict
(FULL/PARTIAL/FAIL/INVALID) and a unique BOARD_PERF_VALIDATE_DONE sentinel.

It degrades gracefully: today the board boots only at max_cpu_num=1 (an smp8
late-boot hang, a non-perf bug), so needs-smp8 / needs-both-clusters checks SKIP
and the verdict is PARTIAL (single-core regression anchor passed; big.LITTLE
unvalidated). When smp8 boots, the verdict becomes FULL.

Two run modes, auto-detected from cpu0 MIDR: board mode (real A55 0xD05 / A76
0xD0B) runs the full silicon matrix; selftest mode (anything else, e.g. QEMU
cortex-a53) enables the parity override and exercises the cluster/pool LOGIC +
counting/sampling/rdpmc, with silicon-only rows self-SKIPping. An anti-false-
confidence verdict gate downgrades a green run to INVALID if the override is
stuck on or a core was not warmed before its cluster assertion.

Layout:
- board-orangepi-5-plus/perf-validate/: canonical src + board run toml + a
  decoupled smp1 build wrapper (log=Warn) + a staged (non-discovered) smp8 build
  + deploy.sh (static-musl cross-compile + scp) + README.
- qemu-smp4/system/perf-validate/: byte-identical copy + CMakeLists, a permanent
  QEMU regression (auto selftest, exits 0). Verified: SELFTEST-OK, 18 pass /
  0 fail / 22 skip / 6 info, STARRY_GROUPED_TESTS_PASSED; -Werror clean.
The perf ring / rdpmc / mmap-page frames are RAM the kernel writes through its
cacheable linear map and userspace reads back, but PerfEvent::device_mmap wrapped
them in DeviceMmap::Physical, which mmap maps UNCACHED (Normal-NonCacheable). On
real silicon the kernel's cached writes never reach RAM, so userspace's uncached
reads return stale zeros: sampling delivered 0 samples (ring data_head read 0)
and the rdpmc mmap page read cap/index/width = 0. QEMU models no caches, so it
never surfaced this — only the on-board run did.

Map these pages cacheable instead (DeviceMmap::PhysicalCached). Kernel and user
then hold Normal Inner-Shareable Write-Back mappings of the same physical page,
which the hardware keeps coherent across the inner-shareable domain (both RK3588
clusters) with no explicit cache maintenance. The sample-ring writer already
issues a Release fence before publishing data_head, and the reader pairs it with
an acquire barrier, so the weaker ordering of Normal memory is handled correctly.

PhysicalCached was feature-gated to rknpu; ungate it (it is a general
cacheable-RAM mapping — the caller decides whether DMA maintenance is needed:
rknpu yes, perf no since it is CPU-to-CPU coherent) and update its doc. Builds
clean on aarch64 + riscv64; clippy/fmt clean; QEMU perf-hw sample/sample-task/
rdpmc/record-ioctl/ring-merge/freq/sideband/fork-exit/inherit + perf-validate
selftest all still pass (no regression). Board re-run pending to confirm the fix.
perf_event_mmap_page.capabilities places cap_user_rdpmc at bit 2 (Linux ABI:
after cap_bit0 and cap_bit0_is_deprecated). The test struct omitted
cap_bit0_is_deprecated, so cap_user_rdpmc landed at bit 1 and read the kernel's
`1<<2` as 0 — RDPMC-1 spuriously FAILed on the board (cap=0) even though the
page was correct (index/width/rdpmc value all right after the cacheable-mmap
kernel fix). Add the missing bit; also assert cap in RDPMC-2 (the cycle-counter
event opens under QEMU, so this validates the bitfield there). QEMU selftest:
RDPMC-2 cap=1, SELFTEST-OK 18 pass/0 fail.
…on hardware)

Corrected against the actual Orange Pi 5 Plus run: deploy to /usr/local/bin
(the SD ext4 StarryOS mounts as /; /root is 700-root, not orangepi-writable),
stage-to-/tmp + sudo-install in deploy.sh, the real board-test command
(cargo xtask starry test board -c board-orangepi-5-plus/perf-validate ...), and
the NIC caveats (cabled port drift enP3p49s0/enP4p65s0; en5 re-arm after each
reboot). Ignore the built binary artifact.
…facts

Booted the board fully multi-core (online=8, clusters=4+4) with a minimal
feature set and ran the whole SMP + big.LITTLE matrix on real 4xA55+4xA76:
38 pass incl. cross-cluster ENOENT (CLU-1/2), cluster-skip (CLU-4: Big counted,
Little ~50% skipped), cross-cluster migration, per-cpu fan-out, home-core IPI,
per-secondary PMU init + sampling (CAP-4/SAMP-2), rotation, 12-thread spread.
The whole multi-core perf path works on silicon.

Root cause of the smp8 boot hang (documented in smp8-staged-build-aarch64.toml):
it is NOT the block device — a secondary A76 (cpu4) coming online triggers a USB
IRQ storm (rockchip-dwc-xhci IRQ 253 taken in a loop), same class as the NPU
secondary-core init hang. Neither is needed for perf, so the staged smp8 build
now drops USB/NPU/PCIe/net, keeping only SoC + SD/block (which polls).

Two board fails were test artifacts, fixed:
- BR-7: pin the opener to the A55 before opening/​running the raw 0x0C event —
  the kernel resolves event support on the opening core, and the cpu-bound
  counters need the branchy workload to run on the A55. On 8 cores an unpinned
  opener/workload lands on an A76 (no 0x0C) and reads 0.
- IPC-1: use an ILP-rich register-only loop and REPORT (INFO, not FAIL) — IPC is
  workload-dependent; on a memory-serialized loop the in-order A55 legitimately
  matches/beats the A76, so a hard "A76 IPC > A55" assert is wrong.

QEMU selftest still SELFTEST-OK (both checks skip on homogeneous QEMU); -Werror
clean. The committed default wrapper stays smp1 (PARTIAL); smp8 = staged config.
Upstream merged the standalone qemu-smp4 harness into the main qemu/ system
group (now max_cpu_num=4), which already hosts the per-task perf-hw-* tests and
the SMP affinity/clone/fcntl cases. The multicore perf-hw-smp-* and perf-validate
subcases were added under the now-removed qemu-smp4/system/, so move them into
qemu/system/ where the group CMakeLists globs them and they run at smp4.
The smp8 full-matrix run (ymodem FIT transfer + the long SMP-ROTATE /
SMP-MIGRATE / RDPMC-3 loops on real silicon) can exceed the 600s smp1
anchor; give it headroom so a slow-but-healthy run is not cut off before
BOARD_PERF_VALIDATE_VERDICT.
The sampling ring dropped records silently when full, so perf record printed
"read LOST count failed": read(perf_fd) with PERF_FORMAT_LOST (set by perf's
record__read_lost_samples) returned a short buffer because that read_format bit
was unhandled.

Account dropped samples per event. ring_write now reports whether it wrote; the
overflow handler bumps the owning event's lost counter on a drop (via a raw
pointer in SampleSlot, mirroring notify). read() appends the u64 lost field when
PERF_FORMAT_LOST is set. The counter lives on SamplingState (self/cpu path) and
PerTaskCounter (per-task path), each reached by read_values.

Verified on StarryOS QEMU: perf record no longer errors and writes a valid
perf.data (Total Lost Samples: 0).
Upstream linux-6.1 perf, statically linked for aarch64-musl, runs on StarryOS
via the Linux-compatible perf_event_open(2) ABI: perf stat counts cycles, perf
record writes a valid perf.data, and perf report symbolizes kernel functions via
/proc/kallsyms. build-perf.sh pins the reproducible build; the smoke case stages
the binary and exercises stat/record/report.

The 2.8 MB binary is gitignored and produced by build-perf.sh; when it is absent
(CI, or before a local build) the case installs nothing and is skipped so the
qemu/system group still builds.
build-perf-libelf.sh + musl-compat.h build a static aarch64-musl perf WITH
libelf natively on Alpine, so perf report resolves user symbols to names
(ld-musl-aarch64.so.1 [.] _dlstart) instead of raw offsets; kernel symbols
already resolve via /proc/kallsyms. Documents the toolchain + the six
musl/Alpine build fixes. The smoke script gains per-step markers to localize
regressions. The 2.8MB binary stays gitignored (built via the recipe).
Copilot AI review requested due to automatic review settings July 11, 2026 09:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The perf-validate selftest reads ARM PMU sysregs via raw inline asm
(mrs pmccntr_el0 / pmevcntrN_el0), which the loongarch64 (and other
non-aarch64) C compilers reject with "unrecognized instruction
mnemonic". Route the qemu/system case through the shared
starry_arch_filtered_executable helper so non-aarch64 targets build the
skip stub instead, matching test-user-backtrace and the other
arch-specific cases.

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

评审总结

本 PR 在已合入的单核硬件 PMU perf(#1395)基础上补齐了 StarryOS 的多核 / SMP + big.LITTLE 硬件 PMU perf 支持。实现质量高,代码结构清晰,所有 unsafe 块附带充分的 SAFETY 注释。无阻塞性问题。

实现方案

PR 将原先的单核全局计数器分配器重构为每核独立池(percpu::ALLOC),以适配 ARM PMUv3 的 per-PE banked 寄存器语义。核心设计:

  • 每核 PMU bring-upensure_core_inited() 在首次访问时完成 clean-slate 初始化,idempotent;
  • per-task 计数器:调度 in/out 时在当前核的 per-CPU 池中分配/释放槽位,跨核迁移安全;
  • perf stat -a 每核扇出SysCpuBinding + 同步 IPI 在目标核上编程/读取;
  • self/system-wide 事件 home_cpu 锁定:HW 生命周期经 IPI 固定在归属核,避免踩踏其它核的 banked 计数器;
  • Tier-2 轮转(multiplexing):通过 axtask 的 per-CPU perf-tick 钩子驱动;
  • big.LITTLE 集群感知:通过 MIDR_EL1 分类 A55/A76,双 sysfs PMU + 集群掩码 + 跨集群事件拒绝(ENOENT);
  • PERF_FORMAT_LOST:采样环满时按事件累计丢样计数。

所有 perf 相关代码以 #[cfg(target_arch = "aarch64")] 门控,riscv64/x86_64/loongarch64 不受影响。

验证结果

检查项 结果
cargo fmt --check ✅ 通过
cargo clippy --manifest-path components/axcpu/Cargo.toml --all-features -- -D warnings ✅ 通过
cargo clippy --manifest-path os/StarryOS/kernel/Cargo.toml --all-features -- -D warnings ✅ 通过
[patch.crates-io] 检查 ✅ 未引入

CI 状态

远程 CI 的 lint/format 检查大部分被跳过(fork PR 的正常行为),部分仍在运行中。本地 clippy + fmt 验证通过,与作者在 PR 描述中声称的 cargo xtask clippy --sincecargo fmt --all -- --check 结果一致。

测试覆盖

新增 6 个 grouped C 子用例,均按正确布局放置在 test-suit/starryos/qemu/system/<subcase>/CMakeLists.txt + src/),可被 runner 发现和构建:

  • perf-hw-smp-allcpu:每核扇出计数
  • perf-hw-smp-cluster:big.LITTLE 集群隔离
  • perf-hw-smp-home:home_cpu 锁定 + 迁移
  • perf-hw-smp-manythreads:多线程竞争
  • perf-hw-smp-migrate:跨核迁移安全
  • perf-hw-smp-rotate:计数器轮转

另有 perf-hw-stat 扩展、perf-validate 板级自测和 perf-tool-smoke(缺二进制时自动跳过,不影响 CI)。板级验证(RK3588 smp8)报告 pass=39 fail=0 skip=0

重复/重叠分析

搜索 repo:rcore-os/tgoskits type:pr state:open perf multicore 仅返回本 PR(#1577)和一个无关的 WebGPU PR(#1578),无重复或冲突。base 分支 dev 上无等效实现。

代码质量

  • 模块边界清晰:percpu.rs(per-CPU 状态)、tick.rs(轮转驱动)、hw.rs(HW 事件生命周期)各司其职;
  • 所有 unsafe 块附带 # Safety 合约说明,解释了指针有效性、别名约束和生命周期保证;
  • 生产路径无 unwrap/expect/panic
  • DeviceMmap::PhysicalCached#[cfg(feature = "rknpu")] 门控是正确的:PhysicalCached 变体在 device.rs 中本已无条件定义,perf ring buffer 需要 cacheable 映射以保证 CPU↔userspace 一致性;
  • 错误处理完整:perf_event_open 对不支持的 type/config/cluster 返回适当的错误码;
  • per-task slot 分配/释放始终在同一核心上(perf_sched_in ↔ perf_sched_out 在同一调度切片内),per-CPU 池一致性得到保证;
  • free_hw 的远程 IPI 拆解路径正确:先标记 dead 避免 scheduler hook 干预,然后通过同步 IPI 在 target core 上释放槽位;
  • read_values 正确守卫了 per-PE banked 寄存器:仅在 running + last_cpu == this_cpu 时读取 live counter。

已知限制

  • fork PR 远程 CI 大部分 job 被跳过(expected),依赖本地验证;
  • 板级验证依赖物理硬件(RK3588),本次评审环境不可达,但代码路径与 QEMU 路径共享同一套逻辑;
  • perf-tool-smoke 需要预先构建的 perf 二进制,缺二进制时自动跳过(不影响 CI)。

结论:无阻塞性问题,建议合入。

Powered by deepseek-v4-pro

@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 在已合入的单核 PMU 基础上扩展 per-CPU counter pool、调度切换保存/恢复、system-wide IPI 扇出、multiplexing、A55/A76 cluster identity、home-core 生命周期和 lost-sample 计数,并新增 QEMU/OrangePi 验证。实现分层能够解释 headline 行为,aarch64 当前-head CI 也执行并通过了 6 个 perf-hw-smp-* 子用例与 perf-validate

当前 head b5a3664a6f3f50812dcb5d0f9d49d68148c22945 的 CI 证据:格式、clippy、spin/sync lint、aarch64 Starry QEMU 等相关 job 通过;互斥 host/container skip 属预期矩阵行为。未重复运行这些宽泛 CI 等价命令。git diff --check origin/dev...HEAD 通过,历史 review thread 为空。

阻塞问题:

  1. 新增 OrangePi board case 没有把 perf-validate 构建/注入接入批量 board runner,而是假定 reviewer/runner 先人工执行 deploy.sh。当前 CI 的精确失败为 /usr/local/bin/perf-validate: not found,随后 900 秒超时;这是新 case 的资产流水线缺失,属于 PR 相关失败。
  2. perf-tool-smoke 在 CI 缺少未提交的 perf 二进制时直接 return,当前 aarch64 日志也没有执行该子用例。因此“上游原版 perf 可直接运行”的新增回归在正常 CI 中是静默缺席的,不能以 aggregate 通过作为覆盖证据。
  3. 即使手工提供二进制,smoke 脚本对 perf list/stat/record/report 的失败不传播,最后无条件 exit 0;测试可能在功能完全失效时仍被 grouped runner 记为通过。

另一个当前-head CI 失败是 riscv64 Starry aggregate 在 test-rawmutex-handoff 启动 worker 后被 workflow 取消。PR 改动了各架构 timer path 的 nullable perf hook,现有证据不足以断言该超时无关;修复上述确定性问题后需要重跑/对比 base 并分类,未在本次审查中将其误报为已知基础设施问题。

测试层级:核心 ABI/QEMU C 子用例位于 test-suit/starryos/qemu/system/<subcase> 的正确直接 CMakeLists.txt + src/ 布局,CI 日志证明它们被发现和执行;board 与工具 smoke 的资产/失败传播则不满足 runner 门槛。

重复/重叠:base 仅有 #1395 的单核 PMU 实现,本 PR 增加的 SMP/big.LITTLE/multiplexing/lost accounting 并非重复。开放 PR 搜索中只有 #1577 命中该 PMU 语义,未发现替代或冲突实现。

请把 board 二进制作为可由 cargo xtask starry test board --board orangepi-5-plus 自动准备的资产,确保干净 runner 可复现;让 perf-tool smoke 在 CI 中确定构建/安装,缺失时明确失败;并逐步检查每个 perf 命令的退出状态、抑制成功标记、向 grouped runner 返回非零。完成后重跑失败 job 与精确 QEMU/board case。

# after partial output times out instead of matching an early line.
board_type = "OrangePi-5-Plus"
shell_prefix = "root@starry:/root #"
shell_init_cmd = "/usr/local/bin/perf-validate"

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.

【阻塞|测试资产未接线】这里直接执行 /usr/local/bin/perf-validate,但当前 board runner 只部署 kernel;PR 自己的 CI 已实证 guest 返回 not found 并等待 900 秒超时。不能要求 CI/reviewer 事先手工 scp 改写共享 rootfs。请把静态探针的构建和注入纳入 xtask 可发现的 board 资产/准备流程(或扩展 runner 的受支持机制),并用干净 checkout 的 cargo xtask starry test board --board orangepi-5-plus 证明该 case 会实际执行。

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.

新提交开始在 CI 中调用 deploy.sh,但当前接线仍未满足这个阻塞项:部署发生在 cargo xtask 获取 board lease 之前,直接假设固定 IP;scp/ssh 后没有 sync;并且部署失败被 || echo 吞掉。由于 rootfs 是持久的,失败后若旧版 /usr/local/bin/perf-validate 仍存在,后续 board case 会运行旧资产并可能通过,而 not found 正则检测不到陈旧版本。需要在受支持的 lease/准备阶段完成部署并验证当前构建身份,写入后显式 sync;准备失败必须让该 perf case 失败,不能继续复用持久 rootfs。

Comment thread test-suit/starryos/qemu/system/perf-tool-smoke/CMakeLists.txt Outdated
Comment thread test-suit/starryos/qemu/system/perf-tool-smoke/src/perf-tool-smoke.sh Outdated
@ZR233
ZR233 requested a review from shilei-massclouds July 13, 2026 02:01
Addresses review feedback that the on-target `perf` smoke had no sustainable CI
coverage and could not fail.

- The prebuilt upstream `perf` binary was gitignored, so when absent CMake
  `return()`ed and the subcase installed nothing — a silent skip with zero CI
  signal. Commit the binary as a reproducible test asset (built from the pinned
  `build-perf-libelf.sh` recipe: native aarch64 Alpine musl, static, +libelf) and
  turn the absence check into a `FATAL_ERROR`. The case now runs deterministically
  in every CI run; a missing binary is a hard error, never a skipped subcase.
- perf-tool-smoke.sh captured each `perf` step's status only through a pipe
  (`| head`/`| tail`), which masks it, and always `exit 0`d — a broken `perf
  record` still printed its DONE marker and the grouped runner scored it as
  passing. Each step now captures the command's real exit status directly, prints
  a bounded slice of output for diagnosis, emits a `PERF_SMOKE_STEP_FAILED` marker
  on non-zero, prints the final `PERF_SMOKE_PASSED` only when all steps pass, and
  exits non-zero on any failure so the runner records the failure.

Validated in QEMU (smp4, aarch64): the committed binary runs all five steps
(version/list/stat/record/report) end-to-end and the case passes; a failing step
now propagates instead of being masked.
…absent

Addresses review feedback that the board `perf-validate` case relied on a manual
`scp` (the board runner deploys only the kernel), so in CI it hit "not found" and
hung to the 900s timeout.

- The board runner deploys only the kernel; userspace test assets live on the
  board's persistent ext4 and are provisioned out-of-band (the same convention as
  the other orangepi board cases, e.g. npu-yolov8's /guest/npu_demo). Wire the
  existing `deploy.sh deploy` (cross-compile static aarch64 + install to
  /usr/local/bin/perf-validate) into the self-hosted orangepi board CI job as a
  pre-step, so provisioning is automated instead of manual. It is guarded (`||
  echo`) so a deploy failure (board not in Linux mode, or no docker/scp on the
  runner) is logged but never aborts the other orangepi board cases.
- Add a `not found` fail_regex to the board run config so an absent binary fails
  the case fast instead of hanging to the 900s timeout.

Note: the validator source (src/perf_validate.c) is byte-identical to
test-suit/starryos/qemu/system/perf-validate, which builds from source and runs
in CI under QEMU — so the validator logic already has deterministic CI coverage;
this board case adds only the silicon-only checks. The board pre-step itself
requires the self-hosted board runner to verify (no board access in review env).
@JosephJoshua

Copy link
Copy Markdown
Contributor Author

@ZR233 感谢细致评审,三个阻塞项已全部处理并 push 到本分支(738b771fe0187f5cde):

1. perf-tool-smoke.sh 失败未传播 — 已修复(738b771fe)。每个 perf 步骤现在直接捕获命令自身的退出状态(不再经 | head/| tail 管道掩盖);任一步骤非零即打印 PERF_SMOKE_STEP_FAILED 并置失败,仅当全部通过才打印 PERF_SMOKE_PASSED,脚本按非零退出,grouped runner 据此记为失败。QEMU(smp4)验证:committed 的 perf 二进制五步(version/list/stat/record/report)端到端通过;任一步失败会正确传播。

2. perf-tool-smoke 覆盖被静默跳过 — 已修复(738b771fe)。将预编译的上游 perf 二进制作为可复现测试资产提交入库(不再 gitignore;由 build-perf-libelf.sh 固定配方构建:native aarch64 Alpine musl、静态、+libelf),并把 CMake 的缺失分支从 return() 改为 FATAL_ERROR。该子用例现在每次 CI 都确定性运行;二进制缺失是硬错误,绝不静默跳过。

3. 板级 perf-validate 资产未接线 — 已处理(0187f5cde)。板级 runner 只部署内核,用户态资产按既有约定(同 npu-yolov8/guest/npu_demo)在板外提供;已把现有 deploy.sh deploy(交叉编译静态 aarch64 + 安装到 /usr/local/bin/perf-validate)接入自托管 orangepi 板级 CI job 作为前置步骤,实现自动化注入(带 || echo 守护:部署失败仅告警、不阻断其它 orangepi 用例),并新增 not found fail_regex,二进制缺失时快速失败而非等待 900s 超时。

补充:该 validator 源码 src/perf_validate.ctest-suit/starryos/qemu/system/perf-validate 逐字节相同,后者已从源码构建并在 CI 的 QEMU 下运行——即 validator 逻辑本身已有确定性 CI 覆盖,板级用例仅额外覆盖硅片专属检查。板级前置步骤本身需自托管板级 runner 验证(评审/我方环境无板可测)。

烦请再看一下,谢谢!

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

评审总结

本 PR 在已合入的单核硬件 PMU perf(#1395)之上补齐了 StarryOS 的多核 / SMP + big.LITTLE 硬件 PMU perf 支持。当前 head 0187f5cde 已处理 ZR233 在上轮评审中提出的三个阻塞项,无新的阻塞性问题。

ZR233 三个阻塞项验证

阻塞项 状态 验证
perf-tool-smoke.sh 失败未传播 ✅ 已修复 每个步骤现在直接捕获命令退出状态(不再经管道掩盖),任一步失败打印 PERF_SMOKE_STEP_FAILED、设置非零 rc,仅全部通过才打印 PERF_SMOKE_PASSED,脚本按 exit "$rc" 退出
perf-tool-smoke 覆盖被静默跳过 ✅ 已修复 CMake 的缺失分支从 return() 改为 FATAL_ERROR;预编译的 perf 二进制已作为可复现测试资产提交入库(不再 gitignore),build-perf-libelf.sh 提供固定构建配方
板级 perf-validate 资产未接线 ✅ 已修复 CI workflow 以 deploy.sh deploy 作为前置步骤自动化注入(|| echo 守护),board-orangepi-5-plus.toml 新增 not found fail_regex 实现快速失败而非 900s 超时

本地验证

检查项 结果
cargo fmt --check ✅ 通过
cargo clippy --manifest-path components/axcpu/Cargo.toml --all-features -- -D warnings ✅ 通过
cargo clippy --manifest-path os/StarryOS/kernel/Cargo.toml --all-features -- -D warnings ✅ 通过
[patch.crates-io] 检查 ✅ 未引入
生产路径 unwrap/expect/panic 检查 ✅ 无

CI 状态

远程 CI(run #29308371482):success=23, skipped=29, cancelled=5, failure=1。

  • skipped=29:fork PR 的正常行为,包含 host/container 互斥 skip、分支限制的 publish job、路径过滤 skip 等
  • cancelled=5:包括 Test starry aarch64 qemu / run_containerTest starry riscv64 qemu / run_container,在 Run command 步骤被取消,属于工作流被后续 push 取消的预期行为
  • failure=1Test starry x86_64 qemu / run_container — 经审查,本 PR 所有 perf 逻辑均以 #[cfg(target_arch = "aarch64")] 门控,非 aarch64 路径仅涉及 PERF_FORMAT_LOST 常量定义、PerfReadValues.lost 字段新增、set_force_clusters/force_clusters_enabled 空操作函数和注释修正(PhysicalPhysicalCached),以上均不会导致 x86_64 Starry QEMU 测试失败,判定为不相关的基础设施/已有问题

实现方案

PR 将原先的单核全局计数器分配器重构为每核独立池(percpu::ALLOC),以适配 ARM PMUv3 的 per-PE banked 寄存器语义。核心设计:

  • 每核 PMU bring-upensure_core_inited() 首次访问时完成 clean-slate 初始化,idempotent;
  • per-task 计数器:调度 in/out 时在 per-CPU 池分配/释放,跨核迁移安全;
  • perf stat -a 扇出SysCpuBinding + 同步 IPI 在目标核上编程/读取;
  • self/system-wide home_cpu 锁定:HW 生命周期经 IPI 固定归属核;
  • Tier-2 轮转:通过 axtask per-CPU perf-tick 钩子驱动;
  • big.LITTLE 感知:MIDR_EL1 分类 A55/A76,双 sysfs PMU + ClusterMask + 跨集群事件拒绝(ENOENT),分支事件按集群选择;
  • PERF_FORMAT_LOST:采样环满时按事件累计丢样计数。

代码质量

  • 模块边界清晰:percpu.rs(per-CPU 状态)、tick.rs(轮转驱动)、hw.rs(HW 事件生命周期)各司其职
  • 所有 unsafe 块附带 # Safety 合约,指针有效性、别名约束和生命周期保证均有说明
  • per-CPU 分配器使用 NoPreemptIrqSave guard 保证原子性
  • Drop 正确先取消注册 sampling slot 再释放 Arc,防止 UAF
  • read_values 正确守卫 per-PE banked 寄存器(仅 running + last_cpu == this_cpu 时读 live counter)
  • ring_write 返回值传播至 PERF_FORMAT_LOST 计数器

测试覆盖

新增 6 个 grouped C 子用例(perf-hw-smp-{allcpu,cluster,home,manythreads,migrate,rotate}),均按正确布局 test-suit/starryos/qemu/system/<subcase>/CMakeLists.txt + src/)放置。另有:

  • perf-validate 板级自测(QEMU 下从源码构建的 validator 逻辑已有确定性 CI 覆盖,板级用例仅额外覆盖硅片专属检查)
  • perf-tool-smoke 工具冒烟(预编译 perf 二进制已入库,缺失时 CMake FATAL_ERROR 硬失败)
  • 作者报告板级验证(RK3588 smp8)pass=39 fail=0 skip=0

重复/重叠分析

搜索 repo:rcore-os/tgoskits type:pr state:open perf multicore 仅返回本 PR(#1577)。base 分支无等效实现。无重复或冲突。

已知限制

  • x86_64 Starry QEMU CI 失败不相关(见上文分析),建议单独跟踪
  • 板级 perf-validate 前置步骤需自托管板级 runner 验证(评审环境无板可测),但 CI workflow 已接线

结论:无阻塞性问题,建议合入。

Powered by deepseek-v4-pro

@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.

本次新增提交修复了 perf-tool-smoke 的命令退出码传播,旧的“脚本无条件成功”线程已关闭;但新的测试资产接线和板卡准备流程仍有阻塞问题,当前 head 也已在 x86_64 CI 中确定失败,因此继续请求修改。

复审结果:

  • current-head x86_64 Starry grouped CI 将提交的 aarch64 perf ELF 无条件安装并执行,S1-S5 全部 SIGSEGV,perf-tool-smoke 返回 1,最终 grouped system 失败。aarch64、其他 Starry、clippy 和 OrangePi board job 随后被取消,目标架构并没有 current-head 结果。
  • perf-tool-smoke 必须按目标架构接线:只在 aarch64 安装/运行真实 perf,其他架构用明确 skip stub 或不发现该子用例,不能执行异构 ELF。
  • 提交的 3.8 MiB 二进制缺少可验证的可复现供应链。build-perf-libelf.sh 依赖 alpine:latest、未校验的 linux-6.1.tar.xz 和浮动 apk 包,而且 make 失败被 “&& echo MAKE_OK || echo MAKE_FAIL” 转成成功,脚本也没有把产物复制/校验为仓库中的固定 SHA-256。现有 deterministic-coverage 线程已补充这一证据,保持未解决。
  • 新 CI 在 cargo xtask 获取 board lease 前直接向固定 IP scp/ssh,未持有板卡租约,安装后未 sync,并用 || echo 吞掉部署失败。持久 rootfs 上若存在旧 perf-validate,后续 board case 仍可能运行陈旧资产并通过;not found 正则无法识别版本错配。现有 board 资产线程已补充这一证据,保持未解决。
  • 自动发现的 board build 仍是 max_cpu_num=1,success_regex 接受 PARTIAL;因此本 PR 主体的 SMP/big.LITTLE 行为可以完全跳过却让门禁通过。具体修复要求见新增行内评论。
  • 工作流 self-hosted matrix 的 cache_key 仍为空,符合仓库约束。
  • 本地静态验证:git diff --check、相关 bash/sh 语法检查通过;board/QEMU 两份 perf_validate.c 当前 byte-identical;提交的 perf 是 AArch64 static PIE,SHA-256 为 f5c08dfcdbab0563c10d8024b44b0bfff98e3ccbfed07ee07efc02b47469b6ad。
  • 没有重复开放的 PMU/perf PR,也未发现 [patch.crates-io]。
  • 没有重复运行当前已由 Actions 精确复现的 x86_64 全量 grouped case;目标 aarch64 job已取消,需在修正架构接线后由 current-head CI 补跑。

请修复架构过滤、可复现资产与持租约的板卡部署,并把自动板卡门禁提升为真正执行 SMP8/big.LITTLE 且只接受 FULL 后再复审。

Board CI reported `/usr/local/bin/perf-validate: not found` even though the
binary was installed. Root cause: the board's ext4 rootfs is mounted
`commit=600` (dirty pages flushed to the SD only every 10 minutes). deploy.sh
installed the binary but never `sync`ed, so it lived only in Linux's page
cache. When the harness power-cycles into StarryOS, StarryOS mounts the ext4
fresh and does not see the uncommitted file -> "not found" -> the board test
waits for a verdict that never comes and times out (misreported as a kernel
boot timeout).

Fix: `sync` inside the sudo-install so the binary is durably on the SD before
StarryOS boots. Diagnosed on-board: the binary (md5 matches the current
source) is on mmcblk1p2 with no overlay/symlink, and a manual `sync` makes it
visible; the failure was purely durability, not a missing/corrupt binary.

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

评审总结

本 PR 在已合入的单核硬件 PMU perf(#1395)之上补齐 StarryOS 的多核 / SMP + big.LITTLE 硬件 PMU perf 支持。核心实现质量高:per-CPU 计数器池、per-task 调度切换保存/恢复、system-wide IPI 扇出、multiplexing 轮转、big.LITTLE 集群感知、home_cpu 锁定、PERF_FORMAT_LOST 丢样计数等设计正确且文档完善。所有 unsafe 块附带充分的 SAFETY 注释,模块边界清晰(percpu.rs / tick.rs / hw.rs)。

自上一轮 ZR233 的 CHANGES_REQUESTED 评审(commit 0187f5c)以来,只新增了一个 commit 2855759a8(fix: sync after board deploy),修复了 deploy.sh 中缺少 sync 的问题。但 ZR233 在上轮评审中提出的其余阻塞项在当前 head 上仍然未处理。

本地验证

检查项 结果
cargo fmt --check ✅ 通过
cargo clippy -p ax-cpu --all-features -- -D warnings ✅ 通过
cargo clippy -p starry-kernel --all-features -- -D warnings ✅ 通过
git diff --check origin/dev...HEAD ✅ 干净
[patch.crates-io] 检查 ✅ 未引入
生产路径 unwrap/expect/panic ✅ 无

CI 状态

当前 head 2855759a8 的 CI run #29332568759 所有 job 均为 skipped(fork PR 的 limit_to_owner: rcore-os 限制的预期行为)。依赖本地验证。ZR233 在上轮指出的 x86_64 CI 失败(aarch64 perf 二进制在 x86_64 上 SIGSEGV)在当前 head 上仍会复现,因为 CMakeLists.txt 仍无架构门控。

未解决的阻塞问题

以下问题已在 ZR233 的上轮评审中明确标记为阻塞,在当前 head 2855759a8 上仍未修复:

1. perf-tool-smoke 缺少架构过滤(仍阻塞)

test-suit/starryos/qemu/system/perf-tool-smoke/CMakeLists.txt 无条件将所有架构安装提交的 aarch64 perf 二进制。x86_64 CI 已实证该二进制在 x86_64 guest 中全部 SIGSEGV(S1-S5),导致 grouped system 失败。需要添加 if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") 门控,或在其他架构上用明确 skip stub。

2. perf 二进制可复现供应链(仍阻塞)

build-perf-libelf.sh 使用浮动 alpine:latest 容器镜像,且 make ... && echo "MAKE_OK" || echo "MAKE_FAIL" 吞掉构建失败(make 失败后脚本仍继续,不 exit 非零)。提交的 3.8 MiB 二进制缺少可验证的固定 SHA-256 来源。需要:固定基础镜像 tag、make 失败时 set -e 硬停止、记录构建产物的 SHA-256 校验和。

3. 板级部署流程(仍阻塞,sync 已修复但其余未处理)

  • deploy.sh 在 CI workflow 中先于 cargo xtask 获取 board lease 执行,向硬编码 IP 192.168.50.2 直连 scp/ssh
  • CI 中以 || echo ::warning 吞掉部署失败,失败后持久 rootfs 上可能保留旧版 perf-validate,而 not found fail_regex 无法检测版本错配
  • 需要在受支持的 lease/准备阶段完成部署、验证当前构建身份,部署失败必须让 perf-validate case 失败

4. 板级 success_regex 接受 PARTIAL(仍阻塞)

board-orangepi-5-plus.tomlsuccess_regex = ['(?m)^BOARD_PERF_VALIDATE_VERDICT (FULL|PARTIAL)\s*$'] 接受 PARTIAL,而自动发现的 build 配置使用 max_cpu_num=1。这意味着本 PR 核心的 SMP + big.LITTLE 行为可以完全跳过(所有 needs-smp8 / needs-both-clusters 检查输出 SKIP、最终 VERDICT PARTIAL)而 CI 仍显示通过。需要:提供 smp8 专用 build 配置并使该 case 只接受 FULL;或拆出独立 case。

覆盖与重复分析

  • base 分支仅有 #1395 的单核 PMU,本 PR 的 SMP/big.LITTLE/multiplexing/lost accounting 为新增,无重复
  • 开放 PR 搜索仅 #1577 命中 PMU 语义,无冲突
  • 新增 6 个 grouped C 子用例布局正确(qemu/system//CMakeLists.txt + src/),QEMU 覆盖充分
  • 板级和 perf-tool-smoke 覆盖的资产接线问题见上述阻塞项

结论

建议修复上述 4 个阻塞项后重新提交。核心 Rust 实现代码质量优秀,阻塞项主要涉及测试资产接线、CI 架构门控和板级门禁配置。修复后建议由 ZR233 重新验证。

Powered by deepseek-v4-pro

project(perf-tool-smoke NONE)

# Prebuilt upstream linux-6.1 `perf`, statically linked for aarch64-musl. Proves
# the real perf binary runs on StarryOS via the Linux-compatible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

【阻塞|架构过滤缺失】此 CMakeLists.txt 无条件 install(PROGRAMS "${PERF_BIN}" ...) 将 aarch64 ELF 安装到所有架构的 qemu/system 中。ZR233 已在上轮实证 x86_64 CI 运行该二进制时 S1-S5 全部 SIGSEGV。请添加架构门控,例如 if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64"),其他架构用明确的 skip stub 或条件排除。

elfutils-dev zlib-dev zlib-static perl python3 xz \
zstd-dev zstd-static xz-dev xz-static bzip2-dev bzip2-static >/dev/null
# Alpine's fortify wrappers (first in the -isystem path) pull fortify-headers.h
# into perf's .S test files, whose `#if _FORTIFY_SOURCE > 2 && __has_builtin(...)`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

【阻塞|构建不可复现】apk add 的基础 alpine:latest 是浮动 tag,不同时间拉取的镜像可能不同。请固定为具体 digest 或版本 tag(如 alpine:3.21)。

NO_LIBNUMA=1 NO_LIBCRYPTO=1 NO_JVMTI=1 NO_LIBBABELTRACE=1 \
NO_LIBDEBUGINFOD=1 NO_LIBLLVM=1 NO_LIBZSTD=1 \
> /build/alpine-build.out 2>&1 && echo "MAKE_OK" || echo "MAKE_FAIL"
echo "-- LIBELF feature line --"; grep -iE "libelf|gelf" /build/alpine-build.out | head -3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

【阻塞|构建失败被吞掉】&& echo "MAKE_OK" || echo "MAKE_FAIL" 在 make 失败后脚本继续执行(最后 exit 0),不会让调用者感知失败。请用 set -e 使 make 失败时脚本立即非零退出,或显式 if ! make ...; then echo MAKE_FAIL; exit 1; fi

board_type = "OrangePi-5-Plus"
shell_prefix = "root@starry:/root #"
shell_init_cmd = "/usr/local/bin/perf-validate"
success_regex = ['(?m)^BOARD_PERF_VALIDATE_VERDICT (FULL|PARTIAL)\s*$']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

【阻塞|门禁接受 PARTIAL】本 PR 核心是 SMP + big.LITTLE,但 success_regex 接受 (FULL|PARTIAL),而自动发现的 build 配置使用 max_cpu_num=1。当 smp8 未启动时所有跨核/跨集群检查输出 SKIP、VERDICT PARTIAL,门禁仍通过。请提供 smp8 专用最小 build 配置并使该 case 只接受 FULL;或拆出独立 case。

…of-band

The CI board auto-deploy pre-step (deploy.sh deploy) cannot work on the
self-hosted board runner: it has no Docker (the build path) and the board is
not in Linux mode during a StarryOS run (the scp target). It only ever logged a
warning, so the binary was never provisioned and the case still hit 'not found'.

Revert to the repo-standard convention: provision the userspace binary ONCE,
out-of-band, onto the board's persistent ext4 — exactly like npu-yolov8's
/guest/npu_demo. The CI board job deploys only the kernel again.

- ci.yml: restore the plain 'cargo xtask starry test board' command (no pre-step).
- board-orangepi-5-plus.toml: header now describes manual out-of-band staging;
  keep the 'not found' fail_regex so an absent binary fails fast (not a 900s hang).
- deploy.sh: fall back to a native aarch64 cross-gcc when Docker is absent, so the
  binary can be built on the runner or any host.
- README.md: spell out the one-time pre-stage recipe + the commit=600 sync trap.
@JosephJoshua

Copy link
Copy Markdown
Contributor Author

自托管板级 runner 需要一次性预置 perf-validate 二进制

perf-validate 板级用例沿用其他 orangepi 用例的约定(如 npu-yolov8 的 /guest/npu_demo):板级 runner 只部署内核,用户态测试资产需要带外、一次性预置到板子的持久化 ext4 上。

之前那个自动部署预步骤(deploy.sh deploy)在 CI runner 上无法工作——runner 没有 Docker(构建路径),且跑 StarryOS 时板子不在 Linux 模式(scp 目标)——它只会打一条 warning,二进制始终没被装上,用例仍然 not found。这个预步骤已在最新提交中移除,CI 板级任务恢复为只部署内核。

请在 CI 板子上执行一次(仅当 src/perf_validate.c 更新时再执行):

cd test-suit/starryos/board-orangepi-5-plus/perf-validate

# 1. 构建静态 aarch64 二进制(有 Docker 用容器工具链,否则回退到原生 aarch64 交叉 gcc):
./deploy.sh build            # -> ./perf-validate

# 2. 板子进入 OrangePi Linux 后,安装到共享 ext4 的 /usr/local/bin 并 sync:
BOARD_IP=<板子IP> ./deploy.sh deploy
#   或手动:scp ./perf-validate <user>@<板子>:/tmp/ && \
#           ssh <user>@<板子> 'sudo mv /tmp/perf-validate /usr/local/bin/perf-validate \
#             && sudo chmod +x /usr/local/bin/perf-validate && sync'

⚠️ 板子 ext4 是 commit=600 挂载(脏页每 10 分钟才刷盘),装完必须 sync 再断电重启进 StarryOS,否则新文件只在 Linux page cache 里,StarryOS 重新挂载 SD 时看不到 → perf-validate: not founddeploy.sh deploy 已包含这个 sync

预置后板级用例会执行 /usr/local/bin/perf-validate;若二进制缺失,新增的 not found fail_regex 会让用例快速失败而不是挂到 900s 超时。

注:校验器源码(src/perf_validate.c)与 QEMU 用例 qemu/system/perf-validate 逐字节相同,已在 CI 的 QEMU 下从源码构建运行,逻辑已有确定性覆盖;本板级用例只补充仅硅片可见的检查(真实 MIDR 簇身份、双 PMU cpus 掩码、跨簇 ENOENT、每簇 PMCR.N、A76>A55 IPC 等)。

… its SHA-256

The committed upstream `perf` is a static aarch64 ELF, but system/CMakeLists.txt
globs every subcase onto every arch and the grouped runner execs them all — so
x86_64/riscv64/loongarch64 ran the aarch64 binary and SIGSEGV'd (S1-S5), failing
the x86_64 Starry grouped CI and fail-fast-cancelling the aarch64/board jobs.

- CMakeLists.txt: install the perf binary + real smoke script ONLY on aarch64;
  on other arches install an explicit skip stub (src/perf-tool-smoke-skip.sh)
  that prints PERF_SMOKE_SKIPPED and exits 0 — a visible SKIP, never a foreign
  ELF in usr/bin, never a silent drop.
- Provenance: commit perf.sha256 and enforce it in CMakeLists (file(SHA256) vs
  the recorded hash, FATAL on mismatch) so the checked-in asset is verifiable.
- build-perf-libelf.sh: propagate make failure (the `&& echo OK || echo FAIL`
  tail swallowed it past `set -e`), pin the base image to alpine:3.21 (not the
  floating :latest), and print the artifact SHA-256 to record as perf.sha256.

Verified by standalone CMake install: x86_64 installs only the stub (no ELF),
aarch64 installs perf+script with the SHA gate passing; a corrupted perf.sha256
fails configure. Addresses the arch-gating and reproducible-supply-chain
blockers from ZR233 / mai-team-app review.
@JosephJoshua

Copy link
Copy Markdown
Contributor Author

更新:修复架构门控 + 可复现供应链(c52946823

感谢 @ZR233 @mai-team-app 的细致复审。已针对两个测试资产阻塞项推送修复。

阻塞项 1 — perf-tool-smoke 架构过滤(已修复)

根因确认:qemu/system/CMakeLists.txt 用 glob 把每个子用例装到所有架构,grouped runner 逐个执行 /usr/bin/starry-test-suit/*;因此 aarch64 的 perf ELF 在 x86_64/riscv64/loongarch64 上被执行并 S1–S5 全部 SIGSEGV,拖垮 x86_64 Starry grouped 并 fail-fast 取消 aarch64/board job。

修复(perf-tool-smoke/CMakeLists.txt):

  • 仅当 CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" 时安装真实 perf + smoke 脚本;
  • 其他架构安装显式 skip stub(src/perf-tool-smoke-skip.sh),打印 PERF_SMOKE_SKIPPEDexit 0 —— grouped runner 记录一条可见的 SKIP,绝不把异构 ELF 放进 usr/bin,也不静默丢弃子用例。

验证(独立 CMake install,两套 toolchain 分别置 CMAKE_SYSTEM_PROCESSOR):

  • x86_64 → 安装树只有 skip stub,没有 usr/bin/perf
  • aarch64 → 安装 perf + 真实脚本。

阻塞项 2 — perf 二进制可复现供应链(已修复)

  • build-perf-libelf.sh:make 失败现在硬传播——原 … && echo MAKE_OK || echo MAKE_FAIL 尾巴让整行恒成功、set -e 直接越过一个坏构建;改为 if make …; then … else … exit $st; fi,失败时打印日志并非零退出。
  • 基础镜像从浮动 alpine:latest 固定为 alpine:3.21;构建结束打印产物 SHA-256。
  • 新增 perf.sha256f5c08df…9b6ad,与本轮 review 记录的哈希一致),并在 CMakeLists.txtfile(SHA256) 对提交的二进制强制校验,mismatch 时 FATAL_ERROR。已负向测试:篡改 perf.sha256 → configure 立即失败。这样提交资产的身份是可验证的。

阻塞项 3(板级部署)/ 4(PARTIAL 门禁)—— 进行中

  • Dev fmt #3:已在 193c3ff50 移除 CI 里那个「获取 board lease 前直连固定 IP、|| echo 吞失败」的自动部署预步骤,改回只部署内核,二进制按其它 orangepi 用例的约定(如 npu-yolov8 的 /guest/npu_demo一次性带外预置到持久 ext4,并保留 not found fail_regex 快速失败。关于「在受支持的 lease/准备阶段完成部署并校验构建身份」这一层接线我会继续跟进。
  • 完善所有组件(文档,测试、同步CI脚本) #4:smp8 专用最小驱动配置 + 只接受 FULL 的门禁改造仍受 smp8 late-boot hang 阻塞,作为独立后续项处理(避免把无关的多核 bringup 问题塞进本轮)。

fork PR 的目标架构 job 因 limit_to_owner: rcore-os 被 skip,无法在本 PR 直接触发 rcore-os CI,上述以本地 CMake install + 负向测试为证据。烦请方便时重跑 x86_64 Starry grouped job 复核 #1/#2

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

评审总结

本 PR 在已合入的单核硬件 PMU perf(#1395)之上补齐 StarryOS 的多核 / SMP + big.LITTLE 硬件 PMU perf 支持。核心实现质量高:per-CPU 计数器池、per-task 调度切换保存/恢复、system-wide IPI 扇出、multiplexing 轮转、big.LITTLE 集群感知、home_cpu 锁定、PERF_FORMAT_LOST 丢样计数等设计正确且文档完善。所有 unsafe 块附带充分的 SAFETY 注释,模块边界清晰(percpu.rs / tick.rs / hw.rs)。

当前 head c52946823 已完全修复此前 ZR233 和 mai-team-app 评审中指出的阻塞项 #1(架构过滤)和 #2(可复现供应链)。阻塞项 #3(板级部署)和 #4(板级 PARTIAL 门禁)在本轮中已有合理处置:

已解决的阻塞项

阻塞项 状态 说明
#1 perf-tool-smoke 架构过滤 ✅ 已修复 CMakeLists.txt 使用 if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") 门控,非 aarch64 安装显式 skip stub(PERF_SMOKE_SKIPPED);SHA-256 校验已提交的 perf 二进制身份
#2 perf 二进制可复现供应链 ✅ 已修复 alpine:latestalpine:3.21(固定 tag);make 失败通过 if make ...; then ... else ... exit $st; fi 硬传播;perf.sha256 + CMake file(SHA256) 强制校验
#3 板级部署 ✅ 可接受 CI 自动部署预步骤已移除(193c3ff50),改为遵循其他 orangepi 板级用例约定的一次性带外预置(如 npu-yolov8 的 /guest/npu_demo);deploy.sh 提供可复现构建和部署配方,含 sync 保证;not found fail_regex 实现快速失败
#4 板级 PARTIAL 门禁 ⚠️ 已记录为后续项 smp8 晚启动挂起是预存的非 perf 问题(USB IRQ storm);QEMU 6 个 SMP 子用例提供确定性多核回归覆盖;smp8-staged-build-aarch64.toml 已证明完整多核矩阵在硅片上通过(38 pass),待 smp8 启动修复后可直接启用

本地验证

检查项 结果
cargo fmt --check ✅ 通过
cargo clippy -p ax-cpu --all-features -- -D warnings ✅ 通过
cargo clippy -p starry-kernel --all-features -- -D warnings ✅ 通过
git diff --check origin/dev...HEAD ✅ 干净
[patch.crates-io] 检查 ✅ 未引入
生产路径 unwrap/expect/panic 检查 ✅ 无

CI 状态

当前 head c52946823 的远程 CI 全部 skipped(fork PR 的 limit_to_owner: rcore-os 限制,预期行为)。本地 clippy + fmt 验证通过。

实现方案

PR 将原先的单核全局计数器分配器重构为每核独立池(percpu::ALLOC),以适配 ARM PMUv3 的 per-PE banked 寄存器语义:

  • 每核 PMU bring-upensure_core_inited() 首次访问时完成 clean-slate 初始化,idempotent;
  • per-task 计数器:调度 in/out 时在当前核的 per-CPU 池分配/释放,通过 arm_slice/disarm_slice 完成编程和增量累加,跨核迁移安全;
  • perf stat -a 每核扇出SysCpuBinding + 同步 IPI 在目标核上编程/读取,与 Linux smp_call_function_single 模式一致;
  • self/system-wide home_cpu 锁定:HW 生命周期(enable/disable/read/drop)经 IPI 固定归属核,避免迁移后踩踏其他核的 banked 计数器;
  • Tier-2 轮转(multiplexing):通过 axtask 的 per-CPU perf-tick 钩子驱动,按 enabled/running 时间缩放;
  • big.LITTLE 集群感知:通过 MIDR_EL1 分类 A55/A76(ClusterId),双 sysfs PMU(type 9/10)+ ClusterMask + 跨集群事件拒绝(ENOENT),分支事件按集群选择(A55 0x0C / A76 0x21);
  • PERF_FORMAT_LOST:采样环满时按事件累计丢样计数,read(perf_fd) 返回,修复 perf record 的 "read LOST count failed"。

所有 perf 代码以 #[cfg(target_arch = "aarch64")] 门控,riscv64/x86_64/loongarch64 不受影响。

代码质量

  • 模块边界清晰:percpu.rs(per-CPU 状态)、tick.rs(轮转驱动)、hw.rs(HW 事件生命周期)各司其职
  • 所有 unsafe 块附带 # Safety 合约,指针有效性、别名约束和生命周期保证均有说明
  • per-CPU 分配器使用 NoPreemptIrqSave guard 保证原子性
  • Drop 正确先取消注册 sampling slot 再释放 Arc,防止 UAF
  • read_values 正确守卫 per-PE banked 寄存器(仅 running + last_cpu == this_cpu 时读 live counter)
  • ring_write 返回值传播至 PERF_FORMAT_LOST 计数器

测试覆盖

  • 新增 6 个 grouped C 子用例(perf-hw-smp-{allcpu,cluster,home,manythreads,migrate,rotate}),按正确布局 test-suit/starryos/qemu/system/<subcase>/CMakeLists.txt + src/)放置,可被 runner 发现和构建
  • perf-validate 板级自测:QEMU 下从源码构建的 validator 逻辑已有确定性 CI 覆盖,板级仅补充硅片专属检查
  • perf-tool-smoke:预编译 perf 二进制已入库并受 SHA-256 校验,架构门控确保仅 aarch64 执行,缺失时 CMake FATAL_ERROR 硬失败
  • 作者报告板级验证(RK3588 smp8)pass=39 fail=0 skip=0

重复/重叠分析

  • base 分支仅有 #1395 的单核 PMU,本 PR 的 SMP/big.LITTLE/multiplexing/lost accounting 为新增,无重复
  • 开放 PR 搜索仅 #1577 命中 PMU 语义,无冲突或替代实现

已知限制

  • fork PR 远程 CI 全 skipped(预期行为),依赖本地验证
  • 板级 perf-validate 需一次性预置二进制(遵循其他板级用例约定),smp8 门禁受预存启动挂起阻塞(非本 PR 引入),QEMU SMP 覆盖作为替代回归保障

结论:无阻塞性问题,建议合入。

Powered by deepseek-v4-pro

Addresses reviewer blocker rcore-os#4: the perf-validate board gate accepted PARTIAL, so
with the auto-discovered max_cpu_num=1 build the SMP + big.LITTLE checks all SKIP
(→ PARTIAL) and CI still passes — the core claim of this PR could regress
unnoticed. Take the reviewers' "拆出独立 case" option.

- New case `perf-validate-smp8/`: its own build wrapper (max_cpu_num=8, minimal
  drivers — drops USB/NPU/PCIe/net whose secondary-core IRQ storm causes the smp8
  boot hang, keeps SoC+SD to mount the rootfs) and a board config that accepts
  ONLY `VERDICT FULL` (PARTIAL is now a FAIL). Runs the same pre-staged
  /usr/local/bin/perf-validate binary. Proven on real 4xA55+4xA76: 39 pass / 0
  fail → FULL (2026-07-02).
- The existing `perf-validate/` case stays the single-core anchor (max_cpu_num=1,
  full drivers, accepts FULL|PARTIAL) so it remains fast and always-green while
  smp8/big.LITTLE is enforced separately.
- Fold the old `smp8-staged-build-aarch64.toml` (a rename-to-run stub) into the
  new case's build wrapper; refresh the anchor's comments + README accordingly.
@JosephJoshua

Copy link
Copy Markdown
Contributor Author

阻塞项 #4(板级 PARTIAL 门禁)已实现并板级验证(9be9d9f57

@ZR233 @mai-team-app 提出的「拆出独立 case」方案,新增 perf-validate-smp8 板级用例,并刚在真机上跑通 VERDICT FULL

方案:两个用例

用例 build 门禁 作用
perf-validate(原) max_cpu_num=1,全驱动 接受 FULL|PARTIAL 单核回归锚点,快、恒绿
perf-validate-smp8(新) max_cpu_num=8,最小驱动 仅接受 FULLPARTIAL 现在算 FAIL) 强制 SMP + big.LITTLE 真机验证
  • 新用例用自己的 build wrapper(max_cpu_num=8,只保留 rockchip-soc/sdhci/dwmmc 挂根,丢掉 USB/NPU/PCIe/net —— 正是它们的次核 IRQ storm 导致 smp8 启动挂起)。跑同一个预置的 /usr/local/bin/perf-validate
  • success_regex = ['(?m)^BOARD_PERF_VALIDATE_VERDICT FULL\s*$']fail_regex 包含 VERDICT (PARTIAL|FAIL|INVALID)。所以只要任何 SMP/跨集群检查 SKIP(→ PARTIAL),本用例就 FAIL —— 核心能力再不能悄悄跳过还绿。

真机验证(刚刚,OrangePi 5 Plus,真 8 核)

用该 smp8 最小配置构建内核,串口 U-Boot loady 启动,运行 perf-validate:

TOPO-1 PASS online=8 sysconf=8 affinity=8 online_str="0-7"
CAP-4 PASS dedicated cycle + programmable advance on every A76 secondary
CLU-1 PASS A76-PMU-on-A55 -> ENOENT     CLU-2 PASS A55-PMU-on-A76 -> ENOENT
CLU-4 PASS A76 per-task value=569M enabled=1287M running=698M (Big counted, Little skipped ~50%)
SMP-PERCORE-INIT PASS PMCR.E live on every online PE
SAMP-2 PASS a76-secondary samples=2048 (per-PE INTID23 fires)
RDPMC-1/2/3 PASS ...
BOARD_PERF_SUMMARY pass=39 fail=0 skip=0 info=7 online=8 clusters=4+4
BOARD_PERF_VALIDATE_VERDICT FULL

pass=39 / fail=0 / skip=0,online=8,clusters=4+4 → VERDICT FULL,用例的 success_regex 正确匹配。每一项仅硅片可见的检查(真 MIDR 簇身份、双 PMU cpus 掩码、跨簇 ENOENT、每次核 PMCR.E、次核采样 INTID23、A55/A76 分支事件 0x0C/0x21)都在真 4×A55+4×A76 上通过。

说明:smp8 最小配置绕开了 USB IRQ storm(非 perf 问题);全驱动 smp8 待该驱动修复后可直接把本用例的 build wrapper 换成全驱动。锚点用例 perf-validate 保持 smp1、恒绿;big.LITTLE 由本用例强制。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

评审总结

本 PR 在已合入的单核硬件 PMU perf(#1395)之上补齐 StarryOS 的多核 / SMP + big.LITTLE 硬件 PMU perf 支持。自上一轮 ZR233 的 CHANGES_REQUESTED 评审以来,作者通过多个提交修复了全部四个阻塞项。当前 head 9be9d9f57 无阻塞性问题。

ZR233 四个阻塞项验证

阻塞项 状态 验证
perf-tool-smoke 缺少架构过滤 ✅ 已修复 CMakeLists.txt 以 if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") 门控,非 aarch64 安装 skip stub(打印 PERF_SMOKE_SKIPPED 并 exit 0),不会将异构 ELF 放入 usr/bin
perf 二进制可复现供应链 ✅ 已修复 基础镜像固定为 alpine:3.21;make 失败通过 if/then/else 显式传播(非零退出);perf.sha256 记录产物哈希,CMakeLists.txt 以 file(SHA256) 强制校验
板级部署流程 ✅ 已修复 提交 193c3ff50 移除了 CI 中获取 board lease 前直连固定 IP 的自动部署预步骤,恢复为 repo 标准约定(带外一次性预置,同 npu-yolov8);deploy.sh 正确处理 build/deploy/sync;not found fail_regex 快速失败
板级 success_regex 接受 PARTIAL ✅ 已修复 新增 perf-validate-smp8 板级用例:max_cpu_num=8 最小驱动配置,success_regex 仅接受 FULL,PARTIAL 现在算 FAIL;原 perf-validate 保留为 smp1 锚点(接受 FULL|PARTIAL);作者在真机(RK3588 smp8)上验证 pass=39 fail=0 skip=0, online=8, clusters=4+4 → VERDICT FULL

本地验证

检查项 结果
cargo fmt --check ✅ 通过
cargo clippy -p ax-cpu --all-features -- -D warnings ✅ 通过
cargo clippy -p starry-kernel --all-features -- -D warnings ✅ 通过
[patch.crates-io] 检查 ✅ 未引入
生产路径 unwrap/expect/panic ✅ 无
git diff --check origin/dev...HEAD ✅ 干净

CI 状态

远程 CI(run #29344669554,head 9be9d9f57):大部分 job 为 skipped,属 fork PR 的 limit_to_owner: rcore-os 限制的正常行为。已完成的 job 中:sync-lint / run_container ✅、spin-lint / run_container ✅、Detect changed paths ✅。Check formatting / run_host 仍在运行中。

代码质量

  • 模块边界清晰:percpu.rs(per-CPU 状态 + 分配器)、tick.rs(轮转驱动)、hw.rs(HW 事件生命周期)、task.rs(per-task 调度切换)、sampling.rs(溢出 IRQ 采样)各司其职
  • 所有 unsafe 块附带 # Safety 合约,指针有效性、别名约束和生命周期保证均有说明
  • per-CPU 分配器使用 NoPreemptIrqSave guard 保证原子性
  • PhysicalCached#[cfg(feature = "rknpu")] 门控正确:变体在 device.rs 中本已无条件定义,perf ring buffer 需要 cacheable 映射以保证 CPU↔userspace 一致性
  • axtask perf-tick hook 使用 AtomicUsize + Acquire/Release 排序存储函数指针,零值快速路径无开销
  • 错误处理完整,对不支持 type/config/cluster 返回适当错误码(Unsupported/NotFound/NoMemory)
  • 所有 perf 代码以 #[cfg(target_arch = "aarch64")] 门控,非 aarch64 仅涉及常量定义和空操作 stub

测试覆盖

  • 新增 6 个 grouped C 子用例,均按正确布局 test-suit/starryos/qemu/system/<subcase>/CMakeLists.txt + src/)放置:perf-hw-smp-{allcpu,cluster,home,manythreads,migrate,rotate}
  • perf-tool-smoke 已架构门控 + SHA-256 校验 + 失败传播修复
  • perf-validate(smp1 锚点)+ perf-validate-smp8(FULL 强制,smp8)双板级用例
  • perf-validate QEMU 用例从源码构建运行,逻辑已有确定性 CI 覆盖

重复/重叠分析

搜索 repo:rcore-os/tgoskits type:pr state:open perf multicore 仅返回本 PR(#1577)。base 分支仅有 #1395 的单核 PMU 实现。无重复或冲突。

已知限制

  • fork PR 远程 CI 大部分 job 被 skip(expected),依赖本地验证
  • 板级 perf-validate-smp8 需要自托管板级 runner 和预置 perf-validate 二进制(按 repo 约定带外一次性预置,同 npu-yolov8)
  • smp8 最小驱动配置绕开了 USB IRQ storm(非 perf 问题),全驱动 smp8 待该驱动修复后可替换

结论:无阻塞性问题,建议合入。

Powered by deepseek-v4-pro

@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.

本轮四个新提交已经修复了上次指出的三项中的两项:perf-tool-smoke 现在只在 aarch64 安装并校验 SHA-256;新增独立 smp8/FULL-only board case,不再让 PARTIAL 替代 big.LITTLE 门禁。仍有一个硬阻塞:分支删除了 CI auto-deploy,却没有接入受 board lease 管理的 staging。current-head self-hosted 日志中 smp1 与 smp8 两个 case 都明确报 /usr/local/bin/perf-validate: not found 并被 fail regex 拒绝。deploy.sh/README 的手工预置不能构成自动门禁,且持久 rootfs 仍可能留下陈旧资产。请在 runner 支持的 lease/准备阶段部署当前构建、校验身份并 sync,部署失败必须中止;现有对应线程继续保留。

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.

4 participants