使用 Busybox 建立 RISC-V 的迷你系統

RISC-V 初探 一文中我們講到了如何使用 riscv-poky 去產生你的 RISC-V 系統,那如 果我們不想要透過 Yocto 去建立我們的 RISC-V Linux 系統,這樣子要怎樣建立迷你的系 統呢? 答案就是 Busybox

Busybox 是一個非常有趣的程式,舉凡我們在 Linux 下最常用的命令如 ls、cd 等到 sed、 vi 他都具有相對應的簡單實現,此外,這些命令實際上都只是一個軟連結 (symlink) 連結 到名為 busybox 的執行檔,也就是說,如果我們將 busybox 進行靜態編譯 (static link), 則製作出來的系統整體大小大約為 2 MB (kernel) + 1.4 MB (busybox),而這個系統卻又 可以具有許多 UN*X 下的常用命令,也因此 busybox 很常用於空間有限的系統。

本文將講述如何編譯 RISC-V 的 Linux toolchain、Linux Kernel 以及 Busybox 來產生一 個迷你的 Linux 系統。

編譯 RISC-V Linux Toolchain

RISC-V 初探 一文中我們說到了如何編譯 riscv-tools 套件去產生開發用的 toolchain 與模擬器,在當時所產生的開發環境中其實不包含 RISC-V Linux 用的 toolchain,所以我們需要自己動手去編譯他。

首先進入 riscv-gnu-toolchain 這個目錄,我們需要額外編譯的 toolchain 就在這裡

coldnew@Rosia ~/riscv-tools $ cd riscv-gnu-toolchain

接著執行 configure ,其中 RISCV 環境變數為你想要安裝此 toolchain 的路徑

coldnew@Rosia ~/riscv-tools/riscv-gnu-toolchain $ ./configure --prefix=$RISCV

接下來就是編譯與慢長的等待

coldnew@Rosia ~/riscv-tools/riscv-gnu-toolchain $ make linux

當編譯完成後,你會發現在 $RISCV/bin 下面是不是多了 riscv64-unknown-linux-gnu-* 這一類程式

coldnew@sherry ~/riscv-tools/riscv-gnu-toolchain $ ls $RISCV/bin/
elf2hex                         riscv64-unknown-elf-ld.bfd           riscv64-unknown-linux-gnu-gcc-ranlib
fesvr-eth                       riscv64-unknown-elf-nm               riscv64-unknown-linux-gnu-gcov
fesvr-rs232                     riscv64-unknown-elf-objcopy          riscv64-unknown-linux-gnu-gcov-tool
fesvr-zedboard                  riscv64-unknown-elf-objdump          riscv64-unknown-linux-gnu-gfortran
riscv64-unknown-elf-addr2line   riscv64-unknown-elf-ranlib           riscv64-unknown-linux-gnu-gprof
riscv64-unknown-elf-ar          riscv64-unknown-elf-readelf          riscv64-unknown-linux-gnu-ld
riscv64-unknown-elf-as          riscv64-unknown-elf-size             riscv64-unknown-linux-gnu-ld.bfd
riscv64-unknown-elf-c++         riscv64-unknown-elf-strings          riscv64-unknown-linux-gnu-nm
riscv64-unknown-elf-c++filt     riscv64-unknown-elf-strip            riscv64-unknown-linux-gnu-objcopy
riscv64-unknown-elf-cpp         riscv64-unknown-linux-gnu-addr2line  riscv64-unknown-linux-gnu-objdump
riscv64-unknown-elf-elfedit     riscv64-unknown-linux-gnu-ar         riscv64-unknown-linux-gnu-ranlib
riscv64-unknown-elf-g++         riscv64-unknown-linux-gnu-as         riscv64-unknown-linux-gnu-readelf
riscv64-unknown-elf-gcc         riscv64-unknown-linux-gnu-c++        riscv64-unknown-linux-gnu-size
riscv64-unknown-elf-gcc-5.2.0   riscv64-unknown-linux-gnu-c++filt    riscv64-unknown-linux-gnu-strings
riscv64-unknown-elf-gcc-ar      riscv64-unknown-linux-gnu-cpp        riscv64-unknown-linux-gnu-strip
riscv64-unknown-elf-gcc-nm      riscv64-unknown-linux-gnu-elfedit    spike
riscv64-unknown-elf-gcc-ranlib  riscv64-unknown-linux-gnu-g++        spike-dasm
riscv64-unknown-elf-gcov        riscv64-unknown-linux-gnu-gcc        termios-xspike
riscv64-unknown-elf-gcov-tool   riscv64-unknown-linux-gnu-gcc-5.2.0  xspike
riscv64-unknown-elf-gprof       riscv64-unknown-linux-gnu-gcc-ar
riscv64-unknown-elf-ld          riscv64-unknown-linux-gnu-gcc-nm

到此,我們就擁有了 RISC-V 的 Linux toolchain 了

編譯 RISC-V Linux Kernel

RISC-V 官方提供了基於 Linux 3.14 (LTS) 版本的移植,和 RISC-V 相關的移植位於 riscv/riscv-linux 裡面,由於 GitHub 上面只包含的 RISC-V 的移植,因此我們還需要下 載 Linux 3.14.x 的原始碼才行,使用以下命令下載 Linux 3.14.41 並將 RISC-V 的移植 加入進去:

curl -L https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.14.41.tar.xz | tar -xJ
cd linux-3.14.41
git init
git remote add origin https://github.com/riscv/riscv-linux.git
git fetch
git checkout -f -t origin/master

程式碼取得後,別忘記將 $RISCV/bin 加入到你的環境變數

coldnew@Rosia ~/linux-3.14.41 $ export PATH=$RISCV/bin:$PATH

接著我們就可以使用預設的設定去編譯 Linux Kernel

coldnew@Rosia ~/linux-3.14.41 $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make defconfig

如果你喜歡客製化,也可以使用 menuconfig 去加/減你的 Linux Kernel 設定

coldnew@Rosia ~/linux-3.14.41 $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make menuconfig

設定都完成後,就是編譯 Linux Kernel 的時候了,這邊我們只需要編譯 vmlinux 就好

coldnew@Rosia ~/linux-3.14.41 $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make vmlinux

編譯完成後,你會在當前目錄下看到 vmlinux 這個檔案

coldnew@Rosia ~/linux-3.14.41 $ file vmlinux
vmlinux: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked,
BuildID[sha1]=3fa9623740f49023785338f0855ccf024f416ab5, not stripped

我們可以使用 spike 去嘗試啟動這個 kernel 看看

coldnew@Rosia ~/linux-3.14.41 $ spike bbl vmlinux
...
[    0.000000] Linux version 3.14.41-ga2f247d (coldnew@sherry) (gcc version 5.2.0 (GCC) ) #1 Tue Sep 15 16:13:41 2015
[    0.000000] Detected 0x7fc00000 bytes of physical memory
[    0.000000] Initial ramdisk at: 0xffffffff80011a28 (134 bytes)
[    0.000000] Zone ranges:
[    0.000000]   Normal   [mem 0x00200000-0x7fdfffff]
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x00200000-0x7fdfffff]
[    0.000000] Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 516110
[    0.000000] Kernel command line: root=/dev/htifblk0
...
[    0.150000] CPU: 0 PID: 1 Comm: swapper Not tainted 3.14.41-ga2f247d #1
[    0.150000] Call Trace:
[    0.150000] [<ffffffff80013f54>] walk_stackframe+0x0/0xc8
[    0.150000] [<ffffffff801bf70c>] panic+0xb4/0x1c4
[    0.150000] [<ffffffff80000f64>] mount_block_root+0x270/0x2f8
[    0.150000] [<ffffffff80001190>] prepare_namespace+0x134/0x180
[    0.150000] [<ffffffff80000b24>] kernel_init_freeable+0x1a0/0x1d8
[    0.150000] [<ffffffff801bf120>] rest_init+0x80/0x84
[    0.150000] [<ffffffff801bf134>] kernel_init+0x10/0xf4
[    0.150000] [<ffffffff801bf120>] rest_init+0x80/0x84
[    0.150000] [<ffffffff80012bb8>] ret_from_syscall+0x10/0x14

當然,這個時候我們並未提供 Linux Kernel 可以進入的系統,因此會出現 kernel panic 的訊息。

使用 Hello, World 建立最簡易系統

為了避免開機的時候會出現 kernel panic 的訊息,要稍微了解一下 Linux 的開機流程。 當 kernel 載入完成後會去嘗試掛載 rootfs 並執行 /sbin/init 這一隻程式,這個程式 也就是整個系統的第一隻被執行的 user space 程式 (PID: 1)。(這隻程式也可以透過修 改開機參數 init=/init 來設定其他的位置)

也就是說,在預設的狀況下,我們可以建立一個簡單的程式並將其置放在 rootfs 的 /sbin/init ,這樣開機的時候就會被 Linux Kernel 載入,我們可以使用人見人愛的 Hello, World! 來測試看看,首先建立 hello.c,並將以下內容填入:

#include <stdio.h>

int main(int argc, char *argv[])
{
        printf("Hello, RISC-V\n");

        while(1);
        return 0;
}

將其編譯為 hello 這隻執行檔,並且為靜態編譯 (static linked)

coldnew@Rosia ~/linux-3.14.41 $ riscv64-unknown-linux-gnu-gcc hello.c -o hello -static

我們向系統索取 10MB 的空間來建立我們的 rootfs 文件

coldnew@Rosia ~/linux-3.14.41 $ dd if=/dev/zero of=root.ext2 bs=1M count=10

將其格式化為 ext2

coldnew@Rosia ~/linux-3.14.41 $ sudo mkfs.ext2 -F root.ext2

我們建立一個用來臨時掛載 root.ext2 用的資料夾,並將剛剛編譯出來的 hello 執行檔複 製為 /sbin/init ,於是 rootfs 就完成了

mkdir -p /tmp/root
sudo mount root.ext2 /tmp/root
mkdir -p /tmp/root/sbin
cp hello /tmp/root/sbin/init
sudo umount /tmp/root

完成 rootfs 後,我們可以用 spike 去執行看看,就會發現到 Kernel 執行到了我們的 Hello, World 程式

coldnew@Rosia ~/linux-3.14.41 $ spike +disk=root.ext2 bbl vmlinux
...
[    0.150000] console [htifcon0] enabled
[    0.150000] htifblk htif2: detected disk
[    0.150000] htifblk htif2: added htifblk0
[    0.150000] TCP: cubic registered
[    0.150000] VFS: Mounted root (ext2 filesystem) readonly on device 254:0.
[    0.150000] devtmpfs: mounted
[    0.150000] Freeing unused kernel memory: 72K (ffffffff80000000 - ffffffff80012000)
Hello, RISC-V

使用 Busybox 來建立我們的迷你系統

理解了如何使用 Hello, World 建立最簡易的 rootfs 後,這次我們來使用 busybox 來建 立我們的 rootfs,首先先從官網下載 busybox 程式碼

coldnew@Rosia ~ $ git clone git://git.busybox.net/busybox

切換到穩定版本

coldnew@Rosia ~/busybox $ git checkout -b 1_23_stable origin/1_23_stable

進行我們自己的設定

coldnew@Rosia ~/busybox $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make menuconfig

在進行設定時有以下幾點要確實注意,我們要將 busybox 編譯為靜態連結,並且增加 init 功能,主要設定如下:

Busybox Settings  --->
        Build Options  --->
               [*] Build BusyBox as a static binary (no shared libs)

Init Utilities  --->
        [*] init

Networking Utilities  --->
        [ ] inetd

Shells  --->
        [*] ash

設定完成後開始進行編譯

coldnew@Rosia ~/busybox $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make

編譯完成後透過 make install 命令,會將編譯出來的 busybox 與軟連結(symlink)產生 在 _install 資料夾內

coldnew@Rosia ~/busybox $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make install

我們將剛剛測試用的 root.ext2 掛載到 /tmp/root 資料夾下,並將 _install 資料夾內的東西全部複製過去

coldnew@Rosia ~/busybox $ rsync -avr _install/* /tmp/root

建立一些缺少的資料夾 (/dev、/sys …etc)

coldnew@Rosia ~/busybox $ cd /tmp/root && mkdir -p proc sys dev etc/init.d

建立 etc/init.d/rcS 作為啟動腳本,並添加以下內容

coldnew@Rosia ~/busybox $ vim /tmp/root/etc/init.d/rcS

#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s

etc/init.d/rcS 加入可執行權限

coldnew@Rosia ~/busybox $ chmod +x /tmp/root/etc/init.d/rcS

解除掛載 root.ext2,這樣我們的 rootfs 就完成了

coldnew@Rosia ~/busybox $ sudo umount /tmp/root

都完成後,我們就可以用 spike 模擬系統並進入 busybox 的 shell 囉~

coldnew@Rosia ~/linux-3.14.41 $ spike +disk=root.ext2 bbl vmlinux
...
[    0.150000] htifcon htif1: detected console
[    0.150000] console [htifcon0] enabled
[    0.150000] htifblk htif2: detected disk
[    0.150000] htifblk htif2: added htifblk0
[    0.150000] TCP: cubic registered
[    0.150000] VFS: Mounted root (ext2 filesystem) readonly on device 254:0.
[    0.150000] devtmpfs: mounted
[    0.150000] Freeing unused kernel memory: 72K (ffffffff80000000 - ffffffff80012000)
mount: mounting none on /sys failed: No such device
mdev: /sys/class: No such file or directory
can't open /dev/tty4: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty2: No such file or directory

Please press Enter to activate this console.
/ #