feat(times): times (L2 XSI) + CLK_TCK sysconf
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
#ifndef VLIBC_SYS_TIMES_H
|
||||
#define VLIBC_SYS_TIMES_H
|
||||
|
||||
/*
|
||||
* vlibc — <sys/times.h>.
|
||||
*
|
||||
* Process CPU-time accounting in clock ticks. times() and struct tms are
|
||||
* XSI [CX] in POSIX.1-2008 (glibc gates them behind _XOPEN_SOURCE, not
|
||||
* _POSIX_C_SOURCE), so the whole surface sits at level 2 — nothing here is
|
||||
* POSIX.1-2008 base:
|
||||
*
|
||||
* Level 2 (muslmimic): times, struct tms.
|
||||
*
|
||||
* clock_t is the scalar tick type owned by <sys/types.h> (typedef long
|
||||
* clock_t; <time.h> pulls it from the same place for clock()); this header
|
||||
* includes that one rather than re-typedefing clock_t. The unit of every
|
||||
* times() figure is the tick: CLK_TCK per second, the fixed x86_64 USER_HZ
|
||||
* constant of 100 that todo 32's sysconf(_SC_CLK_TCK) reports (defined
|
||||
* once, in src/misc/sysconf.c).
|
||||
*
|
||||
* struct tms mirrors the x86_64 kernel's __kernel_tms exactly: four longs
|
||||
* in the same order (tms_utime, tms_stime, tms_cutime, tms_cstime), which
|
||||
* is why src/sys/times.c hands the struct straight to SYS_times. The kernel
|
||||
* writes all four words on every successful call, so no field is optional.
|
||||
*
|
||||
* The declaration carries no intent attribute: times() performs a syscall
|
||||
* with side effects and reports failures through errno, so const/pure
|
||||
* would be unsound (see the rationale in <sys/time.h>).
|
||||
*/
|
||||
|
||||
#include <vlibc/features.h>
|
||||
|
||||
#include <sys/types.h> /* clock_t */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
/* Level 2 (muslmimic): XSI process-CPU-time accounting. */
|
||||
|
||||
/*
|
||||
* CPU-time accounting for the calling process and its waited-for children,
|
||||
* measured in clock ticks (CLK_TCK per second). tms_utime and tms_stime
|
||||
* are the user and system CPU time of the calling process; tms_cutime and
|
||||
* tms_cstime are the user and system CPU time consumed by children that
|
||||
* have been waited for (all four zeroed before any child is waited on).
|
||||
*/
|
||||
struct tms
|
||||
{
|
||||
clock_t tms_utime; /* user CPU time of the calling process */
|
||||
clock_t tms_stime; /* system CPU time of the calling process */
|
||||
clock_t tms_cutime; /* user CPU time of waited-for children */
|
||||
clock_t tms_cstime; /* system CPU time of waited-for children */
|
||||
};
|
||||
|
||||
/*
|
||||
* Store the process CPU-time accounting through buf and return the number
|
||||
* of clock ticks since an arbitrary fixed point in the past (the Linux
|
||||
* kernel reports ticks since boot). Return (clock_t)-1 with errno set when
|
||||
* buf points outside the address space.
|
||||
*/
|
||||
clock_t
|
||||
times(struct tms *buf);
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VLIBC_SYS_TIMES_H */
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/times.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
|
||||
/*
|
||||
* times(): process CPU-time accounting in CLK_TCK ticks.
|
||||
*
|
||||
* Everything here is XSI — times() is [CX] in POSIX.1-2008, NOT base — so
|
||||
* the whole file is gated at level 2 (whole-file-L2 rule; the wiring pass
|
||||
* puts it in VLIBC_LEVEL2_SRCS, and at level 1 this TU compiles empty).
|
||||
*
|
||||
* SYS_times writes a struct __kernel_tms, which on x86_64 is exactly four
|
||||
* longs (tms_utime, tms_stime, tms_cutime, tms_cstime) — byte-for-byte the
|
||||
* public struct tms from <sys/times.h> (clock_t is long here). The kernel
|
||||
* therefore fills the caller's struct in place; no private mirror is
|
||||
* needed. The syscall's return value is the tick counter since boot, which
|
||||
* is clock_t (long) — a value that can legitimately exceed INT_MAX on a
|
||||
* long-lived host — so the error translation is inlined below with the same
|
||||
* semantics as syscall_ret() instead of routing the full-width counter
|
||||
* through it (house idiom, see src/unistd/lseek.c).
|
||||
*
|
||||
* The tick unit is CLK_TCK (100 per second on x86_64), the fixed USER_HZ
|
||||
* constant reported by sysconf(_SC_CLK_TCK) and defined ONCE in
|
||||
* src/misc/sysconf.c; times() consumes that unit rather than re-defining
|
||||
* it. clock_t here is therefore the raw kernel tick count, matching POSIX
|
||||
* XSI (where the clock_t unit is CLK_TCK); this deliberately does NOT
|
||||
* rescale to the 1 MHz clock() unit that <time.h>'s clock() uses (see
|
||||
* src/time/clock.c, which reads the same SYS_times through its own struct
|
||||
* tms mirror and scales by CLOCKS_PER_SEC / 100).
|
||||
*/
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* Fill *buf with the CPU times of the calling process (tms_utime,
|
||||
* tms_stime) and of its waited-for children (tms_cutime, tms_cstime) in
|
||||
* CLK_TCK ticks, and return the number of clock ticks since boot. On
|
||||
* failure — buf pointing outside the address space — return (clock_t)-1
|
||||
* with errno set; per POSIX times() has no other error returns.
|
||||
*/
|
||||
clock_t
|
||||
times(struct tms *buf)
|
||||
{
|
||||
long r = __syscall1(SYS_times, (long)buf);
|
||||
|
||||
if (r < 0 && r > -4096)
|
||||
{
|
||||
errno = (int)-r;
|
||||
return (clock_t)-1;
|
||||
}
|
||||
return (clock_t)r;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* vlibc — sys/times.h test (todo 35).
|
||||
*
|
||||
* Exercises the whole <sys/times.h> surface end to end:
|
||||
*
|
||||
* 1. times(&t) succeeds: the returned tick counter is not -1 and the four
|
||||
* struct tms fields are non-negative (fresh processes commonly show
|
||||
* zeros here, which is legal).
|
||||
* 2. Monotonicity across real work: a few-million-iteration busy loop
|
||||
* burns user CPU, and a second times() read then reports tms_utime no
|
||||
* smaller than the first. The tick counter itself must never go
|
||||
* backwards between two reads.
|
||||
* 3. A rapid-fire loop of times() calls (no work between them) keeps the
|
||||
* tick counter strictly non-decreasing across every adjacent pair.
|
||||
*
|
||||
* The -f mode runs the one failure shape — times() on an out-of-range
|
||||
* buffer pointer, which the kernel rejects with EFAULT — asserting the
|
||||
* return value only (errno is never read: vlibc's errno slot collides
|
||||
* with the host TCB). It leaves via a raw SYS_exit_group so no host
|
||||
* cleanup runs after the library's errno write (house pattern,
|
||||
* tests/test_stat.c).
|
||||
*
|
||||
* No host libc headers are included (the -Iinclude path would shadow
|
||||
* GCC's internal headers); diagnostics go through raw SYS_write. The whole
|
||||
* body mirrors the header's gate: <sys/times.h> is entirely level 2, so at
|
||||
* level 1 this TU compiles to a no-op runner.
|
||||
*/
|
||||
|
||||
#include <sys/times.h>
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. The
|
||||
* optimize attribute keeps GCC from lowering the length loop into a
|
||||
* strlen call, which would leave a vlibc-owned symbol undefined in this
|
||||
* host-linked standalone binary (house idiom, see src/string). */
|
||||
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
check(int ok, const char *msg)
|
||||
{
|
||||
if (ok)
|
||||
{
|
||||
say(1, "ok ");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "FAIL ");
|
||||
failures++;
|
||||
}
|
||||
say(1, msg);
|
||||
say(1, "\n");
|
||||
}
|
||||
|
||||
/* Burn a few million iterations of real user CPU so tms_utime has a chance
|
||||
* to advance between two times() reads. The sink is volatile so the loop
|
||||
* cannot be optimized away. */
|
||||
static volatile unsigned long t35_sink;
|
||||
|
||||
static void
|
||||
burn_cpu(void)
|
||||
{
|
||||
for (unsigned long i = 0; i < 5000000UL; i++)
|
||||
{
|
||||
t35_sink += i;
|
||||
}
|
||||
}
|
||||
|
||||
/* 1-3. Success shapes: plausible fields, monotonic across work and across
|
||||
* rapid reads. */
|
||||
static void
|
||||
times_scenario(void)
|
||||
{
|
||||
struct tms t1;
|
||||
struct tms t2;
|
||||
struct tms probe;
|
||||
clock_t r1;
|
||||
clock_t r2;
|
||||
clock_t prev;
|
||||
|
||||
r1 = times(&t1);
|
||||
check(r1 != (clock_t)-1, "times(&t) does not return -1");
|
||||
check(t1.tms_utime >= 0 && t1.tms_stime >= 0, "tms_utime/tms_stime are non-negative");
|
||||
check(t1.tms_cutime >= 0 && t1.tms_cstime >= 0, "tms_cutime/tms_cstime are non-negative");
|
||||
|
||||
burn_cpu();
|
||||
r2 = times(&t2);
|
||||
check(r2 != (clock_t)-1, "second times(&t) does not return -1");
|
||||
check(r2 >= r1, "tick counter does not go backwards across work");
|
||||
check(t2.tms_utime >= t1.tms_utime, "tms_utime is nondecreasing after a CPU burn");
|
||||
check(t2.tms_stime >= t1.tms_stime, "tms_stime is nondecreasing after a CPU burn");
|
||||
check(t2.tms_cutime >= t1.tms_cutime && t2.tms_cstime >= t1.tms_cstime,
|
||||
"child-time fields are nondecreasing (no waited-for children here)");
|
||||
|
||||
/* Rapid-fire reads with no work between them: the kernel tick counter
|
||||
* is monotonic, so every adjacent pair must be non-decreasing. */
|
||||
prev = times(&probe);
|
||||
check(prev != (clock_t)-1, "rapid-fire times() baseline is not -1");
|
||||
for (int i = 0; i < 100000; i++)
|
||||
{
|
||||
clock_t now = times(&probe);
|
||||
|
||||
if (now == (clock_t)-1)
|
||||
{
|
||||
check(0, "rapid-fire times() never returns -1");
|
||||
break;
|
||||
}
|
||||
if (now < prev)
|
||||
{
|
||||
check(0, "rapid-fire tick counter never goes backwards");
|
||||
break;
|
||||
}
|
||||
prev = now;
|
||||
}
|
||||
check(prev != (clock_t)-1, "rapid-fire times() loop completed");
|
||||
}
|
||||
|
||||
/* The failure shapes; return values only, never errno. Exit via a raw
|
||||
* SYS_exit_group: the library's errno write on this path corrupts glibc's
|
||||
* private dtv slot at %fs:0+8, so host cleanup must never run (house
|
||||
* pattern, tests/test_stat.c).
|
||||
*
|
||||
* The probe buffer is the page-start address 1: non-NULL (the modern
|
||||
* kernel treats times(NULL) as legal and returns the tick counter without
|
||||
* writing anything) but unmapped, so SYS_times' copy_to_user fails with
|
||||
* EFAULT and the wrapper returns -1. */
|
||||
static void
|
||||
failure_scenarios(void)
|
||||
{
|
||||
check(times((struct tms *)(unsigned long)1) == (clock_t)-1,
|
||||
"-f times(out-of-range buf) returns -1");
|
||||
say(1, "times: raw kernel ticks reported; not rescaled to clock() units\n");
|
||||
|
||||
__syscall1(SYS_exit_group, failures == 0 ? 0 : 1);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
||||
{
|
||||
failure_scenarios();
|
||||
}
|
||||
|
||||
times_scenario();
|
||||
|
||||
if (failures == 0)
|
||||
{
|
||||
say(1, "all times tests passed\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "FAILURES\n");
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
#else /* !VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/*
|
||||
* Level 1: every <sys/times.h> symbol is gated at level 2, so there is
|
||||
* nothing to run. Keep the TU compilable at any configured profile.
|
||||
*/
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
say(1, "SKIP: <sys/times.h> is level 2, not available here\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
Reference in New Issue
Block a user