pthread_local_storage.c 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // Copyright 2017 Espressif Systems (Shanghai) PTE LTD
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. #include <errno.h>
  14. #include <pthread.h>
  15. #include <string.h>
  16. #include "esp_err.h"
  17. #include "esp_log.h"
  18. #include "freertos/FreeRTOS.h"
  19. #include "freertos/task.h"
  20. #include "sys/lock.h"
  21. #include "sys/queue.h"
  22. #include "pthread_internal.h"
  23. #define PTHREAD_TLS_INDEX 0
  24. typedef void (*pthread_destructor_t)(void*);
  25. /* This is a very naive implementation of key-indexed thread local storage, using two linked lists
  26. (one is a global list of registered keys, one per thread for thread local storage values).
  27. It won't work well if lots of keys & thread-local values are stored (O(n) lookup for both),
  28. but it should work for small amounts of data.
  29. */
  30. typedef struct key_entry_t_ {
  31. pthread_key_t key;
  32. pthread_destructor_t destructor;
  33. SLIST_ENTRY(key_entry_t_) next;
  34. } key_entry_t;
  35. // List of all keys created with pthread_key_create()
  36. SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys);
  37. static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED;
  38. // List of all value entries associated with a thread via pthread_setspecific()
  39. typedef struct value_entry_t_ {
  40. pthread_key_t key;
  41. void *value;
  42. SLIST_ENTRY(value_entry_t_) next;
  43. } value_entry_t;
  44. // Type for the head of the list, as saved as a FreeRTOS thread local storage pointer
  45. SLIST_HEAD(values_list_t_, value_entry_t_);
  46. typedef struct values_list_t_ values_list_t;
  47. int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor)
  48. {
  49. key_entry_t *new_key = malloc(sizeof(key_entry_t));
  50. if (new_key == NULL) {
  51. return ENOMEM;
  52. }
  53. portENTER_CRITICAL(&s_keys_lock);
  54. const key_entry_t *head = SLIST_FIRST(&s_keys);
  55. new_key->key = (head == NULL) ? 1 : (head->key + 1);
  56. new_key->destructor = destructor;
  57. *key = new_key->key;
  58. SLIST_INSERT_HEAD(&s_keys, new_key, next);
  59. portEXIT_CRITICAL(&s_keys_lock);
  60. return 0;
  61. }
  62. static key_entry_t *find_key(pthread_key_t key)
  63. {
  64. portENTER_CRITICAL(&s_keys_lock);
  65. key_entry_t *result = NULL;;
  66. SLIST_FOREACH(result, &s_keys, next) {
  67. if(result->key == key) {
  68. break;
  69. }
  70. }
  71. portEXIT_CRITICAL(&s_keys_lock);
  72. return result;
  73. }
  74. int pthread_key_delete(pthread_key_t key)
  75. {
  76. portENTER_CRITICAL(&s_keys_lock);
  77. /* Ideally, we would also walk all tasks' thread local storage value_list here
  78. and delete any values associated with this key. We do not do this...
  79. */
  80. key_entry_t *entry = find_key(key);
  81. if (entry != NULL) {
  82. SLIST_REMOVE(&s_keys, entry, key_entry_t_, next);
  83. free(entry);
  84. }
  85. portEXIT_CRITICAL(&s_keys_lock);
  86. return 0;
  87. }
  88. /* Clean up callback for deleted tasks.
  89. This is called from one of two places:
  90. If the thread was created via pthread_create() then it's called by pthread_task_func() when that thread ends,
  91. or calls pthread_exit(), and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted.
  92. For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted.
  93. There are two reasons for calling it early for pthreads:
  94. - To keep the timing consistent with "normal" pthreads, so after pthread_join() the task's destructors have all
  95. been called even if the idle task hasn't run cleanup yet.
  96. - The destructor is always called in the context of the thread itself - which is important if the task then calls
  97. pthread_getspecific() or pthread_setspecific() to update the state further, as allowed for in the spec.
  98. */
  99. static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls)
  100. {
  101. values_list_t *tls = (values_list_t *)v_tls;
  102. assert(tls != NULL);
  103. /* Walk the list, freeing all entries and calling destructors if they are registered */
  104. while (1) {
  105. value_entry_t *entry = SLIST_FIRST(tls);
  106. if (entry == NULL) {
  107. break;
  108. }
  109. SLIST_REMOVE_HEAD(tls, next);
  110. // This is a little slow, walking the linked list of keys once per value,
  111. // but assumes that the thread's value list will have less entries
  112. // than the keys list
  113. key_entry_t *key = find_key(entry->key);
  114. if (key != NULL && key->destructor != NULL) {
  115. key->destructor(entry->value);
  116. }
  117. free(entry);
  118. }
  119. free(tls);
  120. }
  121. #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
  122. /* Called from FreeRTOS task delete hook */
  123. void pthread_local_storage_cleanup(TaskHandle_t task)
  124. {
  125. void *tls = pvTaskGetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX);
  126. if (tls != NULL) {
  127. pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
  128. vTaskSetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX, NULL);
  129. }
  130. }
  131. void __real_vPortCleanUpTCB(void *tcb);
  132. /* If static task cleanup hook is defined then its applications responsibility to define `vPortCleanUpTCB`.
  133. Here we are wrapping it, so that we can do pthread specific TLS cleanup and then invoke application
  134. real specific `vPortCleanUpTCB` */
  135. void __wrap_vPortCleanUpTCB(void *tcb)
  136. {
  137. pthread_local_storage_cleanup(tcb);
  138. __real_vPortCleanUpTCB(tcb);
  139. }
  140. #endif
  141. /* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */
  142. void pthread_internal_local_storage_destructor_callback(void)
  143. {
  144. void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
  145. if (tls != NULL) {
  146. pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
  147. /* remove the thread-local-storage pointer to avoid the idle task cleanup
  148. calling it again...
  149. */
  150. #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
  151. vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL);
  152. #else
  153. vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
  154. PTHREAD_TLS_INDEX,
  155. NULL,
  156. NULL);
  157. #endif
  158. }
  159. }
  160. static value_entry_t *find_value(const values_list_t *list, pthread_key_t key)
  161. {
  162. value_entry_t *result = NULL;;
  163. SLIST_FOREACH(result, list, next) {
  164. if(result->key == key) {
  165. break;
  166. }
  167. }
  168. return result;
  169. }
  170. void *pthread_getspecific(pthread_key_t key)
  171. {
  172. values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
  173. if (tls == NULL) {
  174. return NULL;
  175. }
  176. value_entry_t *entry = find_value(tls, key);
  177. if(entry != NULL) {
  178. return entry->value;
  179. }
  180. return NULL;
  181. }
  182. int pthread_setspecific(pthread_key_t key, const void *value)
  183. {
  184. key_entry_t *key_entry = find_key(key);
  185. if (key_entry == NULL) {
  186. return ENOENT; // this situation is undefined by pthreads standard
  187. }
  188. values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
  189. if (tls == NULL) {
  190. tls = calloc(1, sizeof(values_list_t));
  191. if (tls == NULL) {
  192. return ENOMEM;
  193. }
  194. #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
  195. vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls);
  196. #else
  197. vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
  198. PTHREAD_TLS_INDEX,
  199. tls,
  200. pthread_local_storage_thread_deleted_callback);
  201. #endif
  202. }
  203. value_entry_t *entry = find_value(tls, key);
  204. if (entry != NULL) {
  205. if (value != NULL) {
  206. // cast on next line is necessary as pthreads API uses
  207. // 'const void *' here but elsewhere uses 'void *'
  208. entry->value = (void *) value;
  209. } else { // value == NULL, remove the entry
  210. SLIST_REMOVE(tls, entry, value_entry_t_, next);
  211. free(entry);
  212. }
  213. } else if (value != NULL) {
  214. entry = malloc(sizeof(value_entry_t));
  215. if (entry == NULL) {
  216. return ENOMEM;
  217. }
  218. entry->key = key;
  219. entry->value = (void *) value; // see note above about cast
  220. // insert the new entry at the end of the list. this is important because
  221. // a destructor may call pthread_setspecific() to add a new non-NULL value
  222. // to the list, and this should be processed after all other entries.
  223. //
  224. // See pthread_local_storage_thread_deleted_callback()
  225. value_entry_t *last_entry = NULL;
  226. value_entry_t *it;
  227. SLIST_FOREACH(it, tls, next) {
  228. last_entry = it;
  229. }
  230. if (last_entry == NULL) {
  231. SLIST_INSERT_HEAD(tls, entry, next);
  232. } else {
  233. SLIST_INSERT_AFTER(last_entry, entry, next);
  234. }
  235. }
  236. return 0;
  237. }
  238. /* Hook function to force linking this file */
  239. void pthread_include_pthread_local_storage_impl(void)
  240. {
  241. }