Next: , Up: Software Environment


4.1 Multitasking

FreeWPC implements a runtime, round-robin, non-preemptive task scheduler. The system manages multiple tasks, each of which has its own call stack. You can create a new task when you want to run some code in parallel with the current thread of execution. The current task has complete control of the CPU and will continue to run until it explicitly exits or sleeps (waiting for a certain amount of time). The minimum sleep time is defined by IRQS_PER_TICK and is currently 16ms.

Contrast this with desktop operating systems, which use timeslicing and preemption to allow multiple threads to run in parallel. There, the OS switches between the tasks. In FreeWPC task switches only happen explicitly; this provides for more deterministic behavior, it requires less overhead, and it eliminates the need for mutexes (locks) between two tasks because task switches cannot happen at arbitrary times.

If a task does not give up control, either by sleeping, exiting, or yielding after a certain amount of time, the fatal error ERR_FCFS_LOCKUP will be asserted.

When a new task is created, it does not begin running immediately; control always continues with the task that started it. This allows you to configure the new task before it can run.

Each task is identified by a process ID, or pid. The PID for a task is assigned by the system when it is created. A task also has a group ID, or gid. The GID is assigned by the programmer, and multiple tasks can share the same GID. GIDs allow you to control a group of related tasks, or to refer to a task using a compile-time ID as opposed to a run-time ID.

In native mode, the multitasking APIs are implemented using the GNU Pth library. A good description of this library can be found at http://www.gnu.org/software/pth/. Note this is not the same as pthreads, which is the predominant preemptive threading library.

For Windows programmers, this threading model is very similar to what Win32 calls fibers.

4.1.1 Periodic Functions

After all tasks have been given a chance to run in a 16ms timeslice, the system runs the periodic functions. These have equal priority to tasks, but they do not have a stack and they cannot sleep. In the code, these are also referred to as idle functions, although that term is slightly incorrect (they are guaranteed to be called even on a fully busy system).

You can register a periodic function at various rates: either every 16ms, 100ms, 1 second, or 10 seconds. Handlers may not be called at the exact rate, but it will be as close as possible. Slippage occurs when it takes longer than 16ms for all tasks to be given a chance to run. If you need exact timing, use a realtime function.

For example, assume that a 1 second handler does not get called until after 1.5 seconds from the previous call, because the system is busy running other tasks. Then the next time it will be called just 500ms later; the scheduler will realize that it is behind and try to catch up.

You cannot dynamically register or deregister periodic handlers at runtime.

4.1.2 Task APIs

task_create_gid
The basic API to create a new task. You specify a code function and a GID. The function must reside in the same bank of ROM. If the function is in a different ROM page, you need to call task_set_rom_page immediately after this call to say where it was placed.
task_create_gid1
Similar, but checks to see if a task with the same GID already exists; if it does, no new task is created.
task_recreate_gid
Similar, but checks to see if a task with the same GID already exists; if it does, the old task is stopped before the new task is created.
task_sleep
Suspends the current running task for at least the given period of time. The parameter is one of the TIME defines which is given in units of approximately 16ms. Because it is 8-bit, this limits it to about 4 seconds.

If you want to sleep for the absolute minimum, instead of task_sleep (0) it is recommended to use task_yield().

task_sleep_sec
Suspends the current running task for the given number of seconds. This handles larger timeouts, but does not allow the same granularity.
task_exit
Exit from the current task. This must be called; task functions cannot just return when they are done (that is guaranteed to cause a crash).
task_find_gid
Returns the first PID found for a task that has a given GID.
task_kill_pid
Stops another task based on PID. This stops exactly one task.
task_kill_gid
Stops another task based on GID. This can stop one or more tasks. If called from a task with the same GID, it stops all other tasks, but keeps the running task alive.
task_set_arg
The creator of a task can pass a single 16-bit value or pointer to it using this call. It should be called before a task switch might occur.
task_get_arg
Called by the newly created task to obtain its argument.