187 lines
5.6 KiB
C
187 lines
5.6 KiB
C
/*
|
|
* 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) */
|