GNU-Linux Rapid Embedded Programming
上QQ阅读APP看书,第一时间看更新

The Network FileSystem (NFS)

In Chapter 2 , Managing the System ConsoleLoading files from the network section, we saw how to load a kernel image (with its DTB file) using an Ethernet connection, and we said that this feature is very useful during the kernel developing stages. Well, this feature is quite useless without the kernel's ability to use a filesystem located on another computer (usually the host PC) as a root filesystem, Simply speaking, instead of mounting a filesystem stored on a local disk or flash memory, the system mounts a remote filesystem using a network.

This allows the developer to test both the kernel, its drivers, and the whole root filesystem by downloading them from the network, avoiding the boring step to reprogram the mass memory devices (this actually saves a lot of the developer's time!).

Due to these reasons, this particular type of filesystem is called Network FileSystem (NFS).

Of course, we can use this feature over several different network connections, but only if our system has an Ethernet connection and its kernel has a running driver for it. However, if this is the case, this feature is very useful for several reasons:

  • If our kernel still does not support its storage devices, we can use anyway a filesystem with a console where we can log in.
  • Even if our system has a small storage device, we can use a complete distribution on it, with all debugging tools ready to be used. For instance, we can use a Debian OS, where we can easily install whatever we need to develop our application, even if our flash memory is very small (64 or 128MB).
  • We can modify one or more files by simply modifying them directly on the host and then avoiding rebuilding the filesystem on the local flash or disk.

Well, to better fix these concepts, let's try to mount a remote filesystem on one of our developer kits. As already stated, we can choose whatever we wish, so for this test, we are going to use the Wandboard.

Exporting an NFS on the host

OK, an NFS is a remote filesystem, but it contains exactly all the files a usual filesystem has. So, we can use the filesystem used in Chapter 1 , Installing the Developing System, in Wandboard  section, to set up the microSD of our developer kits.

We can start by creating a new directory on the host PC and then by putting all files into it:

$ sudo mkdir /opt/armhf-rootfs-debian-jessie
$ cd common/debian-8.4-minimal-armhf-2016-04-02/
$ sudo tar xpf armhf-rootfs-debian-jessie.tar -C /opt/armhf-rootfs-deb ian-jessie/

After that, the contents of our new NFS is ready in the /opt/armhf-rootfs-debian-jessie directory:

$ ls /opt/armhf-rootfs-debian-jessie/
bin dev home media opt root sbin sys usr
boot etc lib mnt proc run srv tmp var

However, this not enough since we have to teach our host PC in order to export this filesystem over the network. To do this, we can use the nfs-kernel-server package. Despite its name, this package holds all user space programs to manage an NFS, and it has the kernel word in its name because it uses the NFS kernel features to do its job.

Now, let's install the package with the usual aptitude command:

$ sudo aptitude install nfs-kernel-server

When the installation is completed, we have to set up the new service by editing the /etc/exports file. This file states which are the directories to be exported, and by taking a look at its contents we can get a brief idea of what we should do:

$ cat /etc/exports
# /etc/exports: the access control list for filesystems which may be # exported to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) # hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#

So, since our files have been placed in /opt/armhf-rootfs-debian-jessie and we have used the IP address 192.168.32.25, that is, the IP of the host, for our developer kits, we can add the following line to the /etc/exports file:

/opt/armhf-rootfs-debian-jessie 192.168.32.25(rw,sync,no_subtree_check
,no_root_squash) 

So, the new file contents should be as follows:

$ tail -3 /etc/exports
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
/opt/armhf-rootfs-debian-jessie 192.168.32.25(rw,sync,no_subtree_check ,no_root_squash)
Tip

Note that the /etc/exports file supports several different configurations that the ones shown in the preceding lines. You can get further information by stating from the man exports pages:  $ man exports

You will notice that we added no_root_squash option because as suggested by the exports man pages, this option is required for diskless clients (that is, systems that have no disks at all and they mount the root filesystem over the network). To finish the settings, we have to restart the NFS daemon:

$ sudo /etc/init.d/nfs-kernel-server restart
[ ok ] Restarting nfs-kernel-server (via systemctl): nfs-kernel-server .service.

Now, to check if we did everything right, we can check all servers' exported directories using the showmount command:

$ showmount -e localhost
Export list for localhost:
/opt/armhf-rootfs-debian-jessie 192.168.32.25

OK, we can go ahead.

Setting up the kernel to mount an NFS

Now, we have to check whether the Wandboard kernel (or the developer kit's kernel) has all the necessary components to support the mount of an NFS.

First of all, we have to come back in the kernel directory and then recall build_kernel.sh:

$ cd WB/armv7-multiplatform/
$ ./build_kernel.sh

Once the kernel configuration menu is opened, we have to navigate to Networking support | Networking options and verify that the entry IP: kernel level autoconfiguration and its sub entries are checked as shown here:

Then, we must go back to the first page and then enter into the File systems menu. Here, here we must check the Network File Systems entry, and then, enter into its menu and copy the configuration reported in the next screenshot:

In reality, we just need support for NFS version 3, but we can safely add other options too.

OK, after all settings are in place, we can exit the kernel configuration menu to start the kernel's compilation. When finished, we can recall what we did in Chapter 2 , Managing the System Console, in Loading files from the network section, to load a kernel image over an Ethernet connection from U-Boot and copy the kernel image and the DTS into the TFTP root directory as shown here:

$ sudo cp deploy/4.4.7-armv7-x6.zImage /srv/tftpboot/vmlinuz-4.4.7-arm v7-x6
$ sudo mkdir -p /srv/tftpboot/dtbs/4.4.7-armv7-x6/
$ sudo tar xf deploy/4.4.7-armv7-x6-dtbs.tar.gz \ -C /srv/tftpboot/dtbs/4.4.7-armv7-x6/

Then. we can switch to U-Boot.

U-Boot and the kernel command line to use a NFS

After stopping U-Boot at the boot, we can set up a kernel command line to instruct the kernel to mount a filesystem as its root filesystem over the network.

The parameters we have to add to the kernel command line are as follows:

  • root: This specifies the root filesystem device to be used at the first mount. Note that it's not a real device, but just a synonym to tell the kernel to use NFS instead of a real device.
  • nfsroot: This specifies where the root filesystem's files are physically located. The syntax is as follows:
           nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] 
    

    The <server-ip> parameter should point to our host PC (that is, 192.168.32.25), <root-dir> must be replaced with the exported directory (we have /opt/armhf-rootfs-debian-jessie), and <nfs-options> can be used to specify version 3 of the protocol.

  • ip: This specifies the networking settings of our embedded device. The syntax is as follows:
           ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:
              <device>:<autoconf>:<dns0-ip>:<dns1-ip> 
    

    So, <client-ip> is the client's IP address (192.168.32.25 for us), <server-ip> is the host PC (192.168.32.43 ), <gw-ip> is the LAN's gateway (my LAN has 192.168.32.8), <netmask> is the network's netmask (my class C networks has 255.255.255.0), <hostname> should be set to whatever describes your machine (we used wb), <device> is the Ethernet port (eth0 in our case), and <autoconf> must be set to off in order to force static IP assignment. The other parameters can be left void.

    Tip

    For further information on these kernel parameters, a good starting point is the Documentation/kernel-parameters.txt file and the Documentation/filesystems/nfs/nfsroot.txt file in Linux's repository.

OK, now, we have to define these new settings into U-Boot in order to mount the NFS. There are several ways to do so. Most of them are very tricky. However, we'd like to show a classic way to resolve this issue, So, you can use it even on a different embedded device.

First of all, we have to do a standard boot to check the command line normally used, so let's use the boot command to continue and to see the command line used:

=> boot
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
Checking for: /uEnv.txt ...
Checking for: /boot/uEnv.txt ...
23 bytes read in 127 ms (0 Bytes/s)
Loaded environment from /boot/uEnv.txt
Checking if uname_r is set in /boot/uEnv.txt...
Running uname_boot ...
loading /boot/vmlinuz-4.4.7-armv7-x6 ...
5802912 bytes read in 405 ms (13.7 MiB/s)
loading /boot/dtbs/4.4.7-armv7-x6/imx6q-wandboard.dtb ...
51193 bytes read in 552 ms (89.8 KiB/s)
debug: [console=ttymxc0,115200 root=/dev/mmcblk0p1 ro rootfstype=ext4 
rootwait]
...
debug: [bootz 0x12000000 - 0x18000000] ...
Kernel image @ 0x12000000 [ 0x000000 - 0x588ba0 ]
## Flattened Device Tree blob at 18000000
 Booting using the fdt blob at 0x18000000
 Using Device Tree in place at 18000000, end 1800f7f8
Starting kernel ...
...
[ 0.000000] PERCPU: Embedded 13 pages/cpu @eed94000 s23936 r8192 d2
1120 u53248
[ 0.000000] Built 1 zonelists in Zone order, mobility grouping on. 
 Total pages: 522560
[ 0.000000] Kernel command line: console=ttymxc0,115200 root=/dev/m
mcblk0p1 ro rootfstype=ext4 rootwait
...

Great! The command line is as follows:

console=ttymxc0,115200 root=/dev/mmcblk0p1 ro rootfstype=ext4 rootwait

So, we need to fix up the root with the /dev/nfs option to tell the kernel to use NFS instead of a real device. We also need to remove the rootfstype option and then rewrite the kernel command line with the setenv command as follows:

=> setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=19 2.168.32.43:/opt/armhf-rootfs-debian-jessie,v3,tcp ip=192.168.32.25:19 2.168.32.43:192.168.32.8:255.255.255.0:wb:eth0:off:: rootwait'

Then, we can proceed to load the kernel and the DTS file:

=> setenv ipaddr 192.168.32.25
=> setenv serverip 192.168.32.43
=> tftpboot ${loadaddr} vmlinuz-4.4.7-armv7-x6
=> tftpboot ${ftd_addr} dtbs/4.4.7-armv7-x6/imx6q-wandboard.dtb
Tip

It may happen that our U-Boot has a bug and it'll not correctly load the DTB file into proper memory area. If we execute the second tftpboot command, we see something like this:

  => tftpboot ${ftd_addr} dtbs/4.4.7-armv7-x6/imx6q-w 
 andboard.dtb 
  Using FEC device 
  TFTP from server 192.168.32.43; our IP address is 1 
 92.168.32.25 
 Filename 'dtbs/4.4.7-armv7-x6/imx6q-wandboard.dtb'. 
 Load address: 0x12000000 
  Loading: ########## 
 756.8 KiB/s 
 done 
 Bytes transferred = 51193 (c7f9 hex) 

The load address is 0x12000000 instead of 0x18000000. We must re-execute the preceding two commands by replacing the loadaddr and fdt_addr variables with the respective memory address as follows:

 => tftpboot 0x12000000 vmlinuz-4.4.7-armv7-x6 
 => tftpboot 0x18000000 dtbs/4.4.7-armv7-x6/imx6q-w
 andboard.dtb

Note that the file names have been deduced by the following lines of the preceding booting messages:

loading /boot/vmlinuz-4.4.7-armv7-x6 ...
5802912 bytes read in 405 ms (13.7 MiB/s)
loading /boot/dtbs/4.4.7-armv7-x6/imx6q-wandboard.dtb ...
51193 bytes read in 552 ms (89.8 KiB/s)

OK, now, we can do the boot using the following command:

=> bootz ${loadaddr} - ${fdt_addr}
Kernel image @ 0x12000000 [ 0x000000 - 0x588000 ]
## Flattened Device Tree blob at 18000000
 Booting using the fdt blob at 0x18000000
 Using Device Tree in place at 18000000, end 1800f7f8
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Initializing cgroup subsys cpuset
...
[ 0.000000] Kernel command line: console=ttymxc0,115200 root=/dev/n fs rw nfsr
oot=192.168.32.43:/opt/armhf-rootfs-debian-jessie,v3,tcp ip= 192.168.32.25:192.16
8.32.43:192.168.32.8:255.255.255.0:wb:eth0:off:: r ootwait

OK the kernel has started with the right command line! Let's see what happens then:

[ 5.456756] fec 2188000.ethernet eth0: Freescale FEC PHY driver [Ge neric PHY]
(mii_bus:phy_addr=2188000.ethernet:01, irq=-1)
[ 5.468079] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
[ 8.456629] fec 2188000.ethernet eth0: Link is Up - 100Mbps/Full - flow contr
ol rx/tx
[ 8.466228] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 8.486384] IP-Config: Complete:
[ 8.489623] device=eth0, hwaddr=00:1f:7b:b4:1e:97, ipaddr=192. 168.32.25,
mask=255.255.255.0, gw=192.168.32.8
[ 8.499920] host=wb, domain=, nis-domain=(none)
[ 8.504888] bootserver=192.168.32.43, rootserver=192.168.32.43 , rootpath
=
[ 8.526701] VFS: Mounted root (nfs filesystem) on device 0:17.
[ 8.533312] devtmpfs: mounted
[ 8.537150] Freeing unused kernel memory: 1032K (c1058000 - c115a00 0)
[ 8.857337] random: systemd urandom read with 53 bits of entropy av ailable
[ 8.875848] systemd[1]: systemd 215 running in system mode. (+PAM + AUDIT +SEL
INUX +IMA +SYSVINIT +LIBCRYPTSETUP +GCRYPT +ACL +XZ -SECCOMP -APPARMOR)
[ 8.889511] systemd[1]: Detected architecture 'arm'.
Welcome to Debian GNU/Linux 8 (jessie)!

Yeah! The kernel has been able to mount our NFS and the Debian OS has started.

At the end, we can log in to our system as we did earlier:

Debian GNU/Linux 8 arm ttymxc0
default username:password is [debian:temppwd]
arm login: root
Password:
Linux arm 4.4.7-armv7-x6 #4 SMP Sat May 14 19:35:00 CEST 2016 armv7l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
inpidual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@arm:~#

Developing into an NFS

Now the question is, Why we should use an NFS during the development?

The answer is because it improves the develop-test-develop stages dramatically! In fact, if we have to replace a wrong version of a program or a complete directory, we can simply do it on the host, without copying anything on the client.

Let's do a simple example by considering what we did with the Hello World program during the cross-compilation. We cross-compiled it on the host, and then, we have to copy it on the target. However, if we use an NFS we can avoid such a copy.

Here is the C program on the target:

root@arm:~# ls
helloworld.c

Here is the same program on the host:

# ls /opt/armhf-rootfs-debian-jessie/root/
helloworld.c
Tip

Note that the root's privileges are necessary due the fact that the /opt/armhf-rootfs-debian-jessie/root/ directory is forbidden to everyone but the root!

Then, we can cross-compile it on the host:

# cd /opt/armhf-rootfs-debian-jessie/root/
# make CC=arm-linux-gnueabihf-gcc CFLAGS="-Wall -O2" helloworld
arm-linux-gnueabihf-gcc -Wall -O2 helloworld.c -o helloworld

Then, program is already on the client too and ready to be used:

root@arm:~# ls
helloworld helloworld.c
root@arm:~# ./helloworld
Hello World

This simple example shows the benefits on a single file. Let's consider it with a more complex program with tons of files.