~jsvoboda/helenos/dnsr

« back to all changes in this revision

Viewing changes to uspace/lib/libc/generic/fibril.c

  • Committer: Martin Decky
  • Date: 2009-08-04 11:19:19 UTC
  • Revision ID: martin@uranus.dsrg.hide.ms.mff.cuni.cz-20090804111919-evyclddlr3v5lhmp
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 <thread.h>
 
39
#include <tls.h>
 
40
#include <malloc.h>
 
41
#include <unistd.h>
 
42
#include <stdio.h>
 
43
#include <libarch/faddr.h>
 
44
#include <futex.h>
 
45
#include <assert.h>
 
46
#include <async.h>
 
47
 
 
48
#ifndef FIBRIL_INITIAL_STACK_PAGES_NO
 
49
#define FIBRIL_INITIAL_STACK_PAGES_NO   1
 
50
#endif
 
51
 
 
52
/**
 
53
 * This futex serializes access to ready_list, serialized_list and manager_list.
 
54
 */ 
 
55
static atomic_t fibril_futex = FUTEX_INITIALIZER;
 
56
 
 
57
static LIST_INITIALIZE(ready_list);
 
58
static LIST_INITIALIZE(serialized_list);
 
59
static LIST_INITIALIZE(manager_list);
 
60
 
 
61
static void fibril_main(void);
 
62
 
 
63
/** Number of threads that are executing a manager fibril. */
 
64
static int threads_in_manager;
 
65
/** Number of threads that are executing a manager fibril and are serialized. */
 
66
static int serialized_threads;  /* Protected by async_futex */
 
67
/** Fibril-local count of serialization. If > 0, we must not preempt */
 
68
static fibril_local int serialization_count;
 
69
 
 
70
/** Setup fibril information into TCB structure */
 
71
fibril_t *fibril_setup(void)
 
72
{
 
73
        fibril_t *f;
 
74
        tcb_t *tcb;
 
75
 
 
76
        tcb = __make_tls();
 
77
        if (!tcb)
 
78
                return NULL;
 
79
 
 
80
        f = malloc(sizeof(fibril_t));
 
81
        if (!f) {
 
82
                __free_tls(tcb);
 
83
                return NULL;
 
84
        }
 
85
 
 
86
        tcb->fibril_data = f;
 
87
        f->tcb = tcb;
 
88
 
 
89
        f->func = NULL;
 
90
        f->arg = NULL;
 
91
        f->stack = NULL;
 
92
        f->clean_after_me = NULL;
 
93
        f->retval = 0;
 
94
        f->flags = 0;
 
95
 
 
96
        return f;
 
97
}
 
98
 
 
99
void fibril_teardown(fibril_t *f)
 
100
{
 
101
        __free_tls(f->tcb);
 
102
        free(f);
 
103
}
 
104
 
 
105
/** Function that spans the whole life-cycle of a fibril.
 
106
 *
 
107
 * Each fibril begins execution in this function. Then the function implementing
 
108
 * the fibril logic is called.  After its return, the return value is saved.
 
109
 * The fibril then switches to another fibril, which cleans up after it.
 
110
 */
 
111
void fibril_main(void)
 
112
{
 
113
        fibril_t *f = __tcb_get()->fibril_data;
 
114
 
 
115
        /* Call the implementing function. */
 
116
        f->retval = f->func(f->arg);
 
117
 
 
118
        fibril_switch(FIBRIL_FROM_DEAD);
 
119
        /* not reached */
 
120
}
 
121
 
 
122
/** Switch from the current fibril.
 
123
 *
 
124
 * If calling with FIBRIL_TO_MANAGER parameter, the async_futex should be
 
125
 * held.
 
126
 *
 
127
 * @param stype         Switch type. One of FIBRIL_PREEMPT, FIBRIL_TO_MANAGER,
 
128
 *                      FIBRIL_FROM_MANAGER, FIBRIL_FROM_DEAD. The parameter
 
129
 *                      describes the circumstances of the switch.
 
130
 * @return              Return 0 if there is no ready fibril,
 
131
 *                      return 1 otherwise.
 
132
 */
 
133
int fibril_switch(fibril_switch_type_t stype)
 
134
{
 
135
        fibril_t *srcf, *dstf;
 
136
        int retval = 0;
 
137
        
 
138
        futex_down(&fibril_futex);
 
139
 
 
140
        if (stype == FIBRIL_PREEMPT && list_empty(&ready_list))
 
141
                goto ret_0;
 
142
 
 
143
        if (stype == FIBRIL_FROM_MANAGER) {
 
144
                if (list_empty(&ready_list) && list_empty(&serialized_list))
 
145
                        goto ret_0;
 
146
                /*
 
147
                 * Do not preempt if there is not enough threads to run the
 
148
                 * ready fibrils which are not serialized.
 
149
                 */
 
150
                if (list_empty(&serialized_list) &&
 
151
                    threads_in_manager <= serialized_threads) {
 
152
                        goto ret_0;
 
153
                }
 
154
        }
 
155
        /* If we are going to manager and none exists, create it */
 
156
        if (stype == FIBRIL_TO_MANAGER || stype == FIBRIL_FROM_DEAD) {
 
157
                while (list_empty(&manager_list)) {
 
158
                        futex_up(&fibril_futex);
 
159
                        async_create_manager();
 
160
                        futex_down(&fibril_futex);
 
161
                }
 
162
        }
 
163
        
 
164
        srcf = __tcb_get()->fibril_data;
 
165
        if (stype != FIBRIL_FROM_DEAD) {
 
166
                /* Save current state */
 
167
                if (!context_save(&srcf->ctx)) {
 
168
                        if (serialization_count)
 
169
                                srcf->flags &= ~FIBRIL_SERIALIZED;
 
170
                        if (srcf->clean_after_me) {
 
171
                                /*
 
172
                                 * Cleanup after the dead fibril from which we
 
173
                                 * restored context here.
 
174
                                 */
 
175
                                void *stack = srcf->clean_after_me->stack; 
 
176
                                if (stack) {
 
177
                                        /*
 
178
                                         * This check is necessary because a
 
179
                                         * thread could have exited like a
 
180
                                         * normal fibril using the
 
181
                                         * FIBRIL_FROM_DEAD switch type. In that
 
182
                                         * case, its fibril will not have the
 
183
                                         * stack member filled.
 
184
                                         */
 
185
                                        free(stack);
 
186
                                }
 
187
                                fibril_teardown(srcf->clean_after_me);
 
188
                                srcf->clean_after_me = NULL;
 
189
                        }
 
190
                        return 1;       /* futex_up already done here */
 
191
                }
 
192
 
 
193
                /* Save myself to the correct run list */
 
194
                if (stype == FIBRIL_PREEMPT)
 
195
                        list_append(&srcf->link, &ready_list);
 
196
                else if (stype == FIBRIL_FROM_MANAGER) {
 
197
                        list_append(&srcf->link, &manager_list);
 
198
                        threads_in_manager--;
 
199
                } else {        
 
200
                        /*
 
201
                         * If stype == FIBRIL_TO_MANAGER, don't put ourselves to
 
202
                         * any list, we should already be somewhere, or we will
 
203
                         * be lost.
 
204
                         */
 
205
                }
 
206
        }
 
207
        
 
208
        /* Choose a new fibril to run */
 
209
        if (stype == FIBRIL_TO_MANAGER || stype == FIBRIL_FROM_DEAD) {
 
210
                dstf = list_get_instance(manager_list.next, fibril_t, link);
 
211
                if (serialization_count && stype == FIBRIL_TO_MANAGER) {
 
212
                        serialized_threads++;
 
213
                        srcf->flags |= FIBRIL_SERIALIZED;
 
214
                }
 
215
                threads_in_manager++;
 
216
 
 
217
                if (stype == FIBRIL_FROM_DEAD) 
 
218
                        dstf->clean_after_me = srcf;
 
219
        } else {
 
220
                if (!list_empty(&serialized_list)) {
 
221
                        dstf = list_get_instance(serialized_list.next, fibril_t,
 
222
                            link);
 
223
                        serialized_threads--;
 
224
                } else {
 
225
                        dstf = list_get_instance(ready_list.next, fibril_t,
 
226
                            link);
 
227
                }
 
228
        }
 
229
        list_remove(&dstf->link);
 
230
 
 
231
        futex_up(&fibril_futex);
 
232
        context_restore(&dstf->ctx);
 
233
        /* not reached */
 
234
 
 
235
ret_0:
 
236
        futex_up(&fibril_futex);
 
237
        return retval;
 
238
}
 
239
 
 
240
/** Create a new fibril.
 
241
 *
 
242
 * @param func          Implementing function of the new fibril.
 
243
 * @param arg           Argument to pass to func.
 
244
 *
 
245
 * @return              Return 0 on failure or TLS of the new fibril.
 
246
 */
 
247
fid_t fibril_create(int (*func)(void *), void *arg)
 
248
{
 
249
        fibril_t *f;
 
250
 
 
251
        f = fibril_setup();
 
252
        if (!f) 
 
253
                return 0;
 
254
        f->stack = (char *) malloc(FIBRIL_INITIAL_STACK_PAGES_NO *
 
255
            getpagesize());
 
256
        if (!f->stack) {
 
257
                fibril_teardown(f);
 
258
                return 0;
 
259
        }
 
260
        
 
261
        f->func = func;
 
262
        f->arg = arg;
 
263
 
 
264
        context_save(&f->ctx);
 
265
        context_set(&f->ctx, FADDR(fibril_main), f->stack,
 
266
            FIBRIL_INITIAL_STACK_PAGES_NO * getpagesize(), f->tcb);
 
267
 
 
268
        return (fid_t) f;
 
269
}
 
270
 
 
271
/** Add a fibril to the ready list.
 
272
 *
 
273
 * @param fid           Pointer to the fibril structure of the fibril to be
 
274
 *                      added.
 
275
 */
 
276
void fibril_add_ready(fid_t fid)
 
277
{
 
278
        fibril_t *f;
 
279
 
 
280
        f = (fibril_t *) fid;
 
281
        futex_down(&fibril_futex);
 
282
        if ((f->flags & FIBRIL_SERIALIZED))
 
283
                list_append(&f->link, &serialized_list);
 
284
        else
 
285
                list_append(&f->link, &ready_list);
 
286
        futex_up(&fibril_futex);
 
287
}
 
288
 
 
289
/** Add a fibril to the manager list.
 
290
 *
 
291
 * @param fid           Pointer to the fibril structure of the fibril to be
 
292
 *                      added.
 
293
 */
 
294
void fibril_add_manager(fid_t fid)
 
295
{
 
296
        fibril_t *f;
 
297
 
 
298
        f = (fibril_t *) fid;
 
299
 
 
300
        futex_down(&fibril_futex);
 
301
        list_append(&f->link, &manager_list);
 
302
        futex_up(&fibril_futex);
 
303
}
 
304
 
 
305
/** Remove one manager from the manager list. */
 
306
void fibril_remove_manager(void)
 
307
{
 
308
        futex_down(&fibril_futex);
 
309
        if (list_empty(&manager_list)) {
 
310
                futex_up(&fibril_futex);
 
311
                return;
 
312
        }
 
313
        list_remove(manager_list.next);
 
314
        futex_up(&fibril_futex);
 
315
}
 
316
 
 
317
/** Return fibril id of the currently running fibril.
 
318
 *
 
319
 * @return fibril ID of the currently running fibril.
 
320
 *
 
321
 */
 
322
fid_t fibril_get_id(void)
 
323
{
 
324
        return (fid_t) __tcb_get()->fibril_data;
 
325
}
 
326
 
 
327
/** Disable preemption
 
328
 *
 
329
 * If the fibril wants to send several message in a row and does not want to be
 
330
 * preempted, it should start async_serialize_start() in the beginning of
 
331
 * communication and async_serialize_end() in the end. If it is a true
 
332
 * multithreaded application, it should protect the communication channel by a
 
333
 * futex as well.
 
334
 *
 
335
 */
 
336
void fibril_inc_sercount(void)
 
337
{
 
338
        serialization_count++;
 
339
}
 
340
 
 
341
/** Restore the preemption counter to the previous state. */
 
342
void fibril_dec_sercount(void)
 
343
{
 
344
        serialization_count--;
 
345
}
 
346
 
 
347
/** @}
 
348
 */