Creating a Minimal Linux Filesystem

Joshua Go
joshuago@users.sourceforge.net

I was recently assigned a project to build a customized filesystem for a small system running Linux. I was given only 16MB of space to work with, because this embedded system has limited flash memory. I would be storing this custom filesystem in a ramdisk, which is a filesystem that a running Linux system access while it's in RAM--not on a hard drive like the typical PC filesystem. A ramdisk is typically stored as a compressed file somewhere in permanent storage such as a hard drive or flash memory--in my case, I'd be storing it in flash memory.

In this scenario, I'm working with U-Boot 1.1.14 and Linux kernel version 2.6.14, but your setup probably differs if you're not working with embedded systems. I also had access to a system with DENX Software Engineering's Embedded Linux Development Kit (ELDK) version 4.0. I only use these tools for embedded software development because I'm working with embedded systems, but you won't need them if you are just experimenting with your home PC.

I have to warn you, if you want to try this with a home x86 PC, that it's very cumbersome to switch back and forth between your properly installed filesystem and this crazy experimental one. Try to work on two separate systems that are side-by-side, if possible. You will find that you'll find a file here and there that you want to add.

The absolute minimum

To start off with, I needed to know what the absolute minimum I could put on the filesystem was. I literally started off by making the ramdisk on my existing Linux desktop system with nothing on it.

# mkfs -t ext2 /dev/ram0
[mkfs output]
...
# mkdir /media/rd
# mount /dev/ram0 /media/rd
# cd /media/rd
# ls
lost+found
# 

First off, you'll need /dev/ entries to open the console. Here, it doesn't matter much whether you copy the /dev/ entries off an x86 machine for a PowerPC machine, since they're not programs to be run. They're just files, albeit special files.

# pwd
/media/rd
# cp -r /dev .
# ls dev 
[list of device files in your custom filesystem's /dev/ directory]
...
# 

Copy /sbin/init and /etc/inittab

Now we need /sbin/init, the first process that the kernel executes when your system boots up. We also know that we need /etc/inittab, which init looks at to figure out what to do.

# pwd
/media/rd
# mkdir sbin etc
# cp /opt/eldk-4.0/ppc_85xx/sbin/init sbin/init
# cp /opt/eldk-4.0/ppc_85xx/etc/inittab etc/inittab

Add the libraries needed by /sbin/init

I thought that putting /sbin/init in my custom filesystem would be enough to get me pretty far. I was wrong. I didn't even get a complaint about not being able to find /etc/inittab, which was what I was expecting. I wasn't even seeing init being executed! A quick investigation told me why: init depends on the standard GNU C library and the dynamic linker.

# ldd /opt/eldk-4.0/ppc_85xx/sbin/init 
        libc.so.6 => /lib/libc.so.6 (...)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (...)

In other words, /sbin/init depends on functionality provided by the GNU C library and the dynamic linker. This might not be a problem if you are basing your custom filesystem on a statically linked init, but since we're building one from what we've got, chances are you have a dynamically linked init. This surprised me because I wasn't expecting that the basic system functions would require a relatively rich environment. C'est la vie.

I'll save you the trouble, but if you want to see what happens, go ahead and try booting. You will probably get far enough that the kernel will print out what it finds on your system, but it'll complain about not having a valid init or somesuch. You can solve this problem by adding to the filesystem the libraries that init needs, namely libc and ld.so.

# pwd
/media/rd
# mkdir lib
# cp /opt/eldk-4.0/ppc_85xx/lib/libc-2.3.5.so lib/
# cp /opt/eldk-4.0/ppc_85xx/lib/ld-2.3.5.so lib/
# cd lib
# pwd /media/rd/lib
# ln -s libc-2.3.5.so libc.so.6
# ln -s ld-2.3.5.so ld-linux.so.2

So, I copied the libraries that init wants into /lib/ on my custom filesystem. On the next boot, it looked like init was successfully executed.

Simplify /etc/inittab

After it looked like init was being run, I rejoiced and celebrated. It was looking at /etc/inittab and spewing out a lot of garbage because it was looking for files that weren't there. It's good garbage, but nobody likes having garbage around for too long, so I aimed to simplify my /etc/inittab to stop looking for files as if it were on a full-featured system.

I only wanted to be dropped into a shell. I didn't need all those extra processes started up in those various runlevels, but for the sake of my sanity later on, I tried to keep runlevel 3 the default.

id:3:initdefault:
x:3::/bin/bash

With this, I tried to reboot, but there were some libraries that BASH needed. If you are starting to wince in pain, you should be. It's only normal. Chances are, though, that if you've come this far, you're either masochistic or really curious to the point where you really want to have a better idea of what exactly is the minimum required to get a Linux system running.

Add the libraries that BASH needs

Whatever /etc/inittab says should be run, such as BASH, you should have the libraries needed by that program. We'll find out what BASH needs by running ldd as we did on /sbin/init early in the process.

# ppc_85xx-ldd /opt/eldk-4.0/ppc_85xx/bin/bash
        libtermcap.so.2 => /lib/libtermcap.so.2 (...)
        libdl.so.2 => /lib/libdl.so.2 (...)
        libc.so.6 => /lib/tls/libc.so.6 (...)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (...)

Since we've already added libc.so.6 and ld-linux.so.2 to deal with letting init run properly, we only need to copy over libtermcap and libdl. Depending on what distribution and what version of that distribution you're running, you may have to copy different libraries over.

# pwd
/media/rd
# cp /opt/eldk-4.0/ppc_85xx/lib/libtermcap.so.2.0.8 lib
# cp /opt/eldk-4.0/ppc_85xx/lib/libdl-2.3.2.so lib
# cd lib
# pwd
/media/rd/lib
# ln -s libtermcap.so.2.0.8 libtermcap.so.2
# ln -s libdl-2.3.2.so libdl.so.2

Once again, we copy the actual files and try to keep our symbolic links nice and tidy. Then we re-package our ramdisk and give it another go. We should be fine on the next boot.

Where to go from here

Just because we've got a minimal system going doesn't mean that it's going to be pleasant to use. We're going to be missing a lot of functionality, namely all the services that are typically started up when a system boots, such as networking or multiple users. The way we've set it up, we don't have multiple users able to use the system because we haven't set up PAM, the password authentication module that regulates user logins and passwords.

Hopefully, though, this document has shown you the bare minimum required to get a Linux system up and running. The rest gets messy--really messy. Nevertheless, I don't want to discourage anyone from trying.

Copy everything else in /bin/ and /sbin/

There will be tools such as ifconfig that you may want. Heck, even ls would be nice to have. When copying these utilities over, you may have to copy over the libraries that they depend on as well, using the same process that we used for init and bash. I had to copy libpthreads to use ls, for example.

This is probably one of the easier things you can do to make it a little more bearable to use your minimal Linux filesystem.

Services and runlevels

You may have seen from your original /etc/inittab that it talks about things in various runlevels. Runlevels are basically different stages at which your system boots.

Typically, /etc/init.d contains the scripts to start and stop services on your system. It may be laid out differently depending on which distribution you use. For each runlevel, you may have a different /etc/rc.d/rcX.d directory, where X is a runlevel. There will probably be symbolic links from those rcX.d directories.

The Linux Bootdisk HOWTO has a good and thorough explanation regarding runlevels.

Password authentication modules (PAM)

The password authentication module (PAM) system is a tricky beast. You'll need it if you want to have multiple users. A good starting point if you really like pain is /etc/pam.d, but I just recommend using a ready-made filesystem if you want multiple users.

Don't forget /etc/passwd if you want multiple users, but you'll need a whole lot more than just that file to get multiple users going. This could be the subject of a future document, but I'm not making any promises.