• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "esp_osal/esp_osal.h"
20 #include "esp_osal/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 
pthread_key_create(pthread_key_t * key,pthread_destructor_t destructor)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 
find_key(pthread_key_t key)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 
pthread_key_delete(pthread_key_t key)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 */
pthread_local_storage_thread_deleted_callback(int index,void * v_tls)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 */
pthread_local_storage_cleanup(TaskHandle_t task)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` */
__wrap_vPortCleanUpTCB(void * tcb)161 void __wrap_vPortCleanUpTCB(void *tcb)
162 {
163     pthread_local_storage_cleanup(tcb);
164     __real_vPortCleanUpTCB(tcb);
165 }
166 #endif
167 #if 0
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 #endif
188 #if 1
find_value(const values_list_t * list,pthread_key_t key)189 static value_entry_t *find_value(const values_list_t *list, pthread_key_t key)
190 {
191     value_entry_t *result = NULL;;
192     SLIST_FOREACH(result, list, next) {
193         if(result->key == key) {
194             break;
195         }
196     }
197     return result;
198 }
199 
pthread_getspecific(pthread_key_t key)200 void *pthread_getspecific(pthread_key_t key)
201 {
202     values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
203     if (tls == NULL) {
204         return NULL;
205     }
206 
207     value_entry_t *entry = find_value(tls, key);
208     if(entry != NULL) {
209         return entry->value;
210     }
211     return NULL;
212 }
213 
pthread_setspecific(pthread_key_t key,const void * value)214 int pthread_setspecific(pthread_key_t key, const void *value)
215 {
216     key_entry_t *key_entry = find_key(key);
217     if (key_entry == NULL) {
218         return ENOENT; // this situation is undefined by pthreads standard
219     }
220 
221     values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
222     if (tls == NULL) {
223         tls = calloc(1, sizeof(values_list_t));
224         if (tls == NULL) {
225             return ENOMEM;
226         }
227 #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
228         vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls);
229 #else
230         vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
231                                                         PTHREAD_TLS_INDEX,
232                                                         tls,
233                                                         pthread_local_storage_thread_deleted_callback);
234 #endif
235     }
236 
237     value_entry_t *entry = find_value(tls, key);
238     if (entry != NULL) {
239         if (value != NULL) {
240             // cast on next line is necessary as pthreads API uses
241             // 'const void *' here but elsewhere uses 'void *'
242             entry->value = (void *) value;
243         } else { // value == NULL, remove the entry
244             SLIST_REMOVE(tls, entry, value_entry_t_, next);
245             free(entry);
246         }
247     } else if (value != NULL) {
248         entry = malloc(sizeof(value_entry_t));
249         if (entry == NULL) {
250             return ENOMEM;
251         }
252         entry->key = key;
253         entry->value = (void *) value; // see note above about cast
254         SLIST_INSERT_HEAD(tls, entry, next);
255     }
256 
257     return 0;
258 }
259 
260 /* Hook function to force linking this file */
pthread_include_pthread_local_storage_impl(void)261 void pthread_include_pthread_local_storage_impl(void)
262 {
263 }
264 #endif
265