Retinix
News
About Retinix
Articles
Projects
Links

Making Bootable Linux CDs

Introduction

These are some discussion notes about my endeavours in creating a bootable CD. The main outcome of all this is my 'Pauls Boot CD'. There is an atypical page describing it here.

Why put linux on a bootable CD? It means being able to take a useable linux system and start it on any PC you like without the pain of installing it all. You could use it as an install disk for a linux distribution or perhaps some other operating system. Or you could make some sort of 'appliance' out of it:
  • MP3 player. You could burn all your MP3s plus a small bootable linux to turn your PC into a jukebox.
  • You could make your own 'Network Appliance' that has the operating system and server software on CD, and use a hard disk for data storage only. It would mean you'd never have to install an OS at all.
  • You could use it as a quick *nix toolkit. Just put enough on the CD to get networking and X windows going, start up Opera or netscape and you have a minimal graphical interface on a network. Or put pppd on the CD and you can dial up an ISP ala an internet appliance.
For other uses of bootable CDs have a look at ThinkNIC . Its a CD based Linux like thin client. DemoLinux is a distribution that only works on a CD.

This article outlines building a very general purpose 'base' for building a bootable linux system ... something that you can add to to create your own personal setup.

By the way, this is a major restructuring of an earlier document I wrote. You can find the old one here.

Research

Unfortunately, when I first started looking for information on the net, there didn't seem to be too much info about how to make bootable Linux CDs. After a bit more searching I've found the following good references: The standard for booting CDs is called 'El Torito'. The concept is that your BIOS treats your CD like a floppy initially and expects to find the 'image' of a bootable floppy on the CD. Your imaginary boot floppy is meant to have CDROM drivers on it which can somehow access the CD in full. This seemed overly complicated until I came across SysLinux. Its a boot loader that is part of Debian I think. It primarily is a floppy bootloader (like LOADLIN), but it now has an extra bit called ISOLINUX, that simplifies booting off a CD. The key benefit is that you no longer need a special floppy image. ISOLINUX will load the kernel and an initrd for you and away you go.

The boot process

The usual Linux boot process is something like:
  • The PC starts up and runs LILO or some other bootloader
  • LILO knows where the kernel image is and starts to load it
  • The kernel runs. When its finished doing all its checks, it attempts to mount the root filesystem. The major and minor numbers for this device are usually encoded in the kernel itself or passed to it as arguments from LILO
  • Once the file system is mounted, /sbin/init is executed and your system starts up as per your inittab ... and your /etc/rc*.d scripts
The CD boot process is somewhat different. Again we need a boot loader, but we don't necessarily know what device our CDROM is. It could be /dev/hdb, /dev/hdc, /dev/hdd. Even if we told the boot loader where the kernel is, we would still need to tell the kernel where its root filesystem is. ISOLINUX helps us get around this, by working out where the CD is. This allows us to boot the kernel, but it doesn't really help us to load the initial root filesystem. Many boot disks use a thing called an initrd (Initial ram disk) to get around this. initrd is an initial root filing system running in RAM. It loads prior to when the kernel attempts to mount the 'real' root file system. Yes this is odd. The idea is is that your initrd starts up, loads some critical modules, then mounts your real root file system.

Now have a look at my CD startup process.

  • The CD is installed with ISOLINUX, so it boots first.
  • ISOLINUX loads the kernel from the /isolinux directory on the CD.
  • ISOLINUX now loads the initrd.gz compressed ext2 file system. Its important to note that ISOLINUX loads this, and not the kernel. The kernel will grab it later.
  • The kernel starts up and eventually decompresses the initrd.gz to ram (in /dev/ram0 actually) and mounts it as root. You have to enable INITRD and Ram disk support in the kernel to have this happen.
  • The kernel tries to execute the /linuxrc file in the new root filesystem (because its an 'initrd').
  • The linuxrc program tries to mount the CDROM (It has to make a few guesses to work out where it is), then copies a compressed 'real' root filesystem from the CD into /dev/ram1. We mount the new filesystem just so we can add a softlink in its /dev directory for the CD (so we don't have to work out where the CD is again).
  • When the linuxrc script finishes, control returns to the kernel and it attempts to mount its configured root filesystem. In this case, I've rdev'd the kernel to make it use /dev/ram1 as the root filesystem. This will mount our newly created ram based root file system. In this file system I have an /sbin/init and the system starts up running entirely in RAM.
So why does it have to be a two phase process? Linux doesn't really know how to boot off a CD yet. The beauty of the initrd phase is that the initrd filesystem is loaded by the bootloader (ie. not the kernel). It means that we can effectively boot off any device, so long as the bootloader is able to read from it.

Basic Requirements for building Bootable CDs

First, you're going to need a machine pre-installed with a Linux distribution to create the CD on. I'm using Slackware 7.1 on a machine with a HP9100i CDwriter. I installed the cdrecord, mkisofs etc tools. I've had to compile into the kernel all the necessary SCSI options to use the IDE CD-Writer (See the CD Writer HOWTO), plus I have loop file support compiled in. I'd suggest you buy yourself a couple of blank CD-RWs to play with as you're going to be formatting, burning, trying it out ... repeatedly.

You'll also need Syslinux. You need Nasm to build it. However, if you're too lazy to do this, all you need is the ISOLINUX.BIN file that is created.

Create a kernel for the CD

Now you need to create a kernel that can load the initrd. I'll assume you know how to build a kernel. All the experimentation here was done with a 2.2.18 kernel. You must compile in initrd support and RAM disk support. I am using the default 4096K RAM disk size. Other things that you'll need are ISO9660 filesystem support, ext2 filesystem support. Once you have this kernel, you need to set the root device on it:
   eg. Say you've just done a make bzImage

    rdev /usr/src/linux/arch/i386/boot/bzImage /dev/ram1

   NB: If you don't have a /dev/ram1 on your system, create one with:

    mknod -m 640 /dev/ram1 b 1 1

Create a directory tree

You need a bit of template structure to work with. The layout I have is below:

The key directories are:
cdimage/ This is what mkisofs will use to write an image to the CD
initrd/ Holds the tree of our initrd filesystem
root/ Holds the tree of our real (final) root filesystem

If you download my bootkit, it lays everything out in this structure.

Setting up initrd

The INITRD phase has a simple goal ; Get a real root filesystem into /dev/ram1. I've actually tried a lot of different alternative ways to do this. In order they are:
  1. Use a little shell script that uses the ash shell to mount the CD, gunzip a rootfs.gz file directly into /dev/ram1 and exit. This requires libc, so the initrd.gz image is about 600k. This is the most straightforward approach ... but the most wateful.
  2. Use an i386 assembly language program as 'linuxrc' to perform the same task except that I used an alternative compression/decompression algorithm as gunzip was too complex to implement in assembly. linuxrc ends up being about 1k!! and requires no external libraries. This ends up with an initrd.gz of about 10k.
  3. Use a statically linked C program for linuxrc that uses the gunzip.c source from Busybox as a basis, so that we effectively perform the same task as the shell script. By statically linking with dietlibc the linuxrc actually ends up being smaller than a dynamically linked one against libc 2.1.3!. The total size of linuxrc is about 20k, and the initrd.gz is about 15k.
The C based linuxrc is really the best option. Its still quite small and uses a 'standard' for compressed files. Read about the asm one here, and the shell one here.

The basic sequence of events in the C program are as follows:

  • Mount /proc
  • Open the /proc/ide/ide0/hda/media file. If it says 'cdrom' inside it then we've found our boot CD, otherwise try /proc/ide/ide0/hdb/media, then /proc.../ide1/hdc/... , then /proc.../ide1/hdd/... until we find the first CDROM drive. This undoubtedly limits us to bootable IDE CDROMs on the two standard internal chains.
  • Change directory to /dev and create a symlink for 'cdrom' to the newly found CDROM device (eg. cdrom --> hdc).
  • Mount /dev/cdrom on /cdrom
  • Open /cdrom/rootfs.gz and extract it into /dev/ram1. It must be a gzip of a 4mb ext2 filesystem.
  • Mount /dev/ram1 on /ram
  • Change directory into /ram/dev and create the same symlink for the CDROM again.
  • Unmount /ram, /cdrom and /proc
The last step is simply to exit. The kernel should now take over, and mount /dev/ram1 as /, and try to run /sbin/init.

The linuxrc.c source is here . To compile it you should get dietlibc . Version 0.9 has a 'diet' command that simplifies compiling programs using the new library. You should be able to compile linuxrc.c using: diet gcc -o linuxrc linuxrc.c

The 'Real' root filesystem

You can't do too much with an initrd only system. You really need to create a useable root filesystem. Again, I'm using a 4mb RAM disk, so its a bit of a squeeze (why didn't I go for a bigger RAM disk? I just hate wasting space, and I wanted this to be useable on machines with 16mb of ram or more). I chose Busybox to provide most of my /bin tools. In 0.51 of Busybox, you even get things like vi and wget. Its getting fatter at 250Kb or so these days with every tool compiled in (NB: You can reduce its code size by editing Config.h and commenting out the defines for the tools you don't want), but 250K is fine for our 4mb filesystem. I've put some libraries in too:
   ld-2.1.3.so
   ld-linux.so.2 -> ld-2.1.3.so
   libbz2.so.1.0 -> libbz2.so.1.0.0
   libbz2.so.1.0.0
   libc-2.1.3.so
   libc.so.6 -> libc-2.1.3.so
   libcom_err.so.2 -> libcom_err.so.2.0
   libcom_err.so.2.0
   libdl-2.1.3.so
   libdl.so.2 -> libdl-2.1.3.so
   libe2p.so.2 -> libe2p.so.2.3
   libe2p.so.2.3
   libext2fs.so.2 -> libext2fs.so.2.4
   libext2fs.so.2.4
   libm-2.1.3.so
   libm.so.6 -> libm-2.1.3.so
   libncurses.so.5 -> libncurses.so.5.0
   libncurses.so.5.0
   libtermcap.so.2 -> libtermcap.so.2.0.8
   libtermcap.so.2.0.8
   libuuid.so.1 -> libuuid.so.1.2
   libuuid.so.1.2
Not all of these are really required to get the system up and running. I think some are left over from when I had mke2fs. /bin is just busybox and a sh*#load of soft links. I have an /sbin/init (not linked to busybox, its a real sysvinit). In /etc I have an inittab, termcap and an rc.d directory. Currently I just have an rc.S startup script that is run during the sysinit phase of the init startup. The key parts of my inittab are:
   id:1:initdefault:

   si::sysinit:/etc/rc.d/rc.S
   c1:1235:respawn:/bin/sulogin /dev/tty1
By using sulogin on the console, I force the user to enter a password (just a space), with the benefit that root's shell starts up in a normal login environment (ie. /etc/profile is run followed by ~/.profile)

/etc/fstab looks like:

   /dev/ram1       /        ext2        defaults   1   1
   none             /dev/pts  devpts     gid=5,mode=620  0   0
   none             /proc    proc        defaults   0   0
I also have an empty /initrd directory. This means that once that the initrd phase has finished, and the real root filesystem on /dev/ram1 is mounted, the initrd filesystem (on /dev/ram0) is moved under /initrd. This means we can unmount it and free the ramdisk space.

My /etc/rc.d/rc.S script looks like:

#!/bin/sh

PATH=/bin;export PATH
echo "System init"
mount -t proc none /proc
mount -o remount,rw /
echo "Find the extras on the CD and mount it"
if [ -r /dev/cdrom ] ; then
   mount -t iso9660 /dev/cdrom /cdrom
   mount /dev/hda9 /a
   fi
fi
cd /
umount /initrd
freeramdisk /dev/ram0
First we mount /proc, and then remount / (remember we have a valid entry for / in /etc/fstab now) as read/write. Now we check if the /dev/cdrom device is there (it should have been created in the initrd phase), then mount the cd.  I have a symlink from /usr (in the root filesystem) to /cdrom/usr. This means that you can add your own extra binaries and extra libraries on the CDROM under its own /cdrom directory. For additional testing, I also mount an ext2 partition on my harddisk as /a.

Burning a CD with a kernel , initrd and a rootfs

Now that we have a kernel, initrd structure and rootfilesystem structure, how do we get them onto the CD. Lets look at the basic structure of the CD (how it would look if you just mounted it as an iso9660 CD):
 /rootfs.gz
 /isolinux/
           isolinux.cfg   - the isolinux config file
           isolinux.bin   - the isolinux boot program
           vmlinuz        - our kernel with initrd support
           initrd.gz      - our initial ram disk
The isolinux.cfg file contains:
   label linux
    kernel vmlinuz
    append initrd=initrd.gz
Its similar to a LILO config. The main things are the kernel name and the append string which includes the name of our initrd. The fact that its gzipped doesn't matter. the kernel will automatically decompress it later.

What about the initrd and rootfs? These are both 4mb files that have been compressed with gzip. The contents of the 4mb file is a valid ext2 filesystem. Here's how I create them, given that I have a directory on my hard disk with the structure of the initrd or rootfs:

   BASE=/src/iso
   SRC=$BASE/initrd
   DEST=$BASE/cdimage/isolinux/initrd
   dd if=/dev/zero of=$DEST bs=1k count=4096
   losetup /dev/loop1 $DEST
   mkfs -t ext2 -m 0 /dev/loop1
   mount /dev/loop1 /mnt
   cd $SRC
   tar cf - . | (cd /mnt ; tar xf - )
   umount /mnt
   losetup -d /dev/loop1
   gzip -f $DEST
To burn the CD, I have the cd tree shown above under my /src/iso/cdimage directory (.ie I have a /src/iso/cdimage/isolinux directory). I run mkisofs as per the isolinux.doc that comes with Syslinux:
        mkisofs -o /iso.img -b isolinux/isolinux.bin -c isolinux/boot.cat \
                -no-emul-boot -boot-load-size 4 -boot-info-table -l \
                -R -r /src/iso/cdimage
The boot.cat file is created by the mkisofs command. The -l , -R and -r options are essentially for RockRidge extensions which allow us to have softlinks on the CD and mixed case filenames. We should end up with a couple of meg iso.img file. You can now burn this to the CD using cdrecord (I'll leave this up to you as your speed and dev settings are undoubtedly different to me).

Reboot your system, enter the BIOS setup screens to check that your system will boot off a CD and load the CD and see if it boots. Hopefully, you should see 'Loading vmlinuz', then 'Loading initrd.gz', then the kernel should do its stuff. You should end up with a 'please enter the root password' message (just enter SPACE the ENTER). You should be able to cd and ls and so forth.

A basic image to try out

I've uploaded a very basic ISO that you can burn to a CD and have a play with. It doesn't do too much as yet, but it has the full set of busybox 0.51 commands available and I've added some scripts to simplify the setting up of your lan card and IP address details. To allow different LAN cards to be configured, I've included all the modules for different LAN cards in the /modules directory of the CD.

First, download the iso.img.gz file. Decompress it with gunzip and burn it to a CD (I'd suggest a CDRW) using cdrecord or if you want to do it from Windows, you probably just need to rename it to blah.iso and Easy CD Creator or whathaveyou should be able to burn it.

The CD contains my own initrd.gz and rootfs.gz. You can have a look at these by decompressing them and mounting them as ext2 filesystems on a loopback mount.

Reboot with the CD in your CD drive and you should get some ISOLINUX message and away it goes. I've enabled framebuffer support, but you can select whether you want plain text or one of the graphical modes.

You'll get an sulogin prompt (please enter the root password or press ctrl-D), so enter the root password (a single space) and you should be at a bash prompt.

To configure your LAN card enter cfgcard and follow the prompts. The list of cards that is shown is simply the modules directory on the CD. Cards like 3c509 are obvious, but others are not. I have an rtl8139 based card which is the most common el-cheapo 10/100Mbps card you can buy.

Now do a cfglan which allows you to set the IP address, netmask and default gateway etc. It also asks you about nameservers and such as it creates a /etc/resolv.conf so name resolution will work.

Note: to keep the size of the ISO quite small, I have not included any X related stuff.... for now.

Framework for extensibility

OR ... how to add your own extra bits. As I said previously the /usr directory on the CD is symlinked in to become the actual /usr. Create a /usr/bin on the CD and add in all the tools that Busybox doesn't include. Add some libraries into /usr/lib ... and what I've been working on is the basic requirements to get X up and running. One thing to remember is that you really do have to run mkisofs with RockRidge extensions on in order for softlinks and mixed case filenames to be created properly on the CD.

Framebuffer

One of my experimental aims is to have an X windows environment on a boot CD. To achieve the widest possible compatibility, I've chosen to enable the Framebuffer console mode and to use the XF86_FBDev X server (its just the one from Slackware 7.1 at the moment). Note: Even though I am aiming for wide compatibility just so I can run X, Framebuffer mode doesn't work with pre VESA 2.0 video cards which means you may not want to add in Framebuffer if all you really need is a console prompt. To activate Framebuffer console mode you need to make sure some things are compiled into the kernel, typically this includes:
   [*] VGA text console
   [*] Video mode selection support
   [*] Support for frame buffer devices (EXPERIMENTAL)
   [*] VESA VGA graphics console
   [*] Advanced low level driver options
   <*> 8 bpp packed pixels support
   <*> 16 bpp packed pixels support
   <*> 24 bpp packed pixels support
   <*> 32 bpp packed pixels support
   <*> VGA characters/attributes support
   [*] Select compiled-in fonts
   [*]   VGA 8x8 font
   [*]   VGA 8x16 font
The other thing that I didn't realise until later is you have to make sure you set a graphical mode for the console when it boots in order to use the X server in default mode. This means putting a specific vga= setting appended to the kernel at boot time. Specifically, you need to change the /isolinux/isolinux.cfg file on the CD so it looks something like:
label linux
 kernel vmlinuz
 append initrd=initrd.gz vga=791
The '791' means to start up in 1024x768x16bit colour mode. Hard coding the display resolution is fine if you know for certain that your video card/monitor can handle it, but what I've done is to let the user choose a display option at boot time. My isolinux.cfg looks like this:
timeout 30
prompt 1
display menu.txt
default 1
label 1
 kernel vmlinuz
 append initrd=initrd.gz

label 2
 kernel vmlinuz
 append initrd=initrd.gz vga=788

label 3
 kernel vmlinuz
 append initrd=initrd.gz vga=791
menu.txt is a simple text file that looks like:
   1) Text Mode
   2) 800x600 x 16bit colour
   3) 1024x768 x 16bit colour
The user just enters '1' if they want text mode, 2 for 800x600 and so on.

Notes

  • The isolinux.cfg file seems rather dodgy if I put something like:
  •   label linux
         kernel vmlinuz
         append initrd=initrd.gz root=/dev/ram
    but seems to work OK if I take out the root=/dev/ram and simply use rdev to set the root fs in the kernel to /dev/ram1.
  • You can't make /linuxrc start init properly. It just keeps spitting out a 'Usage' string. I think its because init is unable to start as pid 1.
  • mkisofs will only write 8.3 style uppercase only filenames by default. If you want filenames up to 31 chars long, then specify the -l option. If you want mixed case filenames you need to enable the Rock Ridge extensions with the -R option, and may want to use the -r option as well which assigns sensible user/group ownership to all files. You will definately need the -R option if you want to copy whole unix directory trees to the CD.

May 17,2001

1