Implement times(2) syscall#162
Conversation
Guests calling times(2) got -ENOSYS: the syscall was absent from dispatch.tbl and abi.h had no SYS_times entry. Shell timing builtins and build tools that poll times() for elapsed/CPU tick accounting fall over on the error return. sys_times reports tms_utime/tms_stime from host getrusage(RUSAGE_SELF). tms_cutime/tms_cstime cannot come from RUSAGE_CHILDREN: the emulator also waits on helper subprocesses (rosettad translate, sysroot tooling), so that aggregate would masquerade helper CPU as guest child time. Instead the proc layer accumulates each reaped guest child's wait4 rusage -- at sys_wait4, sys_waitid, the table-pressure reaper, and the CLONE_VFORK wait -- and times() reads that sum. Fresh guest children start with zeroed counters for free because fork spawns a new emulator process. POSIX leaves autoreaped-children inclusion unspecified, so counting reaper-collected children is conformant. The return value is host CLOCK_MONOTONIC converted to USER_HZ (100) ticks: Linux returns jiffies-since-boot, and POSIX only requires an arbitrary-but-fixed reference point that successive calls can difference. Tick conversion uses the same 100Hz that core/stack.c advertises via AT_CLKTCK, so libc's sysconf(_SC_CLK_TCK) scaling stays consistent. A NULL buffer is accepted as on Linux, returning only the tick count. test-times covers the tick clock advancing with wall time, self and waited-for-child CPU accumulation against a measured burn, the NULL buffer, and EFAULT on a bad pointer.
| pid_t ret = | ||
| wait4(host_pid, &status, mac_options | WNOHANG, &ru); | ||
| if (ret > 0) { | ||
| proc_children_cpu_add(&ru); |
There was a problem hiding this comment.
proc_children_cpu_add runs on every ret > 0, but sys_wait4 passes mac_options built from the guest options, which can carry WUNTRACED/WCONTINUED. A stopped or continued child returns ret > 0 with WIFSTOPPED/WIFCONTINUED and a snapshot of a still-running child, so its CPU lands in cutime/cstime before it exits and again when it is finally reaped -- one child, up to three successful wait4 calls, counted up to three times. Linux only credits child CPU on the terminal reap. Gate the add: if (WIFEXITED(status) || WIFSIGNALED(status)) proc_children_cpu_add(&ru). The WNOHANG-only site and the vfork/reaper sites are unaffected since they only observe terminations.
| wait4(host_pid, &status, mac_options, rusage_gva ? &ru : NULL); | ||
| pid_t ret = wait4(host_pid, &status, mac_options, &ru); | ||
| if (ret > 0) { | ||
| proc_children_cpu_add(&ru); |
There was a problem hiding this comment.
Same gate needed here as the branch above: only add on WIFEXITED(status) || WIFSIGNALED(status), otherwise a stop/continue report inflates cutime/cstime.
| struct rusage ru; | ||
| ret = wait4(host_pid, &status, WNOHANG, &ru); | ||
| if (ret > 0) | ||
| proc_children_cpu_add(&ru); |
There was a problem hiding this comment.
waitid credits child CPU here before the WNOWAIT check at line 1335. Two problems: apply the same terminal-status gate (WIFEXITED/WIFSIGNALED); and when the guest passes WNOWAIT the child must stay waitable with its CPU uncounted until the later consuming reap, but the inner wait4 uses WNOHANG only, so the host zombie is consumed and the credit lands now. Move the add to run only when !(options & LINUX_WNOWAIT) and the status is terminal.
| proc_children_cpu_us(&cutime_us, &cstime_us); | ||
|
|
||
| linux_tms_t tms = { | ||
| .tms_utime = timeval_to_clock_ticks(&self.ru_utime), |
There was a problem hiding this comment.
getrusage(RUSAGE_SELF) aggregates the whole emulator process -- every host thread plus page-table, syscall-servicing, and Rosetta overhead -- so tms_utime/tms_stime over-report relative to a real guest task. Likely unavoidable in this design, but the comment should note the caveat rather than presenting these as the guest's exact CPU times.
| linux_tms_t tms = { | ||
| .tms_utime = timeval_to_clock_ticks(&self.ru_utime), | ||
| .tms_stime = timeval_to_clock_ticks(&self.ru_stime), | ||
| .tms_cutime = (int64_t) (cutime_us / (1000000 / LINUX_USER_HZ)), |
There was a problem hiding this comment.
cutime/cstime divide inline by (1000000 / LINUX_USER_HZ) while tms_utime/tms_stime go through timeval_to_clock_ticks. Same value today; a small usec_to_clock_ticks helper keeps both paths in step if LINUX_USER_HZ ever changes. The tv_nsec / (NSEC_PER_SEC / LINUX_USER_HZ) return expression has the same fragility.
| "utime+stime did not grow"); | ||
| } | ||
|
|
||
| TEST("waited-for child CPU accumulates"); |
There was a problem hiding this comment.
No regression covers the stop/continue or WNOWAIT double-count. Spawn a child, SIGSTOP it, reap with WUNTRACED, and assert cutime does not advance on the stop report; then let it exit and assert it is counted exactly once. Add a WNOWAIT peek that must not advance cutime until the consuming wait. Also assert cstime on its own -- the current checks only test the utime+cstime sum, so a regression zeroing cstime would pass.
Guests calling times(2) got -ENOSYS: the syscall was absent from
dispatch.tbl and abi.h had no SYS_times entry. Shell timing builtins and
build tools that poll times() for elapsed/CPU tick accounting fall over
on the error return.
sys_times fills the four struct tms fields from host getrusage:
RUSAGE_SELF supplies tms_utime/tms_stime and RUSAGE_CHILDREN supplies
tms_cutime/tms_cstime. Guest fork() maps 1:1 to a host fork and
sys_wait4 reaps through host waitpid, so the host's children accounting
covers exactly the guest's waited-for children. The return value is host
CLOCK_MONOTONIC converted to USER_HZ (100) ticks: Linux returns
jiffies-since-boot, and POSIX only requires an arbitrary-but-fixed
reference point that successive calls can difference. Tick conversion
uses the same 100Hz that core/stack.c advertises via AT_CLKTCK, so
libc's sysconf(_SC_CLK_TCK) scaling stays consistent. A NULL buffer is
accepted as on Linux, returning only the tick count.
Testing
test-times(registered in manifest.txt): tick rate viasysconf(_SC_CLK_TCK), basic call, NULL buffer through the raw syscall,
return value advancing across a 60ms sleep, self utime+stime growth
against a measured 100ms CPU burn, cutime/cstime growth after reaping
a child that burns 100ms CPU, and EFAULT on a bad pointer — 7/7.
make checkgreen (driver 81 passed / 0 failed / 3 skipped;skips are pre-existing fixture-absence).
check-syscall-coveragepasses.Summary by cubic
Add the times(2) syscall so guests get correct CPU accounting and an elapsed tick count instead of -ENOSYS. Restores compatibility for shell timing and tools that poll times(), and excludes emulator helper subprocess CPU from child totals.
SYS_timesto the ABI/dispatch and wired thesc_timesforwarder.sys_times: selfutime/stimefromgetrusage(RUSAGE_SELF); childcutime/cstimefrom proc-level accumulators updated at every guest-child reap (wait4insys_wait4,sys_waitid, reaper, andvforkwait). ReturnsCLOCK_MONOTONICin 100 Hz ticks, accepts NULL,-EFAULTon bad pointer; tick rate matchesAT_CLKTCK.test-timestotests/manifest.txt; covers tick advance, self and waited-for child CPU accumulation, NULL buffer, and bad-pointer EFAULT.Written for commit ac520fa. Summary will update on new commits.