Table of content:

prerequisites

root file system

To run a minimal riscv linux, we need a rootfs firstly. Here I polulate rootfs with busybox, a tiny version of many Unix utilities, and some other must included directories and files.

Turn on static link option and compile:

cd busybox
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig

# now open kconfig, select Settings -->
#                   Build static binary (no shared libs)
#                   Press Y
#                   save and exit
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- menuconfig

# make
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j $(nproc)
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- install

Now we have many riscv64 binaries in _install which will run on riscv linux and be as part of the root file system.

It look like this:

tree . -d 1
> .
> ├── bin
> ├── sbin
> └── usr
>     ├── bin
>     └── sbin

next, create another directories:

mkdir dev proc etc lib tmp

All these minimum set of directories is suggested by linux docs.

Note that the lib is not necessary because we statically compile the busybox. And tmp is also not necessary.

We leave the directories we create empty except for dev in which we must create devices to be used by binaries:

# create new devices with `mknod`
cd dev
sudo mknod console c 5 1 
cd ..

# or cp from your linux machine's dev directories
sudo cp -R /dev/console dev

and don’t foget a init program to tell kernel where to begin with.

save these script as init:

#!/bin/sh

mount -t proc none /proc

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"

make it be execuable:

chmod +x init

Now we have a minimum root file system to be run by riscv linux.

Compile linux kernel

The simplest way to run linux in qemu is use initramfs which allocate ram for the above root file system to init kernel. After linux 2.6, the linux kernel always create a gzipped cpio format initramfs archive and links it into the resulting kernel Image. Note that this archive is empty by default which is why we create it above from busybox.

To use generate the customized initramfs from root file system we create above, we firstly should do some config:

cd linux
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig

# open kconfig and select general setup ---> and find
#                  Initial RAM filesystem and RAM disk(initramfs/initrd) support
#                  press Y 
#                  then select Initramfs source file(s)
#                  press enter
#                  input path to your rootfs(../busybox/_install)
#                  save and exit
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- menuconfig

# make
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j $(nproc)

Now we have the linux Image to boot in linux/arch/riscv/boot/.

run in qemu

Now we can run the compiled riscv linux in qemu:

qemu-system-riscv64 \
-machine virt \
-nographic \
-kernel arch/riscv/boot/Image

A riscv linux run in qemu look like this:

...
[    0.440329] usbcore: registered new interface driver usbhid
[    0.440569] usbhid: USB HID core driver
[    0.442874] NET: Registered protocol family 10
[    0.455901] Segment Routing with IPv6
[    0.456785] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
[    0.459711] NET: Registered protocol family 17
[    0.461160] 9pnet: Installing 9P2000 support
[    0.461787] Key type dns_resolver registered
[    0.462668] debug_vm_pgtable: [debug_vm_pgtable         ]: Validating architecture page table helpers
[    0.498488] Freeing unused kernel memory: 3292K
[    0.500666] Run /init as init process

Boot took 0.57 seconds

/bin/sh: can't access tty; job control turned off
~ #
~ #
~ # ls
bin      etc      lib      linuxrc  proc     sbin     usr
dev      init     lib64    mnt      root     sys
~ #

other details

initrd

In fact, you can leave default config and compile linux kernel and specify initramfs using qemu option like this:

qemu-system-riscv64 \
-machine virt \
-nographic \
-kernel arch/riscv/boot/Image
-initrd /path/to/initramfs.cpio

Here the initramfs.cpio can be created from the root file system above like this:

cd ../busybox/_install
find . | cpio -H newc -o > ../../initramfs.cpio

with initrd, the initramfs defaultly linded in kernel Image will be override.

hello kernel

We can make kernel run any program other than /bin/sh.

cat > hello.c << EOF
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
  printf("Hello world!\n");
  sleep(999999999);
}
EOF
riscv64-linux-gnu-gcc -static hello.c -o init
echo init | cpio -o -H newc | gzip > test.cpio.gz 
# Testing external initramfs using the initrd loading mechanism.
qemu -kernel /boot/vmlinuz -initrd test.cpio.gz /dev/zero

Here the kernel find the hello binary named init and execute it to print Hello World!. This small root file system with only one init binary is truely a minimal riscv linux!.

reference