Fundamentals of Symbian C++/Threads, Processes, and IPC
The unit of memory protection in the Symbian OS is called the process, while the unit of execution is the thread. A process may contain one or more threads. What gets scheduled in Symbian OS are the threads, not the processes, which are effectively memory-protection containers.
Each thread has its own stack and heap (unless it shares). Each EXE has its own stack and heap from the main thread. Processes, however, do not have a stack and heap. They do have a data area for shared data/writeable static data. Every executable runs in its own thread within a process.
Many processes can be active on Symbian OS at once. There can be multiple instances of the same executable, but each process is unique. Processes have private address spaces, and a user-side process cannot directly access memory belonging to another user-side process.
By default, a process contains a single execution thread, the main thread, but additional threads can be created as described above. A context switch to a process occurs whenever one of the threads in that process is scheduled to run and becomes active.
RProcess is the class used to manipulate processes. In much the same way as described below for RThread, RProcess::Create() can be used to start a new, named process (such as an EXE), and the RProcess::Open() function can be used to open a handle to a process identified by name or process identity (TProcessId).
The various functions to stop the process are also similar. For example, RProcess::Resume() marks the first thread in the process as eligible for execution. However, there is no RProcess::Suspend() function because processes are not scheduled; threads form the basic unit of execution and run inside the protected address space of a process.
On the emulator, processes are emulator with WIN32 threads. For example, launching an EXE translates to a new thread being created to run it. As such, process memory protection is not enforced on the emulator (processes can ‘see’ each other’s memory).
In many cases on the Symbian platform, it is preferable to use active objects (described in Fundamentals of Symbian C++/Active Objects) rather than threads, because these are optimized for event-driven multi-tasking on the platform. Also, they are fairly scalable to handle many events which must be processed promptly but can be dealt with one at a time. However, when writing code with real-time requirements or when you are porting code written for other platforms, it is often necessary to write multi-threaded code.
RThread is the class used to manipulate threads in Symbian C++. It represents a handle to a thread; the thread itself is a kernel object.
RThread defines several functions for thread creation, each of which takes a descriptor for the thread’s name, a pointer to a function in which thread execution starts, a pointer to data to be passed to that function (if any), and the stack size of the thread (which defaults to 8 KB). RThread::Create() is overloaded to allow for different heap behavior, such as defining the thread’s maximum and minimum heap size or determining whether it shares the creating thread’s heap or uses a separate heap.
RThread::Open() can open a handle to a thread using its unique thread ID (which is returned by RThread::Id()) or alternatively using the thread’s unique name.
A thread is created in the suspended state and its execution initiated by calling RThread::Resume().
On Symbian C++, threads are pre-emptively scheduled and the currently running thread is the highest-priority thread ready to run. If there are two or more threads with equal priority, they are time-sliced on a round-robin basis. A running thread can be removed from the scheduler’s ready-to-run queue by a call to RThread::Suspend(). It can be restarted or can be scheduled by calling Resume(). To terminate a thread permanently, you should call Kill() (to indicate natural termination) or Terminate()(to indicate unnatural termination) to stop it normally. If the main thread of a process dies for any reason, the process and all other threads are also terminated. For this reason, and because of the secure platform, Symbian C++ provides several user library primitives for thread synchronization:
- A semaphore can be used either for sending a signal from one thread to another or for protecting a shared resource from being accessed by multiple threads at the same time. In Symbian C++, a semaphore is created and accessed with a handle class called RSemaphore. A global semaphore can be created, opened and used by any process in the system, while a local semaphore can be restricted to all threads within a single process. Semaphores can be used to limit concurrent access to a shared resource, either to a single thread at a time, or allowing multiple accesses up to a specified limit.
- A mutex is used to protect a shared resource so that it can only be accessed by one thread at a time. On Symbian C++, the RMutex class is used to create and access global and local mutexes.
- A critical section is a region of code that should not be entered simultaneously by multiple threads. An example is code that manipulates global static data, because it could cause problems if multiple threads change the data simultaneously. Symbian C++ provides the RCriticalSection class that allows only one thread within the process into the controlled section, forcing other threads attempting to gain access to that critical section to block until the first thread has exited from the critical section. RCriticalSection objects are always local to a process, and a critical section cannot be used to control access to a resource shared by threads across different processes – a mutex or semaphore should be used instead.
Please see the Symbian Developer Library for more information about working with Symbian C++ threads. Example code is also provided in the SDK to show the use of RThread and other Symbian C++ system classes.
Inter-Process Communication (IPC)
The article Fundamentals of Symbian C++/Client-Server Framework describes a common form of inter-process communication (IPC) on Symbian OS: the client-server framework.
Clients connect to servers (in this context the term 'server' is used to mean a specific Symbian C++ entity, not a web server or similar) and establish a session for all further communication, which consists of client requests and server responses, mediated by the kernel. Session-based communication ensures that all clients will be notified in the case of an error or shutdown of a server, and all server resources will be cleaned up if an error occurs, or when a client disconnects or dies.
This type of communication paradigm is ideal when many clients need reliable concurrent access to a service or shared resource. In that case, the server serializes and mediates access to the service accordingly. However, there are some limitations:
- Clients must know which server provides the service they need.
- A permanent session must be maintained between client and server.
- It is not really suitable for event multi-casting (server-initiated 'broadcast' to multiple clients).
There are additional IPC mechanisms: Publish & Subscribe, message queues and shared buffer I/O. Publish & Subscribe and message queues are described in more detail below. Shared buffer I/O is not discussed because it is intended primarily for device driver developers. (It is used to allow a device driver and its clients to access the same memory area without copying, even during interrupt handling.)
Publish & Subscribe
The Publish & Subscribe mechanism (often abbreviated to P&S) was created to provide asynchronous multi-cast event notification, and to allow for connectionless communication between threads. Publish & Subscribe should be used when a component needs to supply or consume timely and transient information to or from an unknown number and type of interested parties, while remaining decoupled from them. A typical example is the notification of a change to the device’s radio states, for example, flight-mode, Bluetooth radio on/off, WiFi on/off, and so on.
Publish & Subscribe can be used in the kernel side of the Symbian platform.
Publish & Subscribe provides a means to define and publish changes to system-wide global variables known as 'properties'. Changes to the properties can be asynchronously communicated ('published') to more than one interested ('subscribed') peer. Publishers and subscribers can dynamically join and leave without any connection set-up or tear-down.
Properties are uniquely identified by a 64-bit integer – this is the only information that need be shared between the publisher and subscriber (typically through a common header file). There is no need to provide interface classes or functions for a property. Subscribers do not need to know which component is publishing to a property. They only need to know about the Publish & Subscribe API, and the identity of the property of interest to them.
The Publish & Subscribe API is supplied by the RProperty class. The identity of a property is composed of two parts:
- A category (defined by a standard UID) that specifies the category to which the property belongs
- A key, which uniquely identifies a property within a particular category. Its value depends on how keys within the category are enumerated.
Once identified, a property holds a single data variable which may be either a 32-bit integer, a byte array (a descriptor) of up to 512 bytes in length, Unicode text (also up to 512 bytes in size), or even large byte arrays of up to 65,536 bytes.
A thread may take the role of either the publisher or the subscriber, and any party interested in a property can be the one to define it, by calling RProperty::Define() to create the variable and specify its type and access controls. Once a property has been defined, it will persist in the kernel until it is deleted explicitly or the system reboots. The property’s lifetime is not linked to that of the defining thread or process.
It is not necessary for a property to be defined before it is accessed (lazy definition), so it is not a programming error for a property to be published before it has been defined. This is known as speculative publishing.
Properties can be published or retrieved either using a previously attached handle or by specifying the property’s identity for each call. The benefit of the former method is that it has a deterministic bounded execution time, making it suitable for high-priority, real-time tasks.
A property is published by calling RProperty::Set(). This writes a new value atomically to the property, thus ensuring that access by multiple threads is handled correctly. When a property is published, all outstanding subscriptions are completed, even if the value is actually unchanged. This allows the property to be used as a simple broadcast notification.
To subscribe to a property, a client must register interest by attaching to it and calling the asynchronous RProperty::Subscribe() method. Notification happens in the following stages:
- A client registers its interest in the property by attaching to it (RProperty::Attach()) and calling Subscribe() on the resulting handle, passing in a TRequestStatus reference.
- Upon publication of a new value, the client gets notified, via a signal to the TRequestStatus object to complete the Subscribe() request.
- The client retrieves the value of the updated property by calling RProperty::Get().
- The client can resubmit a request for notification of changes to the property by calling Subscribe() again.
Attaching to an undefined property is not necessarily an error. Likewise, a Subscribe() request on an undefined property will not complete until either the property is defined and published, or the subscriber unsubscribes by canceling the request, using RProperty::Cancel().
Publish & Subscribe and Platform Security
To ensure that processes are partitioned so that one process cannot interfere with the property of another process, the category UID of the property should match the secure identifier of the defining process. Alternatively, the process calling RProperty::Define() must have WriteDeviceData capability (see Fundamentals of Symbian C++/Platform Security).
Properties must also be defined with security policies (using TSecurityPolicy objects):
- to specify the capabilities and/or vendor identifier and/or secure identifier required for processes to publish the property value
- to specify the capabilities and/or vendor identifier and/or secure identifier required for processes to subscribe to the property.
For example, before accepting a subscription to a property, the security policy defined when the property was created is checked, and the subscription request completes with KErrPermissionDenied if the check fails.
In contrast to the connection-oriented nature of client-server IPC, message queues (RMsgQueue) offer a uni-directional peer-to-peer, many-to-many communication mechanism. Message queues provide a way to send data (messages) to interested parties without needing to know the identity of a recipient or even whether any thread is listening; in effect, messages are sent to the queue rather than to any specific recipient. A single queue can be shared by many readers and writers.
A message is an object that is placed into a queue for delivery to recipients. A queue is normally created for messages of a given type. This means that a queue is created to deal with messages of a defined (fixed) length, which must be a multiple of four bytes. The size of a queue (the maximum number of messages, or slots, it can contain) is also fixed when the queue is created. The maximum size of message for which a queue is created, and the maximum size of the queue are limited only by system resources.
A message queue allows two or more threads to communicate without setting up a connection to each other, each queue is omni-directional. It is a mechanism for passing data:
- between threads that run in separate processes (using a global queue which is named and visible to other processes)
- between threads within a process (using a local queue which is not visible to other processes). The messages can point to memory mapped to that process and can be used for passing descriptors and pointers between threads.
Message queues allow for 'fire-and-forget' IPC from senders to recipients and lend themselves well to event notification.
While Publish & Subscribe is good for notification of state changes which are inherently transient, message queues are useful for allowing information to be communicated beyond the lifetime of the sender. For example, a central logging subsystem can use a message queue to receive messages from numerous threads that may or may not still be running at the point at which the messages are read and processed.
However, neither messages nor queues are persistent; they are cleaned up when the last handle to the queue is closed.
Pipes are available from Symbian OS v9.3, but at the time of writing the class is not a public API.