Process, Thread, Stack and Memory Attributes

Reference for users of the debug monitor tool to the attributes of Kernel objects and memory structure.

Process and thread priorities

Internally the scheduler always deals with nanokernel threads, NThread objects, and their associated priority between 0 (lowest) and 63 (highest). In general, a thread with a higher priority that is ready to run will always run in preference to threads with a lower priority. The only exception is where a higher priority thread waits on a nanokernel fast mutex held by a lower priority thread. In this case, the higher priority thread will yield to the lower priority thread holding the mutex.

A Symbian platform thread, a DThread object, has an embedded NThread, which enables it to be scheduled by the nanokernel.

There are two ways of setting a priority for Symbian platform thread:

  • using the two-level priority scheme

  • using an absolute priority.

The two level priority scheme

In this scheme, a Symbian platform thread priority is relative to the priority of its owning process. By default, Symbian platform threads inherit the priority of their owning process when they are created. This priority can be raised or lowered relative to the process priority - this just sets the thread’s priority to the process priority plus or minus a specified priority weighting. If the priority of the process is changed, the priority of its threads will change relative to other threads in the system but will remain the same relative to each other.

The default priority of a process is EPriorityForgeround, which is an absolute priority of 350. Threads by default are created with relative priority EPriorityNormal which sets them to the same priority as the owning process. The window server lowers the priority of background UI processes to EPriorityBackground (250).

The NULL thread, also known as the idle thread, runs at priority 0, and means that it will only run when there are no other threads ready to run.

Symbian platform thread priorities map onto NThread priorities in the range 1 to 31 as shown in the table below.

Thread priority

Idle

Much Less

Less

Normal

More

Much More

Real Time

Process priority

Low

1

1

2

3

4

5

22

Background

3

5

6

7

8

9

22

Foreground

3

10

11

12

13

14

22

High

3

17

18

19

20

22

23

SystemServer1

9

15

16

21

23

25

28

SystemServer2

9

15

16

21

23

25

28

SystemServer3

9

15

16

21

23

25

28

RealTimeServer

18

26

27

28

29

30

31

where:

  • the process priority values are defined by the internal Symbian platform enum TProcPriority, defined in ...\e32\include\kernel\kern_priv.h. The symbols in the table correspond to the symbols in the enum.

  • the thread priority values are defined by the internal Symbian platform enum TThrdPriority, defined in ...\e32\include\kernel\kern_priv.h. The symbols in the table correspond to the symbols in the enum.

Absolute priority scheme

It is possible to set an absolute priority that is not relative to the process priority; it is not affected by changes in the process priority.

Thread state summary

This is a brief summary about nanokernel thread states and Symbian platform thread states.

Nanokernel thread states

The state of a nanokernel thread is referred to as the NState (or N-state). This is to disambiguate it from any other state, such as the state of a Symbian platform thread (referred to as the MState or M-state).

The states of a nanokernel thread are defined by the values of the NThreadBase::NThreadState enumeration.

Symbian platform thread states

The state of a Symbian platform thread is referred to as the MState (or M_state). This is in addition to the nanokernel N-state, and tracks threads waiting on Symbian platform synchronization objects. The DThread class representing a Symbian platform thread is internal to Symbian, but the following table defines its possible states. The values in the left-hand column are the enumerators of the internal enumeration DThread::TThreadState.

ECreated

The initial state of all Symbian platform threads. It is a transient state; the thread starts in this state when the DThread object is created, and stays in that state until it is ready to be resumed, typically when DLL linkage and memory allocation is complete. At this point, the state will change to EReady.

EDead

This is the final state of a Symbian platform thread. A thread enters this state when it reaches the end of its exit handler, just before the nanokernel terminates it. In effect, the thread has exited but has not yet been deleted.

EReady

This indicates that the thread is not waiting on, or attached to any Symbian platform kernel wait object. It does not necessarily imply that the thread is actually ready to run - this is indicated by the N-state. For example, a thread that is explicitly suspended or waiting on a nanokernel wait object (generally a fast semaphore) still has a READY M-state provided that it is not attached to any Symbian platform wait object.

EWaitSemaphore

This indicates that the thread is currently blocked waiting for a Symbian platform semaphore, and is enqueued on the semaphore’s wait queue. The thread’s DThread::iWaitObj field points to the semaphore.

For example, this is the case if the thread calls User::WaitForRequest() or RSemaphore::Wait()

EWaitSemaphoreSuspended

This indicates that the thread has been explicitly suspended after blocking on a Symbian platform semaphore, and is enqueued on the semaphore’s suspended queue. The thread’s DThread::iWaitObj field points to the semaphore.

EWaitMutex

This indicates that the thread is currently blocked waiting for a Symbian platform mutex, and is enqueued on the mutex wait queue. The thread’s DThread::iWaitObj field points to the mutex.

EWaitMutexSuspended

This indicates that the thread has been explicitly suspended after blocking on a Symbian platform mutex, and is enqueued on the mutex suspended queue. The thread’s DThread::iWaitObj field points to the mutex.

EHoldMutexPending

This indicates that the thread has been woken up from the EWaitMutex state but has not yet claimed the mutex. The thread is enqueued on the mutex pending queue and the thread’s DThread::iWaitObj field points to the mutex.

EWaitCondVar

This indicates that the thread is waiting on a condition variable.

EWaitCondVarSuspended

This indicates that the thread is suspended while waiting on a condition variable.

Thread and process exit information summary

User threads and processes have “exit information”. When a thread or process terminates the reason for the termination is found in the exit information. For example, a panic will store the panic category and reason in the exit information. Exit information has three parts: the exit type, exit reason and exit category.

Exit type is defined by the TExitType enum.

When a thread or process is created, its exit type is set to 3. An exit type of 3 indicates that the thread is still active, though not necessarily running. If the thread terminates for any reason, then the exit type is changed to reflect the cause of the exit.

Once the thread or process has exited, the exit reason and exit type fields will contain useful information. The contents depends on the type of exit.

Note that if the main thread in a process exits, then the process will exit with the same exit information as the thread.

Exit category: Terminate

if RThread::Terminate() or RProcess::Terminate() is called, then the exit category is Terminate, and the exit reason is the value of the aReason argument passed to these functions.

Exit category: Kill

If RThread::Kill() or RProcess::Kill() is called, then the exit category is Kill, and the exit reason is the value of the aReason argument passed to these functions.

Exit category: panic

If a thread panics, then the exit category is panic, and the exit reason is the panic number. For example a USER-19 panic would give the following exit information:

exit type = 2
exit category = “USER”
exit reason = 19

Critical threads and processes

Marking a thread or process as “system critical” means that it is an integral and essential part of the system, for example, the file server. In effect the thread or process is being declared necessary for correct functioning of the device. If a system critical thread exits or panics then the device will reboot; during development it will enter the debug monitor. A thread can be set as process critical, which means that if it panics the process will be panicked.

Kernel calls and thread context

When a user thread makes a call into any kernel code, the kernel code continues to run in the context of the user thread. This applies to device driver code.

The stack is swapped to a kernel-side stack and the permissions of the thread are increased to kernel privilege, but otherwise the user thread is still running. Each thread has a small kernel stack used to handle kernel calls – it would be dangerous to continue using the normal thread stack in case it overflows. Some calls are handled in this state, others – typically device drivers – will post a message to a kernel side thread to carry out the request.

Stacks

When a process is created, a chunk is allocated to hold the process executable's .data section (initialised data) and .bss section (zero filled data). Sufficient space (default 2Mb) is also reserved as user-side stack space for threads that run in that process.

By default, each thread is allocated 8k of user-side stack space. A guard of 8k is also allocated.

The stack area follows the .data and .bss sections, and each thread's user side stack follows. On ARM processors the stack is descending, so that as items are added to the stack, the stack pointer is decremented. This means that if the stack overflows, the stack pointer points into the guard area and causes a processor exception, with the result that the kernel panics the thread.

Return addresses are stored by pushing them on to the stack so at any point you can trace through the stack looking at the saved return addresses to see the chain of function calls up to the present function.

The size of the user-side stack space has an indirect effect on the number of threads that a process can have. There are other factors involved, but this is an important one. The limit is a consequence of the fact that a process can have a maximum of 16 chunks. This means that if threads within a process can share a heap (allocated from a single chunk), then it is possible to have a maximum of 128 threads per process [2Mb/(8K + 8K)]. More threads may be possible if you allow only 4K of stack per thread.

Apart from the kernel stack attached to each thread, the kernel also maintains stacks that are used during processing of interrupts, exceptions and certain CPU states. Interrupts and exceptions can occur at any time, with the system in any state, and it would be dangerous to allow them to use the current stack which may not even be valid or may overflow and panic the kernel. The kernel stacks are guaranteed to be large enough for all interrupt and exception processing.

Virtual memory and run addresses

Symbian platform devices have an MMU which is used to map the addresses seen by running code to real addresses of memory and I/O. The MMU in effect creates a virtual memory map, allowing scattered blocks of RAM to appear contiguous, or for a section of memory to appear at different addresses in different processes, or not at all.

Symbian platform uses the MMU to provide memory protection between processes, to allow sharing of memory, efficient allocation of RAM and to make all processes “see” the same memory layout. Three different memory models are supported by Symbian platform on ARM CPUs:

  • moving model: this is the model familiar from EKA1 where processes are moved to a run-address in low memory when executing and moved back to a home-address in high memory when not running.

  • direct model: this is used when the CPU does not have an MMU, or is emulating a system without an MMU. Not normally used, but occasionally useful for development boards

  • multiple model: only supported in ARM architecture V6 and above, each process has its own set of MMU tables. A context switch changes the current MMU table to the new thread’s table, instead of moving memory about in a single table as with moving model.

Fixed processes

For ARM architectures with a virtually-tagged cache, fixed processes avoid the need to flush the cache on context switches by keeping all the code and data at a fixed address. This implies that there can only ever be one instance of each fixed process because the data chunk address cannot be changed.

Important servers such as the file server and window server are fixed.

There is no limit to the number of fixed processes that can be supported. The kernel will attempt to use ARM domains for fixed process protection, but there are a limited number of domains so when they are exhausted normal MMU techniques will be used. Domains are slightly faster in a context switch but this is negligible compared to the real purpose of the fixed process in avoiding the cache flush.