|
|
Making Bootable Linux CDs
Introduction
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-).
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. 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 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
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.
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
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.
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
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:
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 |
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: #!/bin/sh
echo "INITRD startup"
mount -t proc none /proc
# find the CDROM
CD=`fcd`
mount -t iso9660 $CD /cdrom
PATH=$PATH:/sbin:/usr/sbin
if [ -r /cdrom/rootfs.gz ]; then
echo " expanding real root fs..."
gunzip -c /cdrom/rootfs.gz | dd of=/dev/ram1
mkdir /ram
mount /dev/ram1 /ram
cd /ram/dev
ln -s "$CD" cdrom
cd /
umount /ram
umount /cdrom
umount /proc
echo "INITRD all done"
exit
fi
# if we arrive here, we drop into the emergency shell.
exec /bin/sh
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/
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.
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-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
l0:0:wait:/bin/sh
l1:1:wait:/bin/sh
/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
if [ -r /cdrom/part1.ext2 ] ;then
echo "Mounting loopback device"
mount -o loop,ro /cdrom/part1.ext2 /a
else
echo "Mounting HD partition instead"
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. 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 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, 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
http://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 linux
system: losetup /dev/loop3 /iso.small
mount -t iso9660 /dev/loop3 /mnt
mkdir /cdimage
cd /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.
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 2
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
May 04,2001 |