Target Emulation and
Virtual Machines
Virtualization is a
mature technology that lets several operating systems share the physical
resources of a machine, such that that each thinks it has exclusive use of the
resources. Emulation means that a program impersonates another—or, in this
case, that a processor impersonates another. Cygwin is software that emulates a
POSIX system on a Windows machine.
However, when
you’re emulating a processor different than the host, you have fewer options. Emulating
a different processor requires software that, in effect, translates the
op-codes of the emulated processor into the op-codes for the host processor.
A common practice in embedded
engineering is to write code that is compiled and tested on the development
host. This makes sense, because the C language is portable enough to make this
possible. Although large amounts of code can be written, compiled using the
native tools, and tested without the benefit of an emulator, some things, such
as the following, require testing using the target processor or emulator:
• Inline assembly: This is the
most obvious. Code that has inline assembly for an
ARM target won’t compile on that
nice new quad core Intel 64 bit host, no matter
how much you want it to.
• Endiannesss: This
describes the byte order used to store data. In a big-endian system, the
high-order bytes precede the low-order bytes; little-endian is the reverse. The
date 2009-01-31 is big endian, whereas 31-01-2009 is little-endian.1 If endianness
isn’t agreed on, imagine the confusion with a date like 02-05-2009, where the
month and day aren’t obvious. This is an example to make plain the notion that
endianness, as the internal storage format for a date in a computer, is usually
an integer.
• Floating point:
The floating-point capacities aren’t the same for all processors and the
emulator. One processor’s very large number is another processor’s overflow. Not
many embedded systems need high precision, but the ones that do should take
these limitations into consideration.
• Optimization: The
GCC compiler is smart enough to optimize not only by refactoring and
re-ordering the code but also by selecting machine instructions that execute
more quickly. This means the classic speed for time optimization may be very
different on the host than the target. Optimization is also likely to find bugs
in the code generated by GCC
Emulation via QEMU
QEMU is a growing
emulation project started by Fabrice Bellard. It’s available for Linux and
Windows hosts and emulated PowerPC, ARM, MIPS, and SPARC targets. QEMU takes
the approach of providing a minimal translation layer between the host and
target processor.
QEMU also provides
support for USB, serial, graphics, and network devices by mapping them to a real
device on the host machine.
Compiling QEMU
QEMU is available in source form;
the site has precompiled binaries as well. In spite of the binary distribution,
this is open source software, so knowing how to compile it is important in case
a patch becomes available—or just because it’s open source, and compiling from
source is the right thing to do. QEMU requires GCC 3.0 in order to build. To
check what version of GCC is currently installed, do the following:
$ gcc –dump-version
4.2.3
If it does present you with a 4.0
or higher version number, install the GCC 3.4 package. Don’t worry about
multiple GCC installations on the system. GCC installs as gcc-<version> and
creates a symlink gcc that points at the newest version. If you set the
environment variable CC to gcc-3.4, that executable is used instead of the most
recent gcc:
$ apt-get install gcc-3.4
$ export CC=gcc-3.4
QEMU’s build also requires some
additional development libraries in order to build. Fetch these by doing the
following on an Ubuntu or a Debian system:
$ sudo apt-get install libsdl-gfx1.2-dev zlib1g-dev
Start compiling QEMU by getting
the source code at http://bellard.org/qemu/download.html.
The current version is 0.9.1; you
can download it using wget, like so:
$ cd ~
$ wget http://bellard.org/qemu/qemu-0.9.1.tar.gz
Then, untar:
$ tar zxf qemu-0.9.1.tar.gz
Start the build process by doing
a configure:
$ cd qemu-0.9.1
$ ./configure
Does this message appear? WARNING: "gcc" looks like gcc 4.x
Looking for gcc 3.x gcc 3.x not found! QEMU is known to have problems when
compiled with gcc 4.x It is recommended that you use gcc 3.x to build QEMU To
use this compiler anyway, configure with --disable-gcc-check
This message means the GCC
installed on the system isn’t a 3.x version. Check that GCC 3.4 has been
installed, and make sure the environment has the CC variable set to gcc-3.4.
Running configure with the –disable-gcc-check flag results in the configure
step working correctly, but the compilation fails. After the configure step,
typing
$ make
$ sudo make install
builds and installs QEMU.
For Windows users, a precompiled
package is available at http://www.h7.dion.ne.jp/~qemuwin/.
It’s recommended that QEMU users on Windows start
with this precompiled package.
Using QEMU to Emulate a Target
The QEMU maintainer
has thoughtfully included ready-to-boot packages for the following processors:
• MIPS
• ARM
• X86
• ColdFire
Visit
http://bellard.org/qemu/download.html and scroll to the bottom to see the
current distributions available. Using the ARM distribution as an example, here
is how to use one of these boot packages to quickly get a system up and running
and prove that the QEMU that you built works as expected. The packages include
a kernel and an initrd root file system. These systems are self-contained and immutable
since the initrd file system is loaded into RAM memory at boot time and there’s
no way to save the changes between boots. This has both benefits and drawbacks.
The greatest benefit is that no matter what you change or delete, completely
resetting the environment is just a few keystrokes away. This is also a
drawback, because changes to the file system can’t be easily stored for the
next power cycle without re-creating the initial RAM disk. Because you’re using
these file systems to test the board, thebenefits outweigh the drawbacks. Follow
these steps to download and unpack the file:
$ cd ~
$ tar xzf arm-test-0.2.tar.gz
Use these commands
to start the machine without a graphical terminal:
$ cd arm-test
$ ../qemu-0.9.1/arm-softmmu/qemu-system-arm -kernel
zImage.integrator-initrd arm_root.img -nographic -append
"console=ttyAMA0"
A lot of parameters
are passed into QEMU to make things work. Table 3-1 describes what they mean.
QEMU is performing the job of a boot loader, so at the least, it needs to know
what kernel to boot.
QEMU kernel boot parameters e
-kernel <kernel
image>
Indicates what kernel to use.
This file is a kernel image that’s been compressed.
It looks for the kernel file in
the current directory if one isn’t specified.
-initrd <initial
ramdisk image>
The initial RAM disk to load for
the kernel. This is the only file system used by
the emulated target.
-nographic
QEMU configures a VGA device for
the board if this isn't specified. The board
being booted doesn’t have
graphics support, so there’s no need for QEMU to
emulate graphics.
-append <parameters> This
is what appears when you add append to the end of the kernel’s command line. The kernel’s command
line is similar to the parameters passed into a program. In this case, setting console=ttyAMA0
means the kernel should use the
/dev/ttyAMA0 device for the console. This device is created by QEMU so the board has a pseudo-serial
device to use for a terminal.
In a few seconds, the kernel
boot-up message appears. Log in as root (no password is necessary), and verify
that the machine is an ARM target:
# cat /proc/cpuinfo
Processor : ARM926EJ-Sid(wb) rev
5 (v5l)
BogoMIPS : 137.21
Features : swp half thumb
fastmult edsp java
CPU implementer : 0x41
CPU architecture: 5TEJ
CPU variant : 0x0
CPU part : 0x926
CPU revision : 5
Cache type : write-through
Cache clean : not required
Cache lockdown : not supported
Cache format : Harvard
I size : 4096
I assoc : 4
I line length : 32
I sets : 32
D size : 65536
D assoc : 4
D line length : 32
D sets : 512
Hardware : ARM-IntegratorCP
Revision : 0000
Serial : 0000000000000000
Pretty neat! A
little investigation reveals that this is a fairly basic root file system, but
it does the job of starting the board and providing a prompt. At this point,
you may be tempted to use the root file system here as the starting point for
your project; and that isn’t an outlandish notion if the project in question
only has a few additional executable files. More complex projects require
enough changes to the kernel and root file system to merit a rebuild, because
the amount of effort to modify or build is similar. To quit the emulator, press
Ctrl+A followed by X. The machine is terminated, and control returns to the
terminal prompt. There’s no confirmation to quit, so be careful not to
unintentionally shut down the machine.
By creating this machine, you can
test development work on the emulated host before the hardware for the project
arrives, allowing activities that were once serialized to have some degree of
overlap. This is very helpful in situations where the board hardware is in
short supply or the project is distributed geographically and getting hardware
to engineers is difficult. In addition, having QEMU run the board also shortens
the iteration time for testing new root file systems and boot-up scripts.
Using QEMU to Compile under Emulation
QEMU also supports a user-space
mode in which instead of running an entire virtual machine to run a program,
QEMU runs a user program. To run that user-space program, you need a binary and
all the libraries used by that binary. The simplest way to get this for an ARM
processor is to download it from ARM directly:
$ cd ~
$ wget http://www.arm.com/linux/armbase_2.5.cramfs
If this link is no longer
available, visit ARM’s distribution page at
http://www.arm.com/products/os/linux.html.
After it’s downloaded, you can loop-back mount the image using this command:
$ sudo mkdir –p /mnt/arm-rfs
$ sudo mount -o loop -t cramfs ./armbase_2.5.cramfs /mnt/arm-rfs/
Loop-back mounting is a way to
connect a file with a block device and then mount the device as if it were a
physical block device. When it’s mounted, the /mnt/arm-rfs directory contains a
file system that has the necessary libraries to run the programs in the
/mnt/arm-rfs/bin directory. When QEMU attempts to run a program that has shared
libraries, it needs to know the location of the shared library loader and be
able to load the libraries. On a Linux system, programs that use shared
libraries depend on an executable to do the final link step so the executable
can run. On a Linux desktop system, that file is usually /lib/ld-linux.so.2,
but
it can have any file name. When
QEMU is built, it looks for these library-handling files in the /usr/gnemul/qemu-arm
directory by default. In order for QEMU to find those files, copy the contents
of the lib directory of /mnt/arm-rfs to that location. A copy is better than
creating a symlink, because that symlink is invalid when the file system is
unmounted from /mnt/arm-rfs:
$ sudo mkdir –p /usr/gnemul/qemu-arm
$ sudo cp –a /mnt/arm-rfs/lib /usr/gnemul/qemu-arm
Now that everything is in place,
run a command by doing the following:
$ cd /mnt/arm-rfs
$ qemu-arm /mnt/arm-rfs/bin/ls
By taking this approach, no
cross-compilation is necessary, because the code thinks it’s running on an ARM
(or whatever) host. Later in the book, I cover using a native compiler running
under an emulator as opposed to a cross-compiler for building a system. The
examples in this section have covered ARM processors, but remember that QEMU
supports more than just the ARM processor.
No comments:
Post a Comment