Rework the internal lock interface

With this patch, the 'futex' syscall gets used for blocking and unblocking
of threads in the Linux-specific lock implementation.

The 'Native_thread_id' type, which was previously used in the
lock-internal 'Applicant' class to identify a thread to be woken up,
was not suitable anymore for implementing this change. With this patch,
the 'Thread_base*' type gets used instead, which also has the positive
effect of making the public 'cancelable_lock.h' header file
platform-independent.

Fixes #646.
This commit is contained in:
Christian Prochaska
2013-02-25 21:18:26 +01:00
committed by Norman Feske
parent 7fef0ba931
commit a99193ad90
36 changed files with 496 additions and 330 deletions

View File

@ -25,6 +25,9 @@
#include <linux_syscalls.h>
extern int main_thread_futex_counter;
/**
* Resolve 'Thread_base::myself' when not linking the thread library
*
@ -42,32 +45,16 @@ static inline void thread_yield()
}
static inline bool thread_check_stopped_and_restart(Genode::Native_thread_id tid)
static inline bool thread_check_stopped_and_restart(Genode::Thread_base *thread_base)
{
lx_tgkill(tid.pid, tid.tid, LX_SIGUSR1);
return true;
const int *futex_counter_ptr = thread_base ?
&thread_base->tid().futex_counter :
&main_thread_futex_counter;
return lx_futex(futex_counter_ptr, LX_FUTEX_WAKE, 1);
}
static inline Genode::Native_thread_id thread_get_my_native_id()
{
return Genode::Native_thread_id(lx_gettid(), lx_getpid());
}
static inline Genode::Native_thread_id thread_invalid_id()
{
return Genode::Native_thread_id();
}
static inline bool thread_id_valid(Genode::Native_thread_id tid)
{
return (tid.pid != 0);
}
static inline void thread_switch_to(Genode::Native_thread_id tid)
static inline void thread_switch_to(Genode::Thread_base *thread_base)
{
thread_yield();
}
@ -75,6 +62,14 @@ static inline void thread_switch_to(Genode::Native_thread_id tid)
static inline void thread_stop_myself()
{
struct timespec ts = { 1000, 0 };
while (lx_nanosleep(&ts, 0) == 0);
/*
* Just go to sleep without modifying the counter value. The
* 'thread_check_stopped_and_restart()' function will get called
* repeatedly until this thread has actually executed the syscall.
*/
Genode::Thread_base *myself = Genode::Thread_base::myself();
const int *futex_counter_ptr = myself ?
&myself->tid().futex_counter :
&main_thread_futex_counter;
lx_futex(futex_counter_ptr, LX_FUTEX_WAIT, 0);
}

View File

@ -203,4 +203,20 @@ inline int lx_connect(int sockfd, const struct sockaddr *serv_addr,
#endif /* SYS_socketcall */
/******************************
** Linux signal dispatching **
******************************/
inline int lx_pipe(int pipefd[2])
{
return lx_syscall(SYS_pipe, pipefd);
}
inline int lx_read(int fd, void *buf, Genode::size_t count)
{
return lx_syscall(SYS_read, fd, buf, count);
}
#endif /* _CORE__INCLUDE__CORE_LINUX_SYSCALLS_H_ */

View File

@ -33,20 +33,53 @@ using namespace Genode;
static char _core_mem[80*1024*1024];
static Lock _wait_for_exit_lock(Lock::LOCKED); /* wakeup of '_wait_for_exit' */
static bool _do_exit = false; /* exit condition */
/*
* Basic semaphore implementation based on the 'pipe' syscall.
*
* This alternative implementation is needed to be able to wake up the
* blocked main thread from a signal handler executed by the same thread.
*/
class Pipe_semaphore
{
private:
int _pipefd[2];
public:
Pipe_semaphore()
{
lx_pipe(_pipefd);
}
void down()
{
char dummy;
while(lx_read(_pipefd[0], &dummy, 1) != 1);
}
void up()
{
char dummy;
while (lx_write(_pipefd[1], &dummy, 1) != 1);
}
};
static Pipe_semaphore _wait_for_exit_sem; /* wakeup of '_wait_for_exit' */
static bool _do_exit = false; /* exit condition */
static void sigint_handler(int signum)
{
_wait_for_exit_lock.unlock();
_do_exit = true;
_wait_for_exit_sem.up();
}
static void sigchld_handler(int signnum)
{
_wait_for_exit_lock.unlock();
_wait_for_exit_sem.up();
}
@ -82,11 +115,10 @@ void Platform::wait_for_exit()
/*
* Block until a signal occurs.
*/
try { _wait_for_exit_lock.lock(); }
catch (Blocking_canceled) { };
_wait_for_exit_sem.down();
/*
* Each time, the '_wait_for_exit_lock' gets unlocked, we could have
* Each time, the '_wait_for_exit_sem' gets unlocked, we could have
* received either a SIGINT or SIGCHLD. If a SIGINT was received, the
* '_exit' condition will be set.
*/

View File

@ -25,6 +25,13 @@
__attribute__((weak)) char **lx_environ = (char **)0;
/**
* Natively aligned memory location used in the lock implementation
*/
int main_thread_futex_counter __attribute__((aligned(sizeof(Genode::addr_t))));
static inline void main_thread_bootstrap()
{
using namespace Genode;

View File

@ -33,6 +33,7 @@
#endif
/* Linux includes */
#include <linux/futex.h>
#include <unistd.h>
#include <signal.h>
#include <sched.h>
@ -338,6 +339,16 @@ inline int lx_nanosleep(const struct timespec *req, struct timespec *rem)
return lx_syscall(SYS_nanosleep, req, rem);
}
enum {
LX_FUTEX_WAIT = FUTEX_WAIT,
LX_FUTEX_WAKE = FUTEX_WAKE,
};
inline int lx_futex(const int *uaddr, int op, int val)
{
return lx_syscall(SYS_futex, uaddr, op, val, 0, 0, 0);
}
/**
* Signal set corrsponding to glibc's 'sigset_t'

View File

@ -130,27 +130,6 @@ namespace Genode {
struct Thread_meta_data
{
/**
* Lock with the initial state set to LOCKED
*/
struct Barrier : Lock { Barrier() : Lock(Lock::LOCKED) { } };
/**
* Used to block the constructor until the new thread has initialized
* 'id'
*/
Barrier construct_lock;
/**
* Used to block the new thread until 'start' is called
*/
Barrier start_lock;
/**
* Used to block the 'join()' function until the 'entry()' is done
*/
Barrier join_lock;
/**
* Filled out by 'thread_start' function in the context of the new
* thread
@ -168,6 +147,128 @@ namespace Genode {
* \param thread associated 'Thread_base' object
*/
Thread_meta_data(Thread_base *thread) : thread_base(thread) { }
/**
* Used to block the constructor until the new thread has initialized
* 'id'
*/
virtual void wait_for_construction() = 0;
virtual void constructed() = 0;
/**
* Used to block the new thread until 'start' is called
*/
virtual void wait_for_start() = 0;
virtual void started() = 0;
/**
* Used to block the 'join()' function until the 'entry()' is done
*/
virtual void wait_for_join() = 0;
virtual void joined() = 0;
};
/*
* Thread meta data for a thread created by Genode
*/
class Thread_meta_data_created : public Thread_meta_data
{
private:
/**
* Lock with the initial state set to LOCKED
*/
struct Barrier : Lock { Barrier() : Lock(Lock::LOCKED) { } };
/**
* Used to block the constructor until the new thread has initialized
* 'id'
*/
Barrier _construct_lock;
/**
* Used to block the new thread until 'start' is called
*/
Barrier _start_lock;
/**
* Used to block the 'join()' function until the 'entry()' is done
*/
Barrier _join_lock;
public:
Thread_meta_data_created(Thread_base *thread) : Thread_meta_data(thread) { }
void wait_for_construction()
{
_construct_lock.lock();
}
void constructed()
{
_construct_lock.unlock();
}
void wait_for_start()
{
_start_lock.lock();
}
void started()
{
_start_lock.unlock();
}
void wait_for_join()
{
_join_lock.lock();
}
void joined()
{
_join_lock.unlock();
}
};
/*
* Thread meta data for an adopted thread
*/
class Thread_meta_data_adopted : public Thread_meta_data
{
public:
Thread_meta_data_adopted(Thread_base *thread) : Thread_meta_data(thread) { }
void wait_for_construction()
{
PERR("wait_for_construction() called for an adopted thread");
}
void constructed()
{
PERR("constructed() called for an adopted thread");
}
void wait_for_start()
{
PERR("wait_for_start() called for an adopted thread");
}
void started()
{
PERR("started() called for an adopted thread");
}
void wait_for_join()
{
PERR("wait_for_join() called for an adopted thread");
}
void joined()
{
PERR("joined() called for an adopted thread");
}
};
}
@ -224,14 +325,14 @@ static void *thread_start(void *arg)
adopt_thread(meta_data);
/* unblock 'Thread_base' constructor */
meta_data->construct_lock.unlock();
meta_data->constructed();
/* block until the 'Thread_base::start' gets called */
meta_data->start_lock.lock();
meta_data->wait_for_start();
Thread_base::myself()->entry();
meta_data->join_lock.unlock();
meta_data->joined();
return 0;
}
@ -271,7 +372,7 @@ Thread_base *Thread_base::myself()
*/
Thread_base *thread = (Thread_base *)malloc(sizeof(Thread_base));
memset(thread, 0, sizeof(*thread));
Thread_meta_data *meta_data = new Thread_meta_data(thread);
Thread_meta_data *meta_data = new Thread_meta_data_adopted(thread);
/*
* Initialize 'Thread_base::_tid' using the default constructor of
@ -290,13 +391,13 @@ void Thread_base::start()
/*
* Unblock thread that is supposed to slumber in 'thread_start'.
*/
_tid.meta_data->start_lock.unlock();
_tid.meta_data->started();
}
void Thread_base::join()
{
_tid.meta_data->join_lock.lock();
_tid.meta_data->wait_for_join();
}
@ -304,7 +405,7 @@ Thread_base::Thread_base(const char *name, size_t stack_size)
:
_list_element(this)
{
_tid.meta_data = new (env()->heap()) Thread_meta_data(this);
_tid.meta_data = new (env()->heap()) Thread_meta_data_created(this);
int const ret = pthread_create(&_tid.meta_data->pt, 0, thread_start,
_tid.meta_data);
@ -315,7 +416,7 @@ Thread_base::Thread_base(const char *name, size_t stack_size)
throw Context_alloc_failed();
}
_tid.meta_data->construct_lock.lock();
_tid.meta_data->wait_for_construction();
Linux_cpu_session *cpu = dynamic_cast<Linux_cpu_session *>(env()->cpu_session());