Introduction
In a multi-threaded environment the C/C++ library must handle all library objects differently depending on whether they are global or local. An object can be a true global object, any updates of its state must then be guarded by some locking mechanism to make sure that only one thread can update it at any one time. In which case an object is local to each thread, the static variables containing the object state must reside in a variable area local for the thread. This area is commonly named thread-local storage, TLS.
The DLIB library uses two kinds of locks - system and file stream locks. These library objects are guarded by system locks:
- the heap, i.e.the usage of
malloc
,free
,realloc
, andcalloc
. - the file system (only available in the full library configuration), but not
the file streams themselves. The file system is updated by opening or closing
a stream, i.e. the usage of
fopen
,fclose
,fdopen
,fflush
, andfreopen
. - the signal system, i.e. the usage of
signal
. - the tempfile system, i.e. the usage of
tmpnam
. - initialization of static function objects.
The file streams locks are only needed in the full library configuration.
These library objects needs TLS:
- error functions, i.e. the usage of
errno
andstrerror
. - locale functions, i.e. the usage of
localeconv
andsetlocale
. - time functions, i.e. the usage of
asctime
,localtime
,gmtime
, andmktime
. - multibyte functions, i.e. the usage of
mbrlen
,mbrtowc
,mbsrtowc
,mbtowc
,wcrtomb
,wcsrtomb
, andwctomb
. - rand functions, i.e. the usage of
rand
andsrand
. - etc functions, i.e. the usage of
atexit
andstrtok
. - C++ exception engine.
Using multi-threaded support
To use the DLIB libraries multi-threading environment you need to do this (see below for more information about each bullet):
- Implement a replacement for the system locks interface.
- If file streams are used, implement a replacement for the file
stream locks interface or redirect (in the ilink linker
--redirect
) the interface to the system locks interface. - Implement code that handles thread creation, thread destruction, and TLS access methoding for DLIB.
- Add a statement to the linker configuration file.
- Use the option
--guard_calls
to the compiler for EC++/C++. Otherwise function static variables with dynamic initializers may be executed by several threads.
System locks interface
The following interface must be fully implemented for system locks to function in DLIB:
typedef void *__iar_Rmtx; // Lock info object
void __iar_system_Mtxinit(__iar_Rmtx *); // Initialize a system lock
void __iar_system_Mtxdst(__iar_Rmtx *); // Destroy a system lock
void __iar_system_Mtxlock(__iar_Rmtx *); // Lock a system lock
void __iar_system_Mtxunlock(__iar_Rmtx *); // Unlock a system lock
The lock and unlock implementation must survive nested calls.
File stream locks interface
The following interface may be disregarded if no file streams are used. If file streams are used, either they can be fully implemented or they can be redirected to the system locks interface:
typedef void *__iar_Rmtx; // Lock info object
void __iar_file_Mtxinit(__iar_Rmtx *); // Initialize a file lock
void __iar_file_Mtxdst(__iar_Rmtx *); // Destroy a file lock
void __iar_file_Mtxlock(__iar_Rmtx *); // Lock a file lock
void __iar_file_Mtxunlock(__iar_Rmtx *); // Unlock a file lock
The lock and unlock implementation must survive nested calls.
TLS handling in DLib
The support for threads in DLIB uses two types of threads:
- the main thread.
- Automatically created and initialized by the program startup sequence.
- Automatically destructed by the program destruct sequence.
- The TLS variables reside in the section
__DLIB_PERTHREAD
. - Even non-threaded programs have it.
- the secondary threads.
- Must be manually created and initialized.
- Must be manually destructed.
- The TLS variables reside in a manually allocated area.
If you need DLIB to support secondary threads, you must override the function
void *__iar_dlib_perthread_access(void *symbp).
Its parameter is the address to the TLS variable that should be accessed, in the main threads TLS area, and it should return the address to the symbol in the current TLS area.
There are two interfaces that can be used for creating and destroying secondary threads. You can use the following interface that allocates a segment on the heap and initializes it. At deallocation it destroys the objects in the segment and then frees it.
void *__iar_dlib_perthread_allocate(void)
void __iar_dlib_perthread_deallocate(void *)
Alternatively, if the application handles the TLS allocation, you can use the following interface for initializing and destroying the objects in the segment:
void __iar_dlib_perthread_initialize(void *)
void __iar_dlib_perthread_destroy(void)
that initializes the segment and destroys the objects in the segment respectively.
These macros can be helpful when implementing an interface for creating and destroying secondary threads:
__IAR_DLIB_PERTHREAD_SIZE
, returns the needed size for the segment.__IAR_DLIB_PERTHREAD_INIT_SIZE
, returns the initializer size for the segment. The rest of the segment, up to__IAR_DLIB_PERTHREAD_SIZE
should be zeroed.__IAR_DLIB_PERTHREAD_SYMBOL_OFFSET(symbolptr)
returns the offset to the symbol in the segment.
Note that the size needed for TLS variables are dependent on what DLIB resources the program uses.
This is an example of how to create a secondary thread:
#include <yvals.h>
void _DLIB_TLS_MEMORY *TLSp = __iar_dlib_perthread_allocate();
// Create thread-local segment
// and store pointer to it in
// TLSp.
int inUserThread = 0; // Are we in a user thread?
This is an example of how to destruct a secondary thread:
#include <yvals.h>
__iar_dlib_perthread_deallocate(TLSp); // Destroy the thread-local
// segment
The access function could look like this:
void _DLIB_TLS_MEMORY *__iar_dlib_perthread_access(
void _DLIB_TLS_MEMORY *symbp)
{
char _DLIB_TLS_MEMORY *p = 0;
if (inUserThread)
p = (char _DLIB_TLS_MEMORY *) TLSp;
else
p = (void _DLIB_TLS_MEMORY *) __segment_begin("__DLIB_PERTHREAD");
p += __IAR_DLIB_PERTHREAD_SYMBOL_OFFSET(symbp);
return (void _DLIB_TLS_MEMORY *) p;
}
The TLSp
is unique for each thread, and must be exchanged by the RTOS
or the user somehow whenever a thread switch occurs.
Changes to the linker configuration file
For ILINK, the linker normally makes an automatic choice on how to initialize static data. If threads are to be used the main threads TLS area has to be initialized by naive copying. The reason for this is that the initializers are used for each secondary threads TLS area as well. Insert the following statement in the used configuration file.
initialize by copy with packing = none { section __DLIB_PERTHREAD };