* Copyright (C) 2013-2021 Canonical, Ltd.
* Copyright (C) 2022-2025 Colin Ian King.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "stress-ng.h"
#include "core-builtin.h"
#if defined(HAVE_UCONTEXT_H)
#include <ucontext.h>
#endif
#define STRESS_CONTEXTS (3)
static const stress_help_t help[] = {
{ NULL, "context N", "start N workers exercising user context" },
{ NULL, "context-ops N", "stop context workers after N bogo operations" },
{ NULL, NULL, NULL }
};
#if defined(HAVE_SWAPCONTEXT) && \
defined(HAVE_UCONTEXT_H)
#define STACK_ALLOC (16384)
typedef struct {
uint32_t check0;
ucontext_t uctx;
uint32_t check1;
} chk_ucontext_t;
typedef struct {
uint32_t check0;
uint32_t check1;
} chk_canary_t;
typedef struct {
chk_ucontext_t cu ALIGN64;
uint8_t *stack;
chk_canary_t canary;
} context_data_t;
static ucontext_t uctx_main;
static context_data_t *context;
static uint64_t context_counter ALIGN64;
static uint64_t stress_max_ops;
static void OPTIMIZE3 stress_thread1(void)
{
register ucontext_t *uctx0 = &context[0].cu.uctx;
register ucontext_t *uctx1 = &context[1].cu.uctx;
register const uint64_t max_ops = stress_max_ops;
if (max_ops) {
do {
(void)swapcontext(uctx0, uctx1);
context_counter++;
} while (stress_continue_flag() && (context_counter < max_ops));
} else {
do {
(void)swapcontext(uctx0, uctx1);
context_counter++;
} while (stress_continue_flag());
}
(void)swapcontext(uctx0, &uctx_main);
}
static void OPTIMIZE3 stress_thread2(void)
{
register ucontext_t *uctx1 = &context[1].cu.uctx;
register ucontext_t *uctx2 = &context[2].cu.uctx;
register const uint64_t max_ops = stress_max_ops;
if (max_ops) {
do {
(void)swapcontext(uctx1, uctx2);
context_counter++;
} while (stress_continue_flag() && (!max_ops || (context_counter < max_ops)));
} else {
do {
(void)swapcontext(uctx1, uctx2);
context_counter++;
} while (stress_continue_flag());
}
(void)swapcontext(uctx1, &uctx_main);
}
static void OPTIMIZE3 stress_thread3(void)
{
register ucontext_t *uctx2 = &context[2].cu.uctx;
register ucontext_t *uctx0 = &context[0].cu.uctx;
register const uint64_t max_ops = stress_max_ops;
if (max_ops) {
do {
(void)swapcontext(uctx2, uctx0);
context_counter++;
} while (stress_continue_flag() && (!max_ops || (context_counter < max_ops)));
} else {
do {
(void)swapcontext(uctx2, uctx0);
context_counter++;
} while (stress_continue_flag());
}
(void)swapcontext(uctx2, &uctx_main);
}
static void (*stress_threads[STRESS_CONTEXTS])(void) = {
stress_thread1,
stress_thread2,
stress_thread3,
};
static int stress_context_init(
stress_args_t *args,
void (*func)(void),
ucontext_t *uctx_link,
context_data_t *context_data)
{
(void)shim_memset(context_data, 0, sizeof(*context_data));
if (getcontext(&context_data->cu.uctx) < 0) {
pr_fail("%s: getcontext failed, errno=%d (%s)\n",
args->name, errno, strerror(errno));
return -1;
}
context_data->stack = (uint8_t *)stress_mmap_populate(NULL,
STACK_ALLOC,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (context_data->stack == MAP_FAILED) {
pr_fail("%s: failed to mmap %d bytes for stack%s, errno=%d (%s)\n",
args->name, STACK_ALLOC, stress_get_memfree_str(),
errno, strerror(errno));
return -1;
}
stress_set_vma_anon_name(context_data->stack, STACK_ALLOC, "context-stack");
context_data->canary.check0 = stress_mwc32();
context_data->canary.check1 = stress_mwc32();
context_data->cu.check0 = context_data->canary.check0;
context_data->cu.check1 = context_data->canary.check1;
context_data->cu.uctx.uc_stack.ss_sp = (void *)context_data->stack;
context_data->cu.uctx.uc_stack.ss_size = STACK_ALLOC;
context_data->cu.uctx.uc_link = uctx_link;
makecontext(&context_data->cu.uctx, func, 0);
return 0;
}
* stress_context()
* stress that exercises CPU context save/restore
*/
static int stress_context(stress_args_t *args)
{
size_t i;
const size_t context_size = 3 * sizeof(*context);
double duration, rate, t;
int rc = EXIT_SUCCESS;
context = (context_data_t *)stress_mmap_populate(NULL,
context_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (context == MAP_FAILED) {
pr_inf("%s: failed to allocate %d x %zu bytes for context buffers%s, skipping stressor\n",
args->name, STRESS_CONTEXTS, sizeof(context_data_t),
stress_get_memfree_str());
return EXIT_NO_RESOURCE;
}
stress_set_vma_anon_name(context, context_size, "context-data");
if (stress_instance_zero(args)) {
pr_dbg("%s: context mapped at %p..%p\n", args->name,
(void *)context,
(void *)((uintptr_t)context + context_size));
}
(void)shim_memset(&uctx_main, 0, sizeof(uctx_main));
context_counter = 0;
stress_max_ops = args->bogo.max_ops * 1000;
for (i = 0; i < STRESS_CONTEXTS; i++) {
if (stress_context_init(args, stress_threads[i], &uctx_main, &context[i]) < 0) {
rc = EXIT_FAILURE;
goto fail;
}
}
stress_set_proc_state(args->name, STRESS_STATE_SYNC_WAIT);
stress_sync_start_wait(args);
stress_set_proc_state(args->name, STRESS_STATE_RUN);
stress_catch_sigsegv();
t = stress_time_now();
if (swapcontext(&uctx_main, &context[0].cu.uctx) < 0) {
pr_fail("%s: swapcontext failed, errno=%d (%s)\n",
args->name, errno, strerror(errno));
rc = EXIT_FAILURE;
goto fail;
}
duration = stress_time_now() - t;
stress_bogo_set(args, context_counter / 1000);
for (i = 0; i < STRESS_CONTEXTS; i++) {
if (context[i].canary.check0 != context[i].cu.check0) {
pr_fail("%s: swapcontext clobbered data before context region\n",
args->name);
rc = EXIT_FAILURE;
}
if (context[i].canary.check1 != context[i].cu.check1) {
pr_fail("%s: swapcontext clobbered data after context region\n",
args->name);
rc = EXIT_FAILURE;
}
}
stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
rate = (duration > 0.0) ? (double)context_counter / duration : 0.0;
stress_metrics_set(args, 0, "swapcontext calls per sec",
rate, STRESS_METRIC_HARMONIC_MEAN);
fail:
for (i = 0; i < STRESS_CONTEXTS; i++) {
if ((context[i].stack != MAP_FAILED) && (context[i].stack))
(void)munmap((void *)context[i].stack, STACK_ALLOC);
}
(void)munmap((void *)context, context_size);
return rc;
}
const stressor_info_t stress_context_info = {
.stressor = stress_context,
.classifier = CLASS_MEMORY | CLASS_CPU,
.verify = VERIFY_ALWAYS,
.help = help
};
#else
const stressor_info_t stress_context_info = {
.stressor = stress_unimplemented,
.classifier = CLASS_MEMORY | CLASS_CPU,
.verify = VERIFY_ALWAYS,
.help = help,
.unimplemented_reason = "built without ucontext.h"
};
#endif