QEMU – Linux Kernel & File System Setup

by Carlos on September 21, 2024

in Linux

Abstract

The following shows how to create a Linux file system and populating it using BusyBox, compiling the Linux kernel and running this on QEMU for ARM. In the second half we’ll use Buildroot to abstract all of this.

Installing QEMU

Runt the following (Ubuntu) command:

sudo apt install qemu-system-arm

Manual Configuration

Create a base directory and cd into it:

mkdir ~/QEMU_Manual
cd ~/QEMU_Manual

Step 1: ARM Cross Compilation Toolchain

1) Download the arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-linux-gnueabihf.tar.xz toolchain.

2) Extract the tar file:
Note: Replace arm-gnu-toolchain-**** with your actual directory name

tar -xf arm-gnu-toolchain-****.tar.xz -C ~/QEMU_Manual

3) Once extracted cd into ~/QEMU_Manual/arm-gnu-toolchain-****/bin

4) Export the following variable. This is used to tell the build system which compiler when compiling code for the ARM architecture:

export CROSS_COMPILE=$(pwd)/arm-none-linux-gnueabihf-

Step 2: Linux Kernel

1) cd into ~/QEMU_Manual/

2) Clone the Linux Kernel

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 

2) cd into the linux directory

3) Export the following variable.

export ARCH=arm 

4) Generate a default configuration file. A configuration file is used to define the settings and behavior of software, system components, or the operating system itself. The following command will generate this configuration file based on the one that exists under linux/arch/arm64/configs/defconfig:

make defconfig

5) Build the Linux kernel:

make -j$(nproc)

6) This will generate the following image (Image) and a compressed image (zImage) under linux/arch/arm/boot/:

Step 3: Busy Box

1) Download BusyBox

2) Extract the tar file into ~/QEMU_Manual/

3) cd into the extracted busybox directory

4) Generate a default configuration file:

make defconfig

5) Update the configuration file:

make menuconfig

6) Select: Settings -> Build static binary (no shared libs)

7) Build BusyBox

make -j$(nproc)

8) Copy everything under the install folder:

make -j$(nproc) install

Step 4: Creating the Root File System

1) cd into ~/QEMU_Manual

2) Create the following (minimum) directories required for the Linux file system:

mkdir -p rootfs/{bin,sbin,etc,proc,sys,dev,lib,tmp,usr/{bin,sbin}}

3) Copy everything from busy box into rootfs:
Note: Replace busybox**** with your actual directory name

cp -a ./busybox****/_install/* ./rootfs

4) cd into rootfs

5) Create an init symbolic link. This will be executed when the kernel tries to call the init function:

ln -sf bin/busybox init

4) cd into dev

5) Create the following character device files:

sudo mknod -m 660 mem c 1 1
sudo mknod -m 660 tty2 c 4 2
sudo mknod -m 660 tty3 c 4 3
sudo mknod -m 660 tty4 c 4 4

4) cd back into rootfs

5) Create a compressed root file system:

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
  • Find all files in the current directory and its subdirectories.
  • Create a cpio archive of these files in the newc format.
  • Compress the archive using gzip with maximum compression.
  • Save the compressed archive to ../rootfs.cpio.gz.rootfs.cpio.gz contains a minimal root filesystem that is loaded into memory at boot time. This is the initial ramdisk.

Step 5: Run QEMU

1) cd into ~/QEMU_Manual/

2) Kick off QEMU by running the following command:
Note: QEMU can be exited by pressing Ctrl+A x

qemu-system-arm -M virt -m 256M -kernel linux/arch/arm/boot/zImage -initrd rootfs.cpio.gz -nographic

BuildRoot

Create a base directory and cd into it:

mkdir ~/QEMU_Buildroot
cd ~/QEMU_Buildroot

1) Download Buildroot

git clone https://git.buildroot.net/buildroot
cd buildroot

2) Configure Buidlroot by running the configuration menu

make menuconfig

3) Set the following:

  • Target architecture: Set this to ARM:
    • Target options -> (Enter) Target Architecture -> ARM (little endian)
  • Kernel: (Optional) Enable building of the kernel if you want Buildroot to handle that too, otherwise you can use the cloned Linux repo from the Manual Configuration steps:
    • Kernel -> (Select) Linux Kernel
    • Kernel -> (Enter) Kernel configuration (Use the architecture default configuration)
    • Kernel -> (Enter) Kernel version (Latest version (6.10))
  • Filesystem images: enable the creation of an initramfs (initrd) or CPIO filesystem:
    • Filesystem images -> (Select) cpio the root filesystem (for use as an initial RAM filesystem) -> Compression method (gzip)
  • BusyBox: Buildroot includes BusyBox by default

4) Build the system: This will download the required packages, coress-compile everything for ARM and generate the output files. The build process may take some time, depending on your system and the configuration:

make -j$(nproc)

5) Once the build is complete, you’ll find the output files in the buildroot/output/images directory.

The root filesystem will be in a compressed CPIO format, which can be used as the initrd. For example, the file rootfs.cpio.gz. If you enabled the kernel build, you’ll also find the kernel image (zImage and/or Image), which you can pass to QEMU.

5) cd back into ~/QEMU_Buildroot

6) Kick off QEMU by running the following command:
Note: QEMU can be exited by pressing Ctrl+A x

qemu-system-arm -M virt -m 256M -kernel buildroot/output/images/zImage -initrd buildroot/output/images/rootfs.cpio.gz -nographic 

Previous post: