Making Bootable Linux CDs
Why put linux on a bootable CD? Why not! Its a great idea if you need to take a 'toolkit' of programs to a customer site. You can fit quite a lot on a 650mb CD. The ThinkNIC takes this CD based Linux approach in creating a purpose built thin client. DemoLinux is a distribution that only works on a CD. The possibilities are endless.... probably 8-).
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.
I have , however, now found a few good sites. Check out the following:
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
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:
Our 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.
- 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
Now have a look at my CD startup process.
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.
- 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 support in the kernel to have this happen.
- The kernel tries to execute the /linuxrc file in the new root filesystem.
- The linuxrc program tries to mount the CDROM (It has to make a few
guesses to work out where it is), then creates another ram based ext2
filing system in /dev/ram1. The new root filesystem is retrieved off the CD
and effectively 'copied' into the /dev/ram1 device. We mount the new
filesystem just so we can add a softlink in its /dev directory for the CD (so
w 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.
First, you're going to need a machine 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're going to have to create a directory structure to build all this. I have a base directory /src/iso and everything sits under it. The key directories are:
|This is what mkisofs will use to write an image to the CD
|Holds the tree of our initrd filesystem
|Holds the tree of our real (final) root filesystem
Setting up initrd
The contents of initrd are essentially a mini working linux system. You'll need some libraries, a shell and a few tools, notably 'mke2fs, mount, umount'. The key part is the /linuxrc script. Here's mine:
echo "INITRD startup"
mount -t proc none /proc
# find the CDROM
mount -t iso9660 $CD /cdrom
if [ -r /cdrom/rootfs.gz ]; then
echo " expanding real root fs..."
gunzip -c /cdrom/rootfs.gz | dd of=/dev/ram1
mount /dev/ram1 /ram
ln -s "$CD" cdrom
echo "INITRD all done"
# if we arrive here, we drop into the emergency shell.
We mount /proc so that some system info becomes visible, then mount the CD.
fcd is a program I wrote that simply scans all the IDE devices until it
sees the first CDROM drive. Now we need to get our real root filesystemi
into /dev/ram1. I have it stored in a tarred gzip file on the CD.
I originally used mke2fs to create the filesystem. It actually requires
quite a lot of extra libraries to make mke2fs work. Being the minimalist
that I am, I just created a blank 4mb ext2 filesystem and copied the
real root fs tree to it, then gzipped it. All the linuxrc does is to gunzip
it into a dd and voila we have our real root filesystem.
The last step is simply to exit. The kernel should now take over, and mount /dev/ram1 as /, and try to run /sbin/init.
Burning a CD with a kernel and initrd
Putting a kernel and initrd on the CD provides the basics of what we're trying to achieve, so you might like to try burning a CD at this stage to see whether it works.
The contents of our CD will look like the following:
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:
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.
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 at a shell prompt. You won't be able to do much though. Because there was no real ramdisk.tgz file to load, you're stuck inside the initrd.
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-linux.so.2 -> ld-2.1.3.so
libbz2.so.1.0 -> libbz2.so.1.0.0
libc.so.6 -> libc-2.1.3.so
libcom_err.so.2 -> libcom_err.so.2.0
libdl.so.2 -> libdl-2.1.3.so
libe2p.so.2 -> libe2p.so.2.3
libext2fs.so.2 -> libext2fs.so.2.4
libm.so.6 -> libm-2.1.3.so
libncurses.so.5 -> libncurses.so.5.0
libtermcap.so.2 -> libtermcap.so.2.0.8
libuuid.so.1 -> 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:
/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:
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
if [ -r /cdrom/part1.ext2 ] ;then
echo "Mounting loopback device"
mount -o loop,ro /cdrom/part1.ext2 /a
echo "Mounting HD partition instead"
mount /dev/hda9 /a
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.
The next bits are part of my 'in-progress' work on bootable CDs. In order to
gain access to more libraries and binaries on the CD, we try and mount a
loop filesystem off the CD in /part1.ext2. We mount it on /a (I just wanted
to save typing). However, since I'm still debugging this, I just get it to mount
an existing partition on my hard disk on /a.
/a contains a bin directory and
other directories for starting up X windows. Potentially, it could contain
everything else one would usually find in a linux distribution.
Currently (23 April 01), I haven't finalised how things will be structured in
the root filesystem. I don't really want to add more to it, as I need some
space for writing logs and other files. The default behaviour is to look for
libs in /lib and /usr/lib. The latter directory doesn't exist, but I have the
ability to soft-link it to a directory under /a.
An example ISO 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.
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
Reboot with the CD in your CD drive and you should get some ISOLINUX message
and away it goes. I've enabled framebuffer support, so if you have an old PC
you may not see past the initrd.gz loading bit. If your video card is VESA 2
compliant you should get a penguin up the top of the screen followed by the
usual blurb of console messages.
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.
Everything else is up to you. You can get DNS working if you just create an
/etc/resolv.conf. Busybox includes nslookup and telnet, so have fun.
Note that to keep the size of the ISO quite small, I have not included
any X related stuff.... for now.
A smaller initrd
If you look at the iso.img.gz image I uploaded
to play with you'll notice that the initrd.gz file is quite large. Around 600kb.
Being the minimalist I am, I thought this was an awful waste considering the
limited job that the initrd does. Most of the space is taken up with the
enormous libc library. I could have looked at smaller libc libraries or tried
statically linking a C program, but having spent my early programming days
dabbling in assembly language, I thought it a good opportunity to write the
equivalent of my linuxrc file in 386 assembly.
With a little help from the framework behind the great asmutils (see
www.linuxassembly.org), I hacked
together a little program that did everything except gunzip the rootfs.gz
file. Instead, I just dd'd an uncompressed copy directly from the disk. Of
course, I was a bit perturbed by the length of time it was taking to load a
4mb file from CDROM, so I looked into writing my own gunzip in assembly (well
I actually searched to see if anyone else had achieved this feat ... sadly no).
Figuring that gunzip was too difficult to convert from C into assembly, I
looked at other compression sources and eventually came across a thing
called the 624. It was designed for the 4k intro
'scene'. I think the basic goal of it was to crunch a 6k assembly program
down to about 4k and attach a very small decruncher to it. The author had the
compressor part written in C and the decruncher part written in 386 asm.
Cool. I just mangled the code so that it wasn't compressing such small files
and it wasn't decompressing as part of an executable.
My complete linuxrc (that doesn't require any external libraries) is now
about 1kbyte in length. Cool. Here's the source for
linuxrc.asm. The source for the compressor is
pcomp.c. You'll need to compile it with something like
gcc -o pcomp pcomp.c. The new linuxrc first looks for an uncompressed
4mb filesystem file called 'rootfs' in the root directory of the CD. If that
doesn't exist it looks for a file called 'rootfs.lz' in the same directory.
This must be a file compressed with pcomp (To run pcomp, just do something
like 'pcomp rootfs' and it should create a 'rootfs.lz' file).
Finally, the new improved 'basic' iso image to play with is
iso.small.gz. Download it, gunzip it, and
burn it to a CD and give it a go. The root password is a 'space', and the
cfgcard and cfglan stuff should work.
Framework for extensibility
OR ... how to add your own extra bits. These so called 'basic images' that I
have to download here all have the /usr directory in the ram based root
filesystem as a softlink to /cdrom/usr. Which means that you can add your
own tools and utilities under /usr on the CD. ie. 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 burn the CD with RockRidge extensions on in order for
softlinks and mixed case filenames to be created properly on the CD.
To build it all yourself, you'll have to do something like this from a real
losetup /dev/loop3 /iso.small
mount -t iso9660 /dev/loop3 /mnt
tar cf - . | (cd /cdimage ; tar xf - )
# Now make sure you have a /cdimage/usr directory and add your own
# tools in there. When you're finished, just use mkisofs on the /cdimage
# directory to build a new image to burn to the CD.
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
[*] 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:
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:
append initrd=initrd.gz vga=788
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.