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