The emulator is a port of the Symbian OS kernel to the Win32 platform. For most circumstances, this provides a faithful emulation of the OS behaviour on target hardware. However, there are some differences that could affect application developers, and these are documented in this page. The sections are:
On real phones, the first Symbian OS program to run is a bootstrap
program, which performs various hardware initialization tasks before starting
the kernel. The emulator does not need to perform the hardware initialization
tasks, so its start-up simply needs to start the kernel. This is done through
the BootEpoc()
function in the EUSER library. The standard
emulator start program EPOC.EXE
simply calls this function. Other
executables that are built for the emulator also always include this start up
call, so that the running any emulator executable boots the emulator.
The remainder of the boot process is similar on the emulator to the
native target. At the end of kernel start-up, a program estart
is
run. This checks to see how the emulator was bootstrapped: if it was started by
EPOC.EXE
, it exits and the emulator boot has completed. Otherwise,
it retrieves the Symbian OS process to start, and any command line to give it,
then creates that process, waits for it to exit, and then terminates the
emulator.
The emulator emulates a variety of file systems and drive types, including ROFS/FAT over NAND flash drive, LFFS over NOR flash drive, MMC, and RAM drive. Such drives are mapped to the single files on the Windows local drive, rather than actual hardware. For details, see Files and locations.
Performance and size of such emulated drives may not be the same as would be expected with real hardware. There are some options that you can use though to get a closer emulation:
Performance for local file system read and write speeds can be reduced for a better approximation of real speeds. For details, see Disk access speed.
There is no support for limiting the size of the emulated C: disk. Low disk space situations can be tested by filling the Windows drive to which the C: drive is mapped. This can sometimes be more easily done by mapping the C: drive to the Windows floppy disk drive.
Access to LFFS drives can be set to have similar performance to most NOR flash hardware. This provides a reasonably accurate emulation. For details, see LFFS speed emulation.
The maximum size of a emulated RAM disk can be set using the
epoc.ini
keyword RamDriveMaxSize
.
Floating point behaviour is different on the emulator and on hardware.
Symbian OS provides access to IEEE-754 single precision and double
precision floating point values through the types TReal32
(C++
float
type) and TReal64
(C++ double
type) respectively.
The various compilers have built-in types float
and
double
and understand how to represent constants of these types,
e.g. "(double)1.1415926". On processors that have floating point hardware, the
compiler generates host instructions which use that hardware directly. On
processors which don't have a floating point unit, the compiler implements the
calculations in software.
The emulator is implemented on the Intel x86 processors which have floating point hardware, so this support is used. Target hardware may or may not have hardware support. You should be careful of large performance differences in code that intensively uses floating-point functions where the target hardware does not have hardware support.
ARM uses a 32-bit RISC architecture with minimal extra silicon for unnecessary control purposes. This is the main reason why the chip is so cheap, and uses so little power, which in turn is why it is so popular for battery-powered consumer devices.
One consequence of this is that 32-bit quantities must be aligned to a 32-bit machine word boundary: in other words, their address must be a multiple of four. So, for instance, the following code
TInt* p; // pointer to integers
...
TInt i=*p; // get from a pointer
will only work if p
is a multiple of four at the time it
is used. If p
is not a multiple of four, an access violation will
result.
For normal purposes, this restriction is transparent to the programmer, because struct and class members are aligned appropriately by the compiler and heap allocator. For example, in
struct S
{
TText8 iText; // offset 0, 1 byte
TText8 iText2; // offset 1, 1 byte
TInt32 iInteger; // offset 4, 4 bytes
};
the second byte quantity is aligned at offset 1, which is permissable
for bytes. But the compiler generates two pad bytes in order to align the
integer at offset 4. Therefore, sizeof(S)
is 8, not 6 as might be
expected. The compiler ensures that all quantities are aligned, whether in
structs or C arrays.
You must be aware of the alignment restriction, however, if you are implementing a packed structure of some kind. For instance, the code
TText8 array[200];
for (TInt i=0; i<=196; i++)
{
TInt* p=(TInt*) array[i]; // needs a cast
TInt n=*p; // four bytes from the array makes one integer?
}
may work on the emulator. It will compile with a native compiler, but
it will generate an access violation the second time through the loop, because
p
will be one greater than a multiple of four.
To avoid this problem, you should do something like
TText8 array[200];
for (TInt i=0; i<196; i++)
{
TAny* p=array[i]; // really a TAny*, so no cast needed!
TInt n;
Mem::Copy(&n, p, sizeof(TInt)); // copy byte by byte
}
In a way, C++'s type system supports the programmer here. The packed structure can only be implemented by using casting. When you do a cast, you must always think about what might be the potentially dangerous consequences. In the ARM environment, machine word alignment is a potentially dangerous consequence that you must always bear in mind. On the other hand, when you are not using casts, the compiler will do all that is necessary to ensure machine word alignment, without programmer intervention.
By default, the emulator emulates a device with a total heap size of 64MB. This value can be changed using the MegabytesOfFreeMemory keyword.
The initial and maximum size for the default heap for an individual
process on the emulator (EKA2) can be set, in the same way as for a process on
a native target, using the epocheapsize
mmp file keyword. This
directive is applicable from Symbian OS v9.1, EKA2 releases. EKA2 supports
multiple process creation and allows the heap size to be calibrated between the
minimum and maximum limits.
Processes on native targets usually have small stack sizes (by default
8K). On the emulator, stack sizes will expand as needed to much larger Windows
limits. This may cause programs that overuse the stack to work on the emulator,
but fail on hardware with stack overflows KERN-EXEC 3 panics. The
epocstacksize
mmp file keyword, used to specify a non-default
stack size.
The EKA2 kernel provides a good emulation of Symbian OS processes on the emulator. There are, however, some limitations:
There is no memory protection between emulated Symbian OS processes. This means that one process could, for example through a pointer error, write into the address space of another process, without an exception being raised. On a real phone, the same program would cause a memory protection error.
If a process has exports (EXEXP
target type), then
only one instance of it can run on the emulator.
Writeable static data in DLLs is supported on the EKA2 emulator.
There is however a restriction in that a DLL with explicit writeable static
data (EPOCALLOWDLLDATA
specified in the mmp file) can only be
loaded into one emulated process. A second process that attempts
to load it in the emulator will fail with KErrNotSupported
.
Native target compilers will fail to build DLLs with writeable
static data, if EPOCALLOWDLLDATA
is not specified in the mmp file.
The Emulator however 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. This makes it is possible for two
processes to write to the same global data (causing undefined behaviour).
The background to these differences is that the emulator runs as a single Windows process. This is because a key purpose of the emulator is debugging, and debugging multiple processes is difficult to do. The requirement to run as a single process means that the emulator has to take some extra steps when creating a new Symbian OS process from an EXE file. It makes a copy of the EXE file, and makes some minor changes to the copy that allows Windows to load the file as a DLL into the emulator process.
The emulator faithfully emulates the thread scheduling functionality used on a native target.
Windows thread scheduling does not provide the necessary functionality to emulate Symbian OS scheduling: Symbian OS provides 64 priority levels for threads, while Win32 provides five usable distinct priorities within a process. This means that the kernel must provide its own scheduling mechanism. In order for this to work, Win32 cannot be allowed to arbitrarily schedule Symbian OS threads to run. The kernel achieves this by only making one Symbian OS thread ready to run as a Win32 thread; the others will either be waiting on a Win32 event object or suspended. One effect of this policy is that all Symbian OS threads can have the same standard Win32 priority.
The emulator does not provide the real-time guarantees that the EKA2 kernel does on target hardware, as timings are subject to what other processes Windows is running.
The emulator provides emulation of serial ports through Windows serial ports. This is adequate for most purposes, but may not provide the same performance as UARTs on a real device. In particular, some applications have found that high latency times in Windows serial ports have caused some communications data to be dropped.
The standard timer resolution used for User::After()
,
RTimer::At()
, etc. is 1/64th second on all platforms, including
the emulator.
There is also a finer ('HiRes') timer, accessed through methods such as
User::AfterHighRes()
, and RTimer::HighRes()
. This is
1ms resolution on reference hardware, but defaults to 5ms on the Emulator. This
can be changed by setting the timer period (in ms) through the
TimerResolution
variable in the epoc.ini
file.