genode/repos/base/src/test/thread/main.cc
2016-05-23 15:52:37 +02:00

361 lines
8.0 KiB
C++

/*
* \brief Testing thread library
* \author Alexander Boettcher
* \author Christian Helmuth
* \date 2013-12-13
*/
/*
* Copyright (C) 2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <base/printf.h>
#include <base/log.h>
#include <base/thread.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <util/volatile_object.h>
#include <cpu_session/connection.h>
using namespace Genode;
/*********************************
** Stack-allocator concurrency **
*********************************/
template <int CHILDREN>
struct Helper : Thread
{
void *child[CHILDREN];
enum { STACK_SIZE = 0x2000 };
Env &_env;
Helper(Env &env) : Thread(env, "helper", STACK_SIZE), _env(env) { }
void *stack() const { return _stack; }
void entry()
{
Lazy_volatile_object<Helper> helper[CHILDREN];
for (unsigned i = 0; i < CHILDREN; ++i)
helper[i].construct(_env);
for (unsigned i = 0; i < CHILDREN; ++i)
child[i] = helper[i]->stack();
}
};
static void test_stack_alloc(Env &env)
{
log("running '", __func__, "'");
/*
* Create HELPER threads, which concurrently create CHILDREN threads each.
* This most likely triggers any race in the stack allocation.
*/
enum { HELPER = 10, CHILDREN = 9 };
Lazy_volatile_object<Helper<CHILDREN> > helper[HELPER];
for (unsigned i = 0; i < HELPER; ++i) helper[i].construct(env);
for (unsigned i = 0; i < HELPER; ++i) helper[i]->start();
for (unsigned i = 0; i < HELPER; ++i) helper[i]->join();
if (0)
for (unsigned i = 0; i < HELPER; ++i)
for (unsigned j = 0; j < CHILDREN; ++j)
log(helper[i]->child[j], " [", i, ".", j, "]");
}
/*********************
** Stack alignment **
*********************/
/*
* Aligned FPU instruction accesses are very useful to identify stack-alignment
* issues. Fortunately, GCC generates pushes of FPU register content for
* vararg functions if floating-point values are passed to the function.
*/
static void test_stack_alignment_varargs(char const *format, ...) __attribute__((noinline));
static void test_stack_alignment_varargs(char const *format, ...)
{
va_list list;
va_start(list, format);
vprintf(format, list);
va_end(list);
}
static void log_stack_address(char const *who)
{
long dummy;
log(who, " stack @ ", &dummy);
}
struct Stack_helper : Thread
{
enum { STACK_SIZE = 0x2000 };
Stack_helper(Env &env) : Thread(env, "stack_helper", STACK_SIZE) { }
void entry()
{
log_stack_address("helper");
test_stack_alignment_varargs("%f\n%g\n", 3.142, 2.718);
}
};
static void test_stack_alignment(Env &env)
{
log("running '", __func__, "'");
Stack_helper helper(env);
helper.start();
helper.join();
log_stack_address("main");
test_stack_alignment_varargs("%f\n%g\n", 3.142, 2.718);
}
/****************************
** Main-thread stack area **
****************************/
static void test_main_thread()
{
log("running '", __func__, "'");
/* check wether my thread object exists */
Thread * myself = Thread::myself();
if (!myself) { throw -1; }
log("thread base ", myself);
/* check whether my stack is inside the first stack region */
addr_t const stack_slot_base = Thread::stack_area_virtual_base();
addr_t const stack_slot_size = Thread::stack_area_virtual_size();
addr_t const stack_slot_top = stack_slot_base + stack_slot_size;
addr_t const stack_top = (addr_t)myself->stack_top();
addr_t const stack_base = (addr_t)myself->stack_base();
if (stack_top <= stack_slot_base) { throw -2; }
if (stack_top > stack_slot_top) { throw -3; }
if (stack_base >= stack_slot_top) { throw -4; }
if (stack_base < stack_slot_base) { throw -5; }
log("thread stack top ", myself->stack_top());
log("thread stack bottom ", myself->stack_base());
/* check wether my stack pointer is inside my stack */
unsigned dummy = 0;
addr_t const sp = (addr_t)&dummy;
if (sp >= stack_top) { throw -6; }
if (sp < stack_base) { throw -7; }
log("thread stack pointer ", (void *)sp);
}
/******************************************
** Using cpu-session for thread creation *
******************************************/
struct Cpu_helper : Thread
{
enum { STACK_SIZE = 0x2000 };
Env &_env;
Cpu_helper(Env &env, const char * name, Cpu_session &cpu)
:
Thread(env, name, STACK_SIZE, Thread::Location(), Thread::Weight(), cpu),
_env(env)
{ }
void entry()
{
log(Thread::name().string(), " : _cpu_session=", _cpu_session,
" env.cpu()=", &_env.cpu());
}
};
static void test_cpu_session(Env &env)
{
log("running '", __func__, "'");
Cpu_helper thread0(env, "prio high ", env.cpu());
thread0.start();
thread0.join();
Cpu_connection con1("prio middle", Cpu_session::PRIORITY_LIMIT / 4);
Cpu_helper thread1(env, "prio middle", con1);
thread1.start();
thread1.join();
Cpu_connection con2("prio low", Cpu_session::PRIORITY_LIMIT / 2);
Cpu_helper thread2(env, "prio low ", con2);
thread2.start();
thread2.join();
}
struct Pause_helper : Thread
{
volatile unsigned loop = 0;
volatile bool beep = false;
enum { STACK_SIZE = 0x1000 };
Pause_helper(Env &env, const char * name, Cpu_session &cpu)
: Thread(env, name, STACK_SIZE, Thread::Location(), Thread::Weight(), cpu) { }
void entry()
{
while (1) {
/*
* Don't log here, since this thread becomes "paused".
* If it is holding the lock of the log backend being paused, all
* other threads of this task trying to print log messages will
* block - looks like a deadlock.
*/
// printf("stop me if you can\n");
loop ++;
if (beep) {
PINF("beep");
beep = false;
loop ++;
return;
}
}
}
};
static void test_pause_resume(Env &env)
{
log("running '", __func__, "'");
Pause_helper thread(env, "pause", env.cpu());
thread.start();
while (thread.loop < 1) { }
Thread_state state;
log("--- pausing ---");
env.cpu().pause(thread.cap());
unsigned loop_paused = thread.loop;
log("--- paused ---");
log("--- reading thread state ---");
try {
state = env.cpu().state(thread.cap());
} catch (Cpu_session::State_access_failed) {
throw -10;
}
if (loop_paused != thread.loop)
throw -11;
thread.beep = true;
log("--- resuming thread ---");
env.cpu().resume(thread.cap());
while (thread.loop == loop_paused) { }
log("--- thread resumed ---");
thread.join();
}
/*
* Test to check that core as the used kernel behaves well if up to the
* supported Genode maximum threads are created.
*/
static void test_create_as_many_threads(Env &env)
{
log("running '", __func__, "'");
addr_t const max = Thread::stack_area_virtual_size() /
Thread::stack_virtual_size();
Cpu_helper * threads[max];
static char thread_name[8];
Heap heap(env.ram(), env.rm());
unsigned i = 0;
try {
for (; i < max; i++) {
try {
snprintf(thread_name, sizeof(thread_name), "%u", i + 1);
threads[i] = new (heap) Cpu_helper(env, thread_name, env.cpu());
threads[i]->start();
threads[i]->join();
} catch (Cpu_session::Thread_creation_failed) {
throw "Thread_creation_failed";
} catch (Thread::Out_of_stack_space) {
throw "Out_of_stack_space";
}
}
} catch (const char * ex) {
PINF("created %u threads before I got '%s'", i, ex);
for (unsigned j = i; j > 0; j--) {
destroy(heap, threads[j - 1]);
threads[j - 1] = nullptr;
}
return;
}
/*
* We have to get a Out_of_stack_space message, because we can't create
* up to max threads, because already the main thread is running ...
*/
throw -21;
}
size_t Component::stack_size() { return 16*1024*sizeof(long); }
void Component::construct(Env &env)
{
log("--- thread test started ---");
Attached_rom_dataspace config(env, "config");
try {
test_stack_alloc(env);
test_stack_alignment(env);
test_main_thread();
test_cpu_session(env);
if (config.xml().has_sub_node("pause_resume"))
test_pause_resume(env);
test_create_as_many_threads(env);
} catch (int error) {
Genode::error("error ", error);
throw;
}
log("--- test completed successfully ---");
}