casaroli opened a new pull request, #21424: URL: https://github.com/apache/nuttx/pull/21424
## Summary NuttX sets `_POSIX_CPUTIME` and `_POSIX_THREAD_CPUTIME` with `CONFIG_SCHED_CRITMONITOR`, and `clock_gettime()` reads `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID`. But `timer_create()` gave `EINVAL` for these clocks, and `setitimer()` gave `EINVAL` for `ITIMER_VIRTUAL` and `ITIMER_PROF`. The CPU-time clocks also lost time: the CPU time of a running thread did not change until it stopped to run, a thread on another CPU lost its CPU time with `CONFIG_SCHED_CPULOAD_CRITMONITOR`, and the process CPU time lost the time of threads that exited. The PR has six commits: - `sched/clock`: `nxsched_critmon_runtime()` adds the time since the thread started to run if the thread runs now. `nxsched_switch_critmon()` sets `run_start` to zero when a thread stops to run: the scheduler changes `task_state` before the context switch, so `task_state` alone cannot tell which time slice is not counted yet. `clock_gettime()` uses `perf_convert()`, which matches `perf_gettime()` also on boards without a perf counter. - `sched/critmon`: with `CONFIG_SCHED_CPULOAD_CRITMONITOR` on SMP, a context switch on one CPU set `run_start` of the running threads of the other CPUs, but did not add the elapsed time to their `run_time`. The CPU load now keeps its own time per CPU (`g_cpuload_start[]`), and only the CPU of a thread changes its `run_start` and `run_time`. `nxsched_update_critmon()` (from `clock_cpuload()`) does not change them any more. Each running thread gets its own elapsed CPU load ticks. - `sched/clock`: `group_leave()` adds the run time of a thread that leaves the group to `tg_run_time`, and the process CPU-time clock adds `tg_run_time`. - `sched/timer`: `timer_create()` takes the CPU-time clocks, also the clocks of `clock_getcpuclockid()` and `pthread_getcpuclockid()`. The timer keeps its expiration time on the CPU-time clock. Its watchdog expires at the first time at which the CPU time can reach it (a thread clock does not run faster than real time, a process clock not faster than `CONFIG_SMP_NCPUS` times real time). The watchdog then reads the clock and starts again for the remaining time if necessary. The scheduler does not change, and tick and tickless builds work the same. `timer_gettime()` returns the remaining CPU time, and a periodic timer counts its overruns in CPU time. - `sched/timer`: `setitimer()` and `getitimer()` take `ITIMER_VIRTUAL` (`SIGVTALRM`) and `ITIMER_PROF` (`SIGPROF`) on the process CPU-time clock. NuttX does not count user time and system time separately, so both count all the CPU time of the process. - `Documentation`: `timer_create()` in the clocks and timers reference, and the `_POSIX_CPUTIME` and XSI timer tables of `posix.rst`. ## Impact Only builds with the CPU-time clocks (`CONFIG_SCHED_CRITMONITOR` with `CONFIG_SCHED_CRITMONITOR_MAXTIME_THREAD >= 0`) change. Without them the new code does not build: nucleo-l432kc:nsh has the same size as on master (text 250764, data 1048, bss 8824). With `CONFIG_SCHED_CRITMONITOR`, nucleo-l432kc:nsh gets 1828 bytes more text, 4 bytes less data and 24 bytes more bss. The timers use only the watchdog and `nxclock_gettime()`, so flat, protected and kernel builds, SMP and tickless builds use the same code. `struct task_group_s` keeps one interval timer per `ITIMER_*` value (three pointers with the CPU-time clocks, one as before without them) and, with the CPU-time clocks and pthreads, `tg_run_time`. `CONFIG_SCHED_CPULOAD_CRITMONITOR` on SMP: the CPU load of a thread on another CPU is now its own elapsed time since the last update of that CPU, not the elapsed time of the thread that stops to run on this CPU. In the flat build too, `timer_create()` on a CPU-time clock and `setitimer()` with `ITIMER_VIRTUAL` or `ITIMER_PROF` now succeed where they returned `EINVAL`, and `clock_gettime()` on a CPU-time clock now gives larger (correct) values. ## Testing A test program `timercpu.c` (25 checks; it gives the same results on Linux with 2 CPUs): the thread CPU time in a busy loop; `EINVAL` for an invalid clock; a process timer and a thread timer do not expire while the thread sleeps or while another thread runs, and expire after the CPU time; `timer_gettime()` counts down CPU time; overruns of a periodic timer; `timer_delete()` of an armed timer; `setitimer()` with `ITIMER_VIRTUAL` and `ITIMER_PROF`; the process CPU time after a thread that used 300 ms exited; with 2 CPUs or more, the CPU time of a thread on CPU 1 that runs 500 ms while CPU 0 switches threads every millisecond. A second program `timercpu2.c` arms a process timer and a thread timer and waits in a busy loop without system calls. All with `CONFIG_SCHED_CRITMONITOR`: | Configuration | master | this PR | |---|---|---| | qemu-armv8a:knsh (kernel build, arm64, HVF, 1 CPU) | FAIL: busy loop CPU time 0 ms, `timer_create()` `EINVAL`, exited thread 0 ms | 24/24 PASS (the CPU 1 check needs SMP), timercpu2 PASS | | rv-virt:ksmp64 (kernel build, rv64, 4 CPUs, QEMU TCG, `CONFIG_SCHED_CPULOAD_CRITMONITOR`) | FAIL: the same, and the thread on CPU 1 has 1 ms of CPU time | 25/25 PASS (exited thread 300 ms, thread on CPU 1 500 ms) | | rv-virt:ksmp64 with only the first two commits | - | the thread on CPU 1 PASS (500 ms), the exited thread FAIL (0 ms) | A cmocka test for nuttx-apps (a PR that follows this one; qemu-armv8a:nsh, flat build): 10 of 12 cases FAIL on master; with this PR 11 PASS and the SMP case is skipped. LTP (testing/ltp, qemu-armv8a:nsh): `timer_create` 10-1 and 11-1 and `pthread_create` 11-1 failed on master with `EINVAL`; with this PR they PASS, and `timer_create` 1-1, `timer_gettime` 1-1, `clock_gettime` 4-1 and `clock_getcpuclockid` 1-1 still PASS. ostest exits with status 0 on qemu-armv8a:knsh, qemu-armv8a:nsh and rv-virt:ksmp64, on master and with this PR. After the rebase on master dcaf0be558a, qemu-armv8a:knsh and rv-virt:ksmp64 ran again with the same results. On qemu-armv8a the CPU-time clocks need `CONFIG_ARCH_PERF_EVENTS`, and `qemu_boot.h` gives `up_perf_init()` 62.5 MHz while QEMU counts `PMCCNTR_EL0` at 1 GHz (the clocks run 16 times too fast); the arm64 runs above used 1 GHz in the test tree, and this PR does not change it. Also the PMU counter is started only on the boot CPU, so on qemu-armv8a SMP the CPU time of threads on the other CPUs does not count (on master too); the SMP results are from rv-virt, where the perf time is the global timer. Build with `EXTRAFLAGS="-Wno-cpp -Werror"` and `CONFIG_SCHED_CRITMONITOR`, each OK: nucleo-l432kc:nsh (arm, flat), mps2-an500:knsh (arm, protected), qemu-armv8a:knsh (arm64, kernel), qemu-armv8a:nsh_smp_tickless (arm64, SMP, tickless), rv-virt:knsh64 (rv64, kernel), rv-virt:pnsh64 (rv64, protected); with `CONFIG_SCHED_CPULOAD_CRITMONITOR` also nucleo-l432kc:nsh and qemu-armv8a:nsh_smp (with `CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION=0` and `_CSECTION=0`). nucleo-l432kc:nsh without `CONFIG_SCHED_CRITMONITOR` is OK too. `tools/checkpatch.sh -c -u -m -g` passes. -- 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]
