source: mainline/uspace/lib/c/generic/fibril_synch.c@ 514d561

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

Fibril/async implementation overhaul.

This commit marks the move towards treating the fibril library as a mere
implementation of a generic threading interface. Understood as a layer that
wraps the kernel threads, we not only have to wrap threading itself, but also
every syscall that blocks the kernel thread (by blocking, we mean thread not
doing useful work until an external event happens — e.g. locking a kernel
mutex or thread sleep is understood as blocking, but an as_area_create() is not,
despite potentially taking a long time to complete).

Consequently, we implement fibril_ipc_wait() as a fibril-native wrapper for
kernel's ipc_wait(), and also implement timer functionality like timeouts
as part of the fibril library. This removes the interdependency between fibril
implementation and the async framework — in theory, the fibril API could be
reimplemented as a simple 1:1 shim, and the async framework would continue
working normally (note that the current implementation of loader complicates
this).

To better isolate the fibril internals from the implementation of high-level
synchronization, a fibril_event_t is added. This object conceptually acts
like a single slot wait queue. All other synchronization is implemented in
terms of this primitive.

  • Property mode set to 100644
File size: 15.4 KB
Line 
1/*
2 * Copyright (c) 2009 Jakub Jermar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libc
30 * @{
31 */
32/** @file
33 */
34
35#include <fibril_synch.h>
36#include <fibril.h>
37#include <async.h>
38#include <adt/list.h>
39#include <futex.h>
40#include <sys/time.h>
41#include <errno.h>
42#include <assert.h>
43#include <stacktrace.h>
44#include <stdlib.h>
45#include <stdio.h>
46#include <io/kio.h>
47#include <mem.h>
48#include <context.h>
49
50#include "private/async.h"
51#include "private/fibril.h"
52
53static fibril_local bool deadlocked = false;
54
55typedef struct {
56 link_t link;
57 fibril_event_t event;
58 fibril_mutex_t *mutex;
59 fid_t fid;
60} awaiter_t;
61
62#define AWAITER_INIT { .fid = fibril_get_id() }
63
64static void print_deadlock(fibril_owner_info_t *oi)
65{
66 // FIXME: Print to stderr.
67
68 fibril_t *f = (fibril_t *) fibril_get_id();
69
70 if (deadlocked) {
71 kio_printf("Deadlock detected while printing deadlock. Aborting.\n");
72 abort();
73 }
74 deadlocked = true;
75
76 printf("Deadlock detected.\n");
77 stacktrace_print();
78
79 printf("Fibril %p waits for primitive %p.\n", f, oi);
80
81 while (oi && oi->owned_by) {
82 printf("Primitive %p is owned by fibril %p.\n",
83 oi, oi->owned_by);
84 if (oi->owned_by == f)
85 break;
86 stacktrace_print_fp_pc(
87 context_get_fp(&oi->owned_by->ctx),
88 context_get_pc(&oi->owned_by->ctx));
89 printf("Fibril %p waits for primitive %p.\n",
90 oi->owned_by, oi->owned_by->waits_for);
91 oi = oi->owned_by->waits_for;
92 }
93}
94
95
96static void check_fibril_for_deadlock(fibril_owner_info_t *oi, fibril_t *fib)
97{
98 futex_assert_is_locked(&async_futex);
99
100 while (oi && oi->owned_by) {
101 if (oi->owned_by == fib) {
102 futex_unlock(&async_futex);
103 print_deadlock(oi);
104 abort();
105 }
106 oi = oi->owned_by->waits_for;
107 }
108}
109
110static void check_for_deadlock(fibril_owner_info_t *oi)
111{
112 check_fibril_for_deadlock(oi, fibril_self());
113}
114
115void fibril_mutex_initialize(fibril_mutex_t *fm)
116{
117 fm->oi.owned_by = NULL;
118 fm->counter = 1;
119 list_initialize(&fm->waiters);
120}
121
122void fibril_mutex_lock(fibril_mutex_t *fm)
123{
124 fibril_t *f = (fibril_t *) fibril_get_id();
125
126 futex_lock(&async_futex);
127
128 if (fm->counter-- > 0) {
129 fm->oi.owned_by = f;
130 futex_unlock(&async_futex);
131 return;
132 }
133
134 awaiter_t wdata = AWAITER_INIT;
135 list_append(&wdata.link, &fm->waiters);
136 check_for_deadlock(&fm->oi);
137 f->waits_for = &fm->oi;
138
139 futex_unlock(&async_futex);
140
141 fibril_wait_for(&wdata.event);
142}
143
144bool fibril_mutex_trylock(fibril_mutex_t *fm)
145{
146 bool locked = false;
147
148 futex_lock(&async_futex);
149 if (fm->counter > 0) {
150 fm->counter--;
151 fm->oi.owned_by = (fibril_t *) fibril_get_id();
152 locked = true;
153 }
154 futex_unlock(&async_futex);
155
156 return locked;
157}
158
159static void _fibril_mutex_unlock_unsafe(fibril_mutex_t *fm)
160{
161 assert(fm->oi.owned_by == (fibril_t *) fibril_get_id());
162
163 if (fm->counter++ < 0) {
164 awaiter_t *wdp = list_pop(&fm->waiters, awaiter_t, link);
165 assert(wdp);
166
167 fibril_t *f = (fibril_t *) wdp->fid;
168 fm->oi.owned_by = f;
169 f->waits_for = NULL;
170
171 fibril_notify(&wdp->event);
172 } else {
173 fm->oi.owned_by = NULL;
174 }
175}
176
177void fibril_mutex_unlock(fibril_mutex_t *fm)
178{
179 futex_lock(&async_futex);
180 _fibril_mutex_unlock_unsafe(fm);
181 futex_unlock(&async_futex);
182}
183
184bool fibril_mutex_is_locked(fibril_mutex_t *fm)
185{
186 futex_lock(&async_futex);
187 bool locked = (fm->oi.owned_by == (fibril_t *) fibril_get_id());
188 futex_unlock(&async_futex);
189 return locked;
190}
191
192void fibril_rwlock_initialize(fibril_rwlock_t *frw)
193{
194 frw->oi.owned_by = NULL;
195 frw->writers = 0;
196 frw->readers = 0;
197 list_initialize(&frw->waiters);
198}
199
200void fibril_rwlock_read_lock(fibril_rwlock_t *frw)
201{
202 fibril_t *f = (fibril_t *) fibril_get_id();
203
204 futex_lock(&async_futex);
205
206 if (!frw->writers) {
207 /* Consider the first reader the owner. */
208 if (frw->readers++ == 0)
209 frw->oi.owned_by = f;
210 futex_unlock(&async_futex);
211 return;
212 }
213
214 f->is_writer = false;
215
216 awaiter_t wdata = AWAITER_INIT;
217 list_append(&wdata.link, &frw->waiters);
218 check_for_deadlock(&frw->oi);
219 f->waits_for = &frw->oi;
220
221 futex_unlock(&async_futex);
222
223 fibril_wait_for(&wdata.event);
224}
225
226void fibril_rwlock_write_lock(fibril_rwlock_t *frw)
227{
228 fibril_t *f = (fibril_t *) fibril_get_id();
229
230 futex_lock(&async_futex);
231
232 if (!frw->writers && !frw->readers) {
233 frw->oi.owned_by = f;
234 frw->writers++;
235 futex_unlock(&async_futex);
236 return;
237 }
238
239 f->is_writer = true;
240
241 awaiter_t wdata = AWAITER_INIT;
242 list_append(&wdata.link, &frw->waiters);
243 check_for_deadlock(&frw->oi);
244 f->waits_for = &frw->oi;
245
246 futex_unlock(&async_futex);
247
248 fibril_wait_for(&wdata.event);
249}
250
251static void _fibril_rwlock_common_unlock(fibril_rwlock_t *frw)
252{
253 if (frw->readers) {
254 if (--frw->readers) {
255 if (frw->oi.owned_by == (fibril_t *) fibril_get_id()) {
256 /*
257 * If this reader fibril was considered the
258 * owner of this rwlock, clear the ownership
259 * information even if there are still more
260 * readers.
261 *
262 * This is the limitation of the detection
263 * mechanism rooted in the fact that tracking
264 * all readers would require dynamically
265 * allocated memory for keeping linkage info.
266 */
267 frw->oi.owned_by = NULL;
268 }
269
270 return;
271 }
272 } else {
273 frw->writers--;
274 }
275
276 assert(!frw->readers && !frw->writers);
277
278 frw->oi.owned_by = NULL;
279
280 while (!list_empty(&frw->waiters)) {
281 link_t *tmp = list_first(&frw->waiters);
282 awaiter_t *wdp;
283 fibril_t *f;
284
285 wdp = list_get_instance(tmp, awaiter_t, link);
286 f = (fibril_t *) wdp->fid;
287
288 if (f->is_writer) {
289 if (frw->readers)
290 break;
291 frw->writers++;
292 } else {
293 frw->readers++;
294 }
295
296 f->waits_for = NULL;
297 list_remove(&wdp->link);
298 frw->oi.owned_by = f;
299 fibril_notify(&wdp->event);
300
301 if (frw->writers)
302 break;
303 }
304}
305
306void fibril_rwlock_read_unlock(fibril_rwlock_t *frw)
307{
308 futex_lock(&async_futex);
309 assert(frw->readers > 0);
310 _fibril_rwlock_common_unlock(frw);
311 futex_unlock(&async_futex);
312}
313
314void fibril_rwlock_write_unlock(fibril_rwlock_t *frw)
315{
316 futex_lock(&async_futex);
317 assert(frw->writers == 1);
318 assert(frw->oi.owned_by == fibril_self());
319 _fibril_rwlock_common_unlock(frw);
320 futex_unlock(&async_futex);
321}
322
323bool fibril_rwlock_is_read_locked(fibril_rwlock_t *frw)
324{
325 futex_lock(&async_futex);
326 bool locked = (frw->readers > 0);
327 futex_unlock(&async_futex);
328 return locked;
329}
330
331bool fibril_rwlock_is_write_locked(fibril_rwlock_t *frw)
332{
333 futex_lock(&async_futex);
334 assert(frw->writers <= 1);
335 bool locked = (frw->writers > 0) && (frw->oi.owned_by == fibril_self());
336 futex_unlock(&async_futex);
337 return locked;
338}
339
340bool fibril_rwlock_is_locked(fibril_rwlock_t *frw)
341{
342 return fibril_rwlock_is_read_locked(frw) ||
343 fibril_rwlock_is_write_locked(frw);
344}
345
346void fibril_condvar_initialize(fibril_condvar_t *fcv)
347{
348 list_initialize(&fcv->waiters);
349}
350
351/**
352 * FIXME: If `timeout` is negative, the function returns ETIMEOUT immediately,
353 * and if `timeout` is 0, the wait never times out.
354 * This is not consistent with other similar APIs.
355 */
356errno_t
357fibril_condvar_wait_timeout(fibril_condvar_t *fcv, fibril_mutex_t *fm,
358 suseconds_t timeout)
359{
360 assert(fibril_mutex_is_locked(fm));
361
362 if (timeout < 0)
363 return ETIMEOUT;
364
365 awaiter_t wdata = AWAITER_INIT;
366 wdata.mutex = fm;
367
368 struct timeval tv;
369 struct timeval *expires = NULL;
370 if (timeout) {
371 getuptime(&tv);
372 tv_add_diff(&tv, timeout);
373 expires = &tv;
374 }
375
376 futex_lock(&async_futex);
377 _fibril_mutex_unlock_unsafe(fm);
378 list_append(&wdata.link, &fcv->waiters);
379 futex_unlock(&async_futex);
380
381 (void) fibril_wait_timeout(&wdata.event, expires);
382
383 futex_lock(&async_futex);
384 bool timed_out = link_in_use(&wdata.link);
385 list_remove(&wdata.link);
386 futex_unlock(&async_futex);
387
388 fibril_mutex_lock(fm);
389
390 return timed_out ? ETIMEOUT : EOK;
391}
392
393void fibril_condvar_wait(fibril_condvar_t *fcv, fibril_mutex_t *fm)
394{
395 (void) fibril_condvar_wait_timeout(fcv, fm, 0);
396}
397
398void fibril_condvar_signal(fibril_condvar_t *fcv)
399{
400 futex_lock(&async_futex);
401
402 awaiter_t *w = list_pop(&fcv->waiters, awaiter_t, link);
403 if (w != NULL)
404 fibril_notify(&w->event);
405
406 futex_unlock(&async_futex);
407}
408
409void fibril_condvar_broadcast(fibril_condvar_t *fcv)
410{
411 futex_lock(&async_futex);
412
413 awaiter_t *w;
414 while ((w = list_pop(&fcv->waiters, awaiter_t, link)))
415 fibril_notify(&w->event);
416
417 futex_unlock(&async_futex);
418}
419
420/** Timer fibril.
421 *
422 * @param arg Timer
423 */
424static errno_t fibril_timer_func(void *arg)
425{
426 fibril_timer_t *timer = (fibril_timer_t *) arg;
427 errno_t rc;
428
429 fibril_mutex_lock(timer->lockp);
430
431 while (timer->state != fts_cleanup) {
432 switch (timer->state) {
433 case fts_not_set:
434 case fts_fired:
435 fibril_condvar_wait(&timer->cv, timer->lockp);
436 break;
437 case fts_active:
438 rc = fibril_condvar_wait_timeout(&timer->cv,
439 timer->lockp, timer->delay);
440 if (rc == ETIMEOUT && timer->state == fts_active) {
441 timer->state = fts_fired;
442 timer->handler_fid = fibril_get_id();
443 fibril_mutex_unlock(timer->lockp);
444 timer->fun(timer->arg);
445 fibril_mutex_lock(timer->lockp);
446 timer->handler_fid = 0;
447 }
448 break;
449 case fts_cleanup:
450 case fts_clean:
451 assert(false);
452 break;
453 }
454 }
455
456 /* Acknowledge timer fibril has finished cleanup. */
457 timer->state = fts_clean;
458 fibril_condvar_broadcast(&timer->cv);
459 fibril_mutex_unlock(timer->lockp);
460
461 return 0;
462}
463
464/** Create new timer.
465 *
466 * @return New timer on success, @c NULL if out of memory.
467 */
468fibril_timer_t *fibril_timer_create(fibril_mutex_t *lock)
469{
470 fid_t fid;
471 fibril_timer_t *timer;
472
473 timer = calloc(1, sizeof(fibril_timer_t));
474 if (timer == NULL)
475 return NULL;
476
477 fid = fibril_create(fibril_timer_func, (void *) timer);
478 if (fid == 0) {
479 free(timer);
480 return NULL;
481 }
482
483 fibril_mutex_initialize(&timer->lock);
484 fibril_condvar_initialize(&timer->cv);
485
486 timer->fibril = fid;
487 timer->state = fts_not_set;
488 timer->lockp = (lock != NULL) ? lock : &timer->lock;
489
490 fibril_add_ready(fid);
491 return timer;
492}
493
494/** Destroy timer.
495 *
496 * @param timer Timer, must not be active or accessed by other threads.
497 */
498void fibril_timer_destroy(fibril_timer_t *timer)
499{
500 fibril_mutex_lock(timer->lockp);
501 assert(timer->state == fts_not_set || timer->state == fts_fired);
502
503 /* Request timer fibril to terminate. */
504 timer->state = fts_cleanup;
505 fibril_condvar_broadcast(&timer->cv);
506
507 /* Wait for timer fibril to terminate */
508 while (timer->state != fts_clean)
509 fibril_condvar_wait(&timer->cv, timer->lockp);
510 fibril_mutex_unlock(timer->lockp);
511
512 free(timer);
513}
514
515/** Set timer.
516 *
517 * Set timer to execute a callback function after the specified
518 * interval.
519 *
520 * @param timer Timer
521 * @param delay Delay in microseconds
522 * @param fun Callback function
523 * @param arg Argument for @a fun
524 */
525void fibril_timer_set(fibril_timer_t *timer, suseconds_t delay,
526 fibril_timer_fun_t fun, void *arg)
527{
528 fibril_mutex_lock(timer->lockp);
529 fibril_timer_set_locked(timer, delay, fun, arg);
530 fibril_mutex_unlock(timer->lockp);
531}
532
533/** Set locked timer.
534 *
535 * Set timer to execute a callback function after the specified
536 * interval. Must be called when the timer is locked.
537 *
538 * @param timer Timer
539 * @param delay Delay in microseconds
540 * @param fun Callback function
541 * @param arg Argument for @a fun
542 */
543void fibril_timer_set_locked(fibril_timer_t *timer, suseconds_t delay,
544 fibril_timer_fun_t fun, void *arg)
545{
546 assert(fibril_mutex_is_locked(timer->lockp));
547 assert(timer->state == fts_not_set || timer->state == fts_fired);
548 timer->state = fts_active;
549 timer->delay = delay;
550 timer->fun = fun;
551 timer->arg = arg;
552 fibril_condvar_broadcast(&timer->cv);
553}
554
555/** Clear timer.
556 *
557 * Clears (cancels) timer and returns last state of the timer.
558 * This can be one of:
559 * - fts_not_set If the timer has not been set or has been cleared
560 * - fts_active Timer was set but did not fire
561 * - fts_fired Timer fired
562 *
563 * @param timer Timer
564 * @return Last timer state
565 */
566fibril_timer_state_t fibril_timer_clear(fibril_timer_t *timer)
567{
568 fibril_timer_state_t old_state;
569
570 fibril_mutex_lock(timer->lockp);
571 old_state = fibril_timer_clear_locked(timer);
572 fibril_mutex_unlock(timer->lockp);
573
574 return old_state;
575}
576
577/** Clear locked timer.
578 *
579 * Clears (cancels) timer and returns last state of the timer.
580 * This can be one of:
581 * - fts_not_set If the timer has not been set or has been cleared
582 * - fts_active Timer was set but did not fire
583 * - fts_fired Timer fired
584 * Must be called when the timer is locked.
585 *
586 * @param timer Timer
587 * @return Last timer state
588 */
589fibril_timer_state_t fibril_timer_clear_locked(fibril_timer_t *timer)
590{
591 fibril_timer_state_t old_state;
592
593 assert(fibril_mutex_is_locked(timer->lockp));
594
595 while (timer->handler_fid != 0) {
596 if (timer->handler_fid == fibril_get_id()) {
597 printf("Deadlock detected.\n");
598 stacktrace_print();
599 printf("Fibril %p is trying to clear timer %p from "
600 "inside its handler %p.\n",
601 fibril_get_id(), timer, timer->fun);
602 abort();
603 }
604
605 fibril_condvar_wait(&timer->cv, timer->lockp);
606 }
607
608 old_state = timer->state;
609 timer->state = fts_not_set;
610
611 timer->delay = 0;
612 timer->fun = NULL;
613 timer->arg = NULL;
614 fibril_condvar_broadcast(&timer->cv);
615
616 return old_state;
617}
618
619/**
620 * Initialize a semaphore with initial count set to the provided value.
621 *
622 * @param sem Semaphore to initialize.
623 * @param count Initial count. Must not be negative.
624 */
625void fibril_semaphore_initialize(fibril_semaphore_t *sem, long count)
626{
627 /*
628 * Negative count denotes the length of waitlist,
629 * so it makes no sense as an initial value.
630 */
631 assert(count >= 0);
632 sem->count = count;
633 list_initialize(&sem->waiters);
634}
635
636/**
637 * Produce one token.
638 * If there are fibrils waiting for tokens, this operation satisfies
639 * exactly one waiting `fibril_semaphore_down()`.
640 * This operation never blocks the fibril.
641 *
642 * @param sem Semaphore to use.
643 */
644void fibril_semaphore_up(fibril_semaphore_t *sem)
645{
646 futex_lock(&async_futex);
647 sem->count++;
648
649 if (sem->count <= 0) {
650 awaiter_t *w = list_pop(&sem->waiters, awaiter_t, link);
651 assert(w);
652 fibril_notify(&w->event);
653 }
654
655 futex_unlock(&async_futex);
656}
657
658/**
659 * Consume one token.
660 * If there are no available tokens (count <= 0), this operation blocks until
661 * another fibril produces a token using `fibril_semaphore_up()`.
662 *
663 * @param sem Semaphore to use.
664 */
665void fibril_semaphore_down(fibril_semaphore_t *sem)
666{
667 futex_lock(&async_futex);
668 sem->count--;
669
670 if (sem->count >= 0) {
671 futex_unlock(&async_futex);
672 return;
673 }
674
675 awaiter_t wdata = AWAITER_INIT;
676 list_append(&wdata.link, &sem->waiters);
677
678 futex_unlock(&async_futex);
679
680 fibril_wait_for(&wdata.event);
681}
682
683/** @}
684 */
Note: See TracBrowser for help on using the repository browser.