hujun260 opened a new pull request, #21475:
URL: https://github.com/apache/nuttx/pull/21475
## Summary
* perf_gettime() folded the 64 bit clock from a 32 bit hardware
counter and an overflow count while holding a spinlock. That made
every user space caller enter the kernel, and the lock is taken on
the scheduler instrumentation path.
* This series tracks the overflow in the periodic watchdog instead and
lets readers sample the counter and the overflow count under a
seqlock, so a 64 bit value can be read from user space without a
system call.
* The series also adds the Kconfig plumbing to expose the ARMv7-A
cycle counter to user space, and fixes four pre-existing defects
found while build and run testing the result.
Commits:
1. `sched/clock: Read the perf watchdog timeout under the lock.`
2. `arch/arm: Expose up_perf_gettime() to user space on ARMv7-A.`
3. `arch: Gate the user-space perf counter behind
ARCH_PERF_EVENTS_USER_ACCESS.`
4. `sched/clock: Start the perf overflow watchdog from perf_setup().`
5. `sched/clock: Use the notrace spinlock in perf_gettime().`
6. `arch: Mark the perf counter readers noinstrument_function.`
7. `sched/clock: Track the perf counter overflow without a spinlock.`
## Impact
* Is new feature added? YES. `CONFIG_ARCH_PERF_EVENTS_USER_ACCESS`
lets an application read the 64 bit perf clock directly.
* Impact on user (will user need to adapt to change)? NO. The user
space symbol is still perf_gettime() with the same signature.
* Impact on build (will build process change)? NO, apart from the new
Kconfig option.
* Impact on hardware (will arch(s) / board(s) / driver(s) change)?
YES. arm_perf.c is now built into the user space blob on ARMv7-A,
and `struct perf_s` moved from `sched/clock/clock_perf.c` to
`include/nuttx/clock.h` so protected builds can reach it through
`struct userspace_data_s`.
* Impact on documentation (is update required / provided)? NO.
* Impact on security (any sort of implications)? NO new syscall and
no new privilege: the counter is already readable by the kernel, and
user space only gets read access to a per-system counter. The
`userspace_data_s` pointer is filled by the kernel at link time.
* Impact on compatibility (backward/forward/interoperability)? NO.
### Behaviour changes worth reviewer attention
* CONFIG_PERF_OVERFLOW_CORRECTION now depends on `!ARCH_64BIT` and
`default y`. On a 32 bit build with a perf counter the overflow
correction is on unless it is explicitly disabled.
* `CONFIG_ARCH_PERF_EVENTS_USER_ACCESS` defaults to `!BUILD_FLAT`, so
a protected or kernel build on a capable arch gets the user space
counter without touching its defconfig.
### Pre-existing defects fixed in the last commit
These were found by build and run testing this series; none of them is
introduced by it, but the series does not build or work without them.
1. `sched/Kconfig`: `PERF_OVERFLOW_CORRECTION` depended on
`SYSTEM_TIME64`, which no longer exists in the tree, so the symbol
was always `n` and the new `default y` could never take effect.
Measured with kconfiglib on a minimal Kconfig: with the
`SYSTEM_TIME64` term the value is `n`, without it `y`.
2. `include/nuttx/clock.h` included `<nuttx/percpu.h>`, which is not
present anywhere in the tree or in master, so every file that
includes clock.h failed to compile. Nothing in `struct perf_s`
uses a per-CPU variable, so the include is dropped.
3. `libs/libc/sched/Make.defs` and CMakeLists.txt listed
`clock_perf.c` twice, once unconditionally and once under
`ARCH_PERF_EVENTS_USER_ACCESS`. g_perf now lives in that file, so
dropping the unconditional entry alone gives
`undefined reference to 'g_perf'` in a flat build. The condition is
`PERF_OVERFLOW_CORRECTION || ARCH_PERF_EVENTS_USER_ACCESS`.
4. perf_gettime() sampled the hardware counter before reading
g_perf.last, so a wrap recorded by the watchdog between the two
reads was missed and the 64 bit value fell by 2^32. Reproduced, see
the testing section.
## Testing
Environment:
* Build host: Linux x86_64, `arm-none-eabi-gcc` from the Vela
prebuilts.
* Target: `qemu-armv7a:smp`, QEMU `virt`, Cortex-A7, 4 CPUs, TCG.
`qemu-armv7a` does not call `up_perf_init()`, so `PMCR.E` stays clear and
`up_perf_gettime()` returns a constant 0. To exercise the code under
test the local test build adds one call in the board bringup:
```c
#ifdef CONFIG_ARCH_PERF_EVENTS
up_perf_init((void *)(uintptr_t)1000000000);
#endif
```
This is **not** part of the pull request; it only makes the counter run
on this board. Without it the first test reports
`ERROR: counter did not advance (start=0 end=0)`, which is the stopped
counter case described in #21428.
Test configuration:
```
CONFIG_BUILD_FLAT=y
CONFIG_SMP=y
CONFIG_ARCH_PERF_EVENTS=y
CONFIG_PERF_OVERFLOW_CORRECTION=y
CONFIG_ARCH_PERF_COUNT_BITWIDTH=32
CONFIG_SCHED_CRITMONITOR=y
CONFIG_TESTING_OSTEST=y
```
### Test program
perf_seqlock.c (new, in the apps PR) is run from
`ostest`. It has two cases:
* **Test 1** busy loops for 500 ms measured with `CLOCK_MONOTONIC`,
then converts the perf_gettime() delta with perf_convert() and
checks that the converted interval is non-zero, below 1 s and above
100 ms. A counter that does not run fails here.
* **Test 2** starts 4 threads, each calling perf_gettime() 200000
times in a loop and checking that the value never decreases. A torn
or stale overflow fold shows up as a step of about -2^32.
### Testing logs before the fix
```
user_main: perf seqlock test
perf_seqlock_test: Starting
Test 1: counter advances over a 500 ms busy loop
500 ms busy loop -> 0.500 s
OK: converted interval is in range
Test 2: 4 threads sample the counter concurrently
perf_seqlock: thread 0: counter went backwards at iteration 129405:
prev=157879718338 curr=153584751512
perf_seqlock: thread 1: counter went backwards at iteration 127396:
prev=157879718530 curr=153584751485
perf_seqlock_test: FAILED (2 error(s))
```
The step is `157879718338 - 153584751512 = 4294967826`, i.e. 2^32 minus
the counter advance between the two samples. Two of four threads hit it
within 200000 iterations.
### Testing logs after the fix
```
user_main: perf seqlock test
perf_seqlock_test: Starting
Test 1: counter advances over a 500 ms busy loop
500 ms busy loop -> 0.500 s
OK: converted interval is in range
Test 2: 4 threads sample the counter concurrently
OK: no backwards step observed
perf_seqlock_test: PASSED (0 error(s))
```
### Build coverage
Both build from a clean tree with `make -j20`, no warnings from the
touched files, and g_perf has exactly one definition in the ELF:
| config | `BUILD_*` | `ARCH_PERF_EVENTS_USER_ACCESS` | result |
| --- | --- | --- | --- |
| `qemu-armv7a:smp` | `BUILD_FLAT` | n | build OK, `nm` shows one g_perf |
| `qemu-armv7a:knsh` | `BUILD_KERNEL` | y (default) | build OK, `nm` shows
one g_perf |
`./tools/nxstyle` reports no issue for every touched file.
Disassembly of the user space copy confirms the seqlock variant is the
one built when `PERF_OVERFLOW_CORRECTION=y` (the `read_seqbegin()` /
`read_seqretry()` retry loop is visible around the `up_perf_gettime()`
call).
## PR verification Self-Check
* [x] This PR introduces only one functional change (the user space
perf counter and the overflow tracking it needs).
* [x] I have updated all required description fields above.
* [x] My PR adheres to Contributing Guidelines and Documentation.
* [ ] My PR is still work in progress (not ready for review).
* [x] My PR is ready for review and can be safely merged into a
codebase.
Depends on the `nuttx-apps` PR that adds
perf_seqlock.c.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]