Multitasking

Multitasking

JeeH supports a limited form of multitasking. There is only a single stack: there are no threads to suspend or resume, but also no need to allocate separate stacks to each one. This single stack expands downwards with the rest of unused RAM available for heap allocation. Just as in any single-tasking bare-metal application.

In JeeH, applications are structured using “tasks”. Each one has a distinct priority defined by their startup order. Lower-priority tasks can send messages (“events”) to higher-priority tasks, which can send reply events back. Each task implements a “process” method, called from JeeH to deal with incoming events. Except in one case described below, each process call runs to completion. Incoming events are queued until that call returns.

Interrupt-driven device drivers are implemented as tasks. There is a “trigger” method which an IRQ handler can call to send an event to its own task. Such events are queued like all other events, but if the priority of the current running task is lower, it gets interrupted and the irq-handler’s task process method is called right away. This is the one case where a running task can be pre-empted by another one before it runs to completion.

In summary, there are two ways in which task switching (or more accurately: nesting) occurs:

  1. Task A sends a message to a higher-priority task B - this immediately switches to B, with the send call returning once B’s process method returns. Such a message send acts like a normal function call.
  2. An interrupt handler triggers its task B, while a lower-priority task A is running. Task A will resume once B’s process method returns (unless B sends or replies to another task with higher priority than A). The difference is that task A could be busy doing anything: it cannot control when the switch happens.

Case 1 is synchronous. A sends to (i.e. activates) B and resumes once B is done. This is the consequence of B’s higher priority, as required by the send call. While B is running, no task with a priority lower than B can run.

Case 2 is asynchronous. A is interrupted and cannot assume anything about data structures touched by B, i.e. neither what changed, nor when. To coordinate, A and B need to exchange events and act on these on arrival. Sending a reply is also asynchronous, since the reply event can only be delivered once B returns.

There is no explicit locking mechanism in JeeH: only task priorities determine what can potentially run at each point in time. Due to this design choice, JeeH’s interrupt latency is absolutely minimal.

Each task will tend to fall in one of the following categories:

  1. Asynchronous device drivers for built-in hardware peripherals with interrupts.
  2. High-priority tasks, dealing with events which must be serviced in real time.
  3. The main application task(s), sending requests to the above two task categories.
  4. Low-urgency tasks, running “once in a while”, e.g. for non-critical chores.

Application tasks cannot send requests to lower-priority tasks. But they can send replies, so the way to deal with such cases is to use replies as a way to pass requests downwards: a low-urgency task sends a request to an app task to “register itself” and that app task can then send replies back whenever needed (also more than once).