1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
# Thread Local Storage in Picolibc
Copyright © 2019 Keith Packard
The standard C library API includes many functions that use persistent
state held by the library not the application. One obvious example is
'errno', a global variable originall designed to hold error status
from the Unix kernel, but which was co-opted by libc to provide
additional error status values from the library itself. There are
numerous other examples of this kind of state.
To permit multiple threads sharing a single address space to use the
library without conflicting over this data, each thread needs a
private copy of data that it uses. Newlib did this by creating a
global structure holding all thread-specific values and then defining
an API for the library to get the global structure for the current
thread.
Picolibc does this by using the built-in thread-local-storage
mechanisms in the toolchain. This has several benefits:
1) Source code is simpler as thread-local variables are
accessed directly by name.
2) Thread local storage contains only values used by the
application.
3) Generated code is smaller and faster.
## TLS model used by Picolibc
Picolibc is normally compiled with -ftls-model=local-exec. The selected
model is included in the .specs file so applications should not
include -ftls-model in their compile commands.
Local-exec defines a single block of storage for all TLS
variables. Each TLS variable is assigned an offset within this block
and those values are placed in the binary during relocation.
## Initial TLS block
The sample Picolibc linker script (picolibc.ld) allocates RAM for an
initial TLS block and arranges for it to be initialized as a part of
the normal data/bss initialization process for the application.
| flash | symbol |
| ----- | ------ |
| code | |
| rodata | |
| data initializers | __data_source |
| TLS data initializers | __tdata_source |
| RAM | symbol |
| ---- | ------ |
| data | __data_start |
| TLS data | __tls_base |
| | __data_end |
| | __tdata_size = . - __tls_base |
| TLS bss | __bss_start |
| | __tbss_size = . - __bss_start |
| | __tls_size = . - __tls_base |
| bss | |
| | __bss_end
The crt0 code copies __data_end - __data_start bytes from _data_source
to _data_start. This initializes the regular data segment *and* the
initial TLS data segment. Then, it clears memory from __bss_start to
__bss_end, initializing the TLS bss segment *and* the regular bss
segment. Finally, it sets the architecture-specific TLS data pointer
to __tls_base. Once that is set, access to TLS variables will
reference this initial TLS block.
## Creating more TLS blocks
If the application is multi-threaded and wants to allow these threads
to have separate TLS data, it may allocate memory for additional TLS
blocks:
1) Allocate a block of size __tls_size
2) Copy __tdata_size bytes from __tdata_source to the new block to
set the initial TLS values.
3) Clear __tbss_size bytes starting _tdata_size bytes into the new
block
4) Set the TLS pointer as necessary
## Picolibc APIs related to TLS
Picolib provides a couple of helper APIs for TLS:
* _set_tls
```c
void
_set_tls(void *tls);
```
This is an architecture-specific function which sets the TLS
block pointer for the processor to `tls`.
* _init_tls
```c
void
_init_tls(void *tls);
```
This function initializes the specified TLS block, copying values
into the initialized data portion and clearing values in the
uninitialized data portion.
Picolib also provides architecture-specific internal GCC APIs as
necessary, for example, __aeabi_read_tp for ARM processors.
|