Nanokernel

The nanokernel is the core of the Kernel, and is a small real-time operating system (RTOS) designed to run Symbian platform.

A Personality Layer for Real Time Applications is a client of the nanokernel, and this topic is intended for writers of personality layers.

Nanokernel threads

The fundamental unit of execution is the nanokernel thread, an NThread object.

A nanokernel thread has an integer priority, between 0 and 63 inclusive, and is scheduled according to priority, the highest priority first. Equal priority threads are scheduled on either a round robin or a run-to-completion basis, selectable for each thread. For round robin scheduling, the time slice is selectable for each thread.

Priority 0 is reserved for the system idle thread.

Priorities 1-27 inclusive, and priority 48 are currently used by Symbian platform threads; 12 is the priority of the foreground Symbian platform application. There are, therefore, 35 distinct priorities that are totally unused and available for a real time application.

Nanokernel threads may be created and destroyed dynamically, but the nanokernel does not do any memory management. Memory for both the thread object and the thread stack must be provided by the caller at create time. In fact the same rule applies throughout the nanokernel - all memory for dynamically created objects must be provided by the caller and freed by the caller after object destruction.

Nanokernel synchronisation

The nanokernel provides the following synchronisation objects:

  • A fast semaphore, a NFastSemaphore object. This is a lightweight counting semaphore owned by a specific nanokernel thread. Any thread can signal it, but only the owning thread can wait on it.

  • A fast mutex, a NFastMutex object. This is a lightweight mutual exclusion primitive. It is optimised for very fast acquisition and release in the case where there is no contention, and it provides priority inheritance. It is non-nestable, i.e. a thread is not allowed to hold two fast mutexes simultaneously or to wait on one which it already holds. In addition, a thread that holds a fast mutex cannot block on any other wait object.

  • Deferred function calls (DFCs).

There is also the option of thread synchronisation by doing one of:

However, these two techniques must be used with caution as they affect all parts of the system.

Scheduling threads following a hardware interrupt

In order to keep interrupt latency to a minimum the vast majority of the nanokernel runs with interrupts enabled; this includes the scheduler and other code which manipulates the thread ready list. A consequence of this is that it is not permissible for an interrupt service routine (ISR) to add a thread to the thread ready list. The only way an ISR can influence the scheduling of threads is by means of an Immediate Deferred Function Call (IDFC).

An IDFC is a function call that runs either:

  • immediately after the completion of the ISR, and any other ISRs that are pending.

or

  • as soon as preemption is re-enabled, if preemption was disabled at the time the interrupt occurred.

IDFCs run in FIFO order, i.e. the first one queued is first one to run, with interrupts enabled and preemption disabled. This means that IDFCs cannot be preempted either by threads or by other IDFCs, but only by ISRs. However, they can add threads to the ready list and thus cause rescheduling.

As IDFCs are nonpreemptive, they need to be kept short. This means that they affect thread latency. If a large amount of work needs to be done in response to an ISR, then a Deferred Function Call (DFC) should be used.

A DFC is a function call that runs in the context of a nominated thread. A DFC is associated with a DFC queue, which in turn is associated with a thread. The thread simply waits forever for a DFC to be added to the queue and then calls back the DFC. DFCs execute with both interrupts and preemption enabled. Because DFCs are so common in Symbian platform driver code, the nanokernel provides a shortcut to allow them to be queued directly by ISRs instead of by an IDFC. The same object, a TDfc, is used for both IDFCs and DFCs. When this object is queued by an ISR, it first executes as an IDFC; the code for this IDFC is buried in the nanokernel. This code transfers the object to the final DFC queue and wakes up the DFC thread as required.

Nanokernel thread handlers

Each nanokernel thread can have the following handlers defined:

  • Exit handler. This is called in the context of the exiting thread just before it is terminated.

  • Exception handler. This is called if a CPU exception occurs during the execution of the thread; it is called, in the context of the thread.

  • Time-out handler. This is called if the thread's wait time-out expires with the thread in the BLOCKED state, or in an unknown state. It is called in the context of the nanokernel timer thread, DFC Thread 1, with preemption enabled. It is used in Symbian platform wait-with-timeout operations.

  • State handler. This is the fundamental hook used for personality layers.. It is called if:

    • the thread is suspended

    • the thread is resumed

    • the thread is released

    • the thread has its priority changed

    • the thread's wait timeout expires and there is no time-out handler and the thread is in an unrecognised nanokernel state.

See also ...\e32\personality\example\personality.cpp.

Timer management

The nanokernel provides a basic relative timer object, an NTimer, which can generate either one-shot or periodic interrupts.

A time-out handler is called when the timer expires, either from the timer ISR or from the nanokernel timer thread. Timer objects may be manipulated from any context. The timers are driven from a periodic system tick interrupt, usually a 1ms period.

For the purposes of power management, an API is provided to return the number of ticks left until the next timer expiry, thus enabling the tick interrupt to be suppressed and a deep sleep mode to be entered.