📄 thread.h
字号:
/****************************************************************************
* *
* cryptlib Thread/Mutex Handling *
* Copyright Peter Gutmann 1992-2008 *
* *
****************************************************************************/
#ifndef _THREAD_DEFINED
#define _THREAD_DEFINED
/* In multithreaded environments we need to use mutexes to protect the
information inside cryptlib data structures from access by other threads
while we use it. In most cases (mutexes not taken) mutexes are extremely
quick, being implemented using compare-and-swap on x86 or load/store
conditional on most RISC CPUs.
The types and macros that are needed to handle mutexes are:
MUTEX_HANDLE -- Handle for mutexes/semaphores
MUTEX_DECLARE_STORAGE -- Declare storage for mutex
MUTEX_CREATE -- Initialise mutex
MUTEX_DESTROY -- Delete mutex
MUTEX_LOCK -- Acquire mutex
MUTEX_UNLOCK -- Release mutex
Before deleting a mutex we lock and unlock it again to ensure that if
some other thread is holding it they'll release it before we delete it.
Many systems don't provide re-entrant semaphores/mutexes. To handle this
we implement our own re-entrant mutexes on top of the OS ones. Using
the Posix terminology, what we do is use mutex_trylock(), which doesn't
re-lock the mutex if it's already locked, and as a side-benefit can be up
to twice as fast as mutex_lock(), depending on the OS. This works as
follows:
// Try and lock the mutex
if( mutex_trylock( mutex ) == error )
{
// The mutex is already locked, see who owns it
if( thread_self() != mutex_owner )
// Someone else has it locked, wait for it to become available
mutex_lock( mutex );
else
// We have it locked, increment its lock count
mutex_lockcount++;
}
mutex_owner = thread_self();
// ....
// Decrement the lock count and if we reach zero, unlock the mutex
if( mutex_lockcount > 0 )
mutex_lockcount--;
else
{
mutex_owner = none;
mutex_unlock( mutex );
}
This is safe from race conditions via the following:
if( mutex_trylock( mutex ) == error )
{ --> Unlock #1
if( thread_self() != mutex_owner )
--> Unlock #2
mutex_lock( mutex );
else
mutex_lockcount++;
}
If the mutex is unlocked after the trylock at one of the two given
points, we can get the following situations:
Unlock #1, no relock: mutex owner = none -> we lock the mutex
Unlock #1, relock by another thread:
mutex owner = other -> we wait on the mutex lock
Unlock #2, no relock: mutex owner = none -> we lock the mutex
Unlock #2, relock by another thread:
mutex owner = other -> we wait on the mutex lock
There's no race condition in the lock process since the only thing that
could cause a problem is if we unlock the mutex ourselves, which we can't
do.
For the unlock process, the only possibility for a race condition is in
conjunction with the lock process, if the owner field isn't reset on
unlock then thread #1 could unlock the mutex and thread #2 could lock it
but be pre-empted before it can (re-)set the owner field. Then thread #1
could reacquire it thinking that it still owns the mutex because the
owner field hasn't been reset yet from its previous ownership. For this
reason it's essential that the owner field be reset before the mutex is
unlocked.
The types and macros that need to be declared to handle threading are:
THREAD_HANDLE -- Handle for threads
THREADFUNC_DEFINE -- Define thread function
THREAD_CREATE -- Create thread
THREAD_EXIT -- Exit from thread
THREAD_INITIALISER -- Value to initialise thread handle
THREAD_SELF -- Get handle of current thread
THREAD_SAME -- Compare two thread handles
THREAD_SLEEP -- Sleep for n milliseconds
THREAD_YIELD -- Yield thread's timeslice
THREAD_WAIT -- Wait for thread to terminate
THREAD_CLOSE -- Clean up thread after THREAD_WAIT
Some systems allow a thread/task handle to be used as a synchronisation
object while others require a separate semaphore object for
synchronisation. To handle this we create a synchronisation semaphore in
the non-signalled state when we create a thread/task, signal it when the
task exits, and wait on it in the calling thread/task:
Parent: Child:
syncSem = createSem( 1 );
thread = createThread( syncSem );
signal( syncSem );
exit();
wait( syncSem );
destroySem( syncSem );
If the thread/task handle can be used as a synchronisation object, these
additional operations are turned into no-ops.
Several of the embedded OSes are extremely difficult to work with because
their kernels perform no memory (or, often, resource) management of their
own, assuming that all memory will be allocated by the caller. In the
simplest case this means that the thread stack/workspace has to be user-
allocated, in the worst case every object handle variable that's normally
a simple scalar value in other OSes is a composite non-scalar type that
contains all of the object's data, requiring that the caller manually
allocate state data for threads, mutexes, and semaphores rather than
having the OS do it for them.
For things like mutex and semaphore 'handles', which have a fixed number
or location, this is manageable by statically allocating the storage in
advance. However it significantly complicates things like thread
handling because the thread that dynamically creates a worker thread has
to be around later on to clean up after it when it terminates, and the
state data has to be maintained in external (non-thread) storage. We
handle this in one of two ways, either by not using cryptlib-internal
threads (they're only used for initialisation and keygen, neither of
which will benefit much from the ability to run them in the background in
an embedded system), or by wrapping the threading functions in our own
ones which allocate memory as required and access the information via a
scalar handle.
To enable the use of thread wrappers, see the xxx_THREAD_WRAPPERS define
for each embedded OS type */
/* Define the following to debug mutex lock/unlock operations */
/* #define MUTEX_DEBUG */
/****************************************************************************
* *
* AMX *
* *
****************************************************************************/
#if defined( __AMX__ )
/* To use resource-management wrappers for the AMX thread functions,
undefine the following */
/* #define AMX_THREAD_WRAPPERS */
#include <cjzzz.h>
/* Object handles */
#define THREAD_HANDLE CJ_ID
#define MUTEX_HANDLE CJ_ID
/* Mutex management functions. AMX resource semaphores are re-entrant so we
don't have to jump through the hoops that are necessary with most other
OSes */
#define MUTEX_DECLARE_STORAGE( name ) \
CJ_ID name##Mutex; \
BOOLEAN name##MutexInitialised
#define MUTEX_CREATE( name, status ) \
status = CRYPT_OK; \
if( !krnlData->name##MutexInitialised ) \
{ \
if( cjrmcreate( &krnlData->name##Mutex, NULL ) == CJ_EROK ) \
krnlData->name##MutexInitialised = TRUE; \
else \
status = CRYPT_ERROR; \
}
#define MUTEX_DESTROY( name ) \
if( krnlData->name##MutexInitialised ) \
{ \
cjrmrsv( krnlData->name##Mutex, threadPriority(), 0 ); \
cjrmrls( krnlData->name##Mutex ); \
cjrmdelete( krnlData->name##Mutex ); \
krnlData->name##MutexInitialised = FALSE; \
}
#define MUTEX_LOCK( name ) \
cjrmrsv( krnlData->name##Mutex, x, 0 )
#define MUTEX_UNLOCK( name ) \
cjrmrls( krnlData->name##Mutex )
/* Thread management functions. AMX threads require that the user allocate
the stack space for them, unlike virtually every other embedded OS, which
make this at most a rarely-used option. To handle this, we use our own
wrappers which hide this mess. A second problem with AMX threads is that
there's no obvious way to pass an argument to a thread. In theory we
could convey the information by sending it via a mailbox, but this
requires first conveying the mailbox ID to the new task, which has the
same problem.
We create the thread with the same priority as the calling thread, AMX
threads are created in the suspended state so after we create the thread
we have to trigger it to start it running.
The 4096 byte storage area provides enough space for the task control
block and about half a dozen levels of function nesting (if no large on-
stack arrays are used), this should be enough for background init but
probably won't be sufficient for the infinitely-recursive OpenSSL bignum
code, so the value may need to be adjusted if background keygen is being
used */
#define THREADFUNC_DEFINE( name, arg ) void name( cyg_addrword_t arg )
#define THREAD_CREATE( function, arg, threadHandle, syncHandle, status ) \
{ \
BYTE *threadData = malloc( 4096 ); \
\
cjsmcreate( &syncHandle, NULL, CJ_SMBINARY ); \
if( cjtkcreate( &threadHandle, NULL, function, threadData, \
4096, 0, threadPriority(), 0 ) != CJ_EROK ) \
{ \
free( threadData ); \
status = CRYPT_ERROR; \
} \
else \
{ \
cjtktrigger( threadHandle ); \
status = CRYPT_OK; \
} \
}
#define THREAD_EXIT( sync ) cjsmsignal( sync ); \
return
#define THREAD_INITIALISER CJ_IDNULL
#define THREAD_SAME( thread1, thread2 ) ( ( thread1 ) == ( thread2 ) )
#define THREAD_SELF() cjtkid()
#define THREAD_SLEEP( ms ) cjtkdelay( cjtmconvert( ms ) )
#define THREAD_YIELD() cjtkdelay( 1 )
#define THREAD_WAIT( sync, status ) \
if( cjsmwait( sync, threadPriority(), 0 ) != CJ_EROK ) \
status = CRYPT_ERROR; \
cjsmdelete( sync )
#define THREAD_CLOSE( sync )
/* Because of the problems with resource management of AMX tasks and
related metadata, we no-op them out unless we're using wrappers by
ensuring that any attempt to spawn a thread inside cryptlib fails,
falling back to the non-threaded alternative. Note that cryptlib itself
is still thread-safe, it just can't do its init or keygen in an internal
background thread */
#ifndef AMX_THREAD_WRAPPERS
#undef THREAD_CREATE
#undef THREAD_EXIT
#undef THREAD_CLOSE
#define THREAD_CREATE( function, arg, threadHandle, syncHandle, status ) \
status = CRYPT_ERROR
#define THREAD_EXIT( sync )
#define THREAD_CLOSE( sync )
#endif /* !AMX_THREAD_WRAPPERS */
/* The AMX task-priority function returns the priority via a reference
parameter. Because of this we have to provide a wrapper that returns
it as a return value */
int threadPriority( void );
/****************************************************************************
* *
* BeOS *
* *
****************************************************************************/
#elif defined( __BEOS__ )
#include <kernel/OS.h>
/* Object handles */
#define THREAD_HANDLE thread_id
#define MUTEX_HANDLE thread_id
/* Mutex management functions */
#define MUTEX_DECLARE_STORAGE( name ) \
sem_id name##Mutex; \
BOOLEAN name##MutexInitialised; \
thread_id name##MutexOwner; \
int name##MutexLockcount
#define MUTEX_CREATE( name, status ) \
status = CRYPT_OK; \
if( !krnlData->name##MutexInitialised ) \
{ \
if( ( krnlData->name##Mutex = create_sem( 1, NULL ) ) < B_NO_ERROR ) \
status = CRYPT_ERROR; \
else \
krnlData->name##MutexInitialised = TRUE; \
}
#define MUTEX_DESTROY( name ) \
if( krnlData->name##MutexInitialised ) \
{ \
acquire_sem( krnlData->name##Mutex ); \
release_sem( krnlData->name##Mutex ); \
delete_sem( krnlData->name##Mutex ); \
krnlData->name##MutexInitialised = FALSE; \
}
#define MUTEX_LOCK( name ) \
if( acquire_sem_etc( krnlData->name##Mutex, 1, \
B_RELATIVE_TIMEOUT, 0 ) == B_WOULD_BLOCK ) \
{ \
if( !THREAD_SAME( krnlData->name##MutexOwner, THREAD_SELF() ) ) \
acquire_sem( krnlData->name##Mutex ); \
else \
krnlData->name##MutexLockcount++; \
} \
krnlData->name##MutexOwner = THREAD_SELF();
#define MUTEX_UNLOCK( name ) \
if( krnlData->name##MutexLockcount > 0 ) \
krnlData->name##MutexLockcount--; \
else \
{ \
krnlData->name##MutexOwner = THREAD_INITIALISER; \
release_sem( krnlData->name##Mutex ); \
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -