Thursday, February 7, 2013

Faster compiling on an emulated Raspberry Pi on Linux

In my last article about RetroShare on the Raspberry Pi, I've written about my experiences ...

Unfortunately, the 256 MB RAM of my Raspi is barely enough to keep RetroShare running, but even a simple 'sudo apt-get update' can crash it. The RetroShare project itself is very active at the moment, bugs are constantly fixed and features added. But compiling RetroShare requires all resources, so I thought about alternatives.

"Simple", I thought, "Emulation!"

Spoiler alert: Emulating a Raspberry Pi using QEMU is slow.

I have no experience with emulation whatsoever, so there is no real reason for choosing QEMU over other emulators like VirtualBox, I just found instructions and information for this way first.

What we need to get started

The Emulator

My desktop PC is running Ubuntu 12.04, the rest of the system specifications shouldn't matter. (Yes, I know that this version is a bit outdated, but as a True Believer in Murphy's Law I tend to not change a running system.)

Installing QEMU

Install the package qemu-kvm-extras the way you usually install packages, e.g., using aptitude or
sudo apt-get install qemu-kvm-extras
This will also install some dependencies that are needed.

You will also need to download a QEMU-ready linux kernel for the Raspberry Pi, which you can do here: http://xecdesign.com/downloads/linux-qemu/kernel-qemu
Alternatively, you can compile your own kernel.

Preparing the environment

Create a directory in which our experiment will take place. I chose:
mkdir ~/development/raspberrypi-qemu

Raspbian Wheezy

I used newest version of Raspbian Wheezy currently available at http://www.raspberrypi.org/downloads:

Torrent2012-12-16-wheezy-raspbian.zip.torrent
Direct download2012-12-16-wheezy-raspbian.zip
SHA-1514974a5fcbbbea02151d79a715741c2159d4b0a
Default loginUsername: pi Password: raspberry

Download it and unpack the image file into the directory you prepared in the last step.

Plan A: Running an emulated Raspberry Pi

In the directory of the image, run the following command:
qemu-system-arm -kernel kernel-qemu -cpu arm1176 -m 256 -M versatilepb -no-reboot -serial stdio -append "root=/dev/sda2 panic=1" -hda 2012-10-28-wheezy-raspbian.img
The parameters have the following functions:
-kernel kernel-qemu
the QEMU-ready kernel we just downloaded
-cpu arm1176
the CPU we want to emulate (ARM1176JZF-S (700 MHz))
-m 256
how much RAM should be available (in MB)
-M versatilepb
the machine type to emulate
-no-reboot
exit instead of rebooting
-serial stdio
redirects the serial port to the standard I/O
-append "root=/dev/sda2 panic=1"
where to find the root partition, depends on the image
-hda 2012-10-28-wheezy-raspbian.img
what should be used as hard drive, in this case our image
Now, you might be tempted to simply increase the amount of available RAM, but it doesn't work that way. I'm not sure why, but it only works with 256 MB RAM. On the other hand, we can circumvent the problems of creating a swap file on an SD card, because most likely the image is not located on one. Increasing the available memory is easy now, just add a sufficiently large swap file.

We also shouldn't forget to resize our image, because right now we only have about 200 MB of free space left. Again: There already are many articles on the net covering this, so I'll only quickly describe what I did.
  1. With QEMU not running, you can use qemu-img to resize an image:
    qemu-img resize 2012-12-16-wheezy-raspbian.img +1G
  2. For other reasons, raspian's built-in functionality of growing the partition to fill the SD card won't work here, so I did it the hard way.
  3. Boot your emulated Raspberry Pi again using QEMU
  4. Resize the partition using fdisk
    sudo fdisk /dev/sda
    It should look similar to this:
    Device Boot  Start       End    Blocks   Id  System
    /dev/sda1     8192    122879     57344    c  W95 FAT32 (LBA)
    /dev/sda2   122880   5885951   2881536   83  Linux
    You need to delete partition 2 and create it again with the same start, but this time with the highest allowed value for end.
  5. Resize the filesystem using
    sudo resize2fs /dev/sda2

It's too slow

Before we start compiling RetroShare again, let's check up on the speed. This is what I get from the QEMU Raspberry Pi:
pi@raspberrypi:~$ cat /proc/cpuinfo | grep MIPS
BogoMIPS        : 565.24
And this is from my actual Raspberry Pi
pi@raspberrypi:~$ cat /proc/cpuinfo | grep MIPS
BogoMIPS        : 697.95
So, my emulated raspi running on a Intel Core i5 is actually slower than the real raspi ... which is not what I wanted. I mean, okay, if I'd have the machine running for something else anyway, that wouldn't be a problem. But I still want it to be faster.

Plan B: Architectural chroot a.k.a. chroot Voodoo

Looking for solutions to speed up QEMU, I stumbled upon another approach: architectural chroot!

Now, I'm familiar with chroot (at least I though so) and I've used it hundreds of times when my Ubuntu got f*cked up because of an update or some other stuff. And I remember the difficulties when I tried to chroot into a 64 bit system from a 32 bit Live-CD. But it seems like there is a way around this. Coincidentally, we're already close to what we need: a static version of QEMU.

Install it via apt-get (or build it yourself)
sudo apt-get install qemu-user-static
We need to mount the image using loopback, but since the image contains multiple partitions, we require kpartx
$ sudo kpartx -a -v 2012-12-16-wheezy-raspbian.img 
add map loop0p1 (252:8): 0 114688 linear /dev/loop0 8192
add map loop0p2 (252:9): 0 5763072 linear /dev/loop0 122880

$ sudo mount /dev/mapper/loop0p2 /mnt/temp
Now, copy the static QEMU binary TODO and mount the special directories:
sudo cp /usr/bin/qemu-arm-static /mnt/temp/usr/bin
sudo mount -o bind /dev /mnt/temp/dev
sudo mount -o bind /proc /mnt/temp/proc
sudo mount -o bind /sys /mnt/temp/sys
Before we can enter the chroot environment, it's time for the magic! As root (simple sudo won't work), do this (all in one line):
echo ':arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:' > /proc/sys/fs/binfmt_misc/register
This registers the static QEMU we copied as arm-interpreter to the kernel. The path specified needs to be the same on both your linux machine and the Raspberry Pi environment.

Now we can chroot:
sudo chroot /mnt/temp
Did it work?
$ uname -a
Linux localhost 2.6.32 #58-Ubuntu SMP Thu Jan 24 15:28:10 UTC 2013 armv7l GNU/Linux

Hooray! Welcome to your (much faster) Raspberry Pi environment! Now, let's do some compiling ;)

Cleaning up

To avoid inconsistencies, make sure you never use QEMU and chroot at the same time! Even more, you need to completely unmount the image before you start QEMU. Otherwise you might see some undesireable side effects.
sudo umount /mnt/temp/dev
sudo umount /mnt/temp/proc
sudo umount /mnt/temp/sys
sudo umount /mnt/temp
sudo kpartx -d -v 2012-12-16-wheezy-raspbian.img

Acknowledgements

I also want to give credit to the following articles that helped me doing this:

21 comments:

  1. Thank you for this article. Very well done and helpful.

    ReplyDelete
  2. Thank you very much for this article. It works up to the point where I want to do anything with it :(
    I wrote a little bash script that does each of your steps and I run it as root [not sudo] the problem is when I try to do an apt-get or whatever it core dumps.
    root@jool:/# uname -a
    Linux jool 2.6.32 #28-Ubuntu SMP Tue Oct 9 19:31:23 UTC 2012 armv7l GNU/Linux
    root@jool:/# cd /devel
    root@jool:/devel# ls
    root@jool:/devel# apt-get install git-core
    qemu: uncaught target signal 4 (Illegal instruction) - core dumped
    Illegal instruction (core dumped)

    Am I missing something? do I need to make a working rasbian from my unit, then dd it to an image and use that?

    ReplyDelete
    Replies
    1. Hi TheQuestor,
      thanks for the feedback! Now let's see if we can solve your problem ;)

      Are you executing those commands from the architectural chroot? If yes, there is the problem. I haven't exactly figured out how so resolve this, but some things won't work in this environment.
      Whenever I wanted to install something using apt-get or even just download something with wget, I had to use QEMU.

      I've described some of those issues in my other article (http://sentryytech.blogspot.de/2013/02/compiling-retroshare-for-raspberry-pi.html), please see if this helps you. If not, I will investigate.

      Delete
    2. From: http://xecdesign.com/qemu-emulating-raspberry-pi-the-easy-way/

      You need to edit /etc/ld.so.preload in the image and comment out the one and only line with a #.

      Delete
    3. Florian, to fix your broken internet access inside the chroot environment, copy /etc/resolv.conf to /mnt/temp/etc/resolv.conf

      Delete
    4. Thx, I will try that next time.

      Delete
  3. Hi all,

    If anyone is interested, I have put together a script which chroot's and un-chroot's an image:

    https://github.com/jeffery/raspbian-tools/blob/master/raspbian-chroot.sh

    This works on opensuse 12.3 but looking for feedback on how it runs on other distros.

    ReplyDelete
  4. Good article. easy to follow. But, as a newby to qemu and on booting qemu-system-arm.....etc, I keep getting "scsi: 0.0.1.0" re-occurring and incrementing ie. scsi: 0.0.2.0. At the end of each attempt I get "sym0: scsi bus has reset". Is there a way to stop this from happening. Cheers!

    ReplyDelete
  5. How to copy any data from base system to chroot raspberry system?

    ReplyDelete
    Replies
    1. You just copy the files like you would do it in any other situation. But you have to copy it from outside the chroot environment into the folder of the chroot.

      Delete
  6. Also, if you need to build something that needs a lot of space to build and find you are running out of room, I typically do a bind mount of a working directory as well.
    sudo mount -o bind /home/me/src/WebKit /mnt/temp/home/rPi/src/WebKit

    ReplyDelete
  7. Cool stuff, tnx for sharing.
    Will this run in a virtualbox environment, too?

    ReplyDelete
    Replies
    1. I have no idea, but I think it should and you can probably find something about it already on the net. All I did here was gather information already available in a way that I found useful, so go ahead ;)

      Delete
  8. Why didnt you use a buildroot, toolchain? cross-compiling is absurdly fast compared to this, you can actually compile using your own computer clock speed, instead of emulating

    ReplyDelete
    Replies
    1. What do you mean? The architectural chroot is no emulation, it compiles with yout own computers clock speed, multiple cores and all the RAM. Can you elaborate more?

      Delete
    2. Maybe Marcus was explaining the CHROOTed QEMU system translate every piece of ARM to the corrispondent of x86 or whatever the host machine is.
      So it slowdown the effective comparison between architectural chroot and the HOST CPU power when using cross-toolchain.
      If you install distcc on the architectural chroot _AND_ the toolchain for cross-compiling in the HOST system you will speed up of a magnitude faster!!! Only linking must be done in the architectural chroot environment...

      Delete
  9. If you use qemu 2.8, they have a bugfix so you don't need to comment the line in /etc/ld.so.preload any more.

    ReplyDelete
    Replies
    1. I'm busy with life and can't test that or write an updated article. But thanks for the update, much appreciated :)

      Delete
    2. Indeed - but thought I'd go round passing this on after I persuaded qemu to fix it.

      At some time I'd like to understand a little more about KVM, and how it can speed up emulating ARM chips, probably need to ask Qemu devs.

      Delete
  10. This comment has been removed by the author.

    ReplyDelete

Note: Only a member of this blog may post a comment.