Skip to content

Implement times(2) syscall#162

Open
Max042004 wants to merge 1 commit into
sysprog21:mainfrom
Max042004:impl-times
Open

Implement times(2) syscall#162
Max042004 wants to merge 1 commit into
sysprog21:mainfrom
Max042004:impl-times

Conversation

@Max042004

@Max042004 Max042004 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

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

  • New test-times (registered in manifest.txt): tick rate via
    sysconf(_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 check green (driver 81 passed / 0 failed / 3 skipped;
    skips are pre-existing fixture-absence).
  • check-syscall-coverage passes.

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.

  • New Features
    • Added SYS_times to the ABI/dispatch and wired the sc_times forwarder.
    • Implemented sys_times: self utime/stime from getrusage(RUSAGE_SELF); child cutime/cstime from proc-level accumulators updated at every guest-child reap (wait4 in sys_wait4, sys_waitid, reaper, and vfork wait). Returns CLOCK_MONOTONIC in 100 Hz ticks, accepts NULL, -EFAULT on bad pointer; tick rate matches AT_CLKTCK.
    • Added test-times to tests/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.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

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.
Comment thread src/syscall/proc.c
pid_t ret =
wait4(host_pid, &status, mac_options | WNOHANG, &ru);
if (ret > 0) {
proc_children_cpu_add(&ru);

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.

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.

Comment thread src/syscall/proc.c
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);

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.

Same gate needed here as the branch above: only add on WIFEXITED(status) || WIFSIGNALED(status), otherwise a stop/continue report inflates cutime/cstime.

Comment thread src/syscall/proc.c
struct rusage ru;
ret = wait4(host_pid, &status, WNOHANG, &ru);
if (ret > 0)
proc_children_cpu_add(&ru);

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.

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.

Comment thread src/syscall/time.c
proc_children_cpu_us(&cutime_us, &cstime_us);

linux_tms_t tms = {
.tms_utime = timeval_to_clock_ticks(&self.ru_utime),

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.

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.

Comment thread src/syscall/time.c
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)),

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.

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.

Comment thread tests/test-times.c
"utime+stime did not grow");
}

TEST("waited-for child CPU accumulates");

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.

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.

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.

2 participants