# 2019q1 Homework4 (riscv) contributed by < `grant7163`> ###### tags: `sysprog2019_q1` ## 作業要求 依據 [F07: riscv](https://hackmd.io/s/ryHaBkrOE) * 回答「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳 * 調整 buildroot 設定,讓原本輸出的 ext2 image 佔用約 14 MB 空間,變得更精簡 * 移除套件,但確保仍可開機並提供必要的服務及工具 * Busybox 裡頭的 vi 也不需要存在,改用 kilo * 編譯 [kilo](https://github.com/sysprog21/kilo) 編輯器,允許在 RISC-V/Linux 模擬環境中運作 * 客製化 buildroot,將 [kilo](https://github.com/sysprog21/kilo) 編輯器加入到 build image,建立 Git repository 來追蹤你對 buildroot 做的變更,並依據 [Buildroot Manual](https://buildroot.org/downloads/manual/manual.html) 更新套件描述和建構方式 * 重新編譯 Linux 核心,確保能在 [riscv-emu](https://github.com/sysprog21/riscv-emu) 運作,並思考縮減 image size 的方法 * 先從 `$ make menuconfig` 調整核心選項開始,當然要能開機才有效益 * 參照 [Linux Kernel Tinification](https://tiny.wiki.kernel.org) ## 自我檢查清單 - [ ] [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) 一文後,對照原始程式碼,你發現什麼? virtio 是一個在模擬器的環境下半虛擬化的技術,針對 I/O 虛擬化的一個通用介面框架。 * 全虛擬化(Full virtualization) : Guest OS 執行在一個實體機器中的 hypervisor 之上。對於底層硬體操作(指令)都交給 VMM 處理(模擬),也因為這樣的關係導致效能不佳。 * 半虛擬化(paravirtualization) : 基於全虛擬化的形式但需要在 Guest OS 端增加額外的 front-end drivers,使得 Guest OS 能夠與 hyperivosr 中的 back-end drivers 好好配合,使其提高對於底層硬體操作的效能。 左圖為全虛擬化,右圖為半虛擬化 ![](https://i.imgur.com/qvDq8UK.png) 而為了溝通 front-end drivers 與 back-end drivers 建立一個通用介面就是 virtio。 ![](https://i.imgur.com/0f9fFme.gif) 觀察在 virtio_net 程式碼中實現的方式 : 依據 [/drivers/net/virtio_net.c](https://elixir.bootlin.com/linux/v4.15/source/drivers/net/virtio_net.c#L2808) 中的定義 對於 virtio_net_driver 變數作初始化。 ```clike= static struct virtio_driver virtio_net_driver = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .feature_table_legacy = features_legacy, .feature_table_size_legacy = ARRAY_SIZE(features_legacy), .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .validate = virtnet_validate, .probe = virtnet_probe, .remove = virtnet_remove, .config_changed = virtnet_config_changed, #ifdef CONFIG_PM_SLEEP .freeze = virtnet_freeze, .restore = virtnet_restore, #endif }; ``` 並在初始化階段調用(註冊) virtio_net_driver。 ```shell module_init(virtio_net_driver_init); static __init int virtio_net_driver_init(void) { ... ret = register_virtio_driver(&virtio_net_driver); if (ret) goto err_virtio; ... return ret; } ``` 依據 [/include/linux/virtio.h](https://elixir.bootlin.com/linux/v4.15/source/include/linux/virtio.h#L125) 中的定義 作為在 guest os 中表示底層裝置的封裝。 ```clike= struct virtio_device { int index; bool failed; bool config_enabled; bool config_change_pending; spinlock_t config_lock; struct device dev; struct virtio_device_id id; const struct virtio_config_ops *config; const struct vringh_config_ops *vringh_config; struct list_head vqs; u64 features; void *priv; }; ``` 當 hypervisor 找到裝置時,將調用 virtnet_probe 函式來傳入該 virtio_device 的資訊並註冊它。 ```shell static int virtnet_probe(struct virtio_device *vdev) { ... /* Allocate ourselves a network device with room for our info */ dev = alloc_etherdev_mq(sizeof(struct virtnet_info), max_queue_pairs); if (!dev) return -ENOMEM; ... /* Set up our device-specific information */ vi = netdev_priv(dev); vi->dev = dev; vi->vdev = vdev; vdev->priv = vi; vi->stats = alloc_percpu(struct virtnet_stats); err = -ENOMEM; if (vi->stats == NULL) goto free; ... /* Allocate/initialize the rx/tx queues, and invoke find_vqs */ err = init_vqs(vi); if(err) goto free_stats; ... err = register_netdev(dev); if (err) { pr_debug("virtio_net: registering device failed\n"); goto free_vqs; } ... } ``` 建立和初始化 tx / rx ring queue 。 ```shell static int init_vqs(struct virtnet_info *vi) { ... /* Allocate send & receive queues */ ret = virtnet_alloc_queues(vi); if (ret) goto err; ret = virtnet_find_vqs(vi); if (ret) goto err_free; ... } ``` 進一步往 virtnet_find_vqs 函式中查看,在這裡就有宣告一個 virtqueue 的結構體。 ```shell static int virtnet_find_vqs(struct virtnet_info *vi) { vq_callback_t **callbacks; struct virtqueue **vqs; ... ret = vi->vdev->config->find_vqs(vi->vdev, total_vqs, vqs, callbacks, names, ctx, NULL); if (ret) goto err_find; ... } ``` 依據 [/include/linux/virtio.h](https://elixir.bootlin.com/linux/v4.15/source/include/linux/virtio.h#L125) 中的定義 從 virtqueue 的結構體來看,發現已經沒有對 virtqueue_ops 的操作了。 ```clike= struct virtqueue { struct list_head list; void (*callback)(struct virtqueue *vq); const char *name; struct virtio_device *vdev; unsigned int index; unsigned int num_free; void *priv; }; ``` - [ ] 透過 `$ temu root-riscv64.cfg`, 我們在 RISCV/Linux 模擬環境中,可執行 `gcc` 並輸出對應的執行檔,而之後我們則執行 `riscv64-buildroot-linux-gnu-gcc`,這兩者有何不同? (提示: cross-compiler, 複習 [你所不知道的 C 語言: 編譯器和最佳化原理篇](https://hackmd.io/s/Hy72937Me) 在 RISCV/Linux 模擬環境中所執行的 gcc 會產生出 RISCV/Linux 平台上能夠執行的執行檔,而後者使用 `riscv64-buildroot-linux-gnu-gcc` 的方式是透過在 host 端(e.g : x86, arm ... etc)執行 gcc 產生出對應於 RISCV/Linux 平台上能夠執行的執行檔,這種方式又稱為 cross compiler。 可應用在一些微處理器中(嵌入式系統),在 CPU 時脈不高的情況下若直接在上面編譯的話會需要花較多的時間,這時候就可以先在 CPU 時脈較高的平台上作編譯,在把產生出的執行檔燒入到 target 中。 - [ ] 在 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 - [ ] 最初實驗輸入 `$ 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"` ```shell [root@localhost ~]# uname -a Linux localhost 4.15.0-00049-ga3b1e7a-dirty #10 Thu Sep 13 20:03:10 CEST 2018 riscv64 GNU/Linux ``` - [ ] 核心啟動的參數 `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)) ## 開發紀錄 ### 環境建置 從老師的 [github](https://github.com/sysprog21/riscv-emu) 上 fork 完 riscv-emu 之後,開始試試環境建置。 編譯完後會產生 temu 執行檔,執行後輸出如老師作業說明一樣。 ```shell $ ./temu temu version 2019-02-10, Copyright (c) 2016-2018 Fabrice Bellard usage: riscvemu [options] config_file options are: -m ram_size set the RAM size in MB -rw allow write access to the disk image (default=snapshot) -ctrlc the C-c key stops the emulator instead of being sent to the emulated software -append cmdline append cmdline to the kernel command line Console keys: Press C-a x to exit the emulator, C-a h to get some help. ``` 成功啟動模擬器並顯示 pi 的結果 ```shell $ ./temu https://bellard.org/jslinux/buildroot-riscv64.cfg Welcome to JS/Linux (riscv64) Use 'vflogin username' to connect to your account. You can create a new account at https://vfsync.org/signup . Use 'export_file filename' to export a file to your computer. Imported files are written to the home directory. [root@localhost ~]# tinypi 1000 pi.txt [root@localhost ~]# cat pi.txt 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989 ``` :::danger "directory" 的中文翻譯是「目錄」,而非「資料夾」,在 UNIX 和相似的作業系統中,一律用 directory,請勿混用 folder 這詞 :notes: jserv ::: 此時下載 diskimage and buildroot 並必須將檔案都放在根目錄底下的 tmp <s>資料夾</s>目錄中,否則之後在使用 mount 9p 時會抓不到 host 端的目錄。 ```shell $ cd /tmp $ wget https://bellard.org/tinyemu/diskimage-linux-riscv-2018-09-23.tar.gz $ tar zxvf diskimage-linux-riscv-2018-09-23.tar.gz $ wget https://bellard.org/tinyemu/buildroot-riscv-2018-10-20.tar.gz $ tar zxvf buildroot-riscv-2018-10-20.tar.gz $ cd buildroot-riscv-2018-10-20 $ cp configs/riscv64_defconfig .config ``` 接著啟動模擬器並嘗試輸入命令觀察 cpu 的資訊( rv64acdfimsu )。 ```shell $ temu root_9p-riscv64.cfg [ 0.195596] NET: Registered protocol family 17 [ 0.195891] 9pnet: Installing 9P2000 support [ 0.198273] EXT4-fs (vda): couldn't mount as ext3 due to feature incompatibilities [ 0.198880] EXT4-fs (vda): mounting ext2 file system using the ext4 subsystem [ 0.203417] EXT4-fs (vda): mounted filesystem without journal. Opts: (null) [ 0.203698] VFS: Mounted root (ext2 filesystem) on device 254:0. [ 0.204348] devtmpfs: mounted [ 0.204593] Freeing unused kernel memory: 80K [ 0.204808] This architecture does not have kernel memory protection. /mnt/buildroot-riscv-2018-10-20 # cat /proc/cpuinfo hart : 0 isa : rv64acdfimsu mmu : sv48 ``` 9p(Plan 9 folder sharing over Virtio - I/O virtualization framework) 是透過 Virtio 讓 host 端與 guest 端能共享資料。 從 config_linux_riscv64.txt 中發現確實有將 9p 的功能開啟。 ```shell CONFIG_NET_9P=y CONFIG_NET_9P_VIRTIO=y CONFIG_NET_9P_DEBUG=y CONFIG_9P_FS=y # CONFIG_9P_FS_POSIX_ACL is not set # CONFIG_9P_FS_SECURITY is not set ``` 接著使用 9p 將 host 端的資料夾掛載到 guest 端。 ```shell ~ # mount -t 9p /dev/root /mnt ~ # ls /mnt buildroot-riscv-2018-10-20 config-err-od7a0v diskimage-linux-riscv-2018-09-23 ssh-ZL7y4p50hPXT systemd-private-53f79f6546644053a17134d1d6af8302-bolt.service-JRFfrV systemd-private-53f79f6546644053a17134d1d6af8302-colord.service-UWoQ0z systemd-private-53f79f6546644053a17134d1d6af8302-fwupd.service-FZKRSP systemd-private-53f79f6546644053a17134d1d6af8302-rtkit-daemon.service-bBL4nJ systemd-private-53f79f6546644053a17134d1d6af8302-systemd-resolved.service-QTlgVy systemd-private-53f79f6546644053a17134d1d6af8302-systemd-timesyncd.service-CqhUYR ``` ### Buildroot 使用 make menuconfig 觀察有哪些配置是可以設定的。 從 Target options 中可以看到有支援哪些 cpu 架構 ```shell ( ) Nios II ( ) PowerPC ( ) PowerPC64 (big endian) ( ) PowerPC64 (little endian) ( ) RISC-V (RV32) (X) RISC-V (RV64) ``` 從 Build options 中觀察支援哪些設定 * 在 gcc optimization level 中可以看到 defult 就已經選 size 最佳化了 ```shell ( ) optimization level 0 ( ) optimization level 1 ( ) optimization level 2 ( ) optimization level 3 ( ) optimize for debugging (X) optimize for size ``` * 可用來降低編譯時期的時間 (太棒了!) 依據 [8.12.3. Using ccache in Buildroot](https://buildroot.org/downloads/manual/manual.html#ccache) 參考 [ccache](https://www.ibm.com/developerworks/cn/linux/l-ccache.html) ```shell [*] Enable compiler cache ($(HOME)/.buildroot-ccache) Compiler cache location (NEW) () Compiler cache initial setup (NEW) [*] Use relative paths (NEW) ``` * 可用來在編譯時期偵測 stack overflow 參考 [ssp](https://www.ibm.com/developerworks/cn/linux/l-cn-gccstack/index.html) ```shell build code with Stack Smashing Protection (None) (X) None ( ) -fstack-protector ( ) -fstack-protector-strong ( ) -fstack-protector-all ``` 從 Toolchain 中觀察支援哪些設定 * 選擇 kernel, 函式庫的版本 ```shell Kernel Headers (Linux 4.15.x kernel headers) ---> C library (glibc) ---> glibc version (2.27) ---> Binutils Version (binutils 2.30) ---> ``` * LTO 的支援,之後可以來試試看 ```shell [ ] Enable compiler link-time-optimization support ``` 接著嘗試第一次編譯,結果出現如下錯誤訊息,從輸出訊息中觀察到 bison 可能遺失或太舊。 ```shell 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/parallels/Desktop/riscv-tinyemu/buildroot-riscv-2018-10-20/output/build/glibc-2.27/.stamp_configured' failed make[1]: *** [/home/parallels/Desktop/riscv-tinyemu/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 ``` 修改 package/glibc 目錄中的 `glibc.mk` 參考 [Buildroot](http://lists.busybox.net/pipermail/buildroot/2018-February/213360.html) ```shell -GLIBC_DEPENDENCIES = host-gcc-initial linux-headers host-gawk +GLIBC_DEPENDENCIES = host-gcc-initial linux-headers host-bison host-gawk ``` 重新在編譯一次並用 file 檢查。 ```shell $ file output/images/rootfs.ext2 output/images/rootfs.ext2: Linux rev 1.0 ext2 filesystem data, UUID=473dcd1d-74e6-461e-8f04-15a90f274db9 ``` 使用新建立的 cfg 檔啟動模擬器 : ```shell $ temu buildroot_9p-riscv64.cfg ... Welcome to Buildroot localhost login: root Jan 1 00:00:16 localhost auth.info login[74]: root login on 'console' [root@localhost ~]# busybox --help | head BusyBox v1.24.2 (2019-04-04 19:38:21 CST) multi-call binary. BusyBox is copyrighted by many authors between 1998-2015. Licensed under GPLv2. See source distribution for detailed copyright notices. Usage: busybox [function [arguments]...] or: busybox --list[-full] or: busybox --install [-s] [DIR] or: function [arguments]... ``` 接著準備將 kilo lib 加入到 buildroot 中。 依據 [17. Adding new packages to Buildroot](https://buildroot.org/downloads/manual/manual.html#adding-packages) 在 package 目錄下建立 kilo 資料夾並包含如下二個檔案 * `config.in` : to be displayed in the configuration tool * `kilo.mk` : how the package should be downloaded, configured, built, installed, et 建立 `config.in` 檔案 ```shell config BR2_PACKAGE_KILO bool "kilo" help a small text editor ``` 在 package 目錄底下的 `config.in` 中加入 kilo 的 `config.in`,這樣才能讓 buildroot 命名的框架辨別並透過 menuconfig 來配置。 ```shell menu "Text editors and viewers" source "package/kilo/Config.in" source "package/ed/Config.in" source "package/joe/Config.in" if BR2_PACKAGE_BUSYBOX_SHOW_OTHERS source "package/less/Config.in" endif source "package/mc/Config.in" source "package/nano/Config.in" source "package/uemacs/Config.in" if BR2_PACKAGE_BUSYBOX_SHOW_OTHERS source "package/vim/Config.in" ``` 成功在 menuconfig 中顯示出 kilo 的選項 ![](https://i.imgur.com/4sPp83i.png) 建立 `kilo.mk` 檔案 第一次用第4行的方式編譯會抓不到 git 的網址,但若先用第3行的方式編譯完之後在換成用第4行的方式編譯就可以抓到 git 的網址,需要在研究一下是什麼問題。 ```clike= KILO_VERSION = next KILO_SITE_METHOD = git KILO_SITE = https://github.com/grant7163/kilo #KILO_SITE = $(call github, grant7163, kilo, $(KILO_VERSION)) define KILO_BUILD_CMDS $(MAKE) CC="$(TARGET_CC)" LD="$(TARGET_LD)" -C $(@D) endef define KILO_INSTALL_TARGET_CMDS $(INSTALL) -m 0755 -D $(@D)/kilo $(TARGET_DIR)/usr/bin endef $(eval $(generic-package)) ``` 在 menuconfig 中開啟 ccache 的功能並重新編譯(加入 kilo 的功能)。 將 cache size 最大限制設為 3G ```shell make CCACHE_OPTIONS="--max-size=3G" ``` 使用 buildroot 內建的命令觀察編譯所耗費的時間差異,發現比第一次編譯時間快蠻多的。 依據 [8.9. Graphing the build duration](https://buildroot.org/downloads/manual/manual.html#_graphing_the_build_duration) 第一次編譯的時間(未開啟 ccache) ![](https://i.imgur.com/knoN2fM.png) 第二次重新編譯的時間(開啟 ccache) ![](https://i.imgur.com/2A4DvaQ.png) 成功將 kilo 加入 build image 中。 ```shell [root@localhost ~]# ls /usr/bin [ dos2unix killall openvt strace unzip [[ dropbearconvert kilo passwd strace-log-merge uptime ar dropbearkey last patch strings uudecode awk du less printf tail uuencode basename eject logger readlink tee vlock bunzip2 env logname realpath telnet wc bzcat expr lsof renice test wget chrt find lspci reset tftp which chvt flock lsusb resize time who cksum fold lzcat scp top whoami clear free lzma seq tr xargs cmp fuser man setkeycodes traceroute xz crontab head md5sum setsid truncate xzcat cut hexdump mesg sha1sum tty yes dbclient hostid microcom sha256sum uniq dc id mkfifo sha3sum unix2dos deallocvt install nohup sha512sum unlink diff ipcrm nslookup sort unlzma dirname ipcs od ssh unxz ``` ### 縮減 image size 此時觀察 .../output/images 目錄中 rootfs.ext2 的檔案大小為 13.7 MB 使用 `busybox-menuconfig` 將 vi 移除 從 Editors 中可以看到有支援哪些編輯器 ```shell ... [ ] ed [*] patch [*] sed [ ] vi ... ``` 在 buildroot help 中有幾個好用的 config 命令 * defconfig : 使用初始的配置 * allnoconfig : * savedefconfig : 將目前的配置儲存在 /tmp/buildroot-riscv-2018-10-20 目錄下 * allnopackageconfig : 不包含 package 的配置 依據 [9.3. Storing the Buildroot configuration](https://buildroot.org/downloads/manual/manual.html#customize-store-buildroot-config) ```shell $ make help ... Configuration: ... defconfig - New config with default answer to all options BR2_DEFCONFIG, if set, is used as input savedefconfig - Save current config to BR2_DEFCONFIG (minimal config) allyesconfig - New config where all options are accepted with yes allnoconfig - New config where all options are answered with no randpackageconfig - New config with random answer to package options allyespackageconfig - New config where pkg options are accepted with yes allnopackageconfig - New config where package options are answered with no ``` 嘗試先使用 allnopackageconfig 配置並重新編譯。 此時在觀察 .../output/images 目錄中 rootfs.ext2 的檔案大小,發現已經縮小到 5.6 MB。 使用 buildroot 內建的命令觀察原始版本與移除掉一些套件的版本對於 image size 的差異 依據 [8.10. Graphing the filesystem size contribution of packages](https://buildroot.org/downloads/manual/manual.html#_graphing_the_filesystem_size_contribution_of_packages) ![](https://i.imgur.com/FLScmHG.png) ![](https://i.imgur.com/hL2QvSx.png)