description |
---|
This article describes a process of setting up an environment to compile, execute and experiment with Linux kernel on Mac. |
I am fascinated by Linux kernel due to a high degree of insight it gives me into internals of an operating system and a vast community of expert developers I can count on for help. I could experiment with it on sacrificial machines during internships and graduate school. All I have now is a personal laptop to experiment with.
This article describes a process of setting up an environment to compile, execute and experiment with Linux kernel on Mac. It is good for understanding kernel internals in action but not for performance testing.
version 0.1.0 is available for download.
host = Mac
guest = Linux
The first challenge before experimenting with the kernel is to create a reliable compilation environment to compile it. The second challenge is to create a reliable and sandboxed execution environment to run the kernel. I considered several options to solve both challenges.
Compilation env | Exec env | |
---|---|---|
Dual-boot Linux and host OS on host machine | Linux or host OS | Host machine |
Use virtual machine (VM) | Linux in VM | VM |
Buy cloud-based VM | Linux in VM | VM |
Usermode Linux or UML | UML or host OS | Host machine |
Raspberry Pi or BeagleBone | OS on Raspberry Pi or BeagleBone | Raspberry Pi or BeagleBone |
Use Docker and qemu | Docker | qemu |
I've dual-booted Linux with Windows and it does not end well. Sooner or later, I lost access to both operating systems. Compiling Linux kernel on a non-Linux environment is clumsy. Popular virtual machines cost money. When free, they didn't work reliably enough for me. Cloud instances cost money too and are accessible only over a network. UML was not a good experience. Raspberry Pi was just too slow and required purchasing peripherals. I realized I can use docker to create a compilation environment and qemu
for execution environment, as shown below.
compilation env execution env
__________________ _____________________
| | | |
| docker container | | qemu with busybox |
| with Fedora | | userspace, linux |
| userspace image. | ---------> | modules & binary. | <-----
| compile linux | | | run linux kernel | |
| src & modules, | | | here. | |
| make initramfs. | | | | |
| | | | | |
------------------ | --------------------- |
A | | | | |
| | 2. initramfs, | 3. initramfs | 4. dmesg | |
| | vmlinuz & modules | | on ttyS0 | |
| | _________________ | | |
| | | | | | |
| | | disk on host | | | |
| -----------> | stores linux | <----- | |
| | src, initramfs, | <-------------------- |
-------------------- | logs & vmlinuz | 5. ftrace on ttyS1 |
1. linux src | | --------------------------------
----------------- 6. file-backed hot-plug memory
host
- Docker
- QEMU
- Setup-code. Download here.
I use Docker to create a containerized compilation environment on my host OS to compile the kernel. Docker is free and easy to use. The container runs in isolation with respect to environment on host OS and can be terminated at will.
I use Quick Emulator or qemu
to create a virtual machine to run the kernel as a guest kernel on the host OS. qemu
is free, open-source, "easy" to use and runs as a process that can be terminated at will.
Following actions should be executed and in order. Each action is elaborated later including scripts to easily execute each one.
First time after downloading setup-code,
- Download Linux kernel source code
- Build docker image
- Launch docker container. This starts a containerized compilation environment.
- Compile guest kernel. This compiles kernel and tools to create guest userspace.
- Extract guest userspace to make modifications to it including adding kernel modules or other scripts to guest filesystem.
- Make guest userspace to build initramfs from which the userspace boots.
- Start qemu and guest kernel. This starts a sandboxed execution environment.
Thereafter,
- Launch docker container. This starts a containerized compilation environment.
- Compile guest kernel and modules, if any
- Extract guest userspace to make modifications to it including adding kernel modules or other scripts to guest filesystem.
- Make guest userspace to build initramfs from which the userspace boots.
- Start qemu and guest kernel. This starts a sandboxed execution environment.
build_image.sh
builds a docker image with Fedora userspacecleanup_docker.sh
removes docker container and imagesdownload_linux.sh
downloads linux source and un-tars itlaunch_image.sh
launches a docker container with mounted volumes including Linux source code and files or folders shared withqemu
reboot_kernel.sh
startsqemu
to run guest OS
host$ cd $top_dir
host$ ./scripts/download_linux.sh
<downloads & un-tars linux-X.Y.Z.tar.gz>
host$ cd $top_dir
host$ ./scripts/build_image.sh
host$ cd $top_dir
host$ ./scripts/launch_image.sh
docker$ <in docker container>
...
docker$ exit
host$ <back in host>
host$ cd $top_dir
host$ ./scripts/cleanup_docker.sh
I configured the guest kernel to include minimal code required to boot it in qemu
. It is configured to boot on virtual hardware and not on real hardware. This drastically reduces kernel compilation time, although I don't compile it often.
Using make menuconfig
I disabled networking support, all filesystems, graphics, power managers, ASLR, ACPI, SELinux, SMP support, NUMA support and lot of drivers for hardware I don't possess or intend to use with qemu
including mouse or keyboard. The kernel config is available here for download.
The guest kernel receives inputs and sends output via serial console ttyS0
. Since I disabled networking in the guest kernel, I had to disable guest userspace components that depend on it including udevadm
. I disabled all filesystems because Tinycore's Busybox userspace boots off of initramfs
in-memory filesystem obtained by decompressing a cpio archive. No modifications to the filesystem are persisted to the archive ensuring a pristine boot each time. However, I configured support for ftrace, memory hot-plug and kernel modules. The kernel config is available for download.
ftrace
is a function tracer in Linux kernel that helps visualize its control flow. As shown in the figure above, the guest kernel emits ftrace
output to the host via serial console ttyS1
that is connected to a log file on the host OS. A similar setup collects dmesg
from the guest kernel in a log file on the host OS. I can then view guest kernel's activity in realtime by tailing both log files or save them for inspection later.
Memory hot-plug is a feature of Linux kernel that allows inserting memory modules into a computer and expanding its core memory while the kernel is running. I share just a small slice of host OS's memory with the guest OS so that it does not deprive other processes on host. Once the guest OS boots successfully, I expand its core memory by hot-plugging more memory. qemu
allows allocating hot-plugged guest memory from a file on the host OS. I decided to use this option since I have significantly more storage area on host OS than memory.
<in docker container>
docker$ cd /home/linux-X.Y.Z
docker$ make -j4
<compiles kernel>
<in host>
host$ cd $top_dir
host$ ./scripts/reboot_kernel.sh
...
guest$ <in guest shell>
<in host>
host$ cd $top_dir
host$ tail -f klogs/ftrace/ftrace.log
<see kernel control flow in realtime>
...
host$ tail -f klogs/dmesg/dmesg.log
<see kernel messages in realtime>
All logs are archived with date in klogs folder. Or in other words, logs are rotated on a daily basis.
<in host>
host$ cd $top_dir
host$ tail -f klogs/qemu/qemu.log
<see qemu log in realtime>
All logs are archived with date in klogs folder. Or in other words, logs are rotated on a daily basis.
<in host>
host$ kill `pgrep qemu`
<guest linux kernel>
...
/init
/sbin/init
/etc/inittab <modified>
/etc/init.d/rcS
/etc/init.d/tc-config <modified>
/sbin/getty
/sbin/autologin <modified>
/root/.profile <modified>
...
<guest shell>
I wanted the smallest possible userspace devoid of GUI for the guest to avoid slowing down other processes on the host OS. I found Tinycore Linux and decided to use its Busybox userspace after some modifications including disabling udevadm
and starting a background process immediately after booting the guest to emit ftrace
to ttyS1
. debootstrap
is another alternative. The guest userspace is available for download.
extract_rootfs.sh
extracts a cpio archive including Busybox userspace into docker containermake_initramfs.sh
builds initramfs as a cpio archive including Busybox userspace
<in docker container>
docker$ cd /home
docker$ ./scripts/extract_rootfs.sh
<extracts guest userspace to /home/rootfs>
docker$ cd /home/rootfs
<make changes>
docker$ cd /home
docker$ ./scripts/make_initramfs.sh
<compresses guest userspace to /home/userspace>