I would really like to thank Nir Lichtman (YouTube) for his video on Making Linux distro from scratch for the inspiration and the creation of this project, this guide changes up the video and make a written format to be read at your own pace.
This is a a guide on making your own minimal linux distro from scratch.
To make a simple distro we need mainly 3 things
- Kernal: the main software that talks between the hardware and software, here its Linux.
- User space: All the tools to actually use the os, Unix tools like
cd
andls
; we will use Busybox - Bootloader: Loads everything, the first piece of code that runs and loads the kernal. Not yet planned what will be used here.
In a simple terms the Bootloader loads the kernal, and kernal loads the userspace allowing you to interact.
Bootloader -> Kernal (Linux) -> Userspace (Busybox)
Lets start making our distro.
This guide expectes you to know your way around a linux distro, specially in a terminal.
In this guide we will use a Ubuntu enviornment. Hopefully any linux distro will work. However, Windows is not supported.
If you need a linux enviornment just use cloud vm like Gitpod, Github Codepspaces or Google Cloud Shell.
Testing of the image is OS agnostic and can run on any machine with qemu installed.
Run this in the shell to install all the required packages.
apt install bzip2 git vim make gcc libncurses-dev flex bison bc cpio libelf-dev libssl-dev syslinux dosfstools mtools grub2-common grub-pc-bin xorriso -y
- bzip2
- git
- vim
- make
- gcc
- libncurses-dev
- flex
- bison
- bc
- cpio
- libelf-dev libssl-dev
- syslinux
- dosfstools
- mtools
- grub2-common
- grub-pc-bin
- xorriso
It is recomended to create a directory to store all of your boot files that you will be creating all over the place.
mkdir boot-files
Note: If you changed
boot-files
location, change any command to point to yourboot-files
directory.
This guide uses qemu
for testing the os. To install it, go to the qemu download page and follow the instructions for your os.
Lets now compile the linux kernal or the base sauce need to run.
Clone the git repository https://github.com/torvalds/linux.git (Github Mirror), with --depth 1
so that you don't download the entire history.
git clone --depth 1 https://github.com/torvalds/linux.git
cd into the Linux directory
cd linux
Next, lets configure for compiling. Lets open the config menu.
make menuconfig
Now just make sure the 64-bit kernal
option is selected, we don't need to touch anything else and we can move on.
Simply exit by selecting exit on the bottom bar and making sure to save the kernal configuration.
Simply run make
, to incerase the speed we can split the process into jobs to run on multiple cores. (use lscpu
to find no of cores if you don't know)
make -j 8
It should take some time, so have a drink.
Once its done copy the image from where the build command says it is to the boot file directory.
Output:
OBJCOPY arch/x86/boot/setup.bin
BUILD arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready (#1)
# /\
# |
# What we are looking for
Command:
cp arch/x86/boot/bzImage ../boot-files
cd ..
Next the Busybox, the tools needed to interface with the os.
Clone the git repository https://git.busybox.net/busybox, with --depth 1
so that you don't download the entire history.
git clone --depth 1 https://git.busybox.net/busybox
cd into the busybox directory
cd busybox
Similar to the kernal, compiling config is done through a config menu.
make menuconfig
Now the only thing to change is set the build option to build static binary, the reason we slect this is to make building as simple as possible and not depend on external libraries.
- Select settings in the top.
- Scroll down till you find the
Build Options
category. - Press space to enable it.
Simply exit by selecting exit on the bottom bar and making sure to select yes to save the configuration.
Simply run make
, to incerase the speed we can split the process into jobs to run on multiple cores. (use lscpu
to find no of cores if you don't know)
make -j 8
It should take some time, so have a drink.
Once its done compiling. Make a directory called initramfs
in the boot-files
directory.
cd ..
mkdir boot-files/initramfs
cd busybox
If you don't know what initramfs is, it is the inital file system the kernal loads after booting, and in this step we are putting busybox into that file system.
make CONFIG_PREFIX=../boot-files/initramfs install
cd into the initramfs
directory.
cd ..
cd boot-files/initramfs
Create a file here called init
(boot-files/initramfs/init
). And add the contents bellow:
#!/bin/sh
# Create a temporary RAM filesystem
mount -t tmpfs none /mnt
mkdir /mnt
mkdir /mnt/dev
mkdir /mnt/proc
mkdir /mnt/sys
# Mount essential filesystems
mount -t devtmpfs devtmpfs /mnt/dev
mount -t proc proc /mnt/proc
mount -t sysfs sysfs /mnt/sys
ln -s /mnt/proc /
ln -s /mnt/sys /
# Networking
ip link set eth0 up
udhcpc -i eth0
# Some defaults
dmesg -n 1
# Note: Can't Ctrl-C without cttyhack
# exec setsid cttyhack /bin/sh --rcfile /usr/.bashrc
exec setsid cttyhack /bin/sh
This file is basicly the first thing the kernal runs. The first line asks it to run using the shell, and the second line is asking it to open a shell for the user.
We also need to add execution permission to the init
file.
chmod +x init
Also delete the linuxrc
file in the initramfs
directory.
rm linuxrc
Here we package the file system into an archive supported by the kernal.
find . | cpio -o -H newc > ../init.cpio.gz
If we break it down:
- We find all the files in the
initramfs
direcotry - We then pipe all the files into the cpio command, telling it to create an archive in an archive format the kernal supports.
- Finally save it to the parrent directory as
init.cpio.gz
Lets now create a bootable image.
This creates an empty 64mb file filled with 0s to house our os and bootloader.
dd if=/dev/zero of=boot bs=1M count=64
We will be using the Fat filesytem as thats what syslinux supports. Here we are formating the boot image to fat.
mkfs -t fat boot
Next we will add the syslinux bootloader to the image.
syslinux boot
Create a file here called syslinux.cfg
(boot-files/syslinux.cfg
). And add the contents bellow:
DEFAULT linux
LABEL linux
SAY Now booting the kernel with initramfs from SYSLINUX...
KERNEL bzImage
APPEND initrd=init.cpio.gz root=/dev/ram0
This file is the configuration file that the syslinux bootloader loads on boot and configures the enviornment it should be booted in. Here we are provide the kernal image and the initramfs it should boot with.
Copy the bzimage
(kernal) and init.cpio.gz
into the image with mcopy
.
mcopy -i boot bzImage ::bzImage
mcopy -i boot init.cpio.gz ::init.cpio.gz
mcopy -i boot syslinux.cfg ::syslinux.cfg
And now your image is ready to roll.
Using qemu lets test the image.
qemu-system-x86_64 -drive file=boot,format=raw -m 2G
Note: replace boot
with whatever you named your file.
Then a window should pop up asking for a boot. Type in the following: (Only applicable if no syslinux config)/bzImage -initrd=/init.cpio.gz
To make it bootable from a storage medium, we need to make an ISO. Also this time we will be switching from syslinux to grub for the bootloader as it provides simple tooling to create an iso.
Create a directory called iso
in the boot-files
directory. Within that create a boot
directory and within that grub
.
mkdir ./boot-files/iso
mkdir ./boot-files/iso/boot
mkdir ./boot-files/iso/boot/grub
Copy the bzImage
and init.cpio.gz
into the boot
directory in the iso
directory.
cp ./boot-files/bzImage ./boot-files/iso/boot
cp ./boot-files/init.cpio.gz ./boot-files/iso/boot
Create a file here called grub.cfg
in the grub
directory in the boot
directory (boot-files/iso/boot/grub/grub.cfg
). And add the contents bellow:
set default=0
set timeout=10
menuentry 'yourOs' --class os {
insmod gzio
insmod part_msdos
linux /boot/bzImage
initrd /boot/init.cpio.gz
}
Using grub-mkrescue to build the ISO.
grub-mkrescue -o ./boot-files/boot.iso ./boot-files/iso
This should create a boot.iso
file in the boot-files
directory.
Now your distro is ready to boot on real hardware.
Lets test the ISO in an emulator then on a real system.
qemu-system-x86_64 -cdrom boot.iso -m 2G
Note: replace boot.iso
with whatever you named your iso.