I do not intend to make this particular post a tutorial, demonstration, or any kind of reference or guideline, though it may end up looking like one.
Introduction
Here I will be exploring the assembly of Linux from zero to functioning system. By the end of this, in an ideal world, I’d like to be able to do some bare minimum things such as multiuser, SSH, a webserver, networking, disk access, NFS, etc.
For my first experiments, I will be using QEMU to test due to its convenient bootloader functionality, and the fact I don’t want to be wasting time booting a physical machine to a broken OS every time I do a tweak.
Observations and Explanations
This section will be where I stick my observations, and where applicable, explanations in a Q&A style.
O1: When booting the kernel, /dev
contains one node, /dev/console
without any intervention. Mounting a tmpfs to /dev removes /dev/console
.
E: Because of the mknod
major and minor numbers, it seems the kernel already knows what the devices are called (seemingly from their driver’s name) and console is just populated by default, sort of like /root
. A tmpfs is pretty useless, but if we mount a devtmpfs to /dev
(as long as it is supported by the kernel) the kernel appears to populate /dev
with all devices it knows exists already.
O2: It appears /root
is created automatically?
E: None yet
O3: Kernel cmdline lets you specify ttyS0
when that node isn’t even created at /dev/ttyS0
as we aren’t even at init yet. Then when init is complete, /dev/ttyS0
doesn’t exist unless init creates it, we also haven’t chosen the name to be ttyS0
either!
E: See O1‘s explanation
Building the Kernel
I am building the Linux kernel from source code using x86_64_defconfig. My procedure for preparing and proceeding with a kernel build is as follows:
#Install compilation dependencies
sudo apt-get install git make gcc libssl-dev libelf-dev libncurses-dev flex bison -y
#Clean directory to work in
mkdir linuxdev
cd linuxdev
#Download and expand kernel, then enter its directory
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.14.10.tar.xz
tar -xf linux-5.14.10.tar.xz
cd linux-5.14.10
#Set up default config and then begin compilation
make x86_64_defconfig
make
Setting up Directories and Scripts
I will now set up the directory in which I will build my linux system, and I will start my creating some directories and scripts to make repetitive things quicker:
#Assuming we just finished the kernel build, so are still in the kernel folder
cd ..
#Make our system directory and enter it
mkdir linuxsystem
cd linuxsystem
#Make our initrd directory
mkdir myrd
Lets make a simple script to package a directory as our initrd called mkinitrd.sh
with the following contents:
#!/bin/bash
cd myrd
find . | cpio -v -H newc -o -F ../myrd.cpio
cd ..
cat myrd.cpio | xz --check=crc32 -9 > myrd.cpio.xz
Lets also make another script called launchrd.sh
which just defines our qemu startup parameters:
#!/bin/bash
qemu-system-x86_64 -kernel my.knl -initrd myrd.cpio.xz -nographic -no-reboot -m 128 -append "panic=1 console=ttyS0 noapic"
The contents of the launch script will likely change throughout this. If I use different parameters, I will specify the full startup line.
We also need to actually install qemu and also make these scripts executable, install the execution environment and copy our compiled kernel locally:
#Install QEMU
sudo apt-get install qemu qemu-kvm -y
#Make scripts executable
chmod +x mkinitrd.sh
chmod +x launchrd.sh
#Copy kernel
cp ../linux-5.14.10/arch/x86_64/boot/bzImage ./my.knl
Building Busybox
As the basis of this system, I’d like to use Busybox. For this, I will also compile it myself. Lets clone it:
#Assuming we just finished creating the two scripts, so are still in our system's folder
cd ..
#Clone Busybox
git clone https://github.com/mirror/busybox
Now we have busybox cloned, let’s set up a default config. We will also need to do a quick tweak to produce a statically linked binary, as dependency libraries will not be included in our Linux system:
#Go into Busybox's cloned directory
cd busybox
#Make a default config
make defconfig
#Enable static linking
make menuconfig
Now you are in the menuconfig, you need to go to Settings -> Build Options -> Build static binary (no shared libs)
and make sure it is switched on. Then exit menuconfig and choose Yes to saving. Now we can run the compile:
#Run the compiler
make
Laying out the initrd
Go into the myrd
directory we created inside of the linuxsystem
directory earlier:
#Assuming we just finished making Busybox, head down to linuxsystem then down to myrd
cd ../linuxsystem/myrd
We will now lay out some very basic folders and set up Busybox and init.
Create some important directories familiar in Linux:
mkdir usr
mkdir bin
mkdir sbin
mkdir usr/bin
mkdir usr/sbin
mkdir etc
mkdir etc/init.d
We will now copy the Busybox binary we created earlier into the initrd and link it as the init also:
#Copy Buxybox in
cp ../../busybox/busybox bin/busybox
#Make it also the init executable by symlinking it. Remember, symlinks are relative to the mounted rootfs
ln -s /bin/busybox init
We will now make the script which will initialise the system. Busybox, as it is linked to /init
, will initialise the system and then run the initialisation script we create. Open the init script with nano etc/init.d/rcS
and fill it with the following contents:
#!/bin/busybox sh
#Set the PATH environment variable
PATH=/bin:/sbin:/usr/bin:/usr/sbin
#Let Busybox set itself up to make all executables it contains available nativelt
/bin/busybox --install
#Mount the kernel's virtual filesystems
mkdir /proc
mkdir /sys
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
Save and exit, then mark the script as executable with chmod +x rcS
.
We now need to set the file permissions correct for the initrd. Change the owner of all files to root with chown -R root:root ./*
.
Build and Launch Your System
Finally, pack your initrd using the script we created earlier, then launch your QEMU VM to demo your system:
#Assuming you're still in mrd whilst we were setting it up
cd ..
#Pack your initrd
./mkinitrd.sh
#Launch your QEMU VM
./launchrd.sh
To be continued