Today we will go over the process of building a tiny Linux kernel, and booting into a shell. To start with, fetch the Linux source tree that you’d like to try this out on. I’m using staging tree for this post. You can get it here:
$ time make -j16
scripts/kconfig/conf --syncconfig Kconfig
SYSTBL arch/x86/include/generated/asm/syscalls_32.h
SYSHDR arch/x86/include/generated/uapi/asm/unistd_32.h
SYSHDR arch/x86/include/generated/uapi/asm/unistd_64.h
SYSHDR arch/x86/include/generated/uapi/asm/unistd_x32.h
WRAP arch/x86/include/generated/uapi/asm/bpf_perf_event.h
.
.
.
.
Setup is 13788 bytes (padded to 13824 bytes).
System is 417 kB
CRC 435fb428
Kernel: arch/x86/boot/bzImage is ready (#1)real 0m15.468s
user 2m12.094s
sys 0m14.603s
The kernel builds to around ~430k. This is a 32-bit kernel by default, so let’s enable 64-bit support:
$ make menuconfig
Enable the TTY for console support:
and support for printk to see console output as the kernel boots:
Build again:
Setup is 13596 bytes (padded to 13824 bytes).
System is 737 kB
CRC d273f4d
Kernel: arch/x86/boot/bzImage is ready (#4)real 0m6.045s
user 0m40.808s
sys 0m3.958s
The size has gone up somewhat, nearly double what we started off with. If you’re only supporting a serial interface, even this additional bloat can be avoided to keep the size of the image down.
The kernel boots and panics when attempting to start init, as expected. In order to boot into a shell as we originally set out to do, we will need a filesystem and a shell that can be started by the kernel as PID 1. Let us create a bare bones ram disk image that we can use to boot into a minimal busybox shell.
There are plenty of ways to achieve this, and here’s just one way:
Before we can use this ram disk, we need to enable init RAM disk (initrd) support in the kernel:
I have only included support for gzip compression and disabled the rest.
We will also need to enable ELF-support to be able to start up the shell:
This time, when booting the kernel, pass in the -initrd argument and specify the initrd image that we created earlier, in addition to an argument to the kernel to specify the binary that it should look for in the ram disk and execute once the kernel has finished booting, which is in this case is ‘/bin/sh’.
Let’s enable the /proc filesystem so we can run some standard commands:
Mount the proc file system after booting, so you can use commands like ‘ps’ and ‘free’:
Here’s the size of the kernel that we just built:
Setup is 13788 bytes (padded to 13824 bytes).
System is 793 kB
Note that this is the size of the compressed kernel on disk and the actual memory used at boot time is comparable to the size of the generated vmlinux file, which in this case is 12MB. You would need at least that much memory to load the kernel into memory, plus 8–16MB additionally for the user space. Here’s the minimum memory configuration that allowed me to boot this kernel in qemu:
Twenty years ago, it was easy to dislike Microsoft. It was the quintessential evil MegaCorp that was quick to squash competition, often ruthlessly, but in some cases slowly through a more insidious process of embracing, extending, and exterminating anything that got in the way. This was the signature personality of
Today we will go over the process of building a tiny Linux kernel, and booting into a shell. To start with, fetch the Linux source tree that you’d like to try this out on. I’m using staging tree for this post. You can get it here:
To get an initial config that’s very minimalist:
Here’s a comparison of the config options enabled by tinyconfig to a stock kernel that come with my Debian distribution:
Let’s try to build it:
The kernel builds to around ~430k. This is a 32-bit kernel by default, so let’s enable 64-bit support:
Enable the TTY for console support:
and support for printk to see console output as the kernel boots:
Build again:
The size has gone up somewhat, nearly double what we started off with. If you’re only supporting a serial interface, even this additional bloat can be avoided to keep the size of the image down.
Boot the kernel with qemu:
The kernel boots and panics when attempting to start init, as expected. In order to boot into a shell as we originally set out to do, we will need a filesystem and a shell that can be started by the kernel as PID 1. Let us create a bare bones ram disk image that we can use to boot into a minimal busybox shell.
There are plenty of ways to achieve this, and here’s just one way:
Before we can use this ram disk, we need to enable init RAM disk (initrd) support in the kernel:
I have only included support for gzip compression and disabled the rest.
We will also need to enable ELF-support to be able to start up the shell:
This time, when booting the kernel, pass in the -initrd argument and specify the initrd image that we created earlier, in addition to an argument to the kernel to specify the binary that it should look for in the ram disk and execute once the kernel has finished booting, which is in this case is ‘/bin/sh’.
And we have a shell.
Let’s enable the /proc filesystem so we can run some standard commands:
Mount the proc file system after booting, so you can use commands like ‘ps’ and ‘free’:
Here’s the size of the kernel that we just built:
Note that this is the size of the compressed kernel on disk and the actual memory used at boot time is comparable to the size of the generated vmlinux file, which in this case is 12MB. You would need at least that much memory to load the kernel into memory, plus 8–16MB additionally for the user space. Here’s the minimum memory configuration that allowed me to boot this kernel in qemu:
Just for kicks, I disabled printk and booted up the kernel, that put me into a shell almost immediately:
Finally, the compressed kernel size comes down to:
Read Next
Windows of Opportunity: Microsoft's Open Source Renaissance
Twenty years ago, it was easy to dislike Microsoft. It was the quintessential evil MegaCorp that was quick to squash competition, often ruthlessly, but in some cases slowly through a more insidious process of embracing, extending, and exterminating anything that got in the way. This was the signature personality of
US-11604662-B2
I’m happy to announce, that after a long wait, patent US-11604662-B2 has been issued.
Parallelizing and running distributed builds with distcc
Parallelizing the compilation of a large codebase is a breeze with distcc, which allows you to spread the load across multiple nodes and…
Getting started with Linkerd
If you’ve done anything in the Kubernetes space in recent years, you’ve most likely come across the words “Service Mesh”. It’s backed by a…