How to port the libjava threading layer
Introduction
The libjava threading layer was designed to expose precisely those
pieces of a thread system that are required by a Java runtime. It
also turns out to be fairly easy to port.
The First Step
The first step to porting the threading layer is adding code to
libjava/configure.in to handle the new system. This is fairly
straightforward, so I won't go into any details here.
It might also be necessary to modify other configure scripts as well.
For instance, you might need to modify qthreads/configure so that
qthreads won't be built when the new thread system is in use. (This
is actually already done, but might need revisiting if we change
libjava/configure to automatically determine thread package based on
host. In this case, qthreads and boehm-gc must make the same
determination.)
Porting Gcc
Pieces of the gcc runtime code, in particular the exception handling,
need to know about the thread system that is in use, so any new thread
port for libjava must also involve adding new code to gcc. Generally
this is much easier than writing the thread code for libjava.
- Add support to gcc/configure.in to support the new thread system.
gcc's configure script understands --enable-threads just as libjava's
does; the two must agree so that a single configure from the top level
will succeed.
- Write a new ``gthr-PORT.h'' header file. See the existing gthr.h
to see what must go into this file.
- Contact the gcc maintainers and try to get your patch installed.
Allocate plenty of time for this step.
- Update java/jvspec.c to tell it about the new thread library.
The Header File
The second step is to write a header file which includes all the
declarations that are required by libjava. This step is the bulk of
the work involved.
This header file consists of a number of definitions and declarations.
At configure time, a link named ``java-threads.h'' will be created to
point to the appropriate package-specific header.
The contents of this file are fixed in the sense that the list of
declarations which must appear is fixed. The reason there is a
separate such header per thread system is so that each system can make
its own decisions about which functions should be inlined.
A useful piece of information which doesn't seem to belong anywhere
else: in Java, a thread can be marked as a ``daemon'' thread before it
is started. The runtime exits when all non-daemon threads have
exited. The thread porting layer is responsible for handling this
somehow.
Boilerplate
Each thread header must contain a declaration of the type
``_Jv_ThreadStartFunc''. This typedef is the same in every thread
package. Declare it thusly:
typedef void _Jv_ThreadStartFunc (java::lang::Thread *);
Mutexes
Mutexes (or "mutices" as some would have it) are one of two
fundamental synchronization objects used by libjava. A mutex is a
lock that is held by a single thread. A thread can hold the same
mutex multiple times; they are reference counted. When one thread
holds a mutex, another thread attempting to grab the mutex will block
until the mutex is free.
Implementing mutexes requires a typedef and several functions:
- _Jv_Mutex_t
- A package-specific typedef which represents a mutex. This type
must be fully declared in the thread header, as memory allocation for
mutexes is handled by the runtime and not by the thread layer.
- void _Jv_MutexInit (_Jv_Mutex_t *mutex)
- A function to initialize a mutex.
- void _Jv_MutexDestroy (_Jv_Mutex_t *mutex)
- A function to destroy a mutex.. This function is optional. If
you define it, you must also define the preprocessor macro
_Jv_HaveMutexDestroy. The presence of this function incurs a
performance penalty.
- int _Jv_MutexLock (_Jv_Mutex_t *mutex)
- Lock the mutex. Note that Java mutexes are recursive, so this function
must maintain a lock-depth counter in the _Jv_Mutex_t if the underlying
platform's mutex is not recursive. Returns 0 on success, -1 on failure.
- int _Jv_MutexUnlock (_Jv_Mutex_t *mutex)
- Unlock the mutex. Returns 0 on success, -1 on failure.
Condition Variables
Condition variables are the other synchronization primitive used by
libjava. They are used to implement the wait and notify functions.
Each condition variable has an associated mutex. A thread acquires
the mutex and then waits on the condition variable. This waiting
takes the form of an atomic release of the mutex (fully releasing it,
if it is held several times by the thread) followed by blocking. When
the condition variable is signalled by some other thread, the waiting
thread reawakens, re-acquires the mutex (restoring it to its previous
state -- the lock depth counting must be handled correctly), and
returns. It's possible for several threads to wait on a condition
variable at the same time, and to all be reawakened simultaneously (of
course, only one thread at a time can re-acquire the mutex).
Once again, a typedef and several functions are required.
- _Jv_ConditionVariable_t
- A package-specific typedef which represents a condition variable.
- void _Jv_CondInit (_Jv_ConditionVariable_t *condvar)
- Initialize a condition variable.
- void _Jv_CondDestroy (_Jv_ConditionVariable_t *condvar)
- Destroy a condition variable. This function is optional. If you
define it, you must also define the preprocessor macro
_Jv_HaveCondDestroy. The presence of this function incurs a
performance penalty.
- int _Jv_CondWait (_Jv_ConditionVariable_t *condvar, _Jv_Mutex_t *mutex, jlong milliseconds, jint nanoseconds)
- Wait on the condition variable using the associated mutex. The
final arguments are a timeout; if 0 then no timeout is to be used. It
isn't necessary to implement nanosecond resolution. However, the best
possible resolution should be implemented.
_Jv_CondWait should check that MUTEX is locked by the current thread. If it
isn't, it should return _JV_NOT_OWNER. The interrupted status flag of the
calling thread should be checked before _Jv_CondWait blocks, if this is not
done automatically by the OS thread layer. If the thread is interrupted (either
before or during the wait), _JV_INTERRUPTED should be returned. The interrupted
status flag is not cleared.
_Jv_CondWait should never throw an exception directly.
Returns 0 if CONDVAR was signalled normally, or if the timeout expired.
- int _Jv_CondNotify (_Jv_ConditionVariable_t *condvar, _Jv_Mutex_t *mutex)
- Signal the condition variable. This wakes up one thread
waiting on the variable. Returns 0 on success. Should return _JV_NOT_OWNER
if current thread does not hold the mutex.
- int _Jv_CondNotifyAll (_Jv_ConditionVariable_t *condvar, _Jv_Mutex_t *mutex)
- Wake up all threads waiting on the condition variable. Returns 0
on success. Should return _JV_NOT_OWNER if current thread does not hold the
mutex.
Everything Else
There is one remaining typedef that must be defined:
``_Jv_Thread_t''. This is a typedef specific to the threads system
which represents the thread system's notion of a thread. Each
``java.lang.Thread'' object has an associated native thread object of
this type.
The remaining functions have to do with thread creation and
manipulation.
Thread Creation and Destruction
- void _Jv_InitThreads (void)
- Initialize the thread system. This is called before any other
initialization is done. This should not be used to start
the first thread; that is handled elsewhere. This probably won't be
needed by most thread packages.
- _Jv_Thread_t * _Jv_ThreadInitData (java::lang::Thread *thread)
- This is called when a new Java thread object is created. The
thread system should create a new thread, allocate any data needed to assist
the implementation of mutexes and condition variables, initialize it as
needed, and return a pointer to it (this pointer is opaque to generic threads
code). The thread should not be started yet -- in Java, thread creation and
execution are separate.
- void _Jv_ThreadDestroyData (_Jv_Thread_t *data)
- This function should clean up and/or free any native resources allocated
for the thread in _Jv_ThreadInitData. It is called when the Java thread object
is finalized.
- void _Jv_ThreadStart (java::lang::Thread *thread, _Jv_Thread_t *data, _Jv_ThreadStartFunc *method)
- Start running the indicated thread. This function should examine
the Thread object and make use of the priority, daemon-ness, etc, if
possible. The argument ``data'' holds the data element previously
initialized with _Jv_ThreadInitData. The ``thread'' argument should
be passed as the sole argument to ``method''.
- void _Jv_ThreadWait (void)
- Wait for all non-daemon threads to exit.
- void _Jv_ThreadRegister (_Jv_Thread_t *data)
- This is called from a newly created thread before the run() method is
invoked, or when a native thread is attached to the Java runtime via
JvAttachThread. It can be used to create and/or register thread-specific data
to assist the implementation of _Jv_ThreadCurrent, for example.
- void _Jv_ThreadUnRegister ()
- This is called from a thread which is about to die, because its run() method
has returned or it is being "detached" from the Java runtime. It gives the
thread layer a chance to clean up thread-specific data allocated in
_Jv_ThreadRegister. Note that the thread object may still be visible to Java,
so _Jv_Thread_t data should only be freed by _Jv_ThreadDestroyData.
Truly Miscellaneous Functions
- java::lang::Thread *_Jv_ThreadCurrent (void)
- Each thread package is required to maintain a mapping from the
current thread to the associated Java Thread object representing that
thread. Typically this is done by storing the pointer (from the
_Jv_ThreadInitData or _Jv_ThreadStart calls) to the thread object
in thread-local storage. This function returns that object.
- void _Jv_ThreadYield (void)
- Cause the current thread to yield. Strictly speaking this need
not do anything.
- void _Jv_ThreadSetPriority (_Jv_Thread_t *data, jint priority)
- This is called when the priority of a thread is set. Priorities
are remembered and manipulated in the Thread class; this call exists
in case the underlying thread system has a useful notion of
priorities to which Java priorities should be mapped.
- void _Jv_ThreadInterrupt (_Jv_Thread_t *data)
- This function is called by Thread.interrupt(). It should set
the interrupt_flag of the Thread object corresponding to DATA, and cause any
"slow" or "blocking" system I/O or a call to _Jv_CondWait to return immediately.
Interrupting of _Jv_CondWait calls should be implemented in such a way that
simultaneous interrupt() and notify() calls always result in one thread waking
up normally (if there are enough threads waiting). That is, if two threads are
waiting up on condition variable and one of them is interrupted while the variable
is concurrently notified by another thread, then EITHER _JV_INTERRUPTED should
be returned for one thread's _Jv_CondWait() and the others to return normally,
or the interrupted thread's _Jv_CondWait() call should just return normally (but
its interrupted status flag should be set). It is also important to prevent
possible races in the interrupt implementation: it should not be possible for
an interrupt to be delivered to a thread immediately after it checks its
interrupted status flag but before it enters an interruptible blocking call,
for example.
Unimplemented functionality
Some Java functionality remains unimplemented in libgcj. Some of
this will change, but some will not.
- Thread.destroy
- Thread.resume
- Thread.suspend
- Thread.stop
- These functions are unimplemented and never will be
implemented. They are deprecated in JDK 1.2. The current
implementations simply throw an exception.
Grey Areas
There are still a few areas which have not been fully explored. This
will change over time.
- There is no documented way for the thread system and the GC to
interact. This is a historical accident, as our only GC right now
knows about thread systems itself.
|
|