t a s k K S
=============
### content
+ overview
+ release-notes
+ work flow
+/+
### overview
--- taskKS --- A small framework to handle task-function-calling
taskKS is _NOT_ an full-featured OS,
it is _NO_ Operating-System at all.
It is just an very tiny framework to support function-calling on demand.
They are called in taskKS : task-functions. (in short: taskFns)
Up to 32 tasks i.e. task-functions (shortly named taskFns) you could
manage with one taskKS instance
(nearly an object (obj) in C; of course you'll need the
instance pointer (iPtr) of the specific taskKS).
The tasks have to be served by frequently calling of an service-function,
here supported by a RUN Macro.
One call serves all waiting tasks, i.e. if they are ready to run (started).
All tasks are cooperative together (KS = Kooperatives-System / DE);
just one is active while the others are non-called at this time.
This is fully single-threaded, _not_ preemptive.
This may save semaphore usage, but you should call all functions of one taskKS
obj in just one thread! (pure single-threaded)
New features in 0.2.0:
Signaling and Messaging.
-
Signaling means: Every taskFn could have up to X signalFns which will be
called directly and before the taskFn (also without taskFn possible)
if the *_SET_SIGNAL macro has been called.
Must be instantiated before, of course.
Hence you could inject direct functionality. (ok, delayed by RUN-macro call)
-
Messaging means: Every taskFn could instantiate an own Message-Box service i.e.
could have a mailbox with waiting messages (msgs), which could be checked and
served during the RUN-macro call.
Other taskFns of this taskKS obj or Fns from the service-thread could
send these Messages to this tasks.
For those receiving tasks a small framework could be used to make handling of
messages easier. The Message-Boxes are instantiated as Ring-Buffers. They are
not endless, like many like to do in times were PCs have Gigabytes of RAM.
But they could be differ in size for every taskFn; i.e. a service taskFn
should get bigger msg-boxes than a seldom used taskFn, but i think they
could be organized big enough.
The macros for taskFn msg-boxes have an own Prefix : "TASKKSM_" (instead
of the usual taskKS prefix "TASKKS_"). Every message has an Id, and may be
additional Data which is needed to service this specific msg-type (id).
-------------------------------------------------------------------------------
### release-notes
* 0.1.0
java: no support
C++:
Impl general support via C-functions. (extern "C" block)
-
C: ..
Impl First integration supports the following features:
- Calling upto X Task-Functions by one service Macro,
called TASKKS_RUN_TASK(iPtr), on demand or periodically till it is stopped
(for polling in a task-fn, should be avoided or only used for a short time).
Currently X-max = 32.
-> TASKKS_MAX_TASKS <------------------------------------------------------!
-
- C-object-like instance-pointer (iPtr) for one taskKS instance (obj).
Although, like in CPP world a no-go, all will be public but should not
be used directly (use only API macros ((or know very well what you
do and be prepared that it may not work with a new version of taskKS)) ).
-> tstTaskKS_OneSysInstanceCB* <-------------------------------------------!
-
- Functions of one taskKS instance should be called by one thread only,
because no mutex-support included.
-> only-one-thread-is-calling-macros-of-one-taskKS-obj rule <-!
This means also that all taskKS functions (in following abbreviated by
taskFn or function with "Fn")
are cooperative together; while one is called the others are not called
(of course this may only be possible by different threads in a
preemptive OS which is not the scope of taskKS, see rule).
(in other way: By the restriction that all taskKS-Fns & macros are only
called by one thread we has nearly a single-thread with different
asynchronous called taskFns, of course)
DE-name is Kooperativ, so taskKS means -> task for taskFns, KS for
Kooperatives-System (cooperative-system).
This means also: all taskKS taskFns must not be blocking, or you block the
whole system (RUN-macro..).
It could be for instance a state-machine function, which has some
wait-states and will wake up by other task-functions to evaluate
an event.
-> TASKKS_SET_TASKFN() -> tstTaskKS_TaskCB <-------------------------------!
-
- Usually a taskFn is started by using the signal-macro (in newer version
it is the start-macro due to avoid misunderstandings to the signal-Fns
in 0.2.0 or greater - better use 0.2.0 directly).
Then the taskFn is called one-time (unless the one-time flag is cleared
which means you setup a poll taskFn). This is the default of taskKS after
creation. The specific SET macro could clear or set the one-time flag.
-> TASKKS_SET_ONETIMEF() <-------------------------------------------------!
-
- poll taskFns are initialized be clearing the one-time flag. Then,
after signaling (starting) the taskFn the poll-taskFn will be called on
every RUN-macro call. This is till the one-time flag is set or if the
unsignal (stop) macro is called.
-> TASKKS_SIGNAL_TASKFN() <------------------------------------------------!
-> TASKKS_UNSIGNAL_TASKFN() <----------------------------------------------!
Attention: in Version 0.2.0 these macros are deprecated and usually not
longer supported. Instead the START & STOP macro has this Fn in 0.2.0.
-
- A taskKS instance could be created and deleted dynamically. The number
of created taskKS instances is upto the power and RAM-size of the
underlying system only. (iPtr == instance or obj pointer)
-> taskks_createSys() <---------(returns iPtr)-----------------------------!
-> iPtr->deleteTaskKSSys(&iPtr) <------------------------------------------!
-
- A taskFn could be any Fn with the given prototype
which is generic, but not type-safe (C++ could do this better of course).
This means a specific User-struct ptr could be used as the arg for
each taskFn; this should contain the ptr to the taskKS-instance (the iPtr)!
(Sry. may be it willB fixed in future)
-> tfnTaskKS_TaskFn <------------------------------------------------------!
-
- A taskFn has a one-time flag, which is set by default.
If this flag is True, the taskFn is called one-time after signaling
(in V>=0.2.0: starting) and then 'sleeps' again (like after init).
If this flag is False, the taskFn is called whenever the RUN-macro is
called when it was already signaled (in V>=0.2.0: started). To stop this you
must either set the one-time flag again (will just called once more) or
you must unsignal (in V>=0.2.0: stopping) it, which stops calling of this
taskFn immediately.
-> TASKKS_RUN_TASK(iPtr) <-------------------------------------------------!
-
- After initialization (create taskKS instance with the createSys Fn -
all other calls must be done usually by macros with this iPtr, setup taskFns
and args of them and begin calling the RUN macro cyclic in a worker-thread
),
you had to start some calls in this taskKS-obj (nearly an object like in
CPP, but without the comfort using the instance ptr implicit).
For the identification of a taskFn in a taskKS-obj we need the index of
the task (also ID named, sry). In macro abbreviated with 'tix' as short
for task-index.
Example: one taskFn is called always (one-time-flag is false) and checks
some states of the I/O. If a specific change is detected it prepares
data for taskFn-x and then starts this taskFn - with this concept the
detection and work is apart.
Task-indices could be defined in your program by using enum-types.
-> arg2 in TASKKS macros <-(usually)---------------------------------------!
-
- In one taskKS-obj only one taskFn is called at a time
(if only-one-thread-is-calling-macros-of-one-taskKS-obj rule is obeyed), (mustB rule!)
which is the same in cooperative operating systems. This allows the taskFn
to identify itself - he could get the tix. This means - if you had to
serve 3 serial lines in the same way for instance - you could use only
one taskFn and determine implicit by tix and/or taskFn data ptr which
line is meant.
-> TASKKS_CURRENT_TASKINDEX(iPtr) <----------------------------------------!
-
- Also, before using the RUN macro, you could determine if it necessary to
call it (may be it is necessary for some use-cases).
-> TASKKS_IS_TASKWAITING(iPtr) <-------------------------------------------!
-
- You could check, if a given tix is prepared to work yet. This means that
the tix is in range (is expected of course) and the tix could use a
taskFn, which is necessary if you will signal (start) this task; otherwise
the signal-macro has no effect of course (in V>=0.2.0: start-macro).
-> TASKKS_IS_VALID_TASKINDEX() <-------------------------------------------!
-
- By using access Fns via iPtr (obj) and free access to them you could
made easy wrapper functions.
E.g. if you do not like to call the RUN-macro always you could change
the 'signalTaskFn' (V>=0.2.0: startTaskFn) in the obj and
signal your own RUN-macro start and include the std. start-Fn of this
taskKS. Example: >>
-setup-
ptr_environment->orig_signalTaskFn= iPtr->signalTaskFn;
iPtr->signalTaskFn= myNewSignalTaskFn;
-use-
..
TASKKS_SIGNAL_TASKFN(iPtr, 0 )
--> iPtr->signalTaskFn(iPtr, 0 );
..
-wrapper-Fn-
void myNewSignalTaskFn(struct TaskKS_OneTaskSysInstanceCB*iPtr,unsigned TaskIndex)
{
ActivateRUNMacro( ptr_environment, iPtr);
ptr_environment->orig_signalTaskFn(iPtr,TaskIndex);
}
<<
Hint:pls. do this in Version 0.2.0 or later, due to changed function name
(signalTaskFn -> startTaskFn, old macro could be used furthermore but
should be changed to do not use "TASKKS_IS_DEPRECATED_WANTED" true)
-> indirect Fns in tstTaskKS_OneSysInstanceCB <----------------------------!
(Usually this Fns in the obj are only called by the TASKKS macros)
-
-/-
Impl Integration of simpletest01, using 4 taskFns with one polling
with one global obj of a TaskKS.
Support for linux (POSIX?) only.
* 0.2.0
java: no support
C++: no changes
-
C: ..
Add Define TASKKS_FEATURE_SETbm to make it possible to control
the feature-set of taskKS from outside. (Optional global define)
Default: Newer features signals&messages are enabled.
0: Nearly features only like in version 0.1.0 with small changes.
It is a bit-mask to control all features by one define. Remark: if
a feature is disabled, it could not be used, if a feature is enabled,
it may be used (in some taskKS instances).
Info "signal/unsignal" of 0.1.0 is moved to "start/stop". Reason:
avoid complications with the new signal-features which meant to
have X specific Fns for one taskFn which could be called directly
without any further data-set. (E.g. to clear an HW-flag-bit)
Signaling in 0.2.0 and above version meant feature
TASKKS_IS_SIGNALLING_WANTED.
Add Signaling feature (User supports Fns of type tfnTaskKS_SignalFn ..),
available if define TASKKS_IS_SIGNALLING_WANTED is not 0.
Add Messaging feature (User could use Ring-buffer on every taskFn for task-
communication with events with optional data <-> messages),
needs additional source-files taskksmsg.c/.h.
available if define TASKKS_IS_MESSAGES_WANTED is not 0.
Chg rename "signalTaskFn" to "startTaskFn" (avoid complic. w. signal feature)
and have usually only the new START-macro.
Chg rename "unsignalTaskFn" to "stopTaskFn" (avoid complic.w. signal feature)
and have usually only the new STOP-macro.
Add/Chg Internal behaviour optimization, e.g. for poll-taskFns.
Add taskks_exit as an Fatal-Program-Exit fn
Add check macro which prooves task-ix, if it is a real one,
TASKKS_IS_VALID_INTERNAL_TASKIX().
Upd Comments enhanced and corrected
Add Enhance simpletest01 with 2 other tests. One for signaling and one for
messaging.
-------------------------------------------------------------------------------
### work flow
1. creation
Create the taskKS subsystem via createSys function.
Afterwards the RUN-macro is ready to use, but without any effect yet.
Has to be called to get a instance ptr (iPtr) which is needed for any
access to this taskKS.
Example code:
>>
tstTaskKS_OneSysInstanceCB* pstOneTaskKS;
unsigned uErrorCode= 0xFEFE;
...
pstOneTaskKS= taskks_createSys( &uErrorCode );
if( pstOneTaskKS==NULL) {
print_log with uErrorCode as info
return;
}
/* now you can use TASKKS_* and TASKKSM_* macros with iPtr "pstOneTaskKS"
* as arg1
*/
...
<<
You could increase TASKKS_MAX_TASKS, if you need more taskFns for a taskKS.
2. Initialization
At least you need one or more taskFns which has some tasks to do. Probably
they has the intention of State-Machines, who gets events to work with
and may be switch his machine-state.
Nearly every macro is of the concept to returning a error-code. Usually
return-code 0 means success, everything is ok, and other codes means, something
went wrong.
In the illustrated examples we ignore this, but if you work with dynamic
arguments or starting a big SW project with many taskKS objs it may be good to
detect software-bugs via the return-code check.
2.1 task (function) definition
Define for each task, you want to serve or you had a dedicated task for, a task-
function and you may use an datainstance-via-ptr for this
taskFn (the argument when calling), if you will
not use global variables for instance to have
multi-process opportunity (multiple: many taskKS objs uses the same taskFN or
one taskKS which uses a taskFn in different tasks (e.g. taskFn1 is used in
task 1, 2 and 3 {tix==1..3}).
Here you have to integrate the kind of work for this
task. May be you'll read a command/event from a Q (for instance the 0.2.0
messaging feature) and now you are going to do the specific acts
when the task is called after reading from the Q (the 1st part in the task).
Example code to initialize a taskFn:
>>
..
typedef struct {
tstTaskKS_OneSysInstanceCB* pstTaskSysCB;
struct MyInput InData;
bool isInputBusy;
..
} tstMyTaskContext;
..
tstMyTaskContext stMyTaskContext;
..
void MyTaskFn_getInput(void* pTaskData)
{
tstMyTaskContext* pstMyTC= (tstMyTaskContext*) pTaskData;
// alternative you could use global data if it is sufficient for you
..
}
..
// init of stMyTaskContext has to be finished, iPtr is the result of step 1.
// GetInputTaskIndex is a definition or enum with an task-index (tix) of
// this taskKS.
void anTaskSysInitFunction(void)
{
..
stMyTaskContext.pstTaskSysCB= ~iPtr~ // iPtr got from createSys call
..
TASKKS_SET_TASKFN(iPtr, GetInputTaskIndex, MyTaskFn_getInput, &stMyTaskContext);
..
}
..
int anInputFunction( tstMyTaskContext* pstMyTC, struct MyInput* pInput)
{ // this is already for starting the taskFn, no init
tstTaskKS_OneSysInstanceCB* iPtr= pstMyTC->pstTaskSysCB; // == iPtr
if(pstMyTC->isInputBusy) return 1;
pstMyTC->isInputBusy= TRUE;
memcpy( &pstMyTC->InData, pInput, sizeof(*pInput));
TASKKS_START_TASKFN(iPtr, GetInputTaskIndex);
// the specific taskFn is called with the next RUN-macro call
..
}
..
<<
2.2 optional defining one or more signal functions
Signal-Number in range 0..31 possible.
After signaling a specific SignalFn is called in a taskKS obj.
Special : It is possible to use only SignalFunctions without a defined taskFn;
but if both exist at 1st the signalFn is called and afterwards the taskFn.
-
Setup your signal Fn with
TASKKS_SET_SIGNALFN().
Example code (belonging to previous Ex.code):
>>
..
void MySignalFn_triggerCounter(void* pTaskData, unsigned SignalNo)
{
tstMyTaskContext* pstMyTC= (tstMyTaskContext*) pTaskData;
tstTaskKS_OneSysInstanceCB* pstTaskSysCB= pstMyTC->pstTaskSysCB; // == iPtr
unsigned uCurTaskIX= TASKKS_CURRENT_TASKINDEX(pstTaskSysCB);
unsigned long uSignalId= pstTaskSysCB->astTaskCB[uCurTaskIX].apstSignalTaskCB[SignalNo].ulSignalId;
// do something immediate on HW (in taskKS-environment, may be depending on uCurTaskIX, SignalNo, uSignalId, ..)
..
}
..
void anTaskSysInitFunction(void)
{
..
// define SignelFn#1 for task with tix "ServeHardwareTaskIndex"
TASKKS_SET_SIGNALFN(iPtr, ServeHardwareTaskIndex, 1/*sigNo*/,
MySignalFn_triggerCounter, 0x2345 );
..
}
..
int anTriggerCtrFunction( tstMyTaskContext* taskCtx)
{ // this is already for signaling, no init
TASKKS_SET_SIGNAL(iPtr, ServeHardwareTaskIndex, 1/*sigNo*/);
// the specific signalFn & taskFn is called with the next RUN-macro call
// Attention: using another TaskIndex will call another SignalFn if defined
}
..
<<
2.3 optional defining mailboxes (messaging) for specific or all taskFns
Ring-Buffer-Size-Bits in range 2..16 possible.
This defines the Buffer-Size in potences of 2 - i.e. the mail-box could
accept max. ( 2^(SizeBits) - 1 ) messages before reading them.
For instance if you are using 6 bit (_RiBuSizeBits = 6) the mailbox stores
maximum 63 messages.
The message itself is stored in malloc-memmory; not very fast but due to
wrapping with alloc/free Fns in taskksmsg.c you could speed it up by your own
message allocation system/software if you need more performance.
-
Setup your your mailbox for every taskFn who should be able to receive
messages with
TASKKSM_MESSAGE_INITCB().
Example code (belonging to previous Ex.code):
>>
..
void anTaskSysInitFunction(void)
{
..
// define a mailbox for task with tix "LogServiceTaskIndex" for buffering max. 255 messages
TASKKSM_MESSAGE_INITCB(iPtr, LogServiceTaskIndex, 8 );
..
}
..
<<
2.4 defining a frame-work in taskFns who uses mailboxes
When a taskFn should use the taskKS message functionality, it may be easier
to use the taskksm style to handle messages inside the taskFn.
-
Use the following macros in a taskFn to evaluate messages via default ptr name.
TASKKSM_DECLARE_MESSAGEp -> define default msg-ptr
TASKKSM_IS_MESSAGE_WAITING() -> chk and - if available - get a msg {out of the mailbox}
TASKKSM_SWITCH_MESSAGE_ID() -> C "switch" with the ID of the msg
TASKKSM_FREE_MESSAGE() -> release the msg ("free")
-
Example code (belonging to previous Ex.code):
>>
..
void MyTaskFn_LogService(void* pTaskData)
{
tstMyTaskContext* pstMyTC= (tstMyTaskContext*) pTaskData;
tstTaskKS_OneSysInstanceCB* pstTaskSysCB= pstMyTC->pstTaskSysCB; // == iPtr
..
TASKKSM_DECLARE_MESSAGEp;
int iRC;
..
iRC= TASKKSM_IS_MESSAGE_WAITING( pstTaskSysCB );
if( iRC!=0) {
/* we MUST check & if exist (RC!0) free the implicit Msg ptr,
* declared by TASKKSM_DECLARE_MESSAGEp, at least
*/
TASKKSM_SWITCH_MESSAGE_ID()
{
case 1: //------------------------------------//
// do something with message, type 1
..
/* if you like to store this msg longer than this call, you could
* use : TASKKSM_MOVE_GETMESSAGEp(_saveP)
* this macro saves the current msg ptr in the given one, and this
* should not be a local Fn var of course
* (e.g. a member var of pstMyTC)
*/
break;
default: //------------------------------------//
printf("unknown message Id (%lu) - ignore\n", (unsigned long) TASKKSM_MESSAGE_ID());
break;
}
TASKKSM_FREE_MESSAGE( pstTaskSysCB );
};
..
}
..
<<
2.5 setup & send messages to the specific taskFns with a mailbox
taskFns which works on mailboxes (message Ring-Buffers) must served with
messages (msgs) from outside (usually).
-
This must be done in the same task-context (thread)! Because no semaphores are
now used by taskKS.
-
How much msgs a taskFn should get in maximum must be estimated with enough
reserve in the Ring-Buffer storage for a taskFn (see 2.3).
.
All messages are located in malloc memory. The pointer/memory is (usually)
created by the Sender (may be a taskFn of this taskKS obj), and stored
in the mailbox of the Receiver till the Receiver reads out the message. This
is done in FIFO manner, of course.
-
Allocate memory for a msg, to get the msg-ptr, via macro:
TASKKSM__ALLOC_MESSAGE(),
TASKKSM_ALLOC_MESSAGE().
This macro assures that the returned msg-ptr (arg2) is valid; due to the
statement : "memory should never end", i made an program-exit ~exception~
when no memory is available. Hence you do not need to check any return-code.
(Although a macro, it is similar like a void Fn)
-
After setting up all data of this message you should transfer it to _one_
task (taskFn with mailbox) via macro:
TASKKSM__SEND_MESSAGE(),
TASKKSM_SEND_MESSAGE().
After this call, the memory-control of this msg (ptr) is moved to the other
task (except the RC is not 0, which is a failure afai-think).
-
Remark: the macros with 2 underscores after TASKKSM should be used normally;
hence you do not get any trouble between incoming and outgoing messages
(The one-underscore macro uses the default message ptr, which is used _always_
in the Receive macros as described in 2.4, via "TASKKSM_DECLARE_MESSAGEp;").
-
Example code (belonging to previous Ex.code):
>>
..
void MyTaskFn_AnyMiscThingsToBeDone(void* pTaskData)
{
tstMyTaskContext* pstMyTC= (tstMyTaskContext*) pTaskData;
tstTaskKS_OneSysInstanceCB* pstTaskSysCB= pstMyTC->pstTaskSysCB; // == iPtr
tstTaskKSM_Message* pstMyMessageIWantToSend= NULL;
..
.. // setup dataBuffer, for this example we assume lenght = 20
// we now want to setup a message for the Log-task (see 2.4)
TASKKSM__CURTASK_ALLOC_MESSAGE(iPtr, pstMyMessageIWantToSend,
DebugLogType, 20, dataBuffer );
// TODO : get message and setup data directly into the message
// (now it has to be done before in "dataBuffer" and willB copied)
TASKKSM__SEND_MESSAGE(iPtr, LogServiceTaskIndex, pstMyMessageIWantToSend);
..
}
..
<<
The example is based on creating a message in a taskFn. But you could do this
everywhere if you got the iPtr of this taskKS obj,
assuming the task-context (thread) is the same.
3. RUN macro embedding in your system
Usually on some process-systems you'll have one
endless-till-process-ends loop.
Here you can insert the RUN macro, which will
often called then. May be you call it with a
periodically time; this service is outside of
the taskKS. You may create - for instance - an own
thread which will call the RUN macro every millisecond, but if you
are going to interact with other threads you have
to take care of semaphore usage by yourself!
4. trigger task function to run
At least you have to start the task, which
means it will be executed on next RUN call.
This could be done inside or outside of this
taskKS but must be done by start macro of
this taskKS instance (taskKS subsystem ptr).
-
Implicit started is a taskFn with the *_SEND_MESSAGE() and the
*_SET_SIGNAL() macro. With this you do not need the explicit additional use
of the start-macro
TASKKS_START_TASKFN()
-
Usually it will be called one time and after calling it is waiting again
for the next start trigger.
You could start permanent calling by clearing the one-time flag of this task.
5. optional enable poll-task
If you need [for some time] a permanently called
task function, e.g for polling, you should
clear the one-time flag of this task before
using it. After this, the started task will
be called as many times as the RUN macro
is called. This will finish only by setting
the one-time flag again or by stopping this task.
6. deleting a taskKS obj
If you are sure that memory blocks (especially messages), used by this taskKS
are all free'd, you could remove this instance completely.
Use taskFn internal Fn "deleteTaskKSSys". (Not wrapped by a macro yet)
iPtr->deleteTaskKSSys(&iPtr);
-
When successful, the iPtr is NULL afterwards. No other copies should exist!