|
||
Developers used to PC operating systems, such as Windows, may be used to using writeable static data (typically meaning global variables) in DLLs. In Symbian OS, this is possible, but not recommended, because it is expensive in memory, and has limited support in the Symbian OS Emulator.
This page describes writeable static data (WSD), the alternatives to using WSD, and the costs and issues associated with its use.
Global writeable static data (WSD) is any per-process variable which exists for the lifetime of the process. In practice, this means any globally scoped data: data that is declared outside of a function, struct, or class, and function scoped static variables.
TBufC<20> fileName; //WSD
void SetFileName()
{
static Tint iCount; //WSD ...
}
Sometimes writeable static data appears in a non-obvious way. It is not
always the case that const
global variables are read-only data
rather than global writeable static data. This is true for const
objects with trivial constructors, such as integers. However if a
const
class has a non-trivial constructor, the const
object will require a real variable and must be stored as WSD. For example:
const TInt myVariable=…; //OK – truly const
const TPtrC KSomeConstPtr=...; //NOT OK – non trivial constructor
const TRgb KSomeConstCol=...; //NOT OK – non trivial constructor
Symbian OS supports global writeable static data in EXEs on all versions and handsets.
Versions of Symbian OS based on the EKA2 kernel (8.1b and later) support WSD in DLLs on target hardware. Versions 8.1a and earlier, based on the EKA1 kernel, do not support global WSD in DLLs.
Your program must make sure that global writeable static data that the program allocates is cleaned up.
The clean up rules are as follows:
Your program must clean up WSD objects that are defined in the process EXE or its statically-loaded DLLs. When a process exits, Symbian OS does not automatically call the destructors of these WSD objects.
Your program does not need to clean up WSD objects defined in DLLs
that the process has dynamically loaded using RLibrary
.
Symbian OS automatically calls destructors of these objects.
Native Symbian OS C++ code rarely uses WSD.
However, code ported from other operating systems may contain large amounts of static data. For example, code written in the C programming language often makes use of WSD as the "glue" between C function calls.
In EKA1, WSD is not supported, so there is no choice but to use the alternative mechanisms provided by Symbian OS to port such code. Even in EKA2 where global data is supported, Symbian recommends that it only be used as a last resort.
The following sections describe the alternatives that can be used to port code that makes use of global writeable static data.
Thread Local Storage (TLS) is a single per-thread word that can be used to simulate global writeable static data.
All the static data in the DLL is grouped into a single struct or
class. On creation of the thread, an instance of the thread is allocated on the
heap and a pointer to this data is saved to TLS (using
Dll::SetTls()
). On destruction of the thread, the data is
destroyed. Throughout the DLL the code references the TLS data (using
Dll::Tls()
) rather than the original global writeable static data.
Symbian OS supports writeable global static data in EXEs. A common porting strategy is therefore to wrap the code in a Symbian server (which is an EXE), and expose its API as a client interface.
With relatively small amounts of code, it may be possible to move most global data inside classes.
In order to enable global writeable static data, simply add a statement
epocallowdlldata
(case insensitive) to the project's MMP file:
TARGET my.dll
TARGETTYPE dll
EPOCALLOWDLLDATA
…
The Symbian OS EKA2 Emulator only allows a DLL with WSD to be loaded into a single process.
This is a very serious restriction. If you have a shared DLL with
WSD, then the second process that attempts to load it in the emulator will fail
with KErrNotSupported
.
The Emulator will allow WSD in DLLs even if
epocallowdlldata
is not declared in the mmp file. However the data
will be truly global: there will be one copy for the entire emulator, rather
than one copy for each emulated process. The only restriction is that if the
data's initialisers call any Symbian OS kernel functions (i.e. executive
calls), then the emulator will fault.
When a process loads its first DLL containing WSD, it creates a single chunk to store the data. The data for subsequent WSD-enabled DLLs is loaded into the same chunk.
The data chunk will occupy at least one 4K RAM page (the smallest possible RAM allocation), irrespective of how much static data is required. Any memory not used for WSD will be wasted. Since the memory is per-process, the memory wastage on the machine is:
(4Kb - WSD Bytes) × number-client-processes
It is very easy for a developer to add a few words of WSD to their DLL thinking that's all the memory that they are using. However, the cost is actually 4K for every process if a DLL with WSD has not already been loaded into the process. If for example the DLL is used by 4 processes, that's potentially an "invisible" cost of 16K.
EKA2 has a hard coded limit of 16 chunks per process; a limit that is required to ensure real-time behaviour. Every process loading WSD-enabled DLLs uses a chunk to hold the data, reducing the number of chunks available for other uses.
There are other significant costs that apply only to DLLs that link against "fixed processes". Fixed processes are a feature of ARM v4 or v5 architecture only; the following behaviour does not apply to devices based on the ARMv6 architecture.
Case 1: Non Execute-in-place DLLs
For non-execute-in-place (non XIP) DLLs, an additional code chunk is required for every fixed process which links against the DLL.
So imagine a 20Kb DLL (with a few bytes of WSD) that is loaded into 4 normal "non-fixed" processes, and 2 fixed processes. The (static) memory consumed is:
Code chunk shared by all moving processes = 20 Kb
Code chunk for each fixed process loading the DLL = 40 Kb
Data chunk for each process loading the DLL = 6×4Kb = 24 Kb
So to allow a few bytes of WSD there is a 64Kb increase in consumed memory. Note that the 20Kb code chunk shared by all processes is consumed whether or not WSD is enabled.
Case 2: Execute-in-place DLLs
For XIP DLLs, there is no additional RAM cost other than the size of the WSD itself, rounded up to the next multiple of 4KB. However:
An XIP DLL can be loaded by any non-fixed process, OR it may be loaded by a single fixed process (and therefore cannot be loaded by any other processes whatsoever)
The ROM build fails if a DLL with static data links to a fixed process and any other process.
A few specific DLLs cannot have WSD
DLLs that are required to initialise the file server cannot have WSD, e.g. HAL.DLL, EUSER.DLL, EFSRV.DLL.
Case 1. For moving processes:
Symbian OS supports a moving memory model, in which the data for a process is moved into a fixed virtual address space (the "run section") when the process is active, and then back into the processes "home" section when another process is active:
The data of processes in the "home section" is uniquely addressed both in terms of physical RAM pages, and in the MMU virtual address space. The data is protected from other processes, except for the Kernel.
The data of processes in the run section occupies the same virtual address space as all other processes when they run (the MMU moves the physical RAM pages into the appropriate virtual address space).
When a DLL with WSD is loaded (or at ROM build time), its code is fixed to point to static data at specific addresses. In order to ensure that only a single copy of the DLL code is required, Symbian's moving-process WSD implementation ensures that the virtual addresses of static data is the same across every running process.
The way this works is:
A specific address space is reserved for a per-process static data chunk. This chunk is used to hold all the static data for all the DLLs loaded into the process.
At ROM build time the kernel reserves specific addresses within the static data chunk for all the WSD in all the ROM loaded DLLs. The addresses for ROM based DLLs are reserved from the top of the static data chunk address space to the bottom. Note that static data addresses for RAM based DLLs are reserved when the DLL is first loaded into any process. In this case, addresses are reserved from the bottom of the static data chunk address space.
When the first DLL with WSD is loaded into a process, a static data chunk is created to hold the static data for all DLLs that are loaded into the process.
Any global static data in the DLL is written to its specific reserved addresses. Note that addresses are reserved for that particular static data across all processes; if the DLL is loaded into another process, any static data will get the same virtual address.
Case 2. For fixed processes:
A fixed process is one in which the process data does not move; code is run on process data stored in the process "home section".
Since the static data for every fixed process is uniquely addressed, and a DLL can only point to a single address for its data, the implication is that a separate copy of the DLL code is required for every fixed process that loads the DLL.
The restrictions listed above for ARM architecture 4 and 5 directly result.
For XIP based devices the DLL code chunk address is fixed at ROM build time, and there can only be one copy of the DLL code. Therefore the DLL code can address the data in either a single fixed process or the virtual address used by all moveable addresses.
For non-XIP based devices the DLL is run from RAM, and the loader is able to fix-up the address that a DLL expects its data at load time (rather than ROM build time). Therefore in this case the loader creates a separate copy of DLL code for each fixed process that loads the DLL and a single copy shared by all moving processes.
Notes: Fixed processes are not supported or required on ARM v6 architectures. This discussion only applies to devices based on ARMv4 or v5. On ARM architecture 6 each DLL with WSD has a reserved address, similar to the ARMv5 moving process case. However there is no 'home section' and memory is not relocated between low and high addresses on a context switch. Instead, each process uses its own set of page tables for the bottom half of virtual address space.
Symbian OS permits a DLL with global data to be loaded into only one Symbian OS process on the emulator. This limitation is a result of the way the Emulator is implemented on top of the Windows process model.
On EKA2, separate Symbian OS processes are emulated within a single Windows process. To preserve Symbian OS semantics there should be one copy of the global data for each emulated process. However, this is not possible since a DLL on the emulator is just a Windows DLL; Windows gives it a single copy of any global data it needs when it is loaded.
epocallowdlldata
isn't declared
for a DLL with WSD?
Case 1: On the EKA2 Emulator:
Most constant data should be treated by the compiler as read-only rather than writeable static data (the exception is when the const data has a non-trivial constructor, so a real variable is required during initialisation).
Unfortunately, different compilers sometimes treat const data as WSD. For example, CodeWarrior puts it in writeable data and initialises it at run time. MSVC generally puts it into read-only data, but occasionally puts it into writeable data.
As most DLLs have const data, this means that the compilers have "accidentally" created WSD in almost every DLL. Symbian cannot therefore rigorously enforce the "single process can load a DLL with WSD" rule, as the Emulator would not work.
On the EKA2 Emulator, the workaround Symbian have implemented is to recognise two types of DLL global data:
'Deliberate' global data is where the programmer specifies that
they want DLL global data (using the epocallowdlldata
keyword in
the MMP file. In this case any global data in the DLL is assumed to be
deliberate, and the "DLL loaded into one-emulated-process" rule applies.
'Accidental' global data is the data introduced by the compiler
with no encouragement from the programmer. If epocallowdlldata
is
absent, global data is assumed to be accidental and the rule does not apply.
Note that the global data includes both const and non-const static data; there
is no way to tell the compilers to only apply it to non-const data - if we
could do that then we could force correct handling of const static data.
In order to prevent abuse of this workaround, there are restrictions on what can be done with accidental global data; specifically, the Emulator will fault if any of the global data's initialisers attempt to call Symbian OS kernel functions (i.e. executive calls).
Note that there is only one copy of the global data. Therefore it is possible for two processes to write to the same 'accidental' global data (causing undefined behaviour). The "DLL loaded into one-emulated-process" rule prevents this being a problem for deliberate global data.
Case 2: On the EKA1 Emulator
The EKA1 emulator has no concept of separate Symbian OS processes. Global data is allowed on the Emulator as there is only one copy of any global data.
Developers often have problems when porting to real hardware, which does not support this type of data. This is discussed in the following section.
Case 3: On native target builds
On either EKA1 or EKA2, target compilers will fail the build with an error indicating that the code has initialised or un-initialised data.
The error message does not specify the symbol(s) that are causing the
problem. The .map
files output by the linker can however be
helpful in finding the problem symbol. The RVCT map files provide a list of
global symbols and the object files in which they occur. FAQ-0329 on the
DevNet
knowledgebase works through an example of using a map file using the GCC
compiler used on older Symbian OS releases.
Yes, WSD is supported for kernel DLLs in both EKA1 and EKA2 (through alternative mechanisms to those described here).
Of course, kernel DLLs are guaranteed to be loaded into only one process, so the per-process multiplication of RAM usage does not apply. EKA2 will work correctly with global data in any kernel DLL. However EKA1 does not call constructors for global C++ objects in kernel extensions or device drivers, and does not call destructors for global C++ objects in device drivers at driver unload time.