Diskless Linux X Terminals
PXELINUX knows where the PXE boot ROMs stashed the network parameters from the DHCP server's response in memory, and it can use these to start another TFTP session to download its configuration file from the server. With the TFTP server configured as described above, the bootloader running on the client first tries to find its configuration file in /tftpboot/pxelinux.cfg/ethermac, where ethermac represents the client's Ethernet hardware address in lowercase hexadecimal, with octets separated by hyphens, for example, fe-ed-de-ad-be-ef. Failing that, the bootloader tries /tftpboot/pxelinux.cfg/iphex, where iphex is the client's IP address in uppercase hexadecimal. For example, if the client has the IP address 192.168.0.12, PXELINUX would try to download the file /tftpboot/pxelinux.cfg/C0A8000C. If that file doesn't exist, the least significant nibble is dropped from the name and the process repeats. Therefore, in the example above, after C0A8000C is not found, PXELINUX tries C0A8000, then C0A800 and so on. This makes it possible to have a single configuration file for an entire subnet, provided that the subnet boundary is nibble-aligned.
Listing 2 shows the contents of a PXELINUX configuration file. The first line gives the name of a file containing a compressed kernel image to be downloaded—all paths are relative to /tftpboot on the server. The second line lists parameters that should be passed to the kernel specifying that the root filesystem be a 64MB RAM disk that should be mounted read/write. The last line causes PXELINUX to generate an additional kernel parameter containing ip=client-ip:server-ip:gateway-ip:netmask and using the values obtained by the PXE boot ROMs from the DHCP server's response. This is useful if the kernel was built with kernel-level autoconfiguration of IP networking enabled. If so, when the kernel boots it uses these parameters to configure the network interface, making it unnecessary to do so using ifconfig or ifup in a startup script.
Listing 2. The PXELINUX configuration file specifies the compressed kernel image to be downloaded.
DEFAULT vmlinuz APPEND initrd=ramdisk.gz ramdisk=65536 root=/dev/ram rw IPAPPEND 1
In order to use kernel-level autoconfiguration of IP parameters, the network device driver must be available early in the boot, even before the root filesystem is mounted. Therefore, it cannot be a loadable module. Because the kernels that come with most distributions use loadable modules extensively, in practice this means it is necessary to build a custom kernel for the X terminal. In addition, the custom kernel should support RAM disks and initial RAM disks. Kernel-level autoconfiguration of IP networking is also convenient. It is not necessary to include any of the dynamic methods of obtaining IP addresses (DHCP, BOOTP and RARP can be selected), however, as the IPAPPEND line included in the PXELINUX configuration file ensures that the kernel receives the correct IP parameters. Finally, device filesystem support with devfs mounted automatically on boot greatly simplifies the /dev directory in the RAM disk root filesystem.
Building and populating the root filesystem would be the most complicated part of setting up a diskless Linux box if it weren't for the advent of Richard Gooch's device filesystem and Erik Anderson's BusyBox combined binary. The device filesystem automatically manages the /dev directory, creating device entry points as needed for the devices loaded in the kernel. This means two things: the directory has no unnecessary entries, and builders of RAM disk root filesystems don't have to spend hours with mknod trying to create all the needed device entry points. The BusyBox combined binary is an executable that changes its personality depending on how it is invoked. The usual methodology is to create symlinks to /bin/busybox from /bin/ls, /bin/cat, /bin/ps, /sbin/mount and so on, to create a minimalist UNIX system. No additional libraries or binaries are needed; the BusyBox rolls them into one.
One way to think of this setup is that the device filesystem takes care of /dev; the BusyBox takes care of /bin and /sbin; the kernel takes care of /proc; a read-only NFS mount takes care of /usr; and /tmp can be empty. So, all you need to worry about is /etc. In fact, /etc can be starkly minimalist, containing only /etc/fstab, /etc/inittab and /etc/init.d/rcS, the latter being the single startup script used by BusyBox when running as init.
BusyBox was written for the world of embedded Linux and normally would be built as a static executable. However, the XFree86 server itself depends on a number of shared libraries normally found in /lib. We are going to NFS-mount /usr, so we don't have to worry about shared libraries found in /usr/lib, but we do have to provide the ones XFree86 expects to find in /lib. Therefore, we might as well take advantage of the space savings made possible by configuring BusyBox as a dynamic executable. The minimum libraries required in /lib to run XFree86 can be discovered by running ldd /usr/X11R6/bin/XFree86. They are glibc (libc.so and libm.so), PAM (libpam.so and libpam_misc.so) and the dynamic loader itself (libdl.so and ld-linux.so).