Originally posted on my medium account
Have you ever wanted to learn how to create a micro distro? Something even smaller than Linux From Scratch (LFS) for no other purpose than to learn how Linux systems work at the most fundamental level? If so you’re in the right place.
To start we need to make sure our environment is setup and ready to function. The tools we’ll need: a working c/c++ compiler (GCC/Clang), make, bash, syslinux, tar, qemu for x86_64 or a flashdrive and system that supports legacy boot and our source code. In theory you can use any version, but I’ll link to the version used for this blog post.
- Linux source code: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.19.tar.xz
- Busybox source code: https://busybox.net/downloads/busybox-1.37.0.tar.bz2
Setting Up the Working Directory
First up we need to create a working directory. Then we will follow it up with creating a minimal rootfs. This setup follows the usr merge that many modern Linux distros follow. Where /bin -> /usr/bin, /sbin -> /usr/sbin and /lib -> /usr/lib
mkdir -pv ~/minidistro && cd ~/minidistro/
mkdir -pv packages
mkdir -pv rootfs && cd rootfs
export ROOTFS=$HOME/minidistro/rootfs
# Core top-level directories
mkdir -vp "$ROOTFS"/{boot,dev,proc,sys,run,tmp,home,mnt,etc,opt}
# Our init dir
mkdir -vp "$ROOTFS"/etc/init.d
# /usr — merged hierarchy, single-lib
mkdir -vp "$ROOTFS/usr"/{bin,sbin,lib,share}
# /var — variable data
mkdir -vp "$ROOTFS/var"/{log,run,cache,tmp,lib}
# Permissions
chmod 0755 "$ROOTFS"
chmod 1777 "$ROOTFS/tmp" "$ROOTFS/var/tmp"
# symlink our rootlevel dirs for backwards compatibility
ln -sv usr/bin "$ROOTFS/bin"
ln -sv usr/sbin "$ROOTFS/sbin"
ln -sv usr/lib "$ROOTFS/lib"
Your root filesystem should look the following. While this is more than we need for a simple busybox distro, it provides the ground work to expand upon later.
/$HOME/minidistro/rootfs
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── mnt
├── opt
├── proc
├── run
├── sbin -> usr/sbin
├── sys
├── tmp
├── usr
│ ├── bin
│ ├── lib
│ ├── sbin
│ └── share
└── var
├── cache
├── lib
├── log
├── run
└── tmp
Grabbing the Source Code
cd $HOME/minidistro/packages
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.19.tar.xz
wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2
Building the Kernel
It’s finally time to start building our kernel. Today we’ll be targeting a default config as it will have everything necessary to get us running inside a bios qemu session. There are many options that can be configured and it’s a good learning opportunity to read through the options and play around. The end result is the bzImage that we’ll use as our kernel.
tar -xf linux-6.19.tar.xz
cd linux-6.19
make defconfig
make -j bzImage
cp -vr arch/x86/boot/bzImage $ROOTFS/boot
Building Busybox
At a minimum you need to have a shell, coreutils, init and build it statically. You must disable tc for static builds. Otherwise the question is what utilities you desire to have.
cd ..
tar -xf busybox-1.37.0.tar.bz2
cd busybox-1.37.0
# A weird bug in the code where I had to run make menuconfig and let it fail then
# patch it with sed and then rerun make menuconfig
make menuconfig
# To get busybox make menuconfig to work
sed -i 's/^\(always\s*:=[^#]*\)$/#\1/' scripts/kconfig/lxdialog/Makefile
# Configure busybox. You must disable under Networking Utilities -> tc for static builds to work
# Static build option is Settings -> Build static binary (no shared libs)
make menuconfig
# Build and install
make -j
make install
# cd into busybox default installer dir
cd ./_install
cp -apvr bin/* $ROOTFS/usr/bin/
cp -apvr sbin/* $ROOTFS/usr/sbin/
cp -apvr usr/bin/* $ROOTFS/usr/bin/
cp -apvr usr/sbin/* $ROOTFS/usr/sbin/
cp -apvr linuxrc $ROOTFS/
# Even though busybox is in the ./_install directory we still need to copy it over
cp -apvr bin/busybox $ROOTFS/bin
Init Scripts
The last thing we need before we can assemble our .img is our init scripts. We merely setup our system directories proc, sys, dev, dev/pts and fire off a POSIX compatible sh.
# ---- inittab ----
cat > "$ROOTFS/etc/inittab" <<'EOF'
::sysinit:/usr/bin/mount -t proc proc /proc
::sysinit:/usr/bin/mount -t sysfs sys /sys
::sysinit:/usr/bin/mount -t devtmpfs dev /dev
::sysinit:/usr/bin/mkdir -p /dev/pts
::sysinit:/usr/bin/mount -t devpts devpts /dev/pts
::sysinit:/usr/bin/sh -c 'dmesg > /var/log/dmesg'
::respawn:/usr/bin/sh
EOF
# Copy busybox as our static init binary
cp -apvr $ROOTFS/usr/bin/busybox $ROOTFS/usr/sbin/init
Assembling the Disk Image
cd $ROOTFS/..
dd if=/dev/zero of=disk.img bs=1M count=1024
sudo mkfs.ext4 disk.img
mkdir -pv mntimg
sudo mount disk.img mntimg
sudo mkdir -vp mntimg/boot/extlinux
sudo cp -avpr $ROOTFS/* mntimg/
# Copy Syslinux modules - Arch Linux
sudo cp -vr /usr/lib/syslinux/bios/*.c32 mntimg/boot/extlinux/
# Copy Syslinux modules - Slackware
sudo cp -vr /usr/share/syslinux/*.c32 mntimg/boot/extlinux/
# If unsure, find them
find / -name "*.c32" 2>/dev/null
cat >> extlinux.conf << 'EOF'
DEFAULT linux
LABEL linux
KERNEL /boot/bzImage
APPEND root=/dev/sda rw init=/usr/sbin/init console=ttyS0
TEXT HELP
Boot the minimal playground kernel with your static rootfs.
ENDTEXT
EOF
sudo cp -apvr extlinux.conf mntimg/boot/extlinux/extlinux.conf
sudo extlinux -i mntimg/boot/extlinux
sync
sudo umount mntimg
Booting in QEMU
qemu-system-x86_64 -cpu host -enable-kvm -m 2G -drive file=disk.img,format=raw -nographic -serial mon:stdio
Sincerely, a concrete worker.
May the peace and grace of our Lord be with you.