Developing a root filesystem for embedded Linux architectures can present some inconveniences: for example if the filesystem should be loaded into the embedded system every time and it’s not always an easy/quick task, or if the disk space is very low. The Linux kernel has the ability to read the root filesystem from the network, specifically from a NFS share, and this can be exploited to solve these problems. A common setup is having an embedded board running a Linux kernel, connected via Ethernet to a server with a directory exported through NFS and a cross-compiler installed. Testing a new root filesystem becomes as easy as resetting the board. When using QEMU as a target instead of a real board, the inconvenience is similar: the root filesystem image should be created every time. Fortunately, QEMU offers a network model that allows to access the host as thought it was connected with an Ethernet network. The details of the default connection can be found in the documentation; briefly, a virtual LAN is created where the guest virtual system is given address 10.0.2.15 while the host workstation can be accessed using address 10.0.2.2.
Prerequisites
qemu-system-arm: can be installed on Ubuntu with “sudo apt-get install qemu-kvm-extras“, on Debian with “aptitude install qemu” as rootNFS server: can be installed on Ubuntu or Debian with thenfs-kernel-serverpackagecurses developer libraries: can be installed on Ubuntu or Debian with thelibncurses-devpackagearm-none-linux-gnueabitoolchain: can be downloaded from the the CodeSourcery ARM GNU/Linux toolchain pagezImage: the Linux kernel created in my previous post hereBusybox _install: the Busybox-based file system created in my previous post here
Exporting root
We will create an exported folder with our user permissions: in this way the user is free to compile ARM software and copy it inside the shared folder. For QEMU to be able to read/write the folder, it should gain the same access permissions as our user. This can be done using NFS functionality that maps the anonymous access to a custom user. Find your uid and gid numeric values with the id command; for example, mine are both 1000. Then from the command line, as root, run:
# mkdir -p /srv/nfs/ # chown 1000:1000 /srv/nfs
Then edit the file /etc/exports adding the following line:
/srv/nfs 127.0.0.1(rw,sync,no_subtree_check,all_squash,insecure,anonuid=1000,anongid=1000)
[Edit 2013-01-27]
A reader pointed out that this method does not work for more complex filesystems. For things to work properly, the /etc/exports should contain instead:
/srv/nfs 127.0.0.1(rw,sync,no_subtree_check,no_root_squash,insecure)
The no_root_squash option is needed to access these files as root from the NFS client. Then the exported root filesystem should have normal ownership (uid=0 and gid=0 for example) so the root privileges are needed also to manipulate the files in /srv/nfs/.
[/Edit]
Run the command as root:
# exportfs -av
You can check that the folder is effectively exported by running as root:
# mkdir ~/nfs_test # mount localhost:/srv/nfs ~/nfs_test # umount ~/nfs_test
Populating the root filesystem
Now we can populate the folder with an ARM root filesystem; we will use a Busybox root tree, that can be built by following my previous post here, and add some initialization files. Copy the _install directory that is generated with Busybox build into the NFS share, in order to have the /srv/nfs/_install folder containing the roof filesystem. Then create some missing folders as your user:
$ cd /srv/nfs/_install $ mkdir -p dev proc sys etc/init.d
[Edit 2013-01-27]
As said before, in order to make things work with more complex filesystems the files should have the correct ownership (uid=0 and gid=0 for example). To set these permissions a normal user would need root privileges.
Another possibility is to:
- run a fakeroot shell (
fakeroot /bin/bash) - populate the root filesystem with Busybox and other files as fake “root”
- create an archive with
tar - the real root user extracts the tar file into
/srv/nfs/
[/Edit]
The first program that the Linux kernel executes is /sbin/init; this program reads a setting file (if available) to know what to do. In our case we want the initialization simply to run a shell script and open a console. Create the file /srv/nfs/_install/etc/inittab with the following contents:
::sysinit:/etc/init.d/rcS ::respawn:/sbin/getty -n -l /bin/sh -L 115200 tty1 vt100 ::restart:/sbin/init
The rcS file, as seen in my previous post about Busybox, can be used to populate the system directories. In our case, since the root tree is owned by a user in the host workstation, we cannot create device nodes; a possible workaround is to mount a temporary filesystem into the /dev directory. Create a new file called /srv/nfs/_install/etc/init.d/rcS with execute permissions (using “chmod +x“), that includes the following content:
mount -t proc none /proc mount -t sysfs none /sys mount -t ramfs ramfs /dev /sbin/mdev -s
Booting Linux
Now we are ready to test the boot process. Run the following command from the directory where the Linux kernel image is located:
$ qemu-system-arm -M versatilepb -m 128M -kernel zImage -append "root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/_install rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init"
The kernel should perform the boot and then a shell should appear. You can try to create a file inside QEMU and see that it appears also in the host system; note that the file’s owner is the user set in the exports file. At the same time, you can write files into the exported folders and they can be accessed by the virtual guest ARM system as well.
To troubleshoot the NFS connection, the log file /var/log/syslog in the host system can be examined.
Entries
robertsong
2010/05/10
hi, balau.
Why insecure option is needed in /etc/exportfs file?
Regards,
robert
Balau
2010/05/11
The insecure option allows to use IP ports that are above 1024. If I don’t include this option the mounting does not succeed, and by looking at the system log I see that the NFS server complains that using a high port is not allowed and does not mount the shared folder. In this controlled environment there is nothing “insecure” about this option, also because the share is exported only for localhost (127.0.0.1).
robertsong
2010/05/12
OK, I see, thank you very much.
By the way, was The system log dumped by dmesg command, and output something as follows?
nfsd: request from insecure port (127.0.0.1:37844)!
Balau
2010/05/12
Yes, it happens to me also: the message is written because 37844 > 1024. When the NFS server tells “insecure” in this context it practically means “I’m not sure that the request comes from someone who has root access to their machine” because even non-root users can bind to port above 1024.
Abhijit
2010/10/20
Hi Balau,
I am running your tutorials on an Ubuntu 10.04 machine. I noticed that the kernel is not executing the /etc/init.d/rcS file in Qemu 0.12.3. When I execute the last qemu command I get some message like the following when the kernel boots up and the execution stalls there…
Freeing init memory: 112K
rcS: applet not found
Balau
2010/10/20
Have you checked that rcS is executable by everyone?
Try also to add the line “#!/bin/sh” at the beginning of rcS.
Chai
2011/03/10
Hi Balau,
Your tutorials are very intriguing . There is just one thing that I could not comprehend. I just wrote a hello world program and qemu is not able to emulate it.
I tried executing ./hello
and got a message /bin/sh could not find ./hello .
Permission and everything is correct. The hello program works fine on the hardware. Could you please suggest why this thing is happening.
Balau
2011/03/10
are you able to execute other binary programs included in busybox? is hello build as static binary or does it need shared libraries?
sid
2011/03/15
Hi Balau,
when using arm port … above commands seems to work but things dont work with x86 port
The following command gave me
qemu -kernel bzImage -append “root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/_install rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init”
“VFS: cannot open root device “nfs” or unknown block …..”
Any idea why ?
Regards,
sid
Balau
2011/03/15
It could be many things:
- Are you sure NFS client support has been enabled in kernel configuration? check with “make menuconfig” and navigate to File Systems –> Network File Systems –> NFS Client support
- Is the emulated network card recognized by the kernel? If you have something like “IP-Config: No network devices available.” in the kernel messages then it is the case. Try appending the options “-net nic,model=ne2k_pci -net user” or “-net nic,model=pcnet -net user” or “-net nic,model=rtl8139 -net user” to QEMU command line.
- is the directory really exported? If you have NFS client installed on your host pc, run “mount -t nfs 127.0.0.1:/srv/nfs/_install /mnt/” to check.
Dwi Sasongko S
2011/04/18
Hello,
I tried qemu with nfs root but it stopped after mounting NFS root.
Freeing init memory: 104K
Warning: unable to open an initial console
Kernel panic - not syncing: Attemted to kill init!
The kernel version is 2.6.27.58 configured with versatile_defconfig
The busybox version is 1.18.4 statically built, the installation is put in the NFS directory
I started qemu with following command (same with your description)
qemu-system-arm -M versatilepb -m 128M -kernel zImage -append "root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/_install rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init"
Any hint? Thanks in advance
Balau
2011/04/18
The first things that come into my mind:
When you configured the kernel, before compiling it, have you enabled EABI support?
is “
/srv/nfs/_install/sbin/init” an executable file?Dwi Sasongko S
2011/04/19
Enabling CONFIG_AEABI solves the kernel panic. But still it was unable to open initial console.
the
/srv/nfs/_install/sbin/initis a symbolic link to/bin/busyboxwhich is exectuable file.Dwi Sasongko S
2011/04/19
Hello,
Thanks for your response.
Enabling CONFIG_AEABI solves the kernel panic. The message saying that “unable to open initial console” seems no big issue since after that i can get the console. I forgot to mount /dev as ramfs as you described, resulting failure while executing mdev command. In my case this leads to kernel panic.
Ankit Agarwal
2011/07/05
Hello,
I am booting the kernel compiled for versatile-express board ARM. I am getting the following error:
nfs: server 10.0.2.2 not responding, still trying
and then nothing happens.
I am using the following command to launch:
$ sudo qemu-system-arm -M vexpress-a9 -kernel arch/arm/boot/zImage -append “root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/_install rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init”
Balau
2011/07/05
are you able to mount the nfs share from localhost?
does “dmesg” show something interesting?
also, it shouldn’t be necessary to run sudo.
Dan Guo
2011/07/16
Hi,
I had the same problem as Dwi Sasongko S did. I could not open the initial console.
VFS: Mounted root (nfs filesystem) on device 0:11.
Freeing init memory: 104k
Warning: unable to open an initial console
What is worse for me is that it freezes after that. It seems that Dwi Sasongko S got a console anyway afterward. But I cannot.
Any idea!
Dan
Balau
2011/07/17
I suspect there could be something wrong in the “/etc/init.d/rcS” file or “/etc/inittab” files. You could launch QEMU with “init=/bin/sh” instead of “init=/sbin/init” inside the “-append” option. This should give you a console where you can experiment and run the commands found in “rcS” and “inittab” directly.
Bali Shyamalan
2011/09/22
hi,
I try to use the IP address of the PC instead of the localhost address.If it succeeds, i wish to set the nfs server on the a separate machine from where i run the QEMU( so that my client and the nfs server will be on separate machines).
I modified the /etc/exports to the IP address.
and executed with the following command:
qemu-system-arm -M versatilepb -m 128M -kernel zImage -append “root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/_install rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init”
I get the below error:
FS: Unable to mount root fs via NFS, trying floppy.
VFS: Cannot open root device “nfs” or unknown-block(2,0)
Please append a correct “root=” boot option; here are the available partitions:
Kernel panic – not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
This is the syslog entry:
refused mount request from 127.0.0.1 for /srv/nfs/_install (/srv/nfs/_install): unmatched host
Any idea..
thanks
Bali
Balau
2011/09/22
The “/etc/exports” is used by the NFS server to export its directory to other machines (or itself).
The “nfsroot” kernel option is used to indicate the IP of the NFS server.
So, if for example your PC has an IP of 192.168.1.1 then you should edit “/etc/exports” and put the same address or a subnet such as “192.168.0.0/255.255.0.0″, then change the “nfsroot” option to “nfsroot=192.168.1.1:/srv/nfs/_install”.
Note that I haven’t tried it.
sam michael
2011/09/23
hi,
qemu-system-arm -M versatilepb -m 128M -kernel zImage -append “root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/_install rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init”
can u please throw me some light on the above command.Please give me the source/ reference for the above command…
thanks
mike
Balau
2011/09/23
The “-append” options passes some parameters to the Linux kernel. These parameters are explained here:
Documentation/kernel-parameters.txt
Documentation/filesystems/nfs/nfsroot.txt
Basically you are telling Linux to use a NFS share as the root filesystem, you configure the IP address and tell it where to find this NFS share on the network.
For other information about networking in QEMU guest systems, see here:
QEMU Networking
shivi
2012/08/27
This was not working for me for a long time… for anyone interested..
iptables –flush did the trick.
josh
2012/10/12
Your tutorials are great! I had some trouble with this one, though, similar to some things other commenters have mentioned… I kept getting a “Warning : unable to open an initial console” message and no command prompt. I finally figured out that mdev was not creating the /dev/console and /dev/null nodes, so I just added dev/console and dev/null to the FS with
# mknod -m 660 console c 5 1
# mknod -m 660 null 1 3
in the dev directory. I also did a “chown 1000:1000″ on the two nodes. This fixed my issue.
Balau
2012/10/12
Thanks to everyone for the feedback!
Bala
2012/11/16
Very very good article.
Ravi
2012/12/03
Hello Balu,
I am facing following problem
while booting through qemu I get following line
=============================================
mount:10.0.2.2:/srv/nfs/_install on / : invalid argument
=============================================
nfs-kernel server is running fine.
Also i can mount a different directory successfully as mentioned in your blog.
Balau
2012/12/03
Looking at NFS-root documentation, it seems that appending nfsrootdebug option to kernel parameter should give you more information.
It seems in this busybox mailing list that adding “nolock” option could help in some cases.
It could be useful also to check the message log of the host, maybe the connection has been tried and rejected for some reason.
Bob Pelerson
2013/01/29
This looks pretty cool but I don’t want to use Linux. How would I do this under FreeBSD?
Balau
2013/01/30
I am not familiar with FreeBSD, but I suppose it should involve these steps if FreeBSD supports all of this:
On the host side:
install qemu-system-arm (https://wiki.freebsd.org/qemu)
setup the NFS (http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/network-nfs.html)
prepare a root filesystem for ARM (http://www.freebsd.org/platforms/arm.html)
On the guest side:
compile the kernel for ARM (http://www.freebsd.org/platforms/arm.html)
boot from network (http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/network-diskless.html)
I don’t know anything more about it.
Keren
2013/02/25
Hi,
This is a really good tutorial. I did encounter a problem, though.
It seems I have everything in place:
the nfs server is running (and the export is working – checked with mount on localhost).
The QEMU command is:
/usr/bin/qemu -m 256M -nographic -kernel bzImage -append “root=/dev/nfs nfsroot=192.168.1.123:/links/nfs.target rw ip=192.168.1.125::192.168.1.123:255.255.255.0 init=/sbin/init console=ttyS0,115200n8″ -net nic,model=e1000 -net user
Whereas my local nic (eth1) is 192.168.1.123, and I assign 192.168.1.125 for the emulator, and the rootfs is built in /links/nfs.target/ (verified).
Also, /links/nfs.target/sbin/init is executable (softlink to busybox).
The qemu (qemu-kvm-0.14.0) loads the kernel image (3.7.9), then echoes:
[ 0.816670] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
[ 0.837579] IP-Config: Complete:
[ 0.837675] device=eth0, addr=192.168.1.125, mask=255.255.255.0, gw=192.168.1.123
[ 0.837838] host=192.168.1.125, domain=, nis-domain=(none)
[ 0.837956] bootserver=255.255.255.255, rootserver=192.168.1.123, rootpath=
[ 1.380513] tsc: Refined TSC clocksource calibration: 3391.418 MHz
[ 1.380734] Switching to clocksource tsc
Then, qemu freezes for about a minute or so, and after that echoes:
[ 95.869583] VFS: Unable to mount root fs via NFS, trying floppy.
[ 95.870348] List of all partitions:
[ 95.870654] No filesystem could mount root, tried:
[ 95.871037] Kernel panic – not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
I ran wireshark on my host, and don’t see any nfs packets on eth1 nor on lo. Seems like a network issue.
Any ideas?
Thanks in advance
Balau
2013/02/25
When you launch QEMU with user mode network, it will create a virtual local network with 10.*.*.* addresses. You are using 192.168.*.* addresses, and try to get to 192.168.1.123 but the virtual local network doesn’t know where to send the packets. If you try with the same addresses I used, it will probably work. You don’t have to change your local nic address to do this, it’s all in the virtual network.
Keren
2013/02/25
Thanks for the reply.
I’ll try using the default addresses and report the results
BTW, According to the qemu man pages, using the -nic command you can (allegedly) configure the IP addresses of the guest and of the host….
Quoting:
“Network options:
‘-net nic[,vlan=n][,macaddr=mac][,model=type][,name=name][,addr=addr][,vectors=v]’
Create a new Network Interface Card and connect it to VLAN n (n = 0 is the default). The NIC is an e1000 by default on the PC target. Optionally, the MAC address can be changed to mac, the device address set to addr (PCI cards only), and a name can be assigned for use in monitor commands. Optionally, for PCI cards, you can specify the number v of MSI-X vectors that the card should have; this option currently only affects virtio cards; set v = 0 to disable MSI-X. If no ‘-net’ option is specified, a single NIC is created. Qemu can emulate several different models of network card. Valid values for type are virtio, i82551, i82557b, i82559er, ne2k_pci, ne2k_isa, pcnet, rtl8139, e1000, smc91c111, lance and mcf_fec. Not all devices are supported on all targets. Use -net nic,model=? for a list of available devices for your target.
‘-net user[,option][,option][,...]’
Use the user mode network stack which requires no administrator privilege to run. Valid options are:
…
‘net=addr[/mask]’
Set IP network address the guest will see. Optionally specify the netmask, either in the form a.b.c.d or as number of valid top-most bits. Default is 10.0.2.0/8.
‘host=addr’
Specify the guest-visible address of the host. Default is the 2nd IP in the guest network, i.e. x.x.x.2. ”
According to that, I should be able to configure the addresses I want and replace the 10.0.2.2 default…. Should that work in your opinion?
Balau
2013/02/25
I say my opinion does not matter when there’s a manual page that says it should work.
I never used those options, you are free to try them first!
Keren
2013/02/27
Further testing. Here are the results.
* The “-net nic,addr=xxx” refers to PCI address of the PCI nic and not to an IP Address.
* The “-net user,net=192.168.1.0/24″ sets the network (subnet) of the guest to 192.168.1.*.
After loading the guest (without NFS) you can “ifconfig eth0 192.168.1.###” and “ping 192.168.1.2″. It works.
* If you add the host address: “-net user,net=192.168.1.0/24,host=192.168.1.123″ – it sets the network of the guest to 192.168.1.*, and also the host address (as the guest sees it) to 192.168.1.123. you can ping this address and it works.
Back to loading from from NFS.
Testing the NFS with the default addresses:
Command:
/usr/bin/qemu -m 256M -nographic -kernel bzImage -append “root=/dev/nfs nfsroot=10.0.2.2:/links/nfs.target rw ip=10.0.2.15::10.0.2.1:255.255.255.0 init=/sbin/init console=ttyS0,115200n8 ”
Produces:
[ 0.569332] 8139cp 0000:00:03.0 eth0: link up, 100Mbps, full-duplex, lpa 0x05E1
[ 0.587898] IP-Config: Complete:
[ 0.588560] device=eth0, addr=10.0.2.15, mask=255.255.255.0, gw=10.0.2.1
[ 0.589087] host=10.0.2.15, domain=, nis-domain=(none)
[ 0.589500] bootserver=255.255.255.255, rootserver=10.0.2.2, rootpath=
[ 1.397273] tsc: Refined TSC clocksource calibration: 3391.422 MHz
[ 1.397727] Switching to clocksource tsc
Still no mount. Any idea what am I doing wrong ?
Balau
2013/02/27
Try to collect more information and see if something pops out.
Maybe in “/var/log/messages” there’s a trace of the guest kernel being denied access.
Appending “loglevel=7″ to the kernel parameters might show something more on the guest console.
Coder
2013/03/27
Hi Balau,
when I am using nfsroot for old kernel 2.6.39 the end log I am getting is
VFS: Mounted root (nfs filesystem) on device 0:12.
Freeing init memory: 180K
But the same nfsroot for kernel v3.7.5 is emulating the kernel properly.
Any clue,why is it so?
Balau
2013/03/27
Maybe your cross-compiler has a libc that is tied to a newer kernel version and is incompatible with older kernels.
If so, rebuilding your nfsroot with an old compiler could work for the old kernel.
Or you could try this to try and debug some more.