Compiling the Linux kernel and creating a bootable ISO
Guide to compile the Linux kernel from source, make the file system from BusyBox and then run it using QEMU and make a bootable ISO and boot it on a computer. There are two parts, one is booting the kernel and the other is the filesystem. It begins with using a ramdisk to boot to a shell or an installation script because we do not know and cannot assume the hard drive situation.
Make the workspace directory to start:
mkdir -p ~/custom_linux/ cd custom_linux
Set up the environment to compile the kernel, other packages not mentioned might also be required:
sudo apt update sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev grub2 xorriso
The Linux Kernel
Firstly, configure, build, compile the Linux kernel, https://kernel.org/ and grab the latest Linux kernel source. To configure the kernel use make decofig which takes the default config file from your running Linux system. To enter a TUI to edit the config file. One thing for older devices is unchecking 64 bit kernel. Use make -j $(nproc) to compile using all the CPU cores.
# Replace with updated version numbers when required wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.7.1.tar.xz tar -xvf linux-6.7.1.tar.xz cd linux-6.7.1 make defconfig make menuconfig make -j4
After the build completes successfully, you should have a bzImage in the arch/x86_64/boot directory (your architecture may differ). If not, check the make file log.
That's done. cd ~/custom_linux
Now, create a directory structure: We could manually create the Linux directory structure using mkdir and copy the over the system programs like cat and ls, the majority of files on a modern Linux system are not statically linked, they rely on of libs. This can be checked with command ldd. BusyBox combines tiny versions of many common UNIX utilities into a single small executable and ln the commands.
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 tar xf busybox-1.36.1.tar.bz2 cd busybox-1.36.1
Run...
make defconfig
to make a default configuration for the BusyBox. Then...
make menuconfig
to bring the TUI and edit the configs. The option which you must edit is located in Settings, and then Build static binary (no shared libs). The reason to enable this option is we do not want to compile Glibc into the Linux distro.
Then...
make -j $(nproc)
to compile BusyBox. To check if the compiled file is fine, use command...
file busybox
The file must be statically linked.
Creating The File System
Busybox also generates the diretories, run make install to create a folder called _install and change directory to _install and you will see a hierarchy like Linux file system.
In this directory, run the following command to create the folders needed for kernel.
mkdir dev proc sys
Now create a file called init and open it with a text editor. Copy and paste the following data to it:
#!/bin/sh mount -t devtmpfs none /dev mount -t proc none /proc mount -t sysfs none /sys echo "Welcome to my Linux!" exec /bin/sh
Then make the script executable with...
chmod +x init
Done. BusyBox made one executable which is capable of providing us a lot of Linux utilities such as sh , echo , vi and so on. With make install we made a filesystem hierarchy which contains these programs as links to BusyBox executable. Next, we make a shell script called init. This script will be ran after kernel loads. At first, it mounts dev, proc and sys directories. It then prints a welcome message and runs sh to open a shell.
You can put ANY executable as init file. You might also want to mount dev, sys and proc directories when the program starts using mount.h header. Last thing we need to do is to create the filesystem itself... To do so, run these commands inside _install directory.
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
This will make the file initramfs.cpio.gz in upper directory.
Testing The Compiled Kernel With QEMU
Before creating the ISO file let us check if the kernel is fine or not. To do so, we use QEMU. Just run the following command: (make sure that you change the path of bzImage and initramfs)
qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz qemu-system-x86_64 -kernel ../../linux-6.7.1/arch/x86/boot/bzImage -initrd ../initramfs.cpio.gz
Running our Linux in QEMU, we know that our kernel boots, we can create a bootable ISO for it.
Creating a Bootable ISO
Create a folder somewhere with any name. I name it iso, then create a folder called boot in it and inside boot create a folder called grub. Then copy bzImage and initramfs.cpio.gz into the boot folder.
mkdir iso mkdir iso/boot mkdir iso/boot/grub cp ../linux-6.7.1/arch/x86_64/boot/bzImage boot/ cp ../busybox-1.36.1/initramfs.cpio.gz boot/
Creating the Grub Config File
We will use grub-mkrescue to create our bootable ISO. But before doing so, we have to know if our current host is booted with UEFI or BIOS. To do so, check if the folder /sys/firmware/efi exists on your system or not. If it does, your computer uses UEFI otherwise it’s BIOS. So why knowing this is important? The grub-mkrescue uses the currently installed grub stuff to create the ISO image. This means that if your operating system is booted in BIOS, the chances are that the ISO created from grub-mkrescue does not support UEFI at all. In some cases, UEFI motherboards support booting BIOS images using CMS. But that is not always the case. If you want to make images for BIOS from UEFI host or vice versa, I suggest you to create a Debian virtual machine in VirtualBox. VirtualBox supports both BIOS and UEFI in it’smotherboard settings. After choosing the appropriate one, install Debian (net install is sufficient) and move the folder which contains boot and grub folders to virtual machine. Then continue reading the guide to configure the grub and create the ISO file.
Now we have to configure grub itself. Create a file named grub.cfg in grub folder of the boot folder. If your host is booted using BIOS (and thus the output ISO is BIOS too) put these lines in the config file:
set default=0 set timeout=10 menuentry 'myos' --class os { insmod gzio insmod part_msdos linux /boot/bzImage initrd /boot/initramfs.cpio.gz }
If you are using UEFI, put these lines in it:
set default=0 set timeout=10 # Load EFI video drivers. This device is EFI so keep the # video mode while booting the linux kernel. insmod efi_gop insmod font if loadfont /boot/grub/fonts/unicode.pf2 then insmod gfxterm set gfxmode=auto set gfxpayload=keep terminal_output gfxterm fi menuentry 'myos' --class os { insmod gzio insmod part_msdos linux /boot/bzImage initrd /boot/initramfs.cpio.gz }
At last run this command to create the ISO file. Replace the last argument with the folder name which you created at first step.
grub-mkrescue -o myos.iso iso/
Testing The ISO With VirtualBox
Before testing the ISO on a real computer, boot it in VirtualBox. To do so, make a new virtual machine and choose the ISO you have just created as the content of the optical disk. Start the operating system. You must see the grub menu and then the operating system must boot. Do not forget to select EFI in motherboard settings if needed.
There are thousands of iterations to polish and improve upon, an installation script would set up partitions, format and install grub. There are so many Linux distributions out there that there is more gain in using an existing distro than starting a new one. Most distributions are doing little more other than maintaining over 20,000 packages with no pay to make sure they do not cause errors.
Installation script
The script...
1) Partition the Disk: Use a partitioning tool like parted or fdisk to create the necessary partitions on the target disk. For example:
parted /dev/sda mklabel msdos parted /dev/sda mkpart primary ext4 1MiB 100%
2) Format Partitions: Use mkfs commands to format the partitions. For example:
mkfs.ext4 /dev/sda1
3) Mount Partitions: Create a mount point and mount the partitions to it.
mkdir /mnt mount /dev/sda1 /mnt
4) Extract Distribution Files: Extract the contents of the distribution to the mounted partition.
mount -o loop /tmp/linux.iso /mnt cp -r /mnt/* /mnt/.* /mnt/..?* /mnt/...?* /
5) Install Bootloader: Using Grub:
grub-install --boot-directory=/mnt/boot /dev/sda update-grub
Cleanup, Unmount and Reboot: umount /mnt; reboot
Sample install script:
#!/bin/bash #install.sh # Check if the script is run as root if [ "$(id -u)" -ne 0 ]; then echo "Please run as root" exit 1 fi # Set variables for disk and partition TARGET_DISK="/dev/sda" PARTITION_NAME="${TARGET_DISK}1" MOUNT_POINT="/mnt" # Partitioning the disk parted -s "$TARGET_DISK" mklabel msdos parted -s "$TARGET_DISK" mkpart primary ext4 1MiB 100% # Format the partition mkfs.ext4 "$PARTITION_NAME" # Mount the partition mount "$PARTITION_NAME" "$MOUNT_POINT" # Download and install the Linux distribution (adjust the URL as needed) DISTRO_ISO_URL="https://example.com/path/to/linux.iso" DISTRO_ISO_NAME="linux.iso" wget "$DISTRO_ISO_URL" -O "/tmp/$DISTRO_ISO_NAME" mount -o loop "/tmp/$DISTRO_ISO_NAME" "$MOUNT_POINT" cp -r "$MOUNT_POINT"/* "$MOUNT_POINT"/.??* "$MOUNT_POINT"/..?* "$MOUNT_POINT"/...?* / # Install the bootloader (Grub in this example) grub-install --boot-directory="$MOUNT_POINT/boot" "$TARGET_DISK" update-grub # Cleanup umount "$MOUNT_POINT" rm -rf "/tmp/$DISTRO_ISO_NAME" echo "Installation completed successfully."
Downloads