The goal of this guide is to provide a simplified version of the Linux From Scratch project, with steps organized in short sections that give you the satisfaction of testing each progress.
One key difference of this approach is that instead of building all the packages and tools that are part of a regular Linux OS, we start with a basic system based on the kernel and BusyBox, a simple binary that provides all the required commands.
Later we will continue adding packages and configurations until we get a system similar to the original LFS project.
Reference Links:
For the guide we are going to use a disk image, but you can do this with real hardware.
30GiB example:
$ dd if=/dev/zero of=disk.img bs=1G count=30 status=progress
$ fdisk disk.img
- Create a new GPT partition table
- Add a new 512MiB EFI partition
- Add a new partition for the root filesystem with the remaining space
- Write the partition table
Take note of the name assigned to the loop device (in this case /dev/loop0
), as it might vary depending on the system and already used loop names, so you might need to adjust some of the later commands.
$ losetup -P -f disk.img
$ losetup -a
/dev/loop0: [2065]:26214403 (/mnt/1TB/LFS/disk.img)
$ lsblk /dev/loop0
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 30G 0 loop
├─loop0p1 259:4 0 512M 0 part
└─loop0p2 259:5 0 29,5G 0 part
$ mkfs.vfat -F32 /dev/loop0p1
$ mkfs.ext4 /dev/loop0p2
$ fdisk -l /dev/loop0
Disk /dev/loop0: 30 GiB, 32212254720 bytes, 62914560 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 41907C5B-56E5-A64C-A89B-688589A53CC9
Device Start End Sectors Size Type
/dev/loop0p1 2048 1050623 1048576 512M EFI System
/dev/loop0p2 1050624 62914526 61863903 29.5G Linux filesystem
$ sudo mkdir -v /mnt/lfs
$ sudo mount -v /dev/loop0p2 /mnt/lfs
$ sudo chown -v $(whoami) /mnt/lfs
$ cd /mnt/lfs
$ mkdir -pv dev etc proc run sys var usr/{bin,lib,src}
$ ln -sv usr/lib lib
$ ln -sv usr/lib lib64
$ ln -sv usr/lib usr/lib64
$ ln -sv usr/bin bin
$ ln -sv usr/bin sbin
$ ln -sv usr/bin usr/sbin
$ cd /mnt/lfs/usr/src
$ wget -O- http://ftp.gnu.org/gnu/libc/glibc-2.34.tar.xz | xz -dc | tar -x
$ cd glibc-2.34
$ mkdir build
$ cd build
$ ../configure --prefix=/usr --enable-kernel=4.4
$ make
$ make DESTDIR=/mnt/lfs install
BusyBox is a software suite that provides several Unix utilities in a single executable file. It was specifically created for embedded operating systems with very limited resources, as the single executable replaces basic functions of more than 300 common commands.
This makes it perfect for this project, as it allows us to have a functional system much earlier, without needing to compile all the packages for each tool.
$ cd /mnt/lfs/usr/src
$ wget -O- https://www.busybox.net/downloads/busybox-1.34.0.tar.bz2 | bzip2 -dc | tar -x
$ cd busybox-1.34.0
$ make defconfig
$ make -j
In case you want to configure the build of BusyBox, instead of make defconfig
you can use make menuconfig
, which shows a menu with all the settings.
Once compiled, we can now move the generated binary to the system's /bin
directory and create all the links for each command it provides.
$ cp -v busybox /mnt/lfs/bin/
$ cd /mnt/lfs/bin/
$ for t in $(./busybox --list); do
ln -sv busybox $t
done
Configure busybox as the init program:
$ cd /mnt/lfs/
$ ln -sv bin/busybox /mnt/lfs/init
$ sudo chroot /mnt/lfs /bin/sh
$ cd /mnt/lfs/usr/src
$ wget -O- https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.14.8.tar.xz | xz -dc | tar -x
$ cd linux-5.14.8
$ make -j x86_64_defconfig
Same as for BusyBox, if you want to customize your kernel build you can run make menuconfig
.
To make the system even simpler we won't use a boot loader, for now, so one thing we do need to configure before compiling is the kernel parameters so that it knows where to find the root partition.
$ PARTUUID=$(blkid /dev/loop0p2 -s PARTUUID -o value)
$ echo "CONFIG_CMDLINE_BOOL=y" >> .config
$ echo "CONFIG_CMDLINE=\"root=PARTUUID=$PARTUUID rw rootwait\"" >> .config
$ echo "CONFIG_CMDLINE_OVERRIDE=n" >> .config
$ make -j$(nproc)
Only running the kernel in Qemu would give us a 'Kernel Panic' error, as it wouldn't find the root filesystem. But as we already have the root partition built, we can specify it to Qemu as the main disk:
$ cd /mnt/lfs/usr/src/linux-5.14.8
$ qemu-system-x86_64 -hda /dev/loop0 -kernel arch/x86/boot/bzImage
You can also use the path to the raw disk image instead of the loop device.
Mount the EFI partition:
$ mkdir -pv /mnt/lfs/boot/efi/
$ sudo mount -v /dev/loop0p1 /mnt/lfs/boot/efi/
Even though it's pretty rare to boot the system without a boot loader, it is possible to use a kernel bzImage as an EFI executable. Now we only need to copy the bzImage kernel we previously compiled to the EFI partition.
The path \EFI\Boot\bootx64.efi
is the only bootloader pathname that the UEFI firmware on 64-bit X86 systems will look for without any preexisting NVRAM boot settings, so this is what we are going to use.
$ sudo mkdir -pv /mnt/lfs/boot/efi/EFI/Boot/
$ sudo cp -v /mnt/lfs/usr/src/linux-5.14.8/arch/x86/boot/bzImage /mnt/lfs/boot/efi/EFI/linux/bootx64.efi
To boot Qemu in EFI mode, we need a bios file, for example OVMF, which is included with most package managers, and once installed you can find it with with locate OVMF.fd
(make sure to use the 64bit file)
$ qemu-system-x86_64 -hda /dev/loop0 -bios OVMF.fd
As for the previous section where we ran the kernel with Qemu, you can use the path of the raw image disk instead of the loop device.
The number of Tuxes on display is set by the number of threads, which is cool to see 😄️ (add -smp
and the threads you want)
- 7.2. Change files ownership
- 7.6. Creating Essential Files and Symlinks
- Mount proc, run and sys
- Proper configuration of glibc
- Users
- Sign efi binary
- Network
- Compiler
- Cool stuff 🤪️
Super cool 😄