Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create minimal driver VMs #122

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7479a8e
create a minimal linux kernel image and rootfs for the virtio example…
Anuraag-Ganesh Aug 14, 2024
7d7d7ac
4867a7c set the target flag for userlevel cc aarch64-linux-musl inste…
Anuraag-Ganesh Aug 14, 2024
206f58d
add a new option to packrootfs that saves a file to the root as init …
Anuraag-Ganesh Aug 14, 2024
fcfc705
add perl as a dependency to nix.shell for shasum
Anuraag-Ganesh Aug 14, 2024
c80e8a2
create a minimal linux kernel image and rootfs for the virtio-snd exa…
Anuraag-Ganesh Aug 14, 2024
d9f3e61
set the target flag for userlevel cc aarch64-linux-musl instead of aa…
Anuraag-Ganesh Aug 14, 2024
2931105
update nix.shell to build a static library for alsa-lib
Anuraag-Ganesh Aug 14, 2024
b9e82ec
compile the uio sound driver and link it with the alsa-lib separately…
Anuraag-Ganesh Aug 14, 2024
50e3800
use capacity instead of size
Anuraag-Ganesh Aug 16, 2024
2e9557d
update the linux kernel image for the virtio example to use same linu…
Anuraag-Ganesh Aug 16, 2024
ccaa83f
create a custom init script for the block driver VM userspace
Anuraag-Ganesh Aug 14, 2024
6713b0b
alsactl init was being called before the sound device node was create…
Anuraag-Ganesh Aug 24, 2024
8b8941c
remove old linux, linux_config, buildroot_config and init script file…
Anuraag-Ganesh Aug 24, 2024
97a0929
find the minumum amount of RAM required by the sound driver VM
Anuraag-Ganesh Aug 16, 2024
30d15dd
find the minumum amount of RAM required by the block driver VM
Anuraag-Ganesh Aug 17, 2024
240a5e8
copy the linux, linux_config and rootfs.cpio.gz from the qemu board t…
Anuraag-Ganesh Sep 8, 2024
ef1afd1
copy the linux, linux_config and rootfs.cpio.gz files from the qemu b…
Anuraag-Ganesh Sep 8, 2024
5dff2fe
comments + minor fix
Anuraag-Ganesh Sep 8, 2024
35e4f70
document the process for creating a minimal driver VM
Anuraag-Ganesh Sep 15, 2024
511edd4
updated instructions for creating a minimal rootfs and included build…
boinkaput Sep 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions docs/VIRTIO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Creating a minimal Linux VM
In certain scenarios, you may want to run only a single process or a limited set of processes within a virtual machine (VM). One common use case is the creation of a driver VM, where the primary purpose of the VM is to run a UIO (Userspace I/O) driver. In such cases, most of the typical Linux configuration is unnecessary and only adds bloat. This excess increases both the memory footprint and cache usage, which can negatively impact VM performance.

By reducing this bloat, we can optimize the VM's resource consumption, improving performance and reducing overhead. To achieve this, it's recommended to begin development and testing with a standard Linux kernel image and a root filesystem (rootfs) generated by tools like Buildroot or similar. These tools provide a more complete environment, allowing for easier debugging and development during the initial phases. Once the driver VM is stable and functioning as expected, you can begin the process of minimizing the kernel image and rootfs, stripping out unnecessary components to create a more lightweight system. This section will guide you through the process of creating a minimal linux VM.

## Minimal linux kernel image
We will be using the same linux kernel image across all the driver VMs and make use of kernel modules to add specific functionality for each of the driver VMs. You will first need to create a minimal linux kernel image by editing its configuration:

To begin, you need to create a minimal Linux kernel image by customizing its configuration. You can copy the [configuration file](/examples/virtio/board/qemu_virt_aarch64/blk_driver_vm/linux_config) from an existing minimalized driver VM and modify it to include the necessary options for your driver, primarily as kernel modules.
```sh
# Clone the linux repo
git clone --depth 1 --branch v5.18 https://github.com/torvalds/linux.git

# Copy linux_config from an existing driver VM
cp linux_config linux/.config

# This will start a terminal-oriented configuration tool
make -C linux ARCH=arm64 CROSS_COMPILE=aarch64-none-elf- menuconfig
```

Note that some kernel configurations cannot be built as modules and must be compiled directly into the kernel. In such cases, you will need to update the kernel image for all driver VMs.

Once all the required options have been selected, you can compile the kernel using:
```sh
# nproc returns the number of available processors
make -C linux ARCH=arm64 CROSS_COMPILE=aarch64-none-elf- all -j$(nproc)
```

Copy the resulting image to your driver VM directory:
```sh
cp linux/arch/arm64/boot/Image driver_vm/linux
```

## Minimal userspace
Similar to the kernel, we aim to create a minimal root filesystem (rootfs) for the driver VM. The goal is to include only the essential components, such as the UIO driver and the necessary files and directories to support its operation, minimizing unnecessary content to reduce the overall size and complexity of the system.

### Steps to create a minimal rootfs:

1. Start by setting up the basic directory layout for the root filesystem:
```sh
mkdir -p rootfs/{dev,sys,modules}
```

2. Now, compile the kernel modules that were selected during the kernel configuration:
```sh
# This will compile the kernel modules and install them in a directory called virtio_modules inside the linux directory
make -C linux ARCH=arm64 CROSS_COMPILE=aarch64-none-elf- modules_install INSTALL_MOD_PATH=./virtio_modules
```

This step will generate the kernel modules for all the drivers, including those not needed for your specific driver VM. Additionally, other metadata files (like module loading order) will be generated, but for your purposes, only the actual modules are relevant since you will load them manually in the VM.

3. Navigate to the `linux/virtio_modules/lib/modules/5.18.0/kernel` directory and locate the specific modules required for your driver VM. For example, if you're building a block driver VM, you will need the `virtio_blk.ko` module. Copy the necessary modules to your rootfs:

```sh
cp linux/virtio_modules/lib/modules/5.18.0/kernel/drivers/block/virtio_blk.ko rootfs/modules
```

4. After copying the kernel modules, include any additional files that are essential for your driver VM to operate. These could include configuration files, or additional programs. Whenever possible, it is recommended to use statically linked binaries to avoid dependencies on shared libraries. However, if static linking is not feasible, you can copy the necessary shared libraries into the rootfs to support these programs.

5. Once all necessary files are in place, you can package the rootfs into an initramfs image, which will be loaded by the VM:

```sh
cd rootfs
find . | cpio -ov -H newc | gzip > rootfs.cpio.gz
```

## Creating an Init Process for the Driver VM
Since the root filesystem (rootfs) doesn't include a shell, you'll need to create a custom init program that runs as process ID 1 (PID 1). This program will be responsible for loading the necessary kernel modules and starting the driver. It is essential that the init program has all its dependencies statically linked to avoid requiring shared libraries within the rootfs.

To help with this, we have provided a bootstrap script, [init.c](/tools/linux/uio/init.c), which handles common initialization tasks for all driver VMs, such as mounting the `/dev` and `/sys` directories. However, to adapt this script for your specific driver VM, you'll need to implement two key functions: `load_modules` and `init_main`, both of which are defined in [init.h](/tools/linux/include/uio/init.h).

- `load_modules`: This function is responsible for loading the required kernel modules. You can use the `insmod` helper (declared in [init.h](/tools/linux/include/uio/init.h)) to load each module individually.
- `init_main`: This function will start your driver. Ideally, init_main should not return unless there is an error, meaning it will keep running the driver for the duration of the VM's operation.

After implementing the init process, you need to include it in the rootfs that you previously created. For this purpose, we have provided a script called [packrootfs](/tools/packrootfs), which automates the process of packaging the rootfs along with the init program into the final initramfs image.

Once the init program is injected into the rootfs, you will have a minimal driver VM that can be run using `libvmm`.

## Minimizing the Memory Allocated to the Driver VM
To optimize the resource usage of your driver VM, the amount of allocated memory can be reduced by decreasing the size of the guest RAM region specified in the system description file. This requires a bit of trial and error to determine the minimum memory needed for the VM to function properly. The general approach involves gradually reducing the guest RAM size and verifying that the driver VM can still boot and operate as expected.

1. Start by lowering the size of the guest RAM region in the system description file.

2. Update the memory node in the DTS (Device Tree Source) file to reflect the new size. This ensures that the linux VM is aware of the updated memory allocation.

3. In the DTS, you'll also need to modify the `initrd-start` and `initrd-end` fields in the chosen node. The `initrd-start` should be a memory address that is beyond the end of the Linux kernel image, while the `initrd-end` should accommodate the full size of the initramfs. Make sure that this range is large enough to hold the entire initramfs image.

4. After updating the DTS file, modify the parameters passed to the `linux_setup_images` and `guest_start` functions in your driver VMM. These parameters need to reflect the new memory addresses for the initrd and DTB (Device Tree Blob).

By following this process, you can gradually minimize the memory allocated to your driver VM, optimizing performance while ensuring the VM still has sufficient resources to run the kernel, and the driver.
88 changes: 88 additions & 0 deletions examples/virtio-snd/board/odroidc4/snd_driver_vm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!--
Copyright 2024, UNSW
SPDX-License-Identifier: CC-BY-SA-4.0
-->

# Odroid-C4 images

## Linux kernel

### Details
* Image name: `linux`
* Config name: `linux_config`
* Git remote: https://github.com/torvalds/linux.git
* Tag: v5.18 (commit hash: `4b0986a3613c92f4ec1bdc7f60ec66fea135991f`)
* Toolchain: `aarch64-none-elf`
* Version: GNU Toolchain for the A-profile Architecture 10.2-2020.11 (arm-10.16)) 10.2.1 20201103

You can also get the Linux config used after booting by running the following
command in userspace: `zcat /proc/config.gz`.

### Instructions for reproducing
```
git clone --depth 1 --branch v5.18 https://github.com/torvalds/linux.git
cp linux_config linux/.config
make -C linux ARCH=arm64 CROSS_COMPILE=aarch64-none-elf- all -j$(nproc)
```

The path to the image is: `linux/arch/arm64/boot/Image`.

## RootFS image

### Details
* Image name: `rootfs.cpio.gz`

### Instructions for reproducing

You can find instructions for building a minimal root filesystem here [here](/docs/VIRTIO.md#minimal-userspace).

You will need to create the following directory structure in the root filesystem for the sound driver VM:
```
rootfs/
├── dev/
├── sys/
├── etc/
├── group
├── modules/
├── led-class.ko
├── snd-hda-codec-generic.ko
├── snd-hda-intel.ko
├── snd.ko
├── ledtrig-audio.ko
├── snd-hda-codec-realtek.ko
├── snd-intel-dspcfg.ko
├── soundcore.ko
├── nls_base.ko
├── snd-hda-codec.ko
├── snd-pcm.ko
├── usbcore.ko
├── snd-ctl-led.ko
├── snd-hda-core.ko
├── snd-timer.ko
├── virtio_snd.ko
├── alsa/
├── alsactl
```

`etc/group`: This file contains the group definitions. The group `audio` should be defined in this file.
```
audio:x:0:
```

`alsa/`, `alsactl`: These files are required for the ALSA sound driver. We will be using buildroot to build these dependencies.

### Details
* Image name: `rootfs.cpio.gz`
* Config name: `buildroot_config`
* Version: 2022.08-rc2

### Instructions for reproducing

```
wget https://buildroot.org/downloads/buildroot-2022.08-rc2.tar.xz
tar xvf buildroot-2022.08-rc2.tar.xz
cp buildroot_config buildroot-2022.08-rc2/.config
make -C buildroot-2022.08-rc2 alsa-utils
```

This will generate the `alsa/` and `alsactl` files, which can be found in `buildroot-2022.08-rc2/output/target/usr/share/alsa` and `buildroot-2022.08-rc2/output/target/usr/sbin/alsactl`.
Loading