Notes on execi()
A task in brckOS is created by the execi function which as first argument
takes the function the newly created task will be executing. If
you want to give arguments to your task you most easily do it by
using global variables.
If you want to send local arguments to your created tasks you
do it by initilizing the argc and *argv[]
arguments. argc stands for argument counter and
should be an integer holding the number of arguments
passed. argv stands for argument vector and is a
vector of char pointers (ie. strings). It is your responsibility
to create a correct argument vector and set the argc
number.
For example a code like: char * arg_to_foo[2] =
{"lego", "mindstorms"}; execi(foo, 2, arg_to_foo,
PRIO_NORMAL, DEFAULT_STACK_SIZE); will create a task
executing the function foo. Assuming that foo looks like:
int foo(int argc, char * argv[]) { ... } we get that
argc = 2, argv[0] = "lego" and argv[1] =
"mindstorms".
Please note that if you allocate the argc vector in a
task which later on dies (eg. in a task running main())
the vector might be deallocated since it vectors are called by
call-by-reference in C.
If you don't want to give any arguments to your created task
just make a call like: execi(foo, 0, NULL, PRIO_NORMAL,
DEFAULT_STACK_SIZE); which sets argc = 0 and
argv = NULL.
If you would like to give your created tasks arguments which
not are char pointers you have to use C:s type casting
facilities. Eg. the code: int nr_of_touch_sensors =
2; arg_to_foo[2] = (char *)
&nr_of_touch_sensors; will save the address of the
integer nr_of_touch_sensors as an char pointer in the
arg_to_foo vector. When we later on in the foo
function wants to extract the integer we typecast the argument
back: int ts; ts = *((int *)
argv[2]); ie. reinterpret the char pointer as an integer
pointer and save what the integer pointer is pointing at in the
integer ts.
The stack size argument to execi() is the amount of
bytes that the task will be using when it makes function calls in
the program (pushing contexts on the stack). In most cases the
DEFAULT_STACK_SIZE value is enough, but if you do a lot
a nested function calls you might need to increase the value.
LegOS use pre-emptive multitasking. Shortly it works as
follows:
By default a task is executed for 20 ms before being preempted.
Every 1 ms a timer interrupt is generated by the hardware. Every
time the timer interrupt is generated the current executing task
is postponed by the function systime_handler function
(defined in systime.c) which runs code which drives the
system.
If 20 ms of the current task has elapsed, (ie. 20 timer
interrupts has been generated), systime_handler calls
tm_switcher, (defined in tm.c), which saves all
registers of the current task on its stack, and then call the
scheduler. The scheduler designates a new task to execute and
returns to tm_switcher which copies the context of the
new task into the registers and returns to
systime_handler which just returns to the code in the new
task.
Ie. system function code is run every 1 ms but the OS only
makes preemptive context switches every 20 ms. However, to avoid
wasting system resources tasks may voluntarily yield control of
the processor by calling the yield() function. This is
often indirectly made eg. by calling sleep or
wait_event functions.
The scheduler has a queue of all tasks in the system, sorted on
priorities. When the scheduler looks for a new task to execute,
it always starts with the highest prioritised task which is in a
ready-to-execute state. A task is ready to execute when its
wait_event function returns a positive value (see the notes_on_wait_event document)
or when the task has earlier been preempted by a higher priority
task.
Fo more details on how the scheduling of tasks is made in the
LegOS kernel see the "Introduction to the LegOS Kernel", (.ps or .pdf), by S. Nilsson.
|