# 2019q1 Homework4 (riscv)
contributed by < `flawless0714` >
## 自我檢查清單
* [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) 一文後,對照原始程式碼,你發現什麼?
- **回答**
* 透過 `$ temu root-riscv64.cfg`, 我們在 RISCV/Linux 模擬環境中,可執行 `gcc` 並輸出對應的執行檔,而之後我們則執行 `riscv64-buildroot-linux-gnu-gcc`,這兩者有何不同? (提示: cross-compiler, 複習 [你所不知道的 C 語言: 編譯器和最佳化原理篇](https://hackmd.io/s/Hy72937Me)
- **回答**
前者使用的編譯器算一般常見的編譯器,其所產生的執行檔就是給編譯這個執行檔的裝置執行的。後者使用的編譯器又稱為 cross-compiler,專用於為架構(aarch, x86)不同於 host 端的裝置編譯執行檔,e.g. 在個人電腦上為嵌入式系統(stm32f429-disc)開發程式,除非個人電腦處理器架構同於嵌入式系統(cortex-m4),否則一般都會用到 cross-compiler。
* 在 Guest 端透過 `$ dmesg | grep 9pnet` 命令,我們可發現 `9P2000` 字樣,這和上述 [VirtFS](https://wiki.qemu.org/Documentation/9psetup) 有何關聯?請解釋運作原理並設計實驗
* 在 [TinyEMU System Emulator by Fabrice Bellard](https://bellard.org/tinyemu/readme.txt) 提到 "Network block device",你能否依據說明,嘗試讓 guest 端透過 host 存取到網際網路呢?
* tap, bridge, NAT, iptables
* **回答**
看說明書說故事,在 host 中第一次執行腳本(netinit.sh)時失敗了,但重開機後就可正常執行,推測是有使用過虛擬機(qemu)的關西。另外腳本中有使用到 `ifconfig`,這個工具在2012年就已經被抽離 ubuntu 的 mainline,取而代之的是 `ip`,我沒試過直接用 `ip` 能不能達到預期功能,而是直接裝 `ifconfig` (apt install 要搜尋 net-tools),至此 host 端的準備工作已經完成,接下來要開模擬器了,但是使用 `temu test_riscv64.cfg` 時遇到問題 (`Error: could not configure /dev/net/tun`),這問題卡蠻久的..,中間還懷疑模組(`tun`)沒載入,結果最後發現是因為存取 `tun` 需要提昇權限...(也太沒常識了,看到要操作 `/dev` 下的東西就應該想到權限問題阿),啟動後再照著說明設定網路就成功了。下圖表示 guest 連線正常:

* 最初實驗輸入 `$ 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)
- 此為 bootloader,用於開機後載入 kernel。常見 bootloader 有 GRUB2 及 U-boot 等等。自訂 kernel 啟動 command 於此階段給定。
* 能否用 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 工具呢?具體作用為何呢?
- 為了要為 guest 生成 rootfs,我們會需要這套工具。
* root file system 在 Linux 核心的存在意義為何?而 [initramfs](http://blog.linux.org.tw/~jserv/archives/001954.html) 的存在的考量為何?
* **回答**
- root file system
Linux 核心在開機時期最先掛載的檔案系統就是 rootfs,其為核心提供一些作業系統執行所需的基本工具程式(/sbin, /bin)與 device file descriptor (/dev)。而 kernel 本身也有個啟動選項(root=)用於指定欲掛載為 rootfs 的 device。開機初期 `root=` 掛載的檔案系統並不是真正的 rootfs (其實是 initrd 或 initramfs),其內部僅提供簡單的驅動程式等等,e.g. 儲存裝置驅動程式,讓 kernel 有能力使用儲存裝置並從其中尋找真正的 rootfs。值得注意的是在真正的應用中,我們有可能永遠只在 ram disk 上運作(tmpfs),而不會去掛載真正的 rootfs。
- initramfs
此為 initrd 的後代,initrd 被拋棄的主要原因是因為其必須綁定檔案系統實做(如 ext2)、有空間限制導致維護繁瑣、非直接的存取(`/dev/initrd` 存在於記憶體中,每次讀寫需要重新對應記憶體)、Linux 在設計上會盡可能將讀/寫自 block device (initrd) 的檔案或目錄予以 cache,而非直接讀寫,進而導致效能低落與資源的浪費。而 initramfs 則是以 tmpfs 作為其檔案系統的實做,因此可直接做讀寫。此外,由於 tmpfs 可自行調整空間使用量的關係,所以開發者可以將一些原本實做於 kernel code 中的開機模式(LVM (Linux Volume Manager)、網路開機、特別儲存裝置的開機)、掛載(`do_mount` 一類操作)特定裝置或邏輯儲存設備的功能(`kinit`)搬到 initramfs,交由 user space (進入 initramfs 後的行為為 early user space)的應用程式專門處理,進而降低系統複雜度、提高可性賴度、降低偵錯困難性、提高分析可行性。
* busybox 這樣的工具有何作用?請搭配原始程式碼解說 (提示: 參見 [取得 GNU/Linux 行程的執行檔路徑](http://blog.linux.org.tw/~jserv/archives/002041.html))
## 筆記
### Virtio
一個存在於 host 與 guest 之間的 interface。比起其他以不同方式實做的裝置模擬機制(network, block 及其他 driver) ,Virtio 有著統一的界面,優點為 front end (guest driver) 可以不用為不同 driver 去研究其大架構的實做、並且增加不同平台間 code 的重複使用性。下圖為 Virtio 在虛擬機中的概念圖:

(圖片取自 [Virtio: An I/O virtualization framework for Linux](https://www.ibm.com/developerworks/library/l-virtio/index.html))
#### Guest 與 Host 間之通訊
雙方通過 buffer 來進行通訊。數據的讀寫是使用 [scatter-gather](https://www.itread01.com/content/1533639370.html) 的方式。通常使用多個 buffer 來通訊,e.g. 第一個 buffer 為 read request,而第二及第三個 buffer 用來儲存拿到的資料。buffer 的資料結構只有 front end 跟 back end 知道,virtio 內部傳輸 buffer 的實做僅知道欲傳輸的資料與大小。
#### Object hierarchy of front end

(圖片取自 [Virtio: An I/O virtualization framework for Linux](https://www.ibm.com/developerworks/library/l-virtio/index.html))
- `virtio_driver`
即為 guest 端的 driver,他是使用 `virtio_device` 建立的。
- `virtio_config_ops`
為各種 driver 設定 block size (以 block device 為例) 或取得/設定 device 的 state 等等。
- `virtqueue`
存放 callback function (當 host 收到資料時會呼叫,但這個 cb 是 optional 的,並且可以在執行時期關閉/開啟)的 reference。中間兩個 reference (`*vdev`,`*vq_ops`)存放 `virtio_device` 及 `virtio_config_ops` 的 callback functions。`*priv` 則存放著 driver 主要功能的實做。
- `virtqueue_ops`
整個 hierarchy 的核心,定義 command 與 data 如何在 host 與 guest 間傳輸等等。
### 檔案系統
#### specfs
全名 Special Filesystem,比起其他 fs 比較特別的地方是不須掛載點**也**可使用。character device 用的 device file 即為 specfs。
#### devfs
全名 Device Filesystem,為一以檔案系統為形式的 device manager。與 specfs 極為相似,僅在運作方式與用途有些差異。`/dev/` 中大部分的 device file 都是使用 devfs。幾乎所有 Unix 與 Unix-like 的作業系統都用此檔案系統作為 device mamager (於 kernel space 中),而特別的地方是 Liunx 使用混合(userspace-kernelspace)的方式來使用 devfs,也就是說 kernel space 與 user space 都可以存取到此虛擬檔案系統。
#### tmpfs
全名 Temporary Filesystem,為一儲存暫存檔案用的虛擬檔案系統。此檔案系統僅生存在記憶體或 swap 中,也就是說其中的資料只要斷電就會消失。通常掛載點為 `/tmp/`。
#### devtmpfs
全名 Device Temporary Filesystem,為 devfs 的進化版,主要用於加快開機時間。比起 devfs,devtmpfs 較相似於 tmpfs。通常掛載於 `/dev/`。devtmpfs 只為 local system 中可用的硬體建立 device file。
#### e2fsprogs
一套用以維護 ext2, ext3 還有 ext4 的工具集。由於這些檔案系統為大部分 Linux distro 的預設檔案系統,所以大部分 Linux distro 都有內建這套工具集。其功能有檢查檔案系統完整性、建立檔案系統、修改已建立的檔案系統(ext2、ext3...)的大小、修改檔案系統的相關參數、顯示檔案系統相關資訊、用於對檔案系統除錯(e.g. 取得某個外接裝置(fd)暫存器中的資料)。
### buildroot 建構 rootfs
#### 嘗試縮小 rootfs 大小
##### 第一次縮減
目前先移除了 `strace`、`openssl` 以及 `vi`。但遇到個尷尬的問題,輸出的 image 不但沒有縮小,反而還大了 135KB...,雖然已知佔最大的部份為 `util-linux`,但在 `menuconfig` 裡面看到其中的套件都是其他套件的 dependency。下圖為縮減後 rootfs 各部份的佔比與其 package dependency 關係圖(其中有 `strace` 是因為在不同電腦上建構的,選項還沒關掉,請忽略):


##### 第二次縮減
抱著死馬當活馬醫的心態把 `util-linux` 關掉了,結論是一樣可以開機。發現原因是我對 `depends on` 跟 `select` 的了解不夠清楚。而 image 的大小已經比原先小了一半左右,目前佔比最大的是 `glibc`,裡面不只有 CRT,它還包含了 kernel 執行時期的動態函式庫,所以應該差不多到瓶頸了。而第二大的 `unknown` 就不確定是什麼了,目前 manual 跟網路上還沒找到資料。以下分別介紹 `depends on` 與 `select`:
- depends on
當 package 使用此方式來宣告相依性時,代表其所指定的 package 或 library 必須已啟用(=y),否則在 menuconfig 中這個 package 不會出現。
- select
使用此方式宣告的 package 或 lib 也會被設為啟用(=y),但不像 `depends on`,就算 package 中 `select` 的 package 或 lib 沒啟用(=n)時,該 package 還是會出現於 menuconfig,通常這種情況會發生在 package 有了 XXX package 或 lib 就會有某個功能或某個功能會有不一樣的實做,而沒有這個 package 或 lib 也無妨。
下圖為第二次縮減後的 rootfs 與其 package dependency 關係圖:


> 剛剛發現 libc 在現成的 buildroot (由 Fabrice Bellard 提供)有提供另個實做(`musl`),[這邊](http://www.etalabs.net/compare_libcs.html)有幾個 libc 的比較,大小比 GNU 的 libc 小很多,相容性也不錯,編譯完看看可不可成功開機。
> > 題外話,官方說要提昇編譯速度在硬體方面建議升級記憶體跟換成 SSD,但我覺得處理器實在差蠻多的...,現在(原為 i7-7500U)用 `E3-1240` 配 HDD 編譯平均都小於十分鐘,儲存裝置感覺起來不像是瓶頸呀!
##### 第三次縮減(?)
應該是我哪邊理解錯了,`musl` 可能是生成的執行檔比較精小。剩下就交給圖片說明吧...:


### 重新編譯 Linux 核心
#### 第一次編譯
以下使用核心版本為 `5.0.7 (riscv)`,第一次編譯完開機後卡在輸入使用者帳號的地方,在登入前有遇到問題`dropbear: uninitialized urandom read`,看起來是亂數產生器還沒初始化完 `dropbear` 就跟他要亂數,開機 log 如下:
```
Jan 1 00:00:00 localhost user.info kernel: [ 0.393902] devtmpfs: mounted
Jan 1 00:00:00 localhost user.info kernel: [ 0.394427] Freeing unused kernel memory: 192K
Jan 1 00:00:00 localhost user.warn kernel: [ 0.394697] This architecture does not have kernel memory protection.
Jan 1 00:00:00 localhost user.info kernel: [ 0.395209] Run /sbin/init as init process
Jan 1 00:00:00 localhost user.info kernel: [ 0.442497] EXT4-fs (vda): re-mounted. Opts: block_validity,barrier,user_xattr,acl,errors=remount-ro
Jan 1 00:00:00 localhost user.notice kernel: [ 0.619565] random: dd: uninitialized urandom read (512 bytes read)
ifup: can't open '/var/run/ifstate': No such file or directory
Starting dropbear sshd: [ 0.826503] random: dropbear: uninitialized urandom read (32 bytes read)
Jan 1 00:00:00 localhost user.notice kernel: [ 0.826503] random: dropbear: uninitialized urandom read (32 bytes read)
OK
Jan 1 00:00:00 localhost daemon.info init: starting pid 82, tty '/dev/console': '/sbin/getty -L console 0 vt100 '
Jan 1 00:00:00 localhost authpriv.info dropbear[81]: Running in background
Welcome to Buildroot
localhost login:
```
> 仔細一看不只 `dropbear`,連 `dd` 也有去要亂數,難怪拿掉 `dropbear` 還是開機失敗。
#### 問題
##### 第一次建構 rootfs
```shell
checking for python3... python3
configure: error:
*** These critical programs are missing or too old: bison
*** Check the INSTALL file for required versions.
package/pkg-generic.mk:185: recipe for target '/home/dces4212/project/riscv-emu/tmp/buildroot-riscv-2018-10-20/output/build/glibc-2.27/.stamp_configured' failed
make[1]: *** [/home/dces4212/project/riscv-emu/tmp/buildroot-riscv-2018-10-20/output/build/glibc-2.27/.stamp_configured] Error 1
Makefile:36: recipe for target '_all' failed
make: *** [_all] Error 2
```
第一次建構失敗了,應該是跟 python3 有關,其中還有提及 `bison` 可能沒裝或太舊。
錯誤中提示從 `INSTALL` 這個檔案中檢查自己的工具是否有符合最低要求版本,從檔案中找到 `bison` 要求最低版本為 `2.7 or higher`,並說明最新驗證過可以使用的版本為 `3.0.4`,而使用套件管理工具 `APT` 安裝的版本剛好為`3.0.4`。以下為 INSTALL 中的相關資訊:
```
* GNU 'bison' 2.7 or later
'bison' is used to generate the 'yacc' parser code in the 'intl'
subdirectory. As of release time, 'bison' version 3.0.4 is the
newest verified to work to build the GNU C Library.
```
看來我們只有遇到這個問題。整個編譯過程在 `i7-7500U` 上花了15分鐘左右,希望之後不會常重編譯...。
測試 cross-compiler 的時候遇到了點問題,虛擬機用的 Linux 映像檔的 `glibc` 版本太低了,我們在 host 編的 `hello` 需要 `2.27`,而 guest 的版本只到 `2.25`。而且 guest 端挺缺乏開發工具的(`make`之類),要直接編 `2.27` 會挺麻煩的,所以就只有先用 `readelf` 確認 `hello` 是 RISC-V 的 elf 而已。反正我們還有自己編的 RISC-V rootfs,到時再測也行。
實測後確定 `hello` 可以執行。此外,新建構的 rootfs 的 `glibc` 版本為 `2.27`。
### Buildroot
#### graph-size
此為 buildroot 內建的 `make` target,用於觀察所安裝的 package 的個別大小,想要讓 output 瘦身這會是你的好夥伴。說明書的最後還有提到有個 script (size-stats-compare) 是用來比較不同次建構的大小,使用不同 buildroot 版本時也可使用。[詳情請見](https://buildroot.org/downloads/manual/manual.html#_graphing_the_build_duration)。

(第一次建構的 rootfs)
#### 手動新增 packages
以下說明新增 custom package 的步驟,詳細說明在[這裡](https://buildroot.org/downloads/manual/manual.html#customize-packages)。一般 custom package 會放在 `package/<up_to_you>/` 中。一個 package 基本上會辦隨著一個 `.mk` 檔(內容為如何建構 package) 與一個 `Config.in` 檔(內容為 package 的建構 option)。
##### `Config.in`
包含 package 的功能介紹以及相依性。有些 package 可能會在 host 上用到,而假如只是在編譯時期會用到的話,將 `host-{package_name}` 放入 package 的 `BAR_DEPENDENCIES` variable 就好。但如果是要讓使用者可以在 configuration menu 中選擇是否安裝的話,那麼我們就會需要額外檔案 (`Config.in.host`),注意除了自己 package 的資料夾下的 `Config.in.host` 外,`package/` 下的 `Config.in.host` 也要更新,這樣 Host utilities menu 中才看得到新增的 host package (**target package 亦是如此**)。
##### 新增來自 GitHub 的 package
Buildroot 對此有特別的新增方式,假如欲新增的 package 的 repo 沒有特別提供載點(tag, release..),那我們在 `.mk` 檔中的 `{package_name}_SITE` 就需要使用 `{package_name}_SITE = $(call github,{GitHub_ID},{repo_name},$({version}))` 來取得原始碼,其中 `version` 欄位可以使用 commit 的 hash 或 tag。
##### 在 Guest 中執行 kilo

### menuconfig
- 使用 `/` 可以快速找到 package 等等;
## 參考資料
- [filesystem](https://nanxiao.me/devfs-tmpfs-devtmpfs/)
- [rootfs](http://www.linfo.org/root_filesystem.html)
###### tags: `linux2019`