source: mainline/uspace/lib/c/generic/fibril.c@ 9d8307a

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 9d8307a was 40abf56a, checked in by Jiří Zárevúcky <jiri.zarevucky@…>, 7 years ago

Make sure that a thread with uninitialized TLS does not need to call malloc()
to initialize it.

For threads and tasks created by loader, we create TLS beforehand and pass
it to the child. For tasks spawned directly by the kernel, we require it is
a static executable and allocate the initial TLS using as_area_create() instead
of the libc allocator.

  • Property mode set to 100644
File size: 10.0 KB
Line 
1/*
2 * Copyright (c) 2006 Ondrej Palkovsky
3 * Copyright (c) 2007 Jakub Jermar
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup libc
31 * @{
32 */
33/** @file
34 */
35
36#include <adt/list.h>
37#include <fibril.h>
38#include <stack.h>
39#include <tls.h>
40#include <stdlib.h>
41#include <abi/mm/as.h>
42#include <as.h>
43#include <stdio.h>
44#include <libarch/barrier.h>
45#include <context.h>
46#include <futex.h>
47#include <assert.h>
48#include <async.h>
49
50#include "private/thread.h"
51#include "private/fibril.h"
52#include "private/libc.h"
53
54/**
55 * This futex serializes access to ready_list,
56 * manager_list and fibril_list.
57 */
58static futex_t fibril_futex = FUTEX_INITIALIZER;
59
60static LIST_INITIALIZE(ready_list);
61static LIST_INITIALIZE(manager_list);
62static LIST_INITIALIZE(fibril_list);
63
64/** Function that spans the whole life-cycle of a fibril.
65 *
66 * Each fibril begins execution in this function. Then the function implementing
67 * the fibril logic is called. After its return, the return value is saved.
68 * The fibril then switches to another fibril, which cleans up after it.
69 *
70 */
71static void fibril_main(void)
72{
73 /* fibril_futex and async_futex are locked when a fibril is started. */
74 futex_unlock(&fibril_futex);
75 futex_unlock(&async_futex);
76
77 fibril_t *fibril = fibril_self();
78
79 /* Call the implementing function. */
80 fibril->retval = fibril->func(fibril->arg);
81
82 futex_lock(&async_futex);
83 fibril_switch(FIBRIL_FROM_DEAD);
84 /* Not reached */
85}
86
87/** Allocate a fibril structure and TCB, but don't do anything else with it. */
88fibril_t *fibril_alloc(void)
89{
90 tcb_t *tcb = tls_make(__progsymbols.elfstart);
91 if (!tcb)
92 return NULL;
93
94 fibril_t *fibril = calloc(1, sizeof(fibril_t));
95 if (!fibril) {
96 tls_free(tcb);
97 return NULL;
98 }
99
100 tcb->fibril_data = fibril;
101 fibril->tcb = tcb;
102 fibril->is_freeable = true;
103
104 fibril_setup(fibril);
105 return fibril;
106}
107
108/**
109 * Put the fibril into fibril_list.
110 */
111void fibril_setup(fibril_t *f)
112{
113 futex_lock(&fibril_futex);
114 list_append(&f->all_link, &fibril_list);
115 futex_unlock(&fibril_futex);
116}
117
118void fibril_teardown(fibril_t *fibril, bool locked)
119{
120 if (!locked)
121 futex_lock(&fibril_futex);
122 list_remove(&fibril->all_link);
123 if (!locked)
124 futex_unlock(&fibril_futex);
125
126 if (fibril->is_freeable) {
127 tls_free(fibril->tcb);
128 free(fibril);
129 }
130}
131
132/** Switch from the current fibril.
133 *
134 * The async_futex must be held when entering this function,
135 * and is still held on return.
136 *
137 * @param stype Switch type. One of FIBRIL_PREEMPT, FIBRIL_TO_MANAGER,
138 * FIBRIL_FROM_MANAGER, FIBRIL_FROM_DEAD. The parameter
139 * describes the circumstances of the switch.
140 *
141 * @return 0 if there is no ready fibril,
142 * @return 1 otherwise.
143 *
144 */
145int fibril_switch(fibril_switch_type_t stype)
146{
147 /* Make sure the async_futex is held. */
148 futex_assert_is_locked(&async_futex);
149
150 futex_lock(&fibril_futex);
151
152 fibril_t *srcf = fibril_self();
153 fibril_t *dstf = NULL;
154
155 /* Choose a new fibril to run */
156 if (list_empty(&ready_list)) {
157 if (stype == FIBRIL_PREEMPT || stype == FIBRIL_FROM_MANAGER) {
158 // FIXME: This means that as long as there is a fibril
159 // that only yields, IPC messages are never retrieved.
160 futex_unlock(&fibril_futex);
161 return 0;
162 }
163
164 /* If we are going to manager and none exists, create it */
165 while (list_empty(&manager_list)) {
166 futex_unlock(&fibril_futex);
167 async_create_manager();
168 futex_lock(&fibril_futex);
169 }
170
171 dstf = list_get_instance(list_first(&manager_list),
172 fibril_t, link);
173 } else {
174 dstf = list_get_instance(list_first(&ready_list), fibril_t,
175 link);
176 }
177
178 list_remove(&dstf->link);
179 if (stype == FIBRIL_FROM_DEAD)
180 dstf->clean_after_me = srcf;
181
182 /* Put the current fibril into the correct run list */
183 switch (stype) {
184 case FIBRIL_PREEMPT:
185 list_append(&srcf->link, &ready_list);
186 break;
187 case FIBRIL_FROM_MANAGER:
188 list_append(&srcf->link, &manager_list);
189 break;
190 case FIBRIL_FROM_DEAD:
191 case FIBRIL_FROM_BLOCKED:
192 // Nothing.
193 break;
194 }
195
196 /* Bookkeeping. */
197 futex_give_to(&fibril_futex, dstf);
198 futex_give_to(&async_futex, dstf);
199
200 /* Swap to the next fibril. */
201 context_swap(&srcf->ctx, &dstf->ctx);
202
203 /* Restored by another fibril! */
204
205 /* Must be after context_swap()! */
206 futex_unlock(&fibril_futex);
207
208 if (srcf->clean_after_me) {
209 /*
210 * Cleanup after the dead fibril from which we
211 * restored context here.
212 */
213 void *stack = srcf->clean_after_me->stack;
214 if (stack) {
215 /*
216 * This check is necessary because a
217 * thread could have exited like a
218 * normal fibril using the
219 * FIBRIL_FROM_DEAD switch type. In that
220 * case, its fibril will not have the
221 * stack member filled.
222 */
223 as_area_destroy(stack);
224 }
225 fibril_teardown(srcf->clean_after_me, true);
226 srcf->clean_after_me = NULL;
227 }
228
229 return 1;
230}
231
232/** Create a new fibril.
233 *
234 * @param func Implementing function of the new fibril.
235 * @param arg Argument to pass to func.
236 * @param stksz Stack size in bytes.
237 *
238 * @return 0 on failure or TLS of the new fibril.
239 *
240 */
241fid_t fibril_create_generic(errno_t (*func)(void *), void *arg, size_t stksz)
242{
243 fibril_t *fibril;
244
245 fibril = fibril_alloc();
246 if (fibril == NULL)
247 return 0;
248
249 size_t stack_size = (stksz == FIBRIL_DFLT_STK_SIZE) ?
250 stack_size_get() : stksz;
251 fibril->stack = as_area_create(AS_AREA_ANY, stack_size,
252 AS_AREA_READ | AS_AREA_WRITE | AS_AREA_CACHEABLE | AS_AREA_GUARD |
253 AS_AREA_LATE_RESERVE, AS_AREA_UNPAGED);
254 if (fibril->stack == AS_MAP_FAILED) {
255 fibril_teardown(fibril, false);
256 return 0;
257 }
258
259 fibril->func = func;
260 fibril->arg = arg;
261
262 context_create_t sctx = {
263 .fn = fibril_main,
264 .stack_base = fibril->stack,
265 .stack_size = stack_size,
266 .tls = fibril->tcb,
267 };
268
269 context_create(&fibril->ctx, &sctx);
270 return (fid_t) fibril;
271}
272
273/** Delete a fibril that has never run.
274 *
275 * Free resources of a fibril that has been created with fibril_create()
276 * but never readied using fibril_add_ready().
277 *
278 * @param fid Pointer to the fibril structure of the fibril to be
279 * added.
280 */
281void fibril_destroy(fid_t fid)
282{
283 fibril_t *fibril = (fibril_t *) fid;
284
285 as_area_destroy(fibril->stack);
286 fibril_teardown(fibril, false);
287}
288
289/** Add a fibril to the ready list.
290 *
291 * @param fid Pointer to the fibril structure of the fibril to be
292 * added.
293 *
294 */
295void fibril_add_ready(fid_t fid)
296{
297 fibril_t *fibril = (fibril_t *) fid;
298
299 futex_lock(&fibril_futex);
300 list_append(&fibril->link, &ready_list);
301 futex_unlock(&fibril_futex);
302}
303
304/** Add a fibril to the manager list.
305 *
306 * @param fid Pointer to the fibril structure of the fibril to be
307 * added.
308 *
309 */
310void fibril_add_manager(fid_t fid)
311{
312 fibril_t *fibril = (fibril_t *) fid;
313
314 futex_lock(&fibril_futex);
315 list_append(&fibril->link, &manager_list);
316 futex_unlock(&fibril_futex);
317}
318
319/** Remove one manager from the manager list. */
320void fibril_remove_manager(void)
321{
322 futex_lock(&fibril_futex);
323 if (!list_empty(&manager_list))
324 list_remove(list_first(&manager_list));
325 futex_unlock(&fibril_futex);
326}
327
328fibril_t *fibril_self(void)
329{
330 assert(__tcb_is_set());
331 tcb_t *tcb = __tcb_get();
332 assert(tcb->fibril_data);
333 return tcb->fibril_data;
334}
335
336/** Return fibril id of the currently running fibril.
337 *
338 * @return fibril ID of the currently running fibril.
339 *
340 */
341fid_t fibril_get_id(void)
342{
343 return (fid_t) fibril_self();
344}
345
346void fibril_yield(void)
347{
348 futex_lock(&async_futex);
349 (void) fibril_switch(FIBRIL_PREEMPT);
350 futex_unlock(&async_futex);
351}
352
353static void _runner_fn(void *arg)
354{
355 futex_lock(&async_futex);
356 (void) fibril_switch(FIBRIL_FROM_BLOCKED);
357 __builtin_unreachable();
358}
359
360/**
361 * Spawn a given number of runners (i.e. OS threads) immediately, and
362 * unconditionally. This is meant to be used for tests and debugging.
363 * Regular programs should just use `fibril_enable_multithreaded()`.
364 *
365 * @param n Number of runners to spawn.
366 * @return Number of runners successfully spawned.
367 */
368int fibril_test_spawn_runners(int n)
369{
370 errno_t rc;
371
372 for (int i = 0; i < n; i++) {
373 thread_id_t tid;
374 rc = thread_create(_runner_fn, NULL, "fibril runner", &tid);
375 if (rc != EOK)
376 return i;
377 thread_detach(tid);
378 }
379
380 return n;
381}
382
383/**
384 * Opt-in to have more than one runner thread.
385 *
386 * Currently, a task only ever runs in one thread because multithreading
387 * might break some existing code.
388 *
389 * Eventually, the number of runner threads for a given task should become
390 * configurable in the environment and this function becomes no-op.
391 */
392void fibril_enable_multithreaded(void)
393{
394 // TODO: Implement better.
395 // For now, 4 total runners is a sensible default.
396 fibril_test_spawn_runners(3);
397}
398
399/**
400 * Detach a fibril.
401 */
402void fibril_detach(fid_t f)
403{
404 // TODO: Currently all fibrils are detached by default, but they
405 // won't always be. Code that explicitly spawns fibrils with
406 // limited lifetime should call this function.
407}
408
409/** @}
410 */
Note: See TracBrowser for help on using the repository browser.