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.
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
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
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
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