From 84f4417638e9588ebcb43c4ed0280d097c46abf9 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Mon, 11 Dec 2023 10:21:48 -0800 Subject: [PATCH] ANDROID: sched: Initial ksched_football test implementation Reimplementation of the sched_football test from LTP: https://github.com/linux-test-project/ltp/blob/master/testcases/realtime/func/sched_football/sched_football.c But reworked to run in the kernel and utilize mutexes/rt_mutexes to illustrate proper boosting of low priority mutex holders. Cc: Joel Fernandes Cc: Qais Yousef Cc: Ingo Molnar Cc: Peter Zijlstra Cc: Juri Lelli Cc: Vincent Guittot Cc: Dietmar Eggemann Cc: Valentin Schneider Cc: Steven Rostedt Cc: Ben Segall Cc: Zimuzo Ezeozue Cc: Mel Gorman Cc: Will Deacon Cc: Waiman Long Cc: Boqun Feng Cc: "Paul E. McKenney" Cc: Metin Kaya Cc: Xuewen Yan Cc: K Prateek Nayak Cc: Thomas Gleixner Cc: Daniel Lezcano Cc: kernel-team@android.com Change-Id: I26e5496c44bf3b1432e9d335b5509bd228b18f8e Signed-off-by: John Stultz Bug: 306081722 --- v8: * Use rt_mutexes for !CONFIG_SCHED_PROXY_EXEC * Bail if spawned players don't check in within 30s * Numerous spelling corrections from Randy Dunlap and Metin Kaya * Lots of other improvements suggested by Metin Kaya * Minor checkpatch fixups v9: * A bunch more cleanups and improvements suggsted by Metin * Use #defines for magic time and prio values, suggested by Metin * Fail if CONFIG_SCHED_PROXY_EXEC=y but !sched_proxy_exec(), again pointed out by Metin * Fix Kconfig entry to be bool instead of tristate as it doesn't really work as a module yet. Pointed out by Metin * Few minor checkpatch fixups v12: * Renamed to ksched_football to avoid confusion with LTP test * Enabled the test to be re-runnable via sysfs trigger * Enabled the test to be run for longer periods of time via sysfs trigger --- kernel/sched/Makefile | 1 + kernel/sched/test_ksched_football.c | 375 ++++++++++++++++++++++++++++ lib/Kconfig.debug | 12 + 3 files changed, 388 insertions(+) create mode 100644 kernel/sched/test_ksched_football.c diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile index ff1738cecd4c..012d8c78a200 100644 --- a/kernel/sched/Makefile +++ b/kernel/sched/Makefile @@ -33,3 +33,4 @@ obj-y += fair.o obj-y += build_policy.o obj-y += build_utility.o obj-$(CONFIG_ANDROID_VENDOR_HOOKS) += vendor_hooks.o +obj-$(CONFIG_SCHED_RT_INVARIANT_TEST) += test_ksched_football.o diff --git a/kernel/sched/test_ksched_football.c b/kernel/sched/test_ksched_football.c new file mode 100644 index 000000000000..dbb2574c2b69 --- /dev/null +++ b/kernel/sched/test_ksched_football.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Module-based test case for RT scheduling invariant + * + * A reimplementation of my old sched_football test + * found in LTP: + * https://github.com/linux-test-project/ltp/blob/master/testcases/realtime/func/sched_football/sched_football.c + * + * Similar to that test, this tries to validate the RT + * scheduling invariant, that the across N available cpus, the + * top N priority tasks always running. + * + * This is done via having N offensive players that are + * medium priority, which constantly are trying to increment the + * ball_pos counter. + * + * Blocking this are N defensive players that are higher + * priority which just spin on the cpu, preventing the medium + * priority tasks from running. + * + * To complicate this, there are also N defensive low priority + * tasks. These start first and each acquire one of N mutexes. + * The high priority defense tasks will later try to grab the + * mutexes and block, opening a window for the offensive tasks + * to run and increment the ball. If priority inheritance or + * proxy execution is used, the low priority defense players + * should be boosted to the high priority levels, and will + * prevent the mid priority offensive tasks from running. + * + * Copyright © International Business Machines Corp., 2007, 2008 + * Copyright (C) Google, 2023, 2024 + * + * Authors: John Stultz + */ + +#define MODULE_NAME "ksched_football" +#define pr_fmt(fmt) MODULE_NAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("John Stultz "); +MODULE_DESCRIPTION("Test case for RT scheduling invariant"); +MODULE_LICENSE("GPL"); + +atomic_t players_ready; +atomic_t ball_pos; +unsigned long players_per_team; +bool game_over; + +#define DEF_GAME_TIME 10 +#define CHECKIN_TIMEOUT 30 + +#define REF_PRIO 20 +#define FAN_PRIO 15 +#define DEF_HI_PRIO 10 +#define OFF_PRIO 5 +#define DEF_MID_PRIO 3 +#define DEF_LOW_PRIO 2 + +#ifdef CONFIG_SCHED_PROXY_EXEC +struct mutex *mutex_low_list; +struct mutex *mutex_mid_list; +#define test_lock_init(x) mutex_init(x) +#define TEST_LOCK_SIZE sizeof(struct mutex) +#define test_lock(x) mutex_lock(x) +#define test_unlock(x) mutex_unlock(x) +#else +struct rt_mutex *mutex_low_list; +struct rt_mutex *mutex_mid_list; +#define test_lock_init(x) rt_mutex_init(x) +#define TEST_LOCK_SIZE sizeof(struct rt_mutex) +#define test_lock(x) rt_mutex_lock(x) +#define test_unlock(x) rt_mutex_unlock(x) +#endif + +static struct task_struct *create_fifo_thread(int (*threadfn)(void *data), + void *data, char *name, int prio) +{ + struct task_struct *kth; + struct sched_attr attr = { + .size = sizeof(struct sched_attr), + .sched_policy = SCHED_FIFO, + .sched_nice = 0, + .sched_priority = prio, + }; + int ret; + + kth = kthread_create(threadfn, data, name); + if (IS_ERR(kth)) { + pr_warn("%s: Error, kthread_create %s failed\n", __func__, + name); + return kth; + } + ret = sched_setattr_nocheck(kth, &attr); + if (ret) { + kthread_stop(kth); + pr_warn("%s: Error, failed to set SCHED_FIFO for %s\n", + __func__, name); + return ERR_PTR(ret); + } + + wake_up_process(kth); + return kth; +} + +static int spawn_players(int (*threadfn)(void *data), char *name, int prio) +{ + unsigned long current_players, start, i; + struct task_struct *kth; + + current_players = atomic_read(&players_ready); + /* Create players_per_team threads */ + for (i = 0; i < players_per_team; i++) { + kth = create_fifo_thread(threadfn, (void *)i, name, prio); + if (IS_ERR(kth)) + return -1; + } + + start = jiffies; + /* Wait for players_per_team threads to check in */ + while (atomic_read(&players_ready) < current_players + players_per_team) { + msleep(1); + if (jiffies - start > CHECKIN_TIMEOUT * HZ) { + pr_err("%s: Error, %s players took too long to checkin " + "(only %ld of %ld checked in)\n", __func__, name, + (long)atomic_read(&players_ready), + current_players + players_per_team); + return -1; + } + } + return 0; +} + +static int defense_low_thread(void *arg) +{ + long tnum = (long)arg; + + atomic_inc(&players_ready); + test_lock(&mutex_low_list[tnum]); + while (!READ_ONCE(game_over)) { + if (kthread_should_stop()) + break; + schedule(); + } + test_unlock(&mutex_low_list[tnum]); + return 0; +} + +static int defense_mid_thread(void *arg) +{ + long tnum = (long)arg; + + atomic_inc(&players_ready); + test_lock(&mutex_mid_list[tnum]); + test_lock(&mutex_low_list[tnum]); + while (!READ_ONCE(game_over)) { + if (kthread_should_stop()) + break; + schedule(); + } + test_unlock(&mutex_low_list[tnum]); + test_unlock(&mutex_mid_list[tnum]); + return 0; +} + +static int offense_thread(void *arg) +{ + atomic_inc(&players_ready); + while (!READ_ONCE(game_over)) { + if (kthread_should_stop()) + break; + schedule(); + atomic_inc(&ball_pos); + } + return 0; +} + +static int defense_hi_thread(void *arg) +{ + long tnum = (long)arg; + + atomic_inc(&players_ready); + test_lock(&mutex_mid_list[tnum]); + while (!READ_ONCE(game_over)) { + if (kthread_should_stop()) + break; + schedule(); + } + test_unlock(&mutex_mid_list[tnum]); + return 0; +} + +static int crazy_fan_thread(void *arg) +{ + atomic_inc(&players_ready); + while (!READ_ONCE(game_over)) { + if (kthread_should_stop()) + break; + schedule(); + udelay(1000); + msleep(2); + } + return 0; +} + +struct completion referee_done; + +static int referee_thread(void *arg) +{ + unsigned long game_time = (long)arg; + unsigned long final_pos; + + WRITE_ONCE(game_over, false); + pr_info("Started referee, game_time: %ld secs !\n", game_time); + /* Create low priority defensive team */ + if (spawn_players(defense_low_thread, "defense-low-thread", DEF_LOW_PRIO)) + goto out; + + if (spawn_players(defense_mid_thread, "defense-mid-thread", DEF_MID_PRIO)) + goto out; + + /* Create mid priority offensive team */ + if (spawn_players(offense_thread, "offense-thread", OFF_PRIO)) + goto out; + + /* Create high priority defensive team */ + if (spawn_players(defense_hi_thread, "defense-hi-thread", DEF_HI_PRIO)) + goto out; + + /* Create high priority crazy fan threads */ + if (spawn_players(crazy_fan_thread, "crazy-fan-thread", FAN_PRIO)) + goto out; + pr_info("All players checked in! Starting game.\n"); + atomic_set(&ball_pos, 0); + msleep(game_time * 1000); + final_pos = atomic_read(&ball_pos); + WRITE_ONCE(game_over, true); + pr_info("Final ball_pos: %ld\n", final_pos); + WARN_ON(final_pos != 0); +out: + pr_info("Game Over!\n"); + WRITE_ONCE(game_over, true); + complete(&referee_done); + return 0; +} + +DEFINE_MUTEX(run_lock); + +static int play_game(unsigned long game_time) +{ + struct task_struct *kth; + int i, ret = -1; + +#ifdef CONFIG_SCHED_PROXY_EXEC + /* Avoid running test if PROXY_EXEC is built in, but off via cmdline */ + if (!sched_proxy_exec()) { + pr_warn("CONFIG_SCHED_PROXY_EXEC=y but disabled via boot arg, skipping ksched_football tests, as they can hang"); + return -1; + } +#endif + + if (!mutex_trylock(&run_lock)) { + pr_err("Game already running\n"); + return -1; + } + + players_per_team = num_online_cpus(); + + mutex_low_list = kmalloc_array(players_per_team, TEST_LOCK_SIZE, GFP_ATOMIC); + if (!mutex_low_list) + goto out; + + mutex_mid_list = kmalloc_array(players_per_team, TEST_LOCK_SIZE, GFP_ATOMIC); + if (!mutex_mid_list) + goto out_mid_list; + + for (i = 0; i < players_per_team; i++) { + test_lock_init(&mutex_low_list[i]); + test_lock_init(&mutex_mid_list[i]); + } + + atomic_set(&players_ready, 0); + init_completion(&referee_done); + kth = create_fifo_thread(referee_thread, (void *)game_time, "referee-thread", REF_PRIO); + if (IS_ERR(kth)) + goto out_create_fifo; + wait_for_completion(&referee_done); + msleep(2000); + ret = 0; + +out_create_fifo: + kfree(mutex_mid_list); +out_mid_list: + kfree(mutex_low_list); +out: + mutex_unlock(&run_lock); + return ret; +} + +static int game_length = DEF_GAME_TIME; + +static ssize_t start_game_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", game_length); +} + +static ssize_t start_game_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + unsigned long len; + int ret; + + ret = kstrtol(buf, 10, &len); + if (ret < 0) + return ret; + + if (len < DEF_GAME_TIME) { + pr_warn("Game length can't be less then %i\n", DEF_GAME_TIME); + len = DEF_GAME_TIME; + } + play_game(len); + game_length = len; + + return count; +} + +static struct kobj_attribute start_game_attribute = + __ATTR(start_game, 0664, start_game_show, start_game_store); + +static struct attribute *attrs[] = { + &start_game_attribute.attr, + NULL, /* need to NULL terminate the list of attributes */ +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static struct kobject *ksched_football_kobj; + +static int __init test_ksched_football_init(void) +{ + int retval; + +#ifdef CONFIG_SCHED_PROXY_EXEC + /* Avoid running test if PROXY_EXEC is built in, but off via cmdline */ + if (!sched_proxy_exec()) { + pr_warn("CONFIG_SCHED_PROXY_EXEC=y but disabled via boot arg, skipping ksched_football tests, as they can hang"); + return -1; + } +#endif + + ksched_football_kobj = kobject_create_and_add("ksched_football", kernel_kobj); + if (!ksched_football_kobj) + return -ENOMEM; + + /* Create the files associated with this kobject */ + retval = sysfs_create_group(ksched_football_kobj, &attr_group); + if (retval) + kobject_put(ksched_football_kobj); + + return play_game(DEF_GAME_TIME); +} +module_init(test_ksched_football_init); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 5e9ce4e2979f..205872a49ed3 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1315,6 +1315,18 @@ config SCHED_DEBUG that can help debug the scheduler. The runtime overhead of this option is minimal. +config SCHED_RT_INVARIANT_TEST + bool "RT invariant scheduling tester" + depends on DEBUG_KERNEL + help + This option provides a kernel module that runs tests to make + sure the RT invariant holds (top N priority tasks run on N + available cpus). + + Say Y here if you want kernel RT scheduling tests + to be built into the kernel. + Say N if you are unsure. + config SCHED_INFO bool default n