6d85de3419
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
294 lines
8.2 KiB
C
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();
|
|
}
|
|
|