123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- /*
- * Copyright (c) 2017-2019 Richard Braun.
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- *
- *
- * This test module is a stress test, expected to never terminate, of
- * priority inheritance with mutexes. It creates a priority inheritance
- * tree by starting multiple threads manipulating multiple mutexes.
- *
- * Here is one intended state of the priority inheritance tree :
- *
- *
- * C->M2-+
- * |
- * D--+ +->B->M1->A
- * | |
- * +->M3-+
- * |
- * E--+
- *
- *
- * M1,M2,M3,etc... are mutexes and A,B,C,etc... are threads. The thread
- * priorities p(thread) are ordered such that p(A) < p(B) < p(C) etc...
- * An arrow from a mutex to a thread indicates ownership, such that
- * M1->A means that A owns M1. An arrow from a thread to a mutex indicates
- * waiting, such that B->M1 means that B is waiting for M1 to be unlocked.
- *
- * In addition, thread B is actually many threads, each terminating after
- * unlocking their mutexes. Also, the priority of thread C is regularly
- * increased to p(E) + 1 and later restored to p(C).
- *
- * Here is the list of all the cases this test must cover :
- * - Priority inheritance: All threads can get their real priority boosted.
- * C can be boosted if it owns M2 and B waits for it while inheriting
- * from E, and E can be boosted when p(C) is temporarily increased.
- * - Return to normal priority: after releasing all locks, a thread must
- * have reset its real priority to its user priority.
- * - Priority changes: When the priority of C is increased, that priority
- * must take precedence over all others.
- * - Thread destruction resilience: Check that priority propagation never
- * accesses a destroyed thread.
- *
- * Note that this test doesn't check that priority propagation correctly
- * adjusts the top priority after lowering the priority of thread C back
- * to p(C).
- *
- * In order to artificially create priority inversions, all threads run on
- * separate processors, making this test require 5 processors.
- *
- * The test should output a couple of messages about thread priorities
- * being boosted, and then frequent updates from each thread to show
- * they're all making progress. Thread B suffers from contention the most
- * so its report frequency should be lower. Thread A suffers from contention
- * the least and should be the most frequent to report progress. Because of
- * contention from B, D and E on M3, D rarely gets boosted. The reason is
- * that, when B owns the mutex, E is likely to wait on M3 soon enough that
- * it will be awoken before D, preventing the conditions for priority
- * inheritance to occur.
- *
- * Note that the test uses regular mutexes instead of real-time mutexes,
- * so that its behaviour can be analyzed for both types depending on
- * build options.
- */
- #include <stddef.h>
- #include <stdio.h>
- #include <string.h>
- #include <kern/cpumap.h>
- #include <kern/error.h>
- #include <kern/init.h>
- #include <kern/mutex.h>
- #include <kern/panic.h>
- #include <kern/syscnt.h>
- #include <kern/thread.h>
- #include <kern/turnstile.h>
- #include <test/test.h>
- #define TEST_MIN_CPUS 5
- #define TEST_PRIO_A (THREAD_SCHED_RT_PRIO_MIN + 1)
- #define TEST_PRIO_B (TEST_PRIO_A + 1)
- #define TEST_PRIO_C (TEST_PRIO_B + 1)
- #define TEST_PRIO_D (TEST_PRIO_C + 1)
- #define TEST_PRIO_E (TEST_PRIO_D + 1)
- #define TEST_NR_LOCK_LOOPS 500
- #define TEST_NR_CONSUME_CPU_LOOPS 10000000
- static struct mutex test_mutex_1;
- static struct mutex test_mutex_2;
- static struct mutex test_mutex_3;
- static const char *
- test_thread_from_priority(unsigned short priority)
- {
- switch (priority) {
- case TEST_PRIO_A:
- return "a";
- case TEST_PRIO_B:
- return "b";
- case TEST_PRIO_C:
- return "c";
- case TEST_PRIO_D:
- return "d";
- case TEST_PRIO_E:
- return "e";
- case TEST_PRIO_E + 1:
- return "e+";
- default:
- panic("invalid priority %u", priority);
- }
- }
- static char
- test_get_name(void)
- {
- const char *name;
- size_t length;
- name = thread_self()->name;
- length = strlen(name);
- return name[length - 1];
- }
- static void
- test_delay(void)
- {
- volatile unsigned int i;
- /*
- * Put the thread to sleep to make some CPU time available, and then
- * busy-wait to avoid synchronizing all threads on the clock tick.
- */
- thread_delay(1, false);
- for (i = 0; i < TEST_NR_CONSUME_CPU_LOOPS; i++);
- }
- static void
- test_check_initial_priority(void)
- {
- unsigned short user_priority, real_priority;
- struct thread *thread;
- thread = thread_self();
- user_priority = thread_user_priority(thread);
- real_priority = thread_real_priority(thread);
- if (user_priority != real_priority) {
- panic("%c: invalid initial priority %hu",
- test_get_name(), real_priority);
- }
- }
- static void
- test_for_priority_boosted(unsigned short *highest_priority)
- {
- unsigned short user_priority, real_priority;
- struct turnstile_td *td;
- struct thread *thread;
- thread = thread_self();
- td = thread_turnstile_td(thread);
- turnstile_td_lock(td);
- user_priority = thread_user_priority(thread);
- real_priority = thread_real_priority(thread);
- if (user_priority != real_priority) {
- if (user_priority > real_priority) {
- panic("%c: invalid real priority: %hu (boosted:%u)",
- test_get_name(), real_priority, thread->boosted);
- }
- if (real_priority > *highest_priority) {
- printf("%c: real priority boosted to %s\n",
- test_get_name(), test_thread_from_priority(real_priority));
- *highest_priority = real_priority;
- }
- }
- turnstile_td_unlock(td);
- }
- static void
- test_for_priority_deboosted(void)
- {
- unsigned short user_priority, real_priority;
- struct turnstile_td *td;
- struct thread *thread;
- thread = thread_self();
- td = thread_turnstile_td(thread);
- turnstile_td_lock(td);
- user_priority = thread_user_priority(thread);
- real_priority = thread_real_priority(thread);
- if (user_priority != real_priority) {
- panic("%c: real priority not reset (boosted:%d)", test_get_name(), thread->boosted);
- }
- turnstile_td_unlock(td);
- }
- static void
- test_report_progress(unsigned int i)
- {
- printf("%c:%u ", test_get_name(), i);
- }
- static void
- test_a(void *arg)
- {
- unsigned short highest_priority;
- unsigned int i, j;
- (void)arg;
- test_check_initial_priority();
- highest_priority = 0;
- for (i = 1; /* no condition */; i++) {
- for (j = 0; j < TEST_NR_LOCK_LOOPS; j++) {
- mutex_lock(&test_mutex_1);
- test_delay();
- test_for_priority_boosted(&highest_priority);
- mutex_unlock(&test_mutex_1);
- test_for_priority_deboosted();
- test_delay();
- }
- test_report_progress(i);
- }
- }
- static void
- test_b(void *arg)
- {
- test_check_initial_priority();
- mutex_lock(&test_mutex_3);
- mutex_lock(&test_mutex_2);
- mutex_lock(&test_mutex_1);
- test_delay();
- test_for_priority_boosted(arg);
- mutex_unlock(&test_mutex_1);
- test_delay();
- mutex_unlock(&test_mutex_2);
- test_delay();
- mutex_unlock(&test_mutex_3);
- /*
- * It would be better if the thread could immediately terminate, but
- * it's also the thread that locks multiple mutexes, so make sure it
- * was correctly deboosted. This should be cheap enough to not matter
- * much.
- */
- test_for_priority_deboosted();
- }
- static void
- test_manage_b(void *arg)
- {
- unsigned short highest_priority;
- struct thread_attr attr;
- struct thread *thread_b;
- struct cpumap *cpumap;
- unsigned int i, j;
- int error;
- (void)arg;
- error = cpumap_create(&cpumap);
- error_check(error, "cpumap_create");
- cpumap_zero(cpumap);
- cpumap_set(cpumap, 1);
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_b");
- thread_attr_set_policy(&attr, THREAD_SCHED_POLICY_FIFO);
- thread_attr_set_priority(&attr, TEST_PRIO_B);
- thread_attr_set_cpumap(&attr, cpumap);
- cpumap_destroy(cpumap);
- highest_priority = 0;
- for (i = 1; /* no condition */; i++) {
- for (j = 0; j < TEST_NR_LOCK_LOOPS; j++) {
- error = thread_create(&thread_b, &attr, test_b, &highest_priority);
- error_check(error, "thread_create");
- thread_join(thread_b);
- test_delay();
- }
- printf("b:%u ", i);
- syscnt_info("thread_boosts", log_info);
- }
- }
- static void
- test_c(void *arg)
- {
- unsigned short highest_priority;
- unsigned int i, j;
- (void)arg;
- test_check_initial_priority();
- highest_priority = 0;
- for (i = 1; /* no condition */; i++) {
- for (j = 0; j < TEST_NR_LOCK_LOOPS; j++) {
- mutex_lock(&test_mutex_2);
- test_delay();
- test_for_priority_boosted(&highest_priority);
- mutex_unlock(&test_mutex_2);
- test_for_priority_deboosted();
- test_delay();
- }
- test_report_progress(i);
- }
- }
- static void
- test_chprio_c(void *arg)
- {
- struct thread *thread_c;
- thread_c = arg;
- test_delay();
- for (;;) {
- thread_setscheduler(thread_c, THREAD_SCHED_POLICY_FIFO,
- TEST_PRIO_E + 1);
- thread_setscheduler(thread_c, THREAD_SCHED_POLICY_FIFO,
- TEST_PRIO_C);
- }
- }
- static void
- test_d(void *arg)
- {
- unsigned short highest_priority;
- unsigned int i, j;
- (void)arg;
- test_check_initial_priority();
- highest_priority = 0;
- for (i = 1; /* no condition */; i++) {
- for (j = 0; j < TEST_NR_LOCK_LOOPS; j++) {
- mutex_lock(&test_mutex_3);
- test_delay();
- test_for_priority_boosted(&highest_priority);
- mutex_unlock(&test_mutex_3);
- test_for_priority_deboosted();
- test_delay();
- }
- test_report_progress(i);
- }
- }
- static void
- test_e(void *arg)
- {
- unsigned short highest_priority;
- unsigned int i, j;
- (void)arg;
- test_check_initial_priority();
- highest_priority = 0;
- for (i = 1; /* no condition */; i++) {
- for (j = 0; j < TEST_NR_LOCK_LOOPS; j++) {
- mutex_lock(&test_mutex_3);
- test_delay();
- test_for_priority_boosted(&highest_priority);
- mutex_unlock(&test_mutex_3);
- test_for_priority_deboosted();
- test_delay();
- }
- test_report_progress(i);
- }
- }
- void __init
- test_setup(void)
- {
- struct thread_attr attr;
- struct thread *thread;
- struct cpumap *cpumap;
- int error;
- if (cpu_count() < TEST_MIN_CPUS) {
- panic("test: at least %u processors are required", TEST_MIN_CPUS);
- }
- mutex_init(&test_mutex_1);
- mutex_init(&test_mutex_2);
- mutex_init(&test_mutex_3);
- error = cpumap_create(&cpumap);
- error_check(error, "cpumap_create");
- cpumap_zero(cpumap);
- cpumap_set(cpumap, 0);
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_a");
- thread_attr_set_detached(&attr);
- thread_attr_set_policy(&attr, THREAD_SCHED_POLICY_FIFO);
- thread_attr_set_priority(&attr, TEST_PRIO_A);
- thread_attr_set_cpumap(&attr, cpumap);
- error = thread_create(&thread, &attr, test_a, NULL);
- error_check(error, "thread_create");
- cpumap_zero(cpumap);
- cpumap_set(cpumap, 1);
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_manage_b");
- thread_attr_set_detached(&attr);
- thread_attr_set_policy(&attr, THREAD_SCHED_POLICY_FIFO);
- thread_attr_set_priority(&attr, TEST_PRIO_B);
- thread_attr_set_cpumap(&attr, cpumap);
- error = thread_create(&thread, &attr, test_manage_b, NULL);
- error_check(error, "thread_create");
- cpumap_zero(cpumap);
- cpumap_set(cpumap, 2);
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_c");
- thread_attr_set_detached(&attr);
- thread_attr_set_policy(&attr, THREAD_SCHED_POLICY_FIFO);
- thread_attr_set_priority(&attr, TEST_PRIO_C);
- thread_attr_set_cpumap(&attr, cpumap);
- error = thread_create(&thread, &attr, test_c, NULL);
- error_check(error, "thread_create");
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_chprio_c");
- thread_attr_set_detached(&attr);
- error = thread_create(&thread, &attr, test_chprio_c, thread);
- error_check(error, "thread_create");
- cpumap_zero(cpumap);
- cpumap_set(cpumap, 3);
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_d");
- thread_attr_set_detached(&attr);
- thread_attr_set_policy(&attr, THREAD_SCHED_POLICY_FIFO);
- thread_attr_set_priority(&attr, TEST_PRIO_D);
- thread_attr_set_cpumap(&attr, cpumap);
- error = thread_create(&thread, &attr, test_d, NULL);
- error_check(error, "thread_create");
- cpumap_zero(cpumap);
- cpumap_set(cpumap, 4);
- thread_attr_init(&attr, THREAD_KERNEL_PREFIX "test_e");
- thread_attr_set_detached(&attr);
- thread_attr_set_policy(&attr, THREAD_SCHED_POLICY_FIFO);
- thread_attr_set_priority(&attr, TEST_PRIO_E);
- thread_attr_set_cpumap(&attr, cpumap);
- error = thread_create(&thread, &attr, test_e, NULL);
- error_check(error, "thread_create");
- cpumap_destroy(cpumap);
- }
|