rockbox/firmware/target/hosted/thread-unix.c
Thomas Martitz 6d85de3419 Implement cooperative threads on hosted platforms using C code.
This replaces SDL threads with real cooperative threads, which are less cpu intensive and allow priority scheduling.
The backend for context switching is dependant on the host (sigaltstack/longjmp on Unix, Fibers on Windows).
configure has options to force or disallow SDL threads.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29327 a1c6a512-1295-4272-9138-f99709370657
2011-02-18 22:46:01 +00:00

294 lines
8.2 KiB
C

#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include "debug.h"
static volatile bool sig_handler_called;
static volatile jmp_buf tramp_buf;
static volatile jmp_buf bootstrap_buf;
static void (*thread_func)(void);
static const int trampoline_sig = SIGUSR1;
static pthread_t main_thread;
static struct ctx {
jmp_buf thread_buf;
} thread_bufs[MAXTHREADS];
static struct ctx* thread_context, *target_context;
static int curr_uc;
static void trampoline(int sig);
static void bootstrap_context(void) __attribute__((noinline));
/* The *_context functions are heavily based on Gnu pth
* http://www.gnu.org/software/pth/
*
* adjusted to work in a multi-thread environment to
* offer a ucontext-like API
*/
/*
* VARIANT 2: THE SIGNAL STACK TRICK
*
* This uses sigstack/sigaltstack() and friends and is really the
* most tricky part of Pth. When you understand the following
* stuff you're a good Unix hacker and then you've already
* understood the gory ingredients of Pth. So, either welcome to
* the club of hackers, or do yourself a favor and skip this ;)
*
* The ingenious fact is that this variant runs really on _all_ POSIX
* compliant systems without special platform kludges. But be _VERY_
* carefully when you change something in the following code. The slightest
* change or reordering can lead to horribly broken code. Really every
* function call in the following case is intended to be how it is, doubt
* me...
*
* For more details we strongly recommend you to read the companion
* paper ``Portable Multithreading -- The Signal Stack Trick for
* User-Space Thread Creation'' from Ralf S. Engelschall. A copy of the
* draft of this paper you can find in the file rse-pmt.ps inside the
* GNU Pth distribution.
*/
static int make_context(struct ctx *ctx, void (*f)(void), char *sp, size_t stack_size)
{
struct sigaction sa;
struct sigaction osa;
stack_t ss;
stack_t oss;
sigset_t osigs;
sigset_t sigs;
disable_irq();
/*
* Preserve the trampoline_sig signal state, block trampoline_sig,
* and establish our signal handler. The signal will
* later transfer control onto the signal stack.
*/
sigemptyset(&sigs);
sigaddset(&sigs, trampoline_sig);
sigprocmask(SIG_BLOCK, &sigs, &osigs);
sa.sa_handler = trampoline;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
if (sigaction(trampoline_sig, &sa, &osa) != 0)
{
DEBUGF("%s(): %s\n", __func__, strerror(errno));
return false;
}
/*
* Set the new stack.
*
* For sigaltstack we're lucky [from sigaltstack(2) on
* FreeBSD 3.1]: ``Signal stacks are automatically adjusted
* for the direction of stack growth and alignment
* requirements''
*
* For sigstack we have to decide ourself [from sigstack(2)
* on Solaris 2.6]: ``The direction of stack growth is not
* indicated in the historical definition of struct sigstack.
* The only way to portably establish a stack pointer is for
* the application to determine stack growth direction.''
*/
ss.ss_sp = sp;
ss.ss_size = stack_size;
ss.ss_flags = 0;
if (sigaltstack(&ss, &oss) < 0)
{
DEBUGF("%s(): %s\n", __func__, strerror(errno));
return false;
}
/*
* Now transfer control onto the signal stack and set it up.
* It will return immediately via "return" after the setjmp()
* was performed. Be careful here with race conditions. The
* signal can be delivered the first time sigsuspend() is
* called.
*/
sig_handler_called = false;
main_thread = pthread_self();
sigfillset(&sigs);
sigdelset(&sigs, trampoline_sig);
pthread_kill(main_thread, trampoline_sig);
while(!sig_handler_called)
sigsuspend(&sigs);
/*
* Inform the system that we are back off the signal stack by
* removing the alternative signal stack. Be careful here: It
* first has to be disabled, before it can be removed.
*/
sigaltstack(NULL, &ss);
ss.ss_flags = SS_DISABLE;
if (sigaltstack(&ss, NULL) < 0)
{
DEBUGF("%s(): %s\n", __func__, strerror(errno));
return false;
}
sigaltstack(NULL, &ss);
if (!(ss.ss_flags & SS_DISABLE))
{
DEBUGF("%s(): %s\n", __func__, strerror(errno));
return false;
}
if (!(oss.ss_flags & SS_DISABLE))
sigaltstack(&oss, NULL);
/*
* Restore the old trampoline_sig signal handler and mask
*/
sigaction(trampoline_sig, &osa, NULL);
sigprocmask(SIG_SETMASK, &osigs, NULL);
/*
* Tell the trampoline and bootstrap function where to dump
* the new machine context, and what to do afterwards...
*/
thread_func = f;
thread_context = ctx;
/*
* Now enter the trampoline again, but this time not as a signal
* handler. Instead we jump into it directly. The functionally
* redundant ping-pong pointer arithmentic is neccessary to avoid
* type-conversion warnings related to the `volatile' qualifier and
* the fact that `jmp_buf' usually is an array type.
*/
if (setjmp(*((jmp_buf *)&bootstrap_buf)) == 0)
longjmp(*((jmp_buf *)&tramp_buf), 1);
/*
* Ok, we returned again, so now we're finished
*/
enable_irq();
return true;
}
static void trampoline(int sig)
{
(void)sig;
/* sanity check, no other thread should be here */
if (pthread_self() != main_thread)
return;
if (setjmp(*((jmp_buf *)&tramp_buf)) == 0)
{
sig_handler_called = true;
return;
}
/* longjump'd back in */
bootstrap_context();
}
void bootstrap_context(void)
{
/* copy to local storage so we can spawn further threads
* in the meantime */
void (*thread_entry)(void) = thread_func;
struct ctx *t = thread_context;
/*
* Save current machine state (on new stack) and
* go back to caller until we're scheduled for real...
*/
if (setjmp(t->thread_buf) == 0)
longjmp(*((jmp_buf *)&bootstrap_buf), 1);
/*
* The new thread is now running: GREAT!
* Now we just invoke its init function....
*/
thread_entry();
DEBUGF("thread left\n");
thread_exit();
}
static inline void set_context(struct ctx *c)
{
longjmp(c->thread_buf, 1);
}
static inline void swap_context(struct ctx *old, struct ctx *new)
{
if (setjmp(old->thread_buf) == 0)
longjmp(new->thread_buf, 1);
}
static inline void get_context(struct ctx *c)
{
setjmp(c->thread_buf);
}
static void setup_thread(struct regs *context);
#define INIT_MAIN_THREAD
static void init_main_thread(void *addr)
{
/* get a context for the main thread so that we can jump to it from
* other threads */
struct regs *context = (struct regs*)addr;
context->uc = &thread_bufs[curr_uc++];
get_context(context->uc);
}
#define THREAD_STARTUP_INIT(core, thread, function) \
({ (thread)->context.stack_size = (thread)->stack_size, \
(thread)->context.stack = (uintptr_t)(thread)->stack; \
(thread)->context.start = function; })
/*
* Prepare context to make the thread runnable by calling swapcontext on it
*/
static void setup_thread(struct regs *context)
{
void (*fn)(void) = context->start;
context->uc = &thread_bufs[curr_uc++];
while (!make_context(context->uc, fn, (char*)context->stack, context->stack_size))
DEBUGF("Thread creation failed. Retrying");
}
/*
* Save the ucontext_t pointer for later use in swapcontext()
*
* Cannot do getcontext() here, because jumping back to the context
* resumes after the getcontext call (i.e. store_context), but we need
* to resume from load_context()
*/
static inline void store_context(void* addr)
{
struct regs *r = (struct regs*)addr;
target_context = r->uc;
}
/*
* Perform context switch
*/
static inline void load_context(const void* addr)
{
struct regs *r = (struct regs*)addr;
if (UNLIKELY(r->start))
{
setup_thread(r);
r->start = NULL;
}
swap_context(target_context, r->uc);
}
/*
* play nice with the host and sleep while waiting for the tick */
extern void wait_for_interrupt(void);
static inline void core_sleep(void)
{
enable_irq();
wait_for_interrupt();
}