# 2019q1 Homework4 (riscv) contributed by < `st9540808`> ## 自我檢查清單 #### [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 首先會使用 `virt_machine_load_config_file()` 初始化 `VirtMachineParams p_s`,這個物件最後會被用來初始化 `VirtMachine *s`。 ```c void virt_machine_load_config_file(VirtMachineParams *p, const char *filename, void (*start_cb)(void *opaque), void *opaque) { VMConfigLoadState *s; s = mallocz(sizeof(*s)); s->vm_params = p; s->start_cb = start_cb; s->opaque = opaque; p->cfg_filename = strdup(filename); config_load_file(s, filename, config_file_loaded, s); } ``` 由 temu `main()` 中呼叫 `virt_machine_load_config_file()`,在最後的 `config_load_file()`,會再利用 callback function `cb` 呼叫 `config_file_loaded()`,最後由 `virt_machine_parse_config()` 初始化。call stack 如下: ![](https://i.imgur.com/6OBXs8D.png) 當輸入 `$ temu root-riscv64.cfg` 時,會去 parse root-riscv64.cfg 檔案設置虛擬機器。 節錄 [virtio](https://www.linux-kvm.org/page/Virtio) 這篇文章, `virtio_device`: represents the front-end driver in the guest. `virtio_config_ops`: a representation of the device in the guest. `virtqueue` object references the `virtqueue_ops` object, which defines the underlying queue operations for dealing with the hypervisor driver. ![](https://www.ibm.com/developerworks/library/l-virtio/figure4.gif) 再來看 VirtMachine 的定義: VirtMachine 有一個參考到 VirtMachineClass,VirtMachineClass 負責提供一些操作給 VirtMachine。 ```c typedef struct VirtMachine { const VirtMachineClass *vmc; /* network */ EthernetDevice *net; /* console */ VIRTIODevice *console_dev; CharacterDevice *console; /* graphics */ FBDevice *fb_dev; } VirtMachine; ``` 而其中有一個 VirtMachineClass 和 VIRTIODevice 類型的資料成員 `vmc`,定義如下: > machine.h ```c struct VirtMachineClass { const char *machine_names; void (*virt_machine_set_defaults)(VirtMachineParams *p); VirtMachine *(*virt_machine_init)(const VirtMachineParams *p); void (*virt_machine_end)(VirtMachine *s); int (*virt_machine_get_sleep_duration)(VirtMachine *s, int delay); void (*virt_machine_interp)(VirtMachine *s, int max_exec_cycle); bool (*vm_mouse_is_absolute)(VirtMachine *s); void (*vm_send_mouse_event)(VirtMachine *s1, int dx, int dy, int dz, unsigned int buttons); void (*vm_send_key_event)(VirtMachine *s1, bool is_down, uint16_t key_code); }; ``` VIRTIODevice 也有類似 virtqueue 的資料結構 QueueState queue[MAX_QUEUE] > virtio.h ```c struct VIRTIODevice { PhysMemoryMap *mem_map; PhysMemoryRange *mem_range; /* PCI only */ PCIDevice *pci_dev; /* MMIO only */ IRQSignal *irq; VIRTIOGetRAMPtrFunc *get_ram_ptr; int debug; uint32_t int_status; uint32_t status; uint32_t device_features_sel; uint32_t queue_sel; /* currently selected queue */ QueueState queue[MAX_QUEUE]; /* device specific */ uint32_t device_id; uint32_t vendor_id; uint32_t device_features; VIRTIODeviceRecvFunc *device_recv; void (*config_write)(VIRTIODevice *s); /* called after the config is written */ uint32_t config_space_size; /* in bytes, must be multiple of 4 */ uint8_t config_space[MAX_CONFIG_SPACE_SIZE]; }; ``` 以寫入 console 這個 character device 的操作為例,用 `memcpy_from_queue()` 會把字串寫入 buf,再用 cs->write_data (為 function pointer,會去呼叫 `console_write()` ) 寫入 buf 的資料到終端機 (stdout)。 ```c static int virtio_console_recv_request(VIRTIODevice *s, int queue_idx, int desc_idx, int read_size, int write_size) { VIRTIOConsoleDevice *s1 = (VIRTIOConsoleDevice *)s; CharacterDevice *cs = s1->cs; uint8_t *buf; if (queue_idx == 1) { /* send to console */ buf = malloc(read_size); memcpy_from_queue(s, buf, queue_idx, desc_idx, 0, read_size); cs->write_data(cs->opaque, buf, read_size); free(buf); virtio_consume_desc(s, queue_idx, desc_idx, 0); } return 0; } ``` 底下是 `virtio_console_recv_request()` 的 call stack,有很多是用 Macro 展開或是 function pointer 呼叫的函式,可以看到一開始以 VirtMachine 為引數呼叫 `virt_machine_run()` 最後呼叫 `virtio_console_recv_request()` 的時候則是以 VIRTIODevice 當作參數,而底層則是用 `virtio_memcpy_from_ram()` 透過 PhysMemoryMap 類型的資料成員 `mem_map` 取得記憶體位置後使用 memcpy() 寫入 buf。 VirtMachine 有點像前端的資料表示,後端則由 VIRTIODevice 來操作。而 `QueueState queue[MAX_QUEUE]` 只有記憶體位置,實際的資料是被放在 PhysMemoryMap 的資料結構裡。 ![](https://i.imgur.com/qIRkjKB.png) ---- #### 透過 `$ temu root-riscv64.cfg`, 我們在 RISCV/Linux 模擬環境中,可執行 `gcc` 並輸出對應的執行檔,而之後我們則執行 `riscv64-buildroot-linux-gnu-gcc`,這兩者有何不同? (提示: cross-compiler, 複習 [你所不知道的 C 語言: 編譯器和最佳化原理篇](https://hackmd.io/s/Hy72937Me) 輸入 `$ gcc -v` 可以看到兩者會產生不同架構的機器碼,在我的筆電上因為是 x86 平台,會得到 Target: x86_64-linux-gnu,而 `riscv64-buildroot-linux-gnu-gcc` 會得到 Target: riscv64-buildroot-linux-gnu。 ---- #### 在 Guest 端透過 `$ dmesg | grep 9pnet` 命令,我們可發現 `9P2000` 字樣,這和上述 [VirtFS](https://wiki.qemu.org/Documentation/9psetup) 有何關聯?請解釋運作原理並設計實驗 #### Plan 9 plan 9 一開始是被用來解決分散式系統中的相關議題,plan 9 的設計原理在 [Grave Robbers from Outer Space Using 9P2000 Under Linux](https://www.usenix.org/legacy/events/usenix05/tech/freenix/full_papers/hensbergen/hensbergen.pdf) 文章中提及: > 1) develop a single set of simple, well-defined interfaces to services > 2) use a simple protocol to securely distribute the interfaces across any network > 3) provide a dynamic hierarchical structure to organize these interfaces 而 9p 則是類似 linux 中 VFS 的抽象界面,就如同 sysfs 提供核心、硬體裝置的資訊到 user-space,9p 也必須實作相關操作讓使用者能用同一套界面存取一個分散式系統中的資源。 9P2000 則是 plan 9 在 2002 發行的第四版本 9P 支援的基本操作 ![](https://i.imgur.com/20vtDWM.png) ---- - [ ] 在 [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"` - [ ] 核心啟動的參數 `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))