Following up on the Building a Barebones Linux System post, today I will be adding BusyBox to make the system a smidge more usable.
What is this BusyBox?
BusyBox is a refers to itself as The Swiss Army Knife of Embedded Linux. It is a collection of common Unix tools living within a single binary that uses the magic of argv[0]
to facilitate a lightweight implementation of common programs like sh
, ls
, cat
and many more. It is extremely common in the embedded systems community because it is a well maintained open source project and can be built very small.
For my purposes, I will use BusyBox instead of going into a full Linux From Scratch route. This will give me a shell and a few other basic userspace tools needed to allow me to make some use of this bare bones system.
Automating the builds
At this point I have been dabbling with this bare bones setup long enough that I want some additional tooling to help manage rebuilding as necessary. There are many good tools out there for managing custom Linux builds, but for now I’d like to continue using custom tools to learn more of what it takes to build a Linux distro from square one.
I’ve made a repo on Github and a Makefile that will pull in the kernel, build it, and build my initramfs. It also has a runvm
command that will help me test my builds quickly.
KERNEL_VERSION=4.13.3
KERNEL_DIRECTORY=linux-$(KERNEL_VERSION)
KERNEL_ARCHIVE=$(KERNEL_DIRECTORY).tar.xz
KERNEL_URL=https://cdn.kernel.org/pub/linux/kernel/v4.x/$(KERNEL_ARCHIVE)
all: vmlinuz initramfs
# Kernel build targets
vmlinuz: $(KERNEL_DIRECTORY)
cd $(KERNEL_DIRECTORY) && make defconfig && make -j`nproc`
cp $(KERNEL_DIRECTORY)/arch/x86_64/boot/bzImage vmlinuz
$(KERNEL_DIRECTORY):
wget $(KERNEL_URL)
tar xf $(KERNEL_ARCHIVE)
# Initramfs build targets
initramfs: initfs initfs/init
cd initfs/ && find . | cpio -o --format=newc > ../initramfs
initfs/init: initfs init.c
gcc -o initfs/init -static init.c
initfs:
mkdir -p initfs/bin
# Utility targets
runvm: vmlinuz initramfs
qemu-system-x86_64 -m 2048 -kernel vmlinuz -initrd initramfs
clean:
rm -rf vmlinuz $(KERNEL_DIRECTORY) $(KERNEL_ARCHIVE)
Configuring and Building BusyBox
BusyBox uses buildroot so its configuration and build process are very similar to the kernel itself. After downloading and extracting the source I’ll use make menuconfig
to use the ncurses based config editor.
I’ll be leaving most settings at their default, but will configure the build to generate a static binary since I don’t yet have a libc implementation to link against.
With BusyBox built, I’ll add it to my initfs
directory so that my Makefile includes it in my initramfs.
The busybox
binary will act differently based on what it is called as, if we renamed it to sh
it would act as a shell, or if named ls
it will list our files and such. To allow it to serve all of these functions I’ll check what commands my build implements and create symlinks for each program it provides.
Switching the Build to BusyBox
With my process for building BusyBox roughly figured out, I’ll save a copy of my BusyBox config, so that future builds will also be static linked. I’ll also include a small shell script that builds my symlinks.
cd initfs/bin
for i in `./busybox --list`
do
ln -s busybox $i
done
Now that I have BusyBox in my initramfs, I’ll switch my init process from my custom binary to a shell script that will forever spawn a shell to use.
#!/bin/sh
while true
do
sh
sleep 1
done
With this script in place I’ll update my Makefile and repo to include the changes I’ve made here.
And now we test!
There are a few deficiencies in here still, e.g. ps
won’t work because I have no procfs mounted and sh
will complain about being unable to access tty.
To get a few more things to run sanely, I’ll add /dev
, /proc
and /sys
to my initramfs and modify my init script to mount the procfs and sysfs. I’ll also run mdev -s
which is provided by BusyBox to populate /dev
and /sys
. Lastly, I’ll launch my shell with setsid cttyhack sh
so that job control is enabled.
With all that setup, the system is substantially more usable! I’ll push these changes to my repo and call it a wrap.