Build Simple Bootstrap Linux System Image

Author: Gao Date: 2020-11-08
Build Simple Bootstrap Linux System Image

Kernel Image for Emulation

We will create a tiny Linux system with absolute minimum additional software. We reuse host kernel image vmlinuz to simply the process.

cp /vmlinuz vmlinuz

vmlinux is a ELF format based Linux kernel executable image and vmlinuz (Virtual Memory Linux gZip) is a compressed version of vmlinux. At the beginning of vmlinuz is a routine that does some minimal amount of hardware setup and then decompresses the kernel contained within the kernel image and places it into high memory. The routine then calls the kernel and the kernel boot begins.

Initramfs

We need a temporary root file system, an initramfs when the kernel boots up. An initramfs is a cpio compressed archive. Unlike the old initrd, initramfs reuse disk cache mechanism of main memory so initramfs doesn’t require creating a synthetic block device nor having a intermediate file-system driver inside the kernel to interpret the data which may have license problem and avoid unnecessarily copying memory from the fake block device into the page cache. Bootloader places initramfs image(s) in a memory location and then pass its location and its size as parameter to kernel. The kernel checks for the presence of the initramfs and if found it is unpacked into a RAM disk at kernel initialization, mounts it as /. Once initramfs is extracted, into rootfs the kernel checks if it contains a file /init. If it does, the kernel executes this /init file.

mkdir simple
cd simple

Shell Environment

sudo apt install busybox-static

We have to install busybox-static, busybox does not install symbolic links for any of the supported utilities.

mkdir -p bin sbin usr/bin usr/sbin
cp /bin/busybox bin/busybox
ln -s busybox bin/sh

Configure Keyboard Layout for Spanish

We have to install the list of keymaps

sudo apt install kbd console-data

Convert to binary format and copy to our initramfs

loadkeys -b /usr/share/keymaps/i386/qwerty/es.kmap.gz > es.kmap

Support USB Devices

We repeatedly use this command sudo lsmod | grep usb_storage to recursively find all modules that are required for USB hot-plug. They are the following modules in total

We then use sudo modinfo [module name] to find each module path and copy to our initramfs.

mkdir -p lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/host/ohci-hcd.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/host/ohci-pci.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/storage/uas.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/storage/usb-storage.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/core/usbcore.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/core/usbcore.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/scsi/scsi_mod.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/usb/common/usb-common.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/drivers/scsi/sd_mod.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/fs/fat/vfat.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/fs/fat/fat.ko lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/kernel/fs/nls/*.ko lib/modules/$(uname -r)

Init Script

We will require a /init program, it is typically a shell script. It configures some basic device nodes and directories, mounts the special /sys and /proc file systems, and starts the processing of hotplug events using mdev and then execute the more-typical /sbin/init program.

nano init

and put following content

#!/bin/sh
# install symlinks for all of the suppoted utilities
# install busybox symbolic links
/bin/busybox --install -s
#
[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir --mode=0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
#
mount -t sysfs none /sys -onodev,noexec,nosuid
mount -t proc none /proc -onodev,noexec,nosuid
#
mknod /dev/zero c 1 5
mknod /dev/null c 1 3
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
# load USB drivers
depmod -a # https://stackoverflow.com/q/45658297/9980245
modprobe ohci-pci
modprobe uas
modprobe sd_mod
# initial population and dynamic updates
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
/sbin/mdev -s
# change keymaps http://ilikelinux69.github.io/How-to-change-the-keyboard-layout-in-Busybox
loadkmap < es.kmap
# traditional init program
exec /sbin/init

add the execution permission

chmod a+x init

compress into cpio images

find . | cpio --quiet -o -H newc | gzip >../simple.igz

The result simple.igz is our initramfs.

The busybox provide a –install options to create symbolic links (-s) for all the supported commands in /bin, /sbin, /usr/sbin. Here are the list of command that busybox install.

  • ls -al /bin | grep “->” | less
    • arch
    • ash
    • cat
    • chgrp
    • chmod
    • chown
    • cp
    • cpio
    • cttyhack
    • date
    • dd
    • df
    • dmesg
    • dnsdomainname
    • dumkmap
    • echo
    • ed
    • egrep
    • false
    • fatattr
    • fgrep
    • getopt
    • grep
    • hunzip
    • gzip
    • hostname
    • ionice
    • ipcalc
    • kill
    • link
    • linux32
    • linux64
    • ln
    • login
    • ls
    • lzop
    • mkdir
    • mknod
    • mktemp
    • more
    • mount
    • mt
    • mv
    • netstat
    • nuke
    • pidof
    • ping
    • ping6
    • ps
    • pwd
    • readlink
    • resume
    • rev
    • rm
    • rmdir
    • rpm
    • run-parts
    • sed
    • setpriv
    • sh
    • sleep
    • slat
    • stty
    • su
    • sync
    • tar
    • touch
    • true
    • umount
    • uname
    • uncompress
    • usleep
    • vi
    • watch
    • zcat
  • ls -al /sbin | grep “->” | less
    • acpid
    • adjtimex
    • arp
    • blockdev
    • depmod
    • devmem
    • fdisk
    • freeramdisk
    • fstrim
    • getty
    • halt
    • hwclock
    • ifconfig
    • ifdown
    • ifup
    • init
    • insmod
    • ip
    • ipneigh
    • klogd
    • loadkmap
    • logread
    • losetup
    • lsmod
    • mdev
    • mkdosfs
    • mke2fs
    • mkswap
    • modinfo
    • modprobe
    • nameif
    • pivot_root
    • poweroff
    • reboot
    • rmmod
    • route
    • run-init
    • start-stop-daemon
    • sulogin
    • swapon
    • switch_root
    • sysctl
    • syslogd
    • tc
    • tunctl
    • udhcpc
    • uevent
    • vconfig
    • watchdog
  • ls -al /usr/bin | grep “->” | less
    • [
    • [[
    • ar
    • awk
    • basename
    • bc
    • blkdiscard
    • bunzip2
    • bzcat
    • bzip2
    • cal
    • chvt
    • clear
    • cmp
    • crontab
    • cut
    • dc
    • deallocvt
    • diff
    • dirname
    • dos2unix
    • dkpg
    • dkpg-deb
    • du
    • dumpleases
    • env
    • expand
    • expr
    • factor
    • fallocate
    • find
    • fold
    • free
    • ftpget
    • ftpput
    • groups
    • head
    • hexdump
    • hostid
    • id
    • killall
    • last
    • less
    • loggger
    • logname
    • lsscsi
    • lzcat
    • lzma
    • md5sum
    • microcom
    • mkfifo
    • mkpasswd
    • nc
    • nl
    • nproc
    • nsenter
    • nslookup
    • od
    • openvt
    • passwd
    • paste
    • patch
    • printf
    • realpath
    • renice
    • reset
    • rpm2cpio
    • seq
    • setkeycodes
    • setsid
    • sha1sum
    • sha256sum
    • sha512sum
    • shred
    • shuf
    • sort
    • ssl_client
    • strings
    • svc
    • svok
    • tac
    • tail
    • taskset
    • tee
    • telnet
    • test
    • tftp
    • time
    • timeout
    • top
    • tr
    • traceroute
    • traceroute6
    • truncate
    • tty
    • unexpand
    • uniq
    • unix2dos
    • unlink
    • unlzma
    • unshare
    • unxz
    • unzip
    • uptime
    • uudecode
    • uuencode
    • w
    • wc
    • wget
    • which
    • who
    • whoami
    • xargs
    • xxd
    • xz
    • xzcat
    • yes

Initial Population and Dynamic Updates

We instruct the kernel to execute /sbin/mdev whenever a device is added or removed so that the device node can be created or destroyed. Then we seed /dev with all the device nodes that were created while the system was booting.

Traditional Init Program

At the end of /init we call /sbin/init program which coordinates the rest of the boot process and configures the enviroment for the user. It becomes the parent or grandparent of all the processes that start up automatically on the system. In our case /sbin/init is our busybox console. At this point we have two busybox running one executing the /init and second one - our console /sbin/init that is executed by /init.

Test the Constructed Image

cd ..

Run with QEMU Emulator

Install the QEMU

sudo apt install qemu-system

Execute the kernel with host architecture e.g. x86_64.

qemu-system-x86_64 -vnc :0 -kernel vmlinuz -initrd simple.igz -append "root=/dev/ram" /dev/zero

Booting a real machine

The syslinux package allows us to construct bootable systems for standard PCs on DOS-formatted storage.

sudo apt install syslinux

A suitable medium should be chosen to boot from, e.g., a DOS-formatted USB flash drive. The DOS partition of the USB flash drive must be marked bootable. Some USB flash drives might need repartitioning and reformatting with the Linux tools in order to work correctly.

Syslinux Boot Process Overview

  1. Stage 1 - Part 1 - Load MBR: At boot, the BIOS loads the 440 byte MBR boot code at the start of the disk (/usr/lib/syslinux/bios/mbr.bin).
  2. Stage 1 - Part 2 - Search active partition: The MBR boot code looks for the active partition that is marked with boot flag. e.g. /boot partition.
  3. Stage 2 - Part 1 - Execute volume boot record: The MBR boot code executes the Volume Boot Record (VBR) of the /boot partition. In the case of Syslinux, the VBR boot code is the starting sector of /boot/syslinux/ldlinux.sys which is created by the extlinux –install command.
  4. Stage 2 - Part 2 - Execute /boot/syslinux/ldlinux.sys: - The VBR will load the rest of /boot/syslinux/ldlinux.sys. The sector location of /boot/syslinux/ldlinux.sys should not change, otherwise syslinux will not boot.
  5. Stage 3 - Load /boot/syslinux/ldlinux.c32: The /boot/syslinux/ldlinux.sys will load the /boot/syslinux/ldlinux.c32 (core module) that contains the rest of the core part of syslinux that could not be fit into ldlinux.sys (due to file-size constraints). The ldlinux.c32 file should be present in every Syslinux installation and should match the version of ldlinux.sys installed in the partition.
  6. Stage 4 - Search and Load configuration file: - Once Syslinux is fully loaded, it looks for /boot/syslinux/syslinux.cfg and loads.

Config for syslinux

The syslinux program should be run on the device something similar to /dev/sdx1 for a USB flash drive. You should be careful, as selecting the wrong devide name might overwrite your host system’s hard drive. The syslinux loader can be
configured using a file called syslinux.cfg

nano syslinux.cfg

which would look something like:

Default simple
timeout 100
prompt 1
label simple
  kernel vmlinuz
  append initrd=simple.igz root=/dev/ram

Burning the Bootloader

We give our user permission that allow syslinux to write code to MBR. We add our username to disk group which have raw access to disks with usermod.

sudo usermod -G disk -a $(whoami)

We execute this command only once, the change is permanent. We now mount the USB devices and copy the remaining archives to normal file system, we can use dmesg to find sdX1.

sudo syslinux /dev/sdX1
# Mount hand if not magicaly mounted when connected.
sudo mkdir /mnt/test
sudo mount -t vfat /dev/sdX1 /mnt/test
# And then work with /mnt/test
sudo cp vmlinuz simple.igz syslinux.cfg /mnt/test
sudo umount /mnt/test

The device may now be removed and booted on an appropriate PC.

Test with VirtualBox

  1. Download Plop Boot Manager
  2. Extract plpbt.iso
  3. From Settings -> Sorage -> Controller: IDE -> Add -> Select plpbt.iso
  4. From Settings -> System -> Boot Order -> Ensure Optical is before any other devices
  5. From Plop boot menu select USB device

When we finish booting from USB, we can unplug USB. All content, kernel and initramfs is available in the main memory, we don’t use USB anymore. We could not access the content of our USB since there is no corresponding driver for USB and file system.

Mount USB

Since there is no udev running, we have to create block device node in /dev manually. First check with dmesg if drivers detect USB and which identifier it has e.g. sda1 and then create our block device manually which following parameters for sda1, if identifier is diferrent you have to check here.

mknod /dev/sda b 8 0
mknod /dev/sda1 b 8 1
mkdir -p /mnt/test
mount -t vfat /dev/sda1 /mnt/test

Reference