blob: ff041bdcd081ee813089d07e819c8be6104423b4 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright 2023 Mike Galbraith <efault-AT-gmx.de> */
/* Copyright 2023 Wei Gao <[email protected]> */
/*\
*
* Thread starvation test. On fauluty kernel the test timeouts.
*
* Original reproducer taken from:
* https://lore.kernel.org/lkml/[email protected]/
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sched.h>
#include "tst_test.h"
#include "tst_kconfig.h"
#include "tst_safe_clocks.h"
#include "tst_timer.h"
static char *str_loop;
static long loop = 1000000;
static char *str_timeout;
static int timeout;
#define CALLIBRATE_LOOPS 120000000
static int callibrate(void)
{
int i;
struct timespec start, stop;
long long diff;
for (i = 0; i < CALLIBRATE_LOOPS; i++)
__asm__ __volatile__ ("" : "+g" (i) : :);
SAFE_CLOCK_GETTIME(CLOCK_MONOTONIC_RAW, &start);
for (i = 0; i < CALLIBRATE_LOOPS; i++)
__asm__ __volatile__ ("" : "+g" (i) : :);
SAFE_CLOCK_GETTIME(CLOCK_MONOTONIC_RAW, &stop);
diff = tst_timespec_diff_us(stop, start);
tst_res(TINFO, "CPU did %i loops in %llius", CALLIBRATE_LOOPS, diff);
return diff;
}
static int wait_for_pid(pid_t pid)
{
int status, ret;
again:
ret = waitpid(pid, &status, 0);
if (ret == -1) {
if (errno == EINTR)
goto again;
return -1;
}
if (WIFSIGNALED(status))
return 0;
return -1;
}
static void setup(void)
{
cpu_set_t mask;
int cpu = 0;
long ncpus = tst_ncpus_conf();
if (tst_check_preempt_rt())
tst_brk(TCONF, "This test is not designed for the RT kernel");
CPU_ZERO(&mask);
/* Restrict test to a single cpu */
if (sched_getaffinity(0, sizeof(mask), &mask) < 0)
tst_brk(TBROK | TERRNO, "sched_getaffinity() failed");
if (CPU_COUNT(&mask) == 0)
tst_brk(TBROK, "No cpus available");
while (CPU_ISSET(cpu, &mask) == 0 && cpu < ncpus)
cpu++;
CPU_ZERO(&mask);
CPU_SET(cpu, &mask);
tst_res(TINFO, "Setting affinity to CPU %d", cpu);
if (sched_setaffinity(0, sizeof(mask), &mask) < 0)
tst_brk(TBROK | TERRNO, "sched_setaffinity() failed");
if (tst_parse_long(str_loop, &loop, 1, LONG_MAX))
tst_brk(TBROK, "Invalid number of loop number '%s'", str_loop);
if (tst_parse_int(str_timeout, &timeout, 1, INT_MAX))
tst_brk(TBROK, "Invalid number of timeout '%s'", str_timeout);
if (!timeout)
timeout = callibrate() / 1000;
if (tst_has_slow_kconfig())
tst_brk(TCONF, "Skip test due to slow kernel configuration");
tst_set_runtime(timeout);
}
static void handler(int sig LTP_ATTRIBUTE_UNUSED)
{
if (loop > 0)
--loop;
}
static void child(void)
{
pid_t ppid = getppid();
TST_CHECKPOINT_WAIT(0);
while (1)
SAFE_KILL(ppid, SIGUSR1);
}
static void do_test(void)
{
pid_t child_pid;
child_pid = SAFE_FORK();
if (!child_pid)
child();
SAFE_SIGNAL(SIGUSR1, handler);
TST_CHECKPOINT_WAKE(0);
while (loop)
sleep(1);
SAFE_KILL(child_pid, SIGTERM);
if (!tst_remaining_runtime())
tst_res(TFAIL, "Scheduler starvation reproduced");
else
tst_res(TPASS, "Haven't reproduced scheduler starvation");
TST_EXP_PASS_SILENT(wait_for_pid(child_pid));
}
static struct tst_test test = {
.test_all = do_test,
.setup = setup,
.forks_child = 1,
.options = (struct tst_option[]) {
{"l:", &str_loop, "Number of loops (default 2000000)"},
{"t:", &str_timeout, "Max timeout (default 240s)"},
{}
},
.needs_checkpoints = 1,
};