All the different tasks C-kern(el) implementation supports.
The following modules exist which allow to run multiple threads of execution.
Taskmanagement | All the different tasks C-kern(el) implementation supports. |
Syncthread | A synchronous, cooperative thread which runs exclusive in a system thread context. |
Some Implementation Details | |
Generic Structure of Syncthread | |
Error Handling | If a running syncthread can not allocate a system resource it needs for its computation it could either wait a little time and try again. |
Blocking OS Call | The C-kern/api encapsulates OS calls and offers asynchronous non-blocking calls if possible. |
System Process | Do not use them except for test code. |
System Thread | Do not use them except for test code. |
A synchronous, cooperative thread which runs exclusive in a system thread context. Explicitly giving up the processor gives other syncthreads in the same system thread a chance to run. Synchronisation mechanisms can be used to wait for exit of another syncthread or for a certain condition.
Also called stackless threads, protothreads or coroutines.
#include "C-kern/api/task/syncrun.h" #include "C-kern/api/task/syncthread.h"
Synchreads are stored and executed by syncrun_t. Every system thread has its own syncrun_t.
Syncthreads do not need to lock objects as long as these objects are local to the system thread. Therefore the programming style is lockless and synchronous.
struct syncthread_t { syncthread_f mainfct ; void * state ; } ;
A syncthread_t consists of a pointer to its main function and a pointer to its state.
Both variables constitutes the execution state of the syncthread. Except if a syncthread waits for an event (see waitforexit_syncrun, waitforevent_syncrun or waitforlist_syncrun) and becomes inactive. In that case its execution state is extended with an address of a label so that execution continues right after the call to the wait function if it is woken up.
struct syncrun_t { /* variable: queues * Queues wich are used to store <syncthread_t>. * Every queue symbolizes a different running state. */ syncqueue_t queues[7] ; /* variable: wakeup_list * List of <syncevent_t>. * Every event references a waiting <syncthread_t> (see <syncwait_t>). ... */ syncwlist_t wakeup_list ; ... } ;
The second type syncthread_t manages all syncthread contained in a single system thread. The thread global singleton can be queried with syncrun_maincontext. It manages a set of queues of type syncqueue_t. The queue in which a syncthread_t is stored determines its state. There is queue for threads which must be initialized (initqueue), there are 2 queues for initialized threads which are ready to run (runqueue) and there are 2 queues for syncthreads which wait for an event to occur. There is an additional wakeup queue for events syncevent_t and a queue to store thee nodes of list type syncwlist_t which implements a list of waiting threads.
struct initparam_t { int dummy1 ; } ; struct runparam_t { int dummy1 ; } ; static int maintest_syncthread(syncthread_t * sthread, uint32_t signalstate) { struct runparam_t * runparam = state_syncthread(sthread) ; handlesignal_syncthread(signalstate, continuelabel_syncrun(syncrun_maincontext()), ONINIT, ONRUN, ONABORT) ; ONINIT: // runparam is initparam (only at initialization) struct initparam_t * initparam = (struct initparam_t *)runparam runparam = malloc(sizeof(struct runparam_t)) ; setstate_syncthread(sthread, runparam) ; if (runparam == 0) abortthread_syncrun(syncrun_maincontext()) ; // ... initialize it ... return 0 ; // yield processor (continue execution) ONRUN: // is called after init bool have_done_enough = false ; // .. do what a syncthread has to do .. if (have_done_enough) exitthread_syncrun(syncrun_maincontext(), 0) ; return 0 ; // yield processor (continue execution) ONABORT: // called if out of memory in component syncrun_t or if malloc in ONINIT went wrong if (runparam) free(runparam) ; return 0 ; }
The main execution function of a syncthread must have the above signature. The first parameter references the executed syncthread. The second is a flag which is used to signal a value from syncthread_signal_e.
Use parameter sthread to access the state of the syncthread with state_syncthread. Use parameter signalstate to switch between the states: initialization (see syncthread_signal_INIT), normal running (see syncthread_signal_NULL), restart after a waiting operation (see syncthread_signal_WAKEUP) and abort situation (see syncthread_signal_ABORT).
Use function handlesignal_syncthread to decode the signalstate and jump to the correct labels which are called “ONINIT”, “ONRUN” and “ONABORT” (you can use other names). There is no special label for wakeup. This label is constructed dynamically (see for example waitforevent_syncrun) and read with function continuelabel_syncrun.
To start a new syncthread with a single initialization argument call function <startthread_syncrun> which copies the start parameter (mainfct + init param) into the initqueue of syncrun_t.
╭───────────────────────────╮ ╭────────────────────────╮ | syncrun_t | | queue_page_t | ├───────────────────────────┤ ├────────────────────────┤ | queues[syncrun_qid_INIT] ─┼─────▸| /* prev entries */ | ╰───────────────────────────╯ ├────────────────────────┤ | initqueue_entry_t { | | mainfct | startthread_syncrun() ┈┈┈┈┈┈┈┈┈> | state /*initparam*/ ┈┼┈┐ // (possible connection) /*creates new entry*/➜ | exitevent | ┊ // use startthread2_syncrun() | initarg[initargsize] |<┘ // to allocate the initparam | } | // in the queue ╰────────────────────────╯
A second function <startthread2_syncrun> provides a way to allocate an additional initialization argument which is freed automatically after the thread has been initialized. That is the reason the above maintest_syncthread() function allocates a new runtime argument in the initialization phase.
If you want to run a syncthread synchronous way call function waitforexit_syncrun directly after startthread_syncrun or startthread2_syncrun. This moves the calling thread into one of the wait queues. After the newly started thread stops running the calling thread is woken up and continues execution after waitforexit_syncrun(),
All threads stored in the initqueue are run the first time and therefore receive the value syncthread_signal_INIT as their signalstate parameter. The simplest way to check the signalstate parameter is to use function handlesignal_syncthread which handles every possible signal value for you. The initsignal indicates the thread state (can be read with state_syncthread) contains the initialization argument which could be automatically allocated and freed in the initqueue and therefore must be copied if the values are needed after init time.
After the syncthread has returned from initialization it is called with a signalstate set to 0 (syncthread_signal_NULL) which indicates normal running mode. If the syncthread calls no exit, abort or waiting function it continues to run forever.
Therefore yielding the processor with “return 0;” after a small amount of time (< 1ms) is necessary to allow other syncthreads to run.
A syncthread_t can wait for an event (see syncevent_t) which supports only one waiting thread. It can also wait for a condition in a waitlist (see syncwlist_t). A syncwlist supports any number of waiting threads. It uses an internal queue in syncrun_t to store necessary information. The functions to wait for an event or in a list are waitforevent_syncrun resp. waitforlist_syncrun. Another thread which wants to wakeup waiting threads uses <signalevent_syncrun> and <signalfirst_syncrun> to put a single thread into the wakeup queue or onto the wakeup_list. It can call <signalall_syncrun> to wakeup all waiting threads stored a syncwlist at once.
The waitqueue in syncrun_t stores an additional continuelabel. Every wait function is implemented as macro and contains the following code sequence
setstatewaitlist_syncrun( ... __extension__ && waitlabel) ; return 0 ; waitlabel:
which uses an extension of gcc. Gcc allows to store the address of a C label and jump to its location with “goto*(address_of_label);” This is used in the function handlesignal_syncthread to continue execution if signalstate is set to value syncthread_signal_WAKEUP. The label where execution should continue can be queried for with continuelabel_syncrun.
The syncthread runs as long as it wants to. But if its omputation is finished it should call exitthread_syncrun with the return code set to value 0 indicating success. If the computation could not be finished successfully a return value > 0 indicates the corresponding error code. Negative return values are reserved for signaling special exit conditions. There is only one special value in use at the time of writing (see syncrun_returncode_ABORT.)
If a running syncthread can not allocate a system resource it needs for its computation it could either wait a little time and try again. Or it could free its already allocated resources and call exitthread_syncrun with a return value != 0. If the error code should be logged it should call a logging function before its exit.
If a running syncthread calls a wait operation and there is no more memory in the waitqueue of syncrun_t the state of the thread is changed to abort. After the running syncthread has returned the syncrun_t component calls the thread again with its signalstate set to syncthread_signal_ABORT. Therefore every syncthread_t must implement an ONABORT handler which frees all allocated resources.
It is also possible that a running thread calls abortthread_syncrun which sets its own state to abort explicitly.
An aborted syncthread is marked as exited and if there is another syncthread waiting for its exit the waiting thread is woken up and function waitforexit_syncrun returns the value syncrun_returncode_ABORT to indicate that the called thread has been aborted.
The C-kern/api encapsulates OS calls and offers asynchronous non-blocking calls if possible. If this is not possible due to limitations of the underlaying OS a thread pool will be used. And every call to a service which calls the OS in a blocking way must start a new thread which calls the OS instead. The syncthread_t is moved into the waitqueue until the system thread returns from the OS.
This functionality is not implemented yet and will be not implemented until the higher level system of starting and stopping services as threads or processes or processes on other nodes in a network is implemented.
Manages a set of syncthread_t.
struct syncrun_t
A simple function context which is executed in a cooperative way.
struct syncthread_t
Implements syncrun_t.waitforexit_syncrun.
#define waitforexit_syncrun( srun ) ( __extension__ ({ __label__ waitlabel ; setstatewait_syncrun((srun), srun->running.laststarted, __extension__ && waitlabel) ; return 0 ; waitlabel: retcode_syncrun(srun) ; }))
Implements syncrun_t.waitforevent_syncrun.
#define waitforevent_syncrun( srun, syncevent ) ( __extension__ ({ __label__ waitlabel ; setstatewait_syncrun((srun), (syncevent), __extension__ && waitlabel) ; return 0 ; waitlabel: (void)0 ; }))
Implements syncrun_t.waitforlist_syncrun.
#define waitforlist_syncrun( srun, syncwlist ) ( __extension__ ({ __label__ waitlabel ; setstatewaitlist_syncrun((srun), (syncwlist), __extension__ && waitlabel) ; return 0 ; waitlabel: (void)0 ; }))
Inline implementation of maincontext_t.syncrun_maincontext.
#define syncrun_maincontext( ) (tcontext_maincontext()->syncrun)
Internal type to store zero or more syncthread_t + additional information.
struct syncqueue_t
Event type stores a pointer to a single waiting syncwait_t.
struct syncevent_t
Implements a double linked list of syncevent_t and stores them in a syncqueue_t.
struct syncwlist_t
Implements syncthread_t.state_syncthread.
#define state_syncthread( sthread ) ((void*)((sthread)->state))
Implements syncthread_t.handlesignal_syncthread.
#define handlesignal_syncthread( signalstate, continuelabel, oninitlabel, onrunlabel, onabortlabel ) ( __extension__ ({ const syncthread_signal_e _sign = (signalstate) ; switch (_sign) { case syncthread_signal_NULL: goto onrunlabel ; case syncthread_signal_WAKEUP: goto * (continuelabel) ; case syncthread_signal_INIT: goto oninitlabel ; case syncthread_signal_ABORT: goto onabortlabel ; } goto onabortlabel ; }))
Implements syncrun_t.continuelabel_syncrun.
#define continuelabel_syncrun( srun ) ((srun)->wakeup.continuelabel)
Implements syncrun_t.exitthread_syncrun.
#define exitthread_syncrun( srun, err ) do { setstateexit_syncrun(srun) ; return err ; } while (0)
Implements syncrun_t.abortthread_syncrun.
#define abortthread_syncrun( srun ) do { setstateabort_syncrun(srun) ; return 0 ; } while (0)