|
|
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:
- 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.
- 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.
- 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
|