mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-05-15 07:34:23 -04:00
kern/test: add some scheduler tests (yields work correctly, all non-special priorities are cooperative/not pre-emptive
This commit is contained in:
parent
ad03be9a38
commit
1f8bf41f0b
8 changed files with 514 additions and 9 deletions
|
@ -14,15 +14,136 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#define CATCH_CONFIG_NOSTDOUT
|
||||
#define CATCH_CONFIG_PREFIX_ALL
|
||||
#define CATCH_CONFIG_DISABLE_EXCEPTIONS
|
||||
#define CATCH_CONFIG_NO_POSIX_SIGNALS
|
||||
#include "catch.hpp"
|
||||
#include "util_common.hpp"
|
||||
#include "util_scoped_heap.hpp"
|
||||
|
||||
namespace ams::test {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit svc::Handle g_read_handles[3] = { svc::InvalidHandle, svc::InvalidHandle, svc::InvalidHandle };
|
||||
constinit svc::Handle g_write_handles[3] = { svc::InvalidHandle, svc::InvalidHandle, svc::InvalidHandle };
|
||||
|
||||
constinit s64 g_thread_wait_ns;
|
||||
constinit bool g_should_switch_threads;
|
||||
constinit bool g_switched_threads;
|
||||
constinit bool g_correct_switch_threads;
|
||||
|
||||
void WaitSynchronization(svc::Handle handle) {
|
||||
s32 dummy;
|
||||
R_ABORT_UNLESS(svc::WaitSynchronization(std::addressof(dummy), std::addressof(handle), 1, -1));
|
||||
}
|
||||
|
||||
void TestYieldHigherOrSamePriorityThread() {
|
||||
/* Wait to run. */
|
||||
WaitSynchronization(g_read_handles[0]);
|
||||
|
||||
/* Reset our event. */
|
||||
R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[0]));
|
||||
|
||||
/* Signal the other thread's event. */
|
||||
R_ABORT_UNLESS(svc::SignalEvent(g_write_handles[1]));
|
||||
|
||||
/* Wait, potentially yielding to the lower/same priority thread. */
|
||||
g_switched_threads = false;
|
||||
svc::SleepThread(g_thread_wait_ns);
|
||||
|
||||
/* Check whether we switched correctly. */
|
||||
g_correct_switch_threads = g_should_switch_threads == g_switched_threads;
|
||||
|
||||
/* Exit. */
|
||||
svc::ExitThread();
|
||||
}
|
||||
|
||||
void TestYieldLowerOrSamePriorityThread() {
|
||||
/* Signal thread the higher/same priority thread to run. */
|
||||
R_ABORT_UNLESS(svc::SignalEvent(g_write_handles[0]));
|
||||
|
||||
/* Wait to run. */
|
||||
WaitSynchronization(g_read_handles[1]);
|
||||
|
||||
/* Reset our event. */
|
||||
R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[1]));
|
||||
|
||||
/* We've switched to the lower/same priority thread. */
|
||||
g_switched_threads = true;
|
||||
|
||||
/* Wait to be instructed to exit. */
|
||||
WaitSynchronization(g_read_handles[2]);
|
||||
|
||||
/* Reset the exit signal. */
|
||||
R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[2]));
|
||||
|
||||
/* Exit. */
|
||||
svc::ExitThread();
|
||||
}
|
||||
|
||||
void TestYieldSamePriority(uintptr_t sp_higher, uintptr_t sp_lower) {
|
||||
/* Test each core. */
|
||||
for (s32 core = 0; core < NumCores; ++core) {
|
||||
for (s32 priority = HighestTestPriority; priority <= LowestTestPriority && !IsPreemptionPriority(core, priority); ++priority) {
|
||||
|
||||
svc::Handle thread_handles[2];
|
||||
|
||||
/* Create threads. */
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core)));
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority, core)));
|
||||
|
||||
/* Start threads. */
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
|
||||
|
||||
/* Wait for higher priority thread. */
|
||||
WaitSynchronization(thread_handles[0]);
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
|
||||
|
||||
/* Signal the lower priority thread to exit. */
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2])));
|
||||
|
||||
/* Wait for the lower priority thread. */
|
||||
WaitSynchronization(thread_handles[1]);
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
|
||||
|
||||
/* Check that the switch was correct. */
|
||||
CATCH_REQUIRE(g_correct_switch_threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestYieldDifferentPriority(uintptr_t sp_higher, uintptr_t sp_lower) {
|
||||
/* Test each core. */
|
||||
for (s32 core = 0; core < NumCores; ++core) {
|
||||
for (s32 priority = HighestTestPriority; priority < LowestTestPriority && !IsPreemptionPriority(core, priority); ++priority) {
|
||||
|
||||
svc::Handle thread_handles[2];
|
||||
|
||||
/* Create threads. */
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core)));
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority + 1, core)));
|
||||
|
||||
/* Start threads. */
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
|
||||
|
||||
/* Wait for higher priority thread. */
|
||||
WaitSynchronization(thread_handles[0]);
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
|
||||
|
||||
/* Signal the lower priority thread to exit. */
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2])));
|
||||
|
||||
/* Wait for the lower priority thread. */
|
||||
WaitSynchronization(thread_handles[1]);
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
|
||||
|
||||
/* Check that the switch was correct. */
|
||||
CATCH_REQUIRE(g_correct_switch_threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CATCH_TEST_CASE( "svc::SleepThread: Thread sleeps for time specified" ) {
|
||||
for (s64 ns = 1; ns < TimeSpan::FromSeconds(1).GetNanoSeconds(); ns *= 2) {
|
||||
const auto start = os::GetSystemTickOrdered();
|
||||
|
@ -34,4 +155,99 @@ namespace ams::test {
|
|||
}
|
||||
}
|
||||
|
||||
CATCH_TEST_CASE( "svc::SleepThread: Yield is behaviorally correct" ) {
|
||||
/* Create events. */
|
||||
for (size_t i = 0; i < util::size(g_write_handles); ++i) {
|
||||
g_read_handles[i] = svc::InvalidHandle;
|
||||
g_write_handles[i] = svc::InvalidHandle;
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CreateEvent(g_write_handles + i, g_read_handles + i)));
|
||||
}
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
for (size_t i = 0; i < util::size(g_write_handles); ++i) {
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(g_read_handles[i])));
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(g_write_handles[i])));
|
||||
g_read_handles[i] = svc::InvalidHandle;
|
||||
g_write_handles[i] = svc::InvalidHandle;
|
||||
}
|
||||
};
|
||||
|
||||
/* Create heap. */
|
||||
ScopedHeap heap(3 * os::MemoryPageSize);
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_None)));
|
||||
ON_SCOPE_EXIT {
|
||||
CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_ReadWrite)));
|
||||
};
|
||||
const uintptr_t sp_higher = heap.GetAddress() + 1 * os::MemoryPageSize;
|
||||
const uintptr_t sp_lower = heap.GetAddress() + 3 * os::MemoryPageSize;
|
||||
|
||||
CATCH_SECTION("svc::SleepThread: Yields do not switch to a thread of lower priority.") {
|
||||
/* Test yield without migration. */
|
||||
{
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = false;
|
||||
g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithoutCoreMigration);
|
||||
|
||||
TestYieldDifferentPriority(sp_higher, sp_lower);
|
||||
}
|
||||
|
||||
/* Test yield with migration. */
|
||||
{
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = false;
|
||||
g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithoutCoreMigration);
|
||||
|
||||
TestYieldDifferentPriority(sp_higher, sp_lower);
|
||||
}
|
||||
}
|
||||
|
||||
CATCH_SECTION("svc::SleepThread: ToAnyThread switches to a thread of same or lower priority.") {
|
||||
/* Test to same priority. */
|
||||
{
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = true;
|
||||
g_thread_wait_ns = static_cast<s64>(svc::YieldType_ToAnyThread);
|
||||
|
||||
TestYieldSamePriority(sp_higher, sp_lower);
|
||||
}
|
||||
|
||||
/* Test to lower priority. */
|
||||
{
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = true;
|
||||
g_thread_wait_ns = static_cast<s64>(svc::YieldType_ToAnyThread);
|
||||
|
||||
TestYieldDifferentPriority(sp_higher, sp_lower);
|
||||
}
|
||||
}
|
||||
|
||||
CATCH_SECTION("svc::SleepThread: Yield switches to another thread of same priority.") {
|
||||
/* Test yield without migration. */
|
||||
{
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = true;
|
||||
g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithoutCoreMigration);
|
||||
|
||||
TestYieldSamePriority(sp_higher, sp_lower);
|
||||
}
|
||||
|
||||
/* Test yield with migration. */
|
||||
{
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = true;
|
||||
g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithCoreMigration);
|
||||
|
||||
TestYieldSamePriority(sp_higher, sp_lower);
|
||||
}
|
||||
}
|
||||
|
||||
CATCH_SECTION("svc::SleepThread: Yield with bogus timeout does not switch to another thread same priority") {
|
||||
/* Configure for yield test. */
|
||||
g_should_switch_threads = false;
|
||||
g_thread_wait_ns = INT64_C(-5);
|
||||
|
||||
TestYieldSamePriority(sp_higher, sp_lower);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue