# 2019q1 Homework4 (riscv)
contributed by < `cjwind` >
## Check List
### Virtio
**[riscv-emu](https://github.com/sysprog21/riscv-emu) 原始程式碼中多次出現 [virtio](https://www.linux-kvm.org/page/Virtio),這樣的機制對於 host 和 guest 兩端有何作用?在閱讀 [Virtio: An I/O virtualization framework for Linux](https://www.ibm.com/developerworks/library/l-virtio/index.html) 一文後,對照原始程式碼,你發現什麼?**
> Rather than have a variety of device emulation mechanisms (for network, block, and other drivers), virtio provides a common front end for these device emulations to standardize the interface and increase the reuse of code across the platforms.
Virtio 是個共通的 virtual IO device emulation 介面,IO device 包含 network、block...等等。它讓 guest OS 跟 hypervisor 在 IO device 上有共通的 interface。在 paravirtualization 下 guest OS 依照 virtio interface implement front-end driver 、hypervisor implement back-end driver 來 emulate IO device。
只要實作 virtio 這個 interface,guest OS 可以跑同樣實作 virtio interface 但不同的 hypervisor 上。IO device emulation 也可以有一致的 interface,不會一種 device 一種 emulation。
[include/linux/virtio.h](https://elixir.bootlin.com/linux/v5.0/source/include/linux/virtio.h) 定義了 `struct virtqueue`、`struct virtio_device`、`struct virtio_driver` 以及許多 `virtqueue_` 開頭的操作 function(與文章描述的不同,已經沒有 `struct virtqueue_ops` 了)
`struct virtio_driver` 定義了幾個 callback function 的 function pointer,其中 `probe` 會在 device 被找到時 call。
以 [virtio_blk.c](https://elixir.bootlin.com/linux/v5.0/source/drivers/block/virtio_blk.c) 來看 front-end driver:定義 virtio 的 callback function、定義 `struct virtio_driver virtio_blk` 後在 [init()](https://elixir.bootlin.com/linux/v5.0/source/drivers/block/virtio_blk.c#L1033) 裡以 `register_virtio_driver()` 註冊。device 被找到時,`virtblk_probe()` 會 call,裡面會 initialize virqueue。code 裡有很多地方用 `virtqueue_*` function 操作 virtqueue。
:::warning
原本預期 [riscv-emu](https://github.com/sysprog21/riscv-emu) 會有 back-end driver 的實作,雖然有 `virtio.h` 跟 `virtio.c`,但看不太出來 virtio 的 interface 在哪?
:::
### Cross-compile
**透過 `$ temu root-riscv64.cfg`, 我們在 RISCV/Linux 模擬環境中,可執行 `gcc` 並輸出對應的執行檔,而之後我們則執行 `riscv64-buildroot-linux-gnu-gcc`,這兩者有何不同? (提示: cross-compiler, 複習 [你所不知道的 C 語言: 編譯器和最佳化原理篇](https://hackmd.io/s/Hy72937Me)**
[buildroot 已經不支援在 target 裡安裝 compiler](https://buildroot.org/downloads/manual/manual.html#faq-no-compiler-on-target),才有 [TinyEMU RISC-V Buildroot](https://bellard.org/tinyemu/buildroot.html) 讓 compiler 可以裝進 target。使用 TinyEMU buildroot 的 compile 選項要打開:
* Target packages -> Development tools -> gcc_target2
* Target packages -> Libraries -> Other -> gmp 及 mpc
編出來的 root filesystem 裡才有 gcc。
用 build 出來的環境 compile `hello.c`:
```
~# gcc hello.c -o hello
/usr/libexec/gcc/riscv64-buildroot-linux-gnu/7.3.0/cc1: error while loading shared libraries: libmpc.so.3: cannot open shared object file: No such file or directory
```
可是看 `/usr/lib` 裡有 `libmpc.so.3.0.0` 啊...幫它指定一下 `LD_LIBRARY_PATH`:
```
~# LD_LIBRARY_PATH=/usr/lib gcc hello.c
gcc: error trying to exec 'as': execvp: No such file or directory
```
再把以下選項打開:
* Target packages -> Development tools -> binutils
* Target packages -> Development tools -> binutils binaries
終於成功編譯:
```
~# LD_LIBRARY_PATH=/usr/lib gcc -o hello hello.c
~# ./hello
Hello!
```
在 RISCV/Linux 模擬環境中執行 gcc,就跟平常在 x86 用 gcc 編譯一樣,只是平台變成 RISC-V。而後來在 host 執行的 `riscv64-buildroot-linux-gnu-gcc`,則是在 x86 平台下編譯出 RISC-V 的可執行檔,也就是 cross-compile。可以用 `file` 觀察編譯出來的可執行檔:
```
$ file hello
hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, not stripped
```
### 讓 Guest 透過 Host 連上網路
**在 [TinyEMU System Emulator by Fabrice Bellard](https://bellard.org/tinyemu/readme.txt) 提到 "Network block device",你能否依據說明,嘗試讓 guest 端透過 host 存取到網際網路呢?(tap, bridge, NAT, iptables)**
修改 `netinit.sh` 實際連到網路的 interface 為 eth0 後在 host 以 `sudo` 執行 script:
```
# host network interface connected to Internet (change it)
internet_ifname="eth0"
# setup bridge interface
ip link add br0 type bridge
# create and add tap0 interface to bridge
ip tuntap add dev tap0 mode tap user $USER
ip link set tap0 master br0
ip link set dev br0 up
ip link set dev tap0 up
ifconfig br0 192.168.3.1
# setup NAT to access to Internet
echo 1 > /proc/sys/net/ipv4/ip_forward
# delete forwarding reject rule if present
# iptables -D FORWARD 1
iptables -t nat -A POSTROUTING -o $internet_ifname -j MASQUERADE
```
VM 設定檔的網路設定改為:
```
eth0: { driver: "tap", ifname: "tap0" }
```
在 VM 中設定網路:
```
~# ifconfig eth0 192.168.3.2
~# route add -net 0.0.0.0 gw 192.168.3.1 eth0
```
指定 eth0 的 IP 為 192.168.3.2,設定 routing 在要去其他地方時走 gateway 192.168.3.1(也就是 host)。
接著在 guest `ping 8.8.8.8`,沒通。但 guest 可以 ping 到 192.168.3.1,表示有到 host,只是出不去,問題可能在 `iptables`。
```
$ sudo iptables -L -v -n -t nat
Chain PREROUTING (policy ACCEPT 10 packets, 1019 bytes)
pkts bytes target prot opt in out source destination
4 240 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 2 packets, 347 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 192 packets, 22798 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 35 packets, 7472 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
199 20160 MASQUERADE all -- * eth0 0.0.0.0/0 0.0.0.0/0
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
```
`POSTROUTING` 看起來沒什麼不對。
```
$ sudo iptables -L -v -n
Chain INPUT (policy ACCEPT 6601 packets, 4177K bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy DROP 30 packets, 2520 bytes)
pkts bytes target prot opt in out source destination
30 2520 DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
30 2520 DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT 7142 packets, 1059K bytes)
pkts bytes target prot opt in out source destination
Chain DOCKER (1 references)
pkts bytes target prot opt in out source destination
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
pkts bytes target prot opt in out source destination
0 0 DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
30 2520 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
pkts bytes target prot opt in out source destination
0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
30 2520 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
```
host 有裝 docker,在 guest ping 外面的同時,FORWARD chain 的 `DOCKER-USER` 跟 `DOCKER-ISOLATION-STAGE-1` target 的 packets 一直往上跳,看起來封包在這邊被丟掉了。
把那兩個 docker target 砍掉,還是不通。
```
Chain FORWARD (policy DROP 15 packets, 1260 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
```
看起來是沒有 match 的 rule 所以走到 policy DROP,還是被丟掉了。
加個限制寬鬆的 ACCEPT rule:`$ sudo iptables -A FORWARD -j ACCEPT`
```
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
6 504 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0
```
```
[root@localhost ~]# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=54 time=11.874 ms
64 bytes from 8.8.8.8: seq=1 ttl=54 time=11.862 ms
64 bytes from 8.8.8.8: seq=2 ttl=54 time=10.823 ms
64 bytes from 8.8.8.8: seq=3 ttl=54 time=11.718 ms
```
成功囉!從 guest 可以 ping 到 8.8.8.8 了。不過沒設 DNS,無法 `ping www.google.com`。
## 讓 kilo 在 RISC-V/Linux 環境中運作
在 host 以 cross-compiler 編譯 [kilo](https://github.com/sysprog21/kilo) 編輯器:
```
$ cd kilo
$ ../buildroot-riscv-2018-10-20/output/host/usr/bin/riscv64-buildroot-linux-gnu-gcc kilo.c -o kilo
```
在 guest mount 相關 directory 後執行 `./kilo README.md`:

## 客製化 buildroot
**客製化 buildroot,將 [kilo](https://github.com/sysprog21/kilo) 編輯器加入到 build image,建立 Git repository 來追蹤你對 buildroot 做的變更,並依據 [Buildroot Manual](https://buildroot.org/downloads/manual/manual.html) 更新套件描述和建構方式**
建立 `package/kilo/`,新增 `package/kilo/Config.in`:
```
config BR2_PACKAGE_KILO
bool "kilo"
help
This is simple editor kilo.
https://github.com/sysprog21/kilo
```
在 `package/Config.in` 的 Text editors and viewers 加入
```
source "package/kilo/Config.in"
```
這樣就能在 `make menuconfig` 找到 kilo。
依照 [generic-package tutoria](https://buildroot.org/downloads/manual/manual.html#generic-package-tutorial) 增加 `package/kilo/kilo.mk`:
```
################################################################################
#
# kilo
#
################################################################################
KILO_VERSION = 1.0
KILO_SOURCE = kilo-$(KILO_VERSION).tar.gz
KILO_SITE = https://github.com/cjwind/07-riscv/raw/master
KILO_LICENSE = BSD 2
KILO_LICENSE_FILES = LICENSE
define KILO_BUILD_CMDS
$(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) all
endef
define KILO_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0755 $(@D)/kilo $(TARGET_DIR)/usr/bin
endef
$(eval $(generic-package))
```
其中要注意的是 build command 要有 `$(TARGET_CONFIGURE_OPTS)`,才會帶入 cross-compiler 的相關參數,也才會以 cross-compiler compile kilo,不然會編出 `x86-64` 的可執行檔。
另外可以用 `make kilo-dirclean` 跟 `make kilo-rebuild` 單獨清除及重編 kilo。
最後加入 `package/kilo/kilo.hash` 做下載 tarball 的檢查:
```
# Locally calculated
sha256 e9ad2d5c7f6c625ac0693bf4ae28640c9fd88cc0a0092f1f3e3b20a19a111739 kilo-1.0.tar.gz
```
---
## 自我檢查清單
* 在 Guest 端透過 `$ dmesg | grep 9pnet` 命令,我們可發現 `9P2000` 字樣,這和上述 [VirtFS](https://wiki.qemu.org/Documentation/9psetup) 有何關聯?請解釋運作原理並設計實驗
* 最初實驗輸入 `$ temu https://bellard.org/jslinux/buildroot-riscv64.cfg`,然後就能載入 RISC-V/Linux 系統,背後的原理是什麼呢?請以 VirtIO 9P 檔案系統和 [riscv-emu](https://github.com/sysprog21/riscv-emu) 對應的原始程式碼來解說
> TinyEMU supports the VirtIO 9P filesystem to access local or remote filesystems. For remote filesystems, it does HTTP requests to download the files.
> The protocol is compatible with the vfsync utility. In the "mount" command, "/dev/rootN" must be used as device name where N is the index of the filesystem. When N=0 it is omitted.
* [riscv-emu](https://github.com/sysprog21/riscv-emu) 內建浮點運算模擬器,使用到 [SoftFP Library](https://bellard.org/softfp/),請以 `sqrt` 為例,解說 `sqrt_sf32`, `sqrt_sf64`, `sqrt_sf128` 的運作機制,以及如何對應到 RISC-V CPU 模擬器中
* 在 `root-riscv64.cfg` 設定檔中,有 `bios: "bbl64.bin"` 描述,這用意為何?提示:參閱 [Booting a RISC-V Linux Kernel](https://www.sifive.com/blog/all-aboard-part-6-booting-a-risc-v-linux-kernel)
* 能否用 buildroot 編譯 Linux 核心呢?請務必參閱 [Buildroot Manual](https://buildroot.org/downloads/manual/manual.html)
* `BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="/tmp/diskimage-linux-riscv-2018-09-23/patches/config_linux_riscv64"`
* 核心啟動的參數 `console=hvc0 root=/dev/vda rw` 代表什麼意思呢?這對應到模擬器內部設計的哪些部分?
* `$ cat /proc/loadavg` 的輸出意義為何?能否對應到 Linux 核心原始碼去解釋呢? (提示: 熟悉的 fixed-point 操作)
* 為何需要在 host 端準備 e2fsprogs 工具呢?具體作用為何呢?
* root file system 在 Linux 核心的存在意義為何?而 [initramfs](http://blog.linux.org.tw/~jserv/archives/001954.html) 的存在的考量為何?
* busybox 這樣的工具有何作用?請搭配原始程式碼解說 (提示: 參見 [取得 GNU/Linux 行程的執行檔路徑](http://blog.linux.org.tw/~jserv/archives/002041.html))
## 作業要求
* 回答上述「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳
* 調整 buildroot 設定,讓原本輸出的 ext2 image 佔用約 14 MB 空間,變得更精簡
* 移除套件,但確保仍可開機並提供必要的服務及工具
* Busybox 裡頭的 vi 也不需要存在,改用 kilo
* 重新編譯 Linux 核心,確保能在 [riscv-emu](https://github.com/sysprog21/riscv-emu) 運作,並思考縮減 image size 的方法
* 先從 `$ make menuconfig` 調整核心選項開始,當然要能開機才有效益
* 參照 [Linux Kernel Tinification](https://tiny.wiki.kernel.org)
###### tags: `Linux 核心設計 2019`