34,7 → 34,7 |
*/ |
|
#include <libadt/list.h> |
#include <psthread.h> |
#include <fibril.h> |
#include <malloc.h> |
#include <unistd.h> |
#include <thread.h> |
44,8 → 44,8 |
#include <assert.h> |
#include <async.h> |
|
#ifndef PSTHREAD_INITIAL_STACK_PAGES_NO |
#define PSTHREAD_INITIAL_STACK_PAGES_NO 1 |
#ifndef FIBRIL_INITIAL_STACK_PAGES_NO |
#define FIBRIL_INITIAL_STACK_PAGES_NO 1 |
#endif |
|
static LIST_INITIALIZE(ready_list); |
52,20 → 52,20 |
static LIST_INITIALIZE(serialized_list); |
static LIST_INITIALIZE(manager_list); |
|
static void psthread_main(void); |
static void fibril_main(void); |
|
static atomic_t psthread_futex = FUTEX_INITIALIZER; |
/** Count of real threads that are in async_serialized mode */ |
static atomic_t fibril_futex = FUTEX_INITIALIZER; |
/** Number of threads that are in async_serialized mode */ |
static int serialized_threads; /* Protected by async_futex */ |
/** Thread-local count of serialization. If >0, we must not preempt */ |
static __thread int serialization_count; |
/** Counter of threads residing in async_manager */ |
static int threads_in_manager; |
/** Counter for fibrils residing in async_manager */ |
static int fibrils_in_manager; |
|
/** Setup psthread information into TCB structure */ |
psthread_data_t *psthread_setup(void) |
/** Setup fibril information into TCB structure */ |
fibril_t *fibril_setup(void) |
{ |
psthread_data_t *pt; |
fibril_t *f; |
tcb_t *tcb; |
|
tcb = __make_tls(); |
72,76 → 72,75 |
if (!tcb) |
return NULL; |
|
pt = malloc(sizeof(*pt)); |
if (!pt) { |
f = malloc(sizeof(*f)); |
if (!f) { |
__free_tls(tcb); |
return NULL; |
} |
|
tcb->pst_data = pt; |
pt->tcb = tcb; |
tcb->fibril_data = f; |
f->tcb = tcb; |
|
return pt; |
return f; |
} |
|
void psthread_teardown(psthread_data_t *pt) |
void fibril_teardown(fibril_t *f) |
{ |
__free_tls(pt->tcb); |
free(pt); |
__free_tls(f->tcb); |
free(f); |
} |
|
/** Function that spans the whole life-cycle of a pseudo thread. |
/** Function that spans the whole life-cycle of a fibril. |
* |
* Each pseudo thread begins execution in this function. |
* Then the function implementing the pseudo thread logic is called. |
* After its return, the return value is saved for a potentional |
* joiner. If the joiner exists, it is woken up. The pseudo thread |
* then switches to another pseudo thread, which cleans up after it. |
* Each fibril begins execution in this function. Then the function |
* implementing the fibril logic is called. After its return, the return value |
* is saved for a potentional joiner. If the joiner exists, it is woken up. The |
* fibril then switches to another fibril, which cleans up after it. |
*/ |
void psthread_main(void) |
void fibril_main(void) |
{ |
psthread_data_t *pt = __tcb_get()->pst_data; |
fibril_t *f = __tcb_get()->fibril_data; |
|
pt->retval = pt->func(pt->arg); |
f->retval = f->func(f->arg); |
|
/* |
* If there is a joiner, wake it up and save our return value. |
*/ |
if (pt->joiner) { |
list_append(&pt->joiner->link, &ready_list); |
pt->joiner->joinee_retval = pt->retval; |
if (f->joiner) { |
list_append(&f->joiner->link, &ready_list); |
f->joiner->joinee_retval = f->retval; |
} |
|
psthread_schedule_next_adv(PS_FROM_DEAD); |
fibril_schedule_next_adv(FIBRIL_FROM_DEAD); |
/* not reached */ |
} |
|
/** Schedule next userspace pseudo thread. |
/** Schedule next fibril. |
* |
* If calling with PS_TO_MANAGER parameter, the async_futex should be |
* If calling with FIBRIL_TO_MANAGER parameter, the async_futex should be |
* held. |
* |
* @param ctype One of PS_SLEEP, PS_PREEMPT, PS_TO_MANAGER, |
* PS_FROM_MANAGER, PS_FROM_DEAD. The parameter describes |
* the circumstances of the switch. |
* @return Return 0 if there is no ready pseudo thread, |
* @param stype One of FIBRIL_SLEEP, FIBRIL_PREEMPT, FIBRIL_TO_MANAGER, |
* FIBRIL_FROM_MANAGER, FIBRIL_FROM_DEAD. The parameter |
* describes the circumstances of the switch. |
* @return Return 0 if there is no ready fibril, |
* return 1 otherwise. |
*/ |
int psthread_schedule_next_adv(pschange_type ctype) |
int fibril_schedule_next_adv(fibril_switch_type_t stype) |
{ |
psthread_data_t *srcpt, *dstpt; |
fibril_t *srcf, *dstf; |
int retval = 0; |
|
futex_down(&psthread_futex); |
futex_down(&fibril_futex); |
|
if (ctype == PS_PREEMPT && list_empty(&ready_list)) |
if (stype == FIBRIL_PREEMPT && list_empty(&ready_list)) |
goto ret_0; |
if (ctype == PS_SLEEP) { |
if (stype == FIBRIL_SLEEP) { |
if (list_empty(&ready_list) && list_empty(&serialized_list)) |
goto ret_0; |
} |
|
if (ctype == PS_FROM_MANAGER) { |
if (stype == FIBRIL_FROM_MANAGER) { |
if (list_empty(&ready_list) && list_empty(&serialized_list)) |
goto ret_0; |
/* |
148,113 → 147,112 |
* Do not preempt if there is not sufficient count of thread |
* managers. |
*/ |
if (list_empty(&serialized_list) && threads_in_manager <= |
if (list_empty(&serialized_list) && fibrils_in_manager <= |
serialized_threads) { |
goto ret_0; |
} |
} |
/* If we are going to manager and none exists, create it */ |
if (ctype == PS_TO_MANAGER || ctype == PS_FROM_DEAD) { |
if (stype == FIBRIL_TO_MANAGER || stype == FIBRIL_FROM_DEAD) { |
while (list_empty(&manager_list)) { |
futex_up(&psthread_futex); |
futex_up(&fibril_futex); |
async_create_manager(); |
futex_down(&psthread_futex); |
futex_down(&fibril_futex); |
} |
} |
|
srcpt = __tcb_get()->pst_data; |
if (ctype != PS_FROM_DEAD) { |
srcf = __tcb_get()->fibril_data; |
if (stype != FIBRIL_FROM_DEAD) { |
/* Save current state */ |
if (!context_save(&srcpt->ctx)) { |
if (!context_save(&srcf->ctx)) { |
if (serialization_count) |
srcpt->flags &= ~PSTHREAD_SERIALIZED; |
if (srcpt->clean_after_me) { |
srcf->flags &= ~FIBRIL_SERIALIZED; |
if (srcf->clean_after_me) { |
/* |
* Cleanup after the dead pseudo thread from |
* which we restored context here. |
* Cleanup after the dead fibril from which we |
* restored context here. |
*/ |
free(srcpt->clean_after_me->stack); |
psthread_teardown(srcpt->clean_after_me); |
srcpt->clean_after_me = NULL; |
free(srcf->clean_after_me->stack); |
fibril_teardown(srcf->clean_after_me); |
srcf->clean_after_me = NULL; |
} |
return 1; /* futex_up already done here */ |
} |
|
/* Save myself to the correct run list */ |
if (ctype == PS_PREEMPT) |
list_append(&srcpt->link, &ready_list); |
else if (ctype == PS_FROM_MANAGER) { |
list_append(&srcpt->link, &manager_list); |
threads_in_manager--; |
if (stype == FIBRIL_PREEMPT) |
list_append(&srcf->link, &ready_list); |
else if (stype == FIBRIL_FROM_MANAGER) { |
list_append(&srcf->link, &manager_list); |
fibrils_in_manager--; |
} else { |
/* |
* If ctype == PS_TO_MANAGER, don't save ourselves to |
* If stype == FIBRIL_TO_MANAGER, don't put ourselves to |
* any list, we should already be somewhere, or we will |
* be lost. |
* |
* The ctype == PS_SLEEP case is similar. The pseudo |
* thread has an external refernce which can be used to |
* wake it up once that time has come. |
* The stype == FIBRIL_SLEEP case is similar. The fibril |
* has an external refernce which can be used to wake it |
* up once that time has come. |
*/ |
} |
} |
|
/* Choose new thread to run */ |
if (ctype == PS_TO_MANAGER || ctype == PS_FROM_DEAD) { |
dstpt = list_get_instance(manager_list.next, psthread_data_t, |
link); |
if (serialization_count && ctype == PS_TO_MANAGER) { |
/* Choose a new fibril to run */ |
if (stype == FIBRIL_TO_MANAGER || stype == FIBRIL_FROM_DEAD) { |
dstf = list_get_instance(manager_list.next, fibril_t, link); |
if (serialization_count && stype == FIBRIL_TO_MANAGER) { |
serialized_threads++; |
srcpt->flags |= PSTHREAD_SERIALIZED; |
srcf->flags |= FIBRIL_SERIALIZED; |
} |
threads_in_manager++; |
fibrils_in_manager++; |
|
if (ctype == PS_FROM_DEAD) |
dstpt->clean_after_me = srcpt; |
if (stype == FIBRIL_FROM_DEAD) |
dstf->clean_after_me = srcf; |
} else { |
if (!list_empty(&serialized_list)) { |
dstpt = list_get_instance(serialized_list.next, |
psthread_data_t, link); |
dstf = list_get_instance(serialized_list.next, fibril_t, |
link); |
serialized_threads--; |
} else { |
dstpt = list_get_instance(ready_list.next, |
psthread_data_t, link); |
dstf = list_get_instance(ready_list.next, fibril_t, |
link); |
} |
} |
list_remove(&dstpt->link); |
list_remove(&dstf->link); |
|
futex_up(&psthread_futex); |
context_restore(&dstpt->ctx); |
futex_up(&fibril_futex); |
context_restore(&dstf->ctx); |
/* not reached */ |
|
ret_0: |
futex_up(&psthread_futex); |
futex_up(&fibril_futex); |
return retval; |
} |
|
/** Wait for uspace pseudo thread to finish. |
/** Wait for fibril to finish. |
* |
* Each pseudo thread can be only joined by one other pseudo thread. Moreover, |
* the joiner must be from the same thread as the joinee. |
* Each fibril can be only joined by one other fibril. Moreover, the joiner must |
* be from the same thread as the joinee. |
* |
* @param psthrid Pseudo thread to join. |
* @param fid Fibril to join. |
* |
* @return Value returned by the finished thread. |
* @return Value returned by the completed fibril. |
*/ |
int psthread_join(pstid_t psthrid) |
int fibril_join(fid_t fid) |
{ |
psthread_data_t *pt; |
psthread_data_t *cur; |
fibril_t *f; |
fibril_t *cur; |
|
/* Handle psthrid = Kernel address -> it is wait for call */ |
pt = (psthread_data_t *) psthrid; |
/* Handle fid = Kernel address -> it is wait for call */ |
f = (fibril_t *) fid; |
|
/* |
* The joiner is running so the joinee isn't. |
*/ |
cur = __tcb_get()->pst_data; |
pt->joiner = cur; |
psthread_schedule_next_adv(PS_SLEEP); |
cur = __tcb_get()->fibril_data; |
f->joiner = cur; |
fibril_schedule_next_adv(FIBRIL_SLEEP); |
|
/* |
* The joinee fills in the return value. |
262,113 → 260,111 |
return cur->joinee_retval; |
} |
|
/** Create a userspace pseudo thread. |
/** Create a new fibril. |
* |
* @param func Pseudo thread function. |
* @param func Implementing function of the new fibril. |
* @param arg Argument to pass to func. |
* |
* @return Return 0 on failure or TLS of the new pseudo thread. |
* @return Return 0 on failure or TLS of the new fibril. |
*/ |
pstid_t psthread_create(int (*func)(void *), void *arg) |
fid_t fibril_create(int (*func)(void *), void *arg) |
{ |
psthread_data_t *pt; |
fibril_t *f; |
|
pt = psthread_setup(); |
if (!pt) |
f = fibril_setup(); |
if (!f) |
return 0; |
pt->stack = (char *) malloc(PSTHREAD_INITIAL_STACK_PAGES_NO * |
f->stack = (char *) malloc(FIBRIL_INITIAL_STACK_PAGES_NO * |
getpagesize()); |
|
if (!pt->stack) { |
psthread_teardown(pt); |
if (!f->stack) { |
fibril_teardown(f); |
return 0; |
} |
|
pt->arg= arg; |
pt->func = func; |
pt->clean_after_me = NULL; |
pt->joiner = NULL; |
pt->joinee_retval = 0; |
pt->retval = 0; |
pt->flags = 0; |
f->arg = arg; |
f->func = func; |
f->clean_after_me = NULL; |
f->joiner = NULL; |
f->joinee_retval = 0; |
f->retval = 0; |
f->flags = 0; |
|
context_save(&pt->ctx); |
context_set(&pt->ctx, FADDR(psthread_main), pt->stack, |
PSTHREAD_INITIAL_STACK_PAGES_NO * getpagesize(), pt->tcb); |
context_save(&f->ctx); |
context_set(&f->ctx, FADDR(fibril_main), f->stack, |
FIBRIL_INITIAL_STACK_PAGES_NO * getpagesize(), f->tcb); |
|
return (pstid_t) pt; |
return (fid_t) f; |
} |
|
/** Add a thread to the ready list. |
/** Add a fibril to the ready list. |
* |
* @param psthrid Pinter to the pseudo thread structure of the |
* pseudo thread to be added. |
* @param fid Pinter to the fibril structure of the fibril to be added. |
*/ |
void psthread_add_ready(pstid_t psthrid) |
void fibril_add_ready(fid_t fid) |
{ |
psthread_data_t *pt; |
fibril_t *f; |
|
pt = (psthread_data_t *) psthrid; |
futex_down(&psthread_futex); |
if ((pt->flags & PSTHREAD_SERIALIZED)) |
list_append(&pt->link, &serialized_list); |
f = (fibril_t *) fid; |
futex_down(&fibril_futex); |
if ((f->flags & FIBRIL_SERIALIZED)) |
list_append(&f->link, &serialized_list); |
else |
list_append(&pt->link, &ready_list); |
futex_up(&psthread_futex); |
list_append(&f->link, &ready_list); |
futex_up(&fibril_futex); |
} |
|
/** Add a pseudo thread to the manager list. |
/** Add a fibril to the manager list. |
* |
* @param psthrid Pinter to the pseudo thread structure of the |
* pseudo thread to be added. |
* @param fid Pinter to the fibril structure of the fibril to be added. |
*/ |
void psthread_add_manager(pstid_t psthrid) |
void fibril_add_manager(fid_t fid) |
{ |
psthread_data_t *pt; |
fibril_t *f; |
|
pt = (psthread_data_t *) psthrid; |
f = (fibril_t *) fid; |
|
futex_down(&psthread_futex); |
list_append(&pt->link, &manager_list); |
futex_up(&psthread_futex); |
futex_down(&fibril_futex); |
list_append(&f->link, &manager_list); |
futex_up(&fibril_futex); |
} |
|
/** Remove one manager from manager list */ |
void psthread_remove_manager(void) |
/** Remove one manager from the manager list. */ |
void fibril_remove_manager(void) |
{ |
futex_down(&psthread_futex); |
futex_down(&fibril_futex); |
if (list_empty(&manager_list)) { |
futex_up(&psthread_futex); |
futex_up(&fibril_futex); |
return; |
} |
list_remove(manager_list.next); |
futex_up(&psthread_futex); |
futex_up(&fibril_futex); |
} |
|
/** Return thread id of the currently running thread. |
/** Return fibril id of the currently running fibril. |
* |
* @return Pseudo thread ID of the currently running pseudo thread. |
* @return Fibril ID of the currently running pseudo thread. |
*/ |
pstid_t psthread_get_id(void) |
fid_t fibril_get_id(void) |
{ |
return (pstid_t) __tcb_get()->pst_data; |
return (fid_t) __tcb_get()->fibril_data; |
} |
|
/** Disable preemption |
* |
* If the thread wants to send several message in a row and does not want to be |
* If the fibril wants to send several message in a row and does not want to be |
* preempted, it should start async_serialize_start() in the beginning of |
* communication and async_serialize_end() in the end. If it is a true |
* multithreaded application, it should protect the communication channel by a |
* futex as well. Interrupt messages can still be preempted. |
*/ |
void psthread_inc_sercount(void) |
void fibril_inc_sercount(void) |
{ |
serialization_count++; |
} |
|
/** Restore the preemption counter to the previous state. */ |
void psthread_dec_sercount(void) |
void fibril_dec_sercount(void) |
{ |
serialization_count--; |
} |