sysprog
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    14
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Linux 核心專題: RISC-V 系統模擬器 > 執行人: JiggerChuang > [專題解說錄影](https://youtu.be/CtZ5QiDcjPE) > [投影片](https://docs.google.com/presentation/d/1phJa4XvK6FdXHxEj_Z0bGUq1r5rqZR2mNwmFC_AA2Vc/edit?usp=sharing) :::success :question: 提問清單 * 這是一個"minimalist RISC-V emulator",請問作為一個minimalist他至少需要完成哪些事情呢??可以詳細說明這部分跟對照spec(https://riscv.org/technical/specifications/)補充嗎?? ::: ## [背景知識](https://hackmd.io/@y8jRQNyoRe6WG-qekloIlA/S1VWjwt4n) * OpenSBI * PLIC * Clint * MMU * Device Tree ## [semu](https://github.com/jserv/semu): 精簡的 RISC-V 系統模擬器 特徵: * RISC-V 指令集架構: RV32IMA * 特權等級: S mode 和 U mode * Control and status registers (CSR) * 虛擬記憶體: RV32 MMU * UART: 8250/16550 * PLIC (platform-level interrupt controller): 32 interrupts, no priority * Standard SBI ([Supervisor Binary Interface](https://github.com/riscv-non-isa/riscv-sbi-doc/)), with the timer extension * 支援 [VirtIO](https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.html): virtio-net, mapped as TAP interface * 原始程式碼在 2000 行左右 ## 利用 Buildroot 建構檔案系統 [RISC-V 和 Buildroot 介紹](https://hackmd.io/@sysprog/ryHaBkrOE) * [2019 年專題: RISCV with TinyEMU](https://hackmd.io/@johnnylord/HkeMrEARE) > [Buildroot 相關演講](https://hackmd.io/@0xff07/rJkMw37mP) 取得 Buildroot 程式碼: ```shell $ git clone https://github.com/buildroot/buildroot.git --depth=1 ``` 依據 semu 提供的組態來建構 root file system: ```shell $ cd buildroot $ cp ../configs/buildroot.config .config $ make $ cd .. ``` > 可將上方的 `make` 改為 `make -j8`,依據有效的處理器數量變更 取得 Linux 核心程式碼: ```shell $ git clone https://github.com/torvalds/linux.git --depth=1 ``` 利用 Buildroot 建構的 GNU Toolchain 來編譯 Linux 核心,先設定環境變數: ```shell $ export PATH=`pwd`/buildroot/output/host/bin:$PATH $ export CROSS_COMPILE=riscv32-buildroot-linux-gnu- $ export ARCH=riscv ``` 編譯 Linux 核心: ```shell $ cd linux $ cp ../configs/linux.config .config $ make Image $ cd .. ``` > 可將上方的 `make` 改為 `make -j8`,依據有效的處理器數量變更 預期會得到 `linux/arch/riscv/boot/Image` 檔案,接著執行: ```shell $ ./semu Image ``` 即可執行 Linux 核心。 ## 網路設定 [semu](https://github.com/jserv/semu) 支援 [TAP/TUN](https://www.kernel.org/doc/html/latest/networking/tuntap.html) 以存取電腦網路。 引述 [Wikipedia](https://en.wikipedia.org/wiki/TUN/TAP): > TUN and TAP are kernel virtual network devices. Being network devices supported entirely in software, they differ from ordinary network devices which are backed by physical network adapters. > Though both are for tunneling purposes, TUN and TAP can't be used together because they transmit and receive packets at different layers of the network stack. TUN, namely network TUNnel, simulates a network layer device and operates in layer 3 carrying IP packets. TAP, namely network TAP, simulates a link layer device and operates in layer 2 carrying Ethernet frames. TUN is used with routing. TAP can be used to create a user space network bridge. TUN 和 TAP 是 Linux 核心模擬出的虛擬網路裝置,TUN 處理 IP 封包,而 TAP 處理 Ethernet 封包,參見下圖: ![](https://hackmd.io/_uploads/SkI_aOkH2.png) 開啟二個終端機,其中 T~semu~ 表示執行 semu 的終端機,T~host~ 表示執行本機網路設定的終端機。 在 T~semu~ 執行以下命令: ``` sudo ./semu ``` 預期應該要在第一行見到 `allocated TAP interface: tap0` 的字樣,接著切到 T~host~,執行以下命令: ``` sudo ip addr add 192.168.10.1/24 dev tap0 sudo ip link set tap0 up ``` 再切換到 T~semu~,等待以下訊息的出現: ``` Welcome to Buildroot buildroot login: ``` 輸入 `root` 之後會出現提示符號 `#`,接著執行以下命令: ``` ip l set eth0 up ip a add 192.168.10.2/24 dev eth0 ping -c 3 192.168.10.1 ``` 預期會見到以下輸出: ``` PING 192.168.10.1 (192.168.10.1): 56 data bytes 64 bytes from 192.168.10.1: seq=0 ttl=64 time=1.021 ms 64 bytes from 192.168.10.1: seq=1 ttl=64 time=0.552 ms 64 bytes from 192.168.10.1: seq=2 ttl=64 time=0.582 ms --- 192.168.10.1 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max = 0.552/0.718/1.021 ms ``` 切換到 T~host~ 來檢驗: ``` ping -c 3 -I tap0 192.168.10.2 ``` 預期會見到以下輸出: ``` ING 192.168.10.2 (192.168.10.2) from 192.168.10.1 tap0: 56(84) bytes of data. 64 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=1.01 ms 64 bytes from 192.168.10.2: icmp_seq=2 ttl=64 time=0.957 ms 64 bytes from 192.168.10.2: icmp_seq=3 ttl=64 time=0.972 ms ``` 至此,具備基本的網路設定。 ## 追蹤封包 在 T~host~ 執行以下命令: ``` sudo tcpdump -i tap0 ``` 預期會有以下輸出: ``` tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on tap0, link-type EN10MB (Ethernet), capture size 262144 bytes ``` 讓 `tcpdump` 保持執行,接著切換到 T~semu~ ,執行以下命令: ``` ping -c 3 192.168.10.1 ``` 切換到 T~host~ ,觀察封包捕捉狀況,參考輸出: ``` 1:55:55.728344 IP 192.168.10.2 > node1: ICMP echo request, id 71, seq 0, length 64 01:55:55.728380 IP node1 > 192.168.10.2: ICMP echo reply, id 71, seq 0, length 64 01:55:57.963882 IP 192.168.10.2 > node1: ICMP echo request, id 71, seq 1, length 64 01:55:57.963904 IP node1 > 192.168.10.2: ICMP echo reply, id 71, seq 1, length 64 01:56:00.201345 IP 192.168.10.2 > node1: ICMP echo request, id 71, seq 2, length 64 01:56:00.201371 IP node1 > 192.168.10.2: ICMP echo reply, id 71, seq 2, length 64 01:56:00.922217 ARP, Request who-has 192.168.10.2 tell node1, length 28 01:56:00.922532 ARP, Reply 192.168.10.2 is-at 56:53:0c:0a:86:24 (oui Unknown), length 28 01:56:07.028471 ARP, Request who-has node1 tell 192.168.10.2, length 28 01:56:07.028487 ARP, Reply node1 is-at ce:d5:02:d4:75:e0 (oui Unknown), length 28 01:56:50.256464 IP node1.mdns > 224.0.0.251.mdns: 0 [2q] PTR (QM)? _ipps._tcp.local. PTR (QM)? _ipp._tcp.local. (45) 01:56:51.457867 IP6 node1.mdns > ff02::fb.mdns: 0 [2q] PTR (QM)? _ipps._tcp.local. PTR (QM)? _ipp._tcp.local. (45) 01:57:42.810231 IP6 node1 > ip6-allrouters: ICMP6, router solicitation, length 16 ``` > 第一欄為時間戳記 ## TODO: 描述程式碼原理 研讀〈[Writing a simple RISC-V emulator in plain C](https://fmash16.github.io/content/posts/riscv-emulator-in-c.html)〉,對照 semu 原始程式碼,解釋系統模擬器之行為和原理。 ### 開始流程 一開始從命令列==讀取命令與參數== ```shell $ ./semu Image ``` ### 檔案載入與模擬器設定 接著將參數 (i.e., 執行檔名) 傳給 `semu_start()` 進行==讀檔與設定==: ```c static int semu_start(int argc, char **argv) { /* Init */ emu_state_t emu; vm_t vm = { .priv = &emu, .mem_fetch = mem_fetch, ... }; /* Set up RAM */ emu.ram = mmap(NULL, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); /* Load file into RAM */ read_file_into_ram(&ram_loc, argv[1]); read_file_into_ram(&ram_loc, (argc == 3) ? argv[2] : "minimal.dtb"); /* Set up RISC-V hart */ emu.timer_hi = emu.timer_lo = 0xFFFFFFFF; vm.s_mode = true; ... /* Set up peripherals */ emu.uart.in_fd = 0, emu.uart.out_fd = 1; #if defined(ENABLE_VIRTIONET) virtio_net_init(&(emu.vnet)) emu.vnet.ram = emu.ram; #endif /* Emulate */ while (!emu.stopped) { ... } } ``` #### Init 當中 `emu_state_t` struct 對應到文章中的 CPU struct,包含 CPU **周邊** (e.g., ram, plic, uart 等),`vm_t` struct 對應到 datapath 內部**結構**與**運作機制**,包含 32 個暫存器、pc、memory fectch 等。 #### Set up RAM [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html) 用來**設定模擬的 RAM**,也就是將要讀寫的檔案內容 (i.e., Image) 映射到一段 (虛擬) 記憶體上,通過對這段記憶體的讀寫,可直接對檔案內容做修改,一般的 I/O 通常需要先將資料放進 buffer,mmap 可省略這一個步驟,提高存取速度,再來是可把檔案當成記憶體來使用,直接用指標來操作,其中第一個參數 NULL 表示由作業系統決定起始位址。 :::warning 注意用詞: file 是「檔案」,document 是「文件」 :notes: jserv ::: #### Load file into RAM ram 設定完成後,就可以 `read_file_into_ram()` 來將 Linux 核心映像檔案 (kernel image) 載入進模擬的 RAM 中: ```c static void read_file_into_ram(char **ram_loc, const char *name) { FILE *input_file = fopen(name, "r"); while (!feof(input_file)) *ram_loc += fread(*ram_loc, sizeof(char), 1024 * 1024, input_file); } ``` > TODO: 這裡可將讀檔改成 memory mapping 的方式 接著以同樣的方式載入 device tree 檔案。 > TODO: 研究 device tree 並透過 virtio_blk 方式載入 disk image #### Set up RISC-V hart 設定 hart 屬性。 > ISSUE: 不知道暫存器內 RV_R_A0 與 RV_R_A1 為何要這樣設定 > 參照 [Machine-Level ISA](https://www.five-embeddev.com/riscv-isa-manual/latest/machine.html) :notes: jserv 根據 [All Aboard, Part 6: Booting a RISC-V Linux Kernel](https://www.sifive.com/blog/all-aboard-part-6-booting-a-risc-v-linux-kernel): ``` Early Boot in Linux When Linux boots, it expects the system to be in the following state: a0 contains a unique per-hart ID. (下略) a1 contains a pointer to the device tree, ... (下略) ``` 以及 [linux/arch/riscv/kernel /head.S](https://github.com/torvalds/linux/blob/master/arch/riscv/kernel/head.S#L292) 之以下組合語言程式碼: ``` ... ENTRY(_start_kernel) ... /* Save hart ID and DTB physical address */ mv s0, a0 mv s1, a1 ... ``` 可知 A0 和 A1 在載入 Kernel 前是用做傳遞 CPU ID 以及 DTB 位址的用途。 #### Set up peripherals 設定 uart 的 I/O handling 與基於 virtio 的網路存取。 > TODO: 研究 virtio 與如何嵌入進這個專案中 #### Emulate 開始模擬,首先檢查 uart 是否有請求,若有則發出中斷進行處理,若沒有就以 polling 的方式查看: ```c if (peripheral_update_ctr-- == 0) { peripheral_update_ctr = 64; u8250_check_ready(&emu.uart); if (emu.uart.in_ready) emu_update_uart_interrupts(&vm); } ``` > ISSUE: 不知道 peripheral_update_ctr 代表與設定成 64 的意義 > 推測是每經過 64 個單位時間後去 polling uart 是否需要處理。 其中內部的中斷是藉由 **PLIC** 來處理: ```c static void emu_update_uart_interrupts(vm_t *vm) { emu_state_t *data = (emu_state_t *) vm->priv; u8250_update_interrupts(&data->uart); if (data->uart.pending_ints) data->plic.active |= IRQ_UART_BIT; else data->plic.active &= ~IRQ_UART_BIT; plic_update_interrupts(vm, &data->plic); } ``` > TODO: 研究 PLIC 與如何嵌入進這個專案中 如果模擬器執行期間**發生 time-out** 則將 sip bit 設成 1,否則設成 0: ```c if (vm.insn_count_hi > emu.timer_hi || (vm.insn_count_hi == emu.timer_hi && vm.insn_count > emu.timer_lo)) vm.sip |= RV_INT_STI_BIT; else vm.sip &= ~RV_INT_STI_BIT; ``` 最後就可以開始針對每個指令執行對應的操作,i.e., fetch -> decode -> execute: ```c vm_step(&vm); if (vm.error == ERR_EXCEPTION && vm.exc_cause == RV_EXC_ECALL_S) { handle_sbi_ecall(&vm); continue; } if (vm.error == ERR_EXCEPTION) { vm_trap(&vm); continue; } ``` > TODO: 研究 SBI ## TODO: 利用 Buildroot 建構系統 運用 Buildroot 自行編譯 Linux 核心和 root file system,需要客製化 Buildroot,加入自訂的套件,如 [kilo](https://github.com/antirez/kilo)。 需要解釋 [initramfs](http://xstarcd.github.io/wiki/Linux/ShengRuLiJie_linux_2.6_initramfs.html) 運作原理以及 Buildroot 如何產生 cpio 檔案。 ### Buildroot > refer to: > [RISC-V 和 Buildroot 介紹 by jserv](https://hackmd.io/@sysprog/ryHaBkrOE) > [2019 年核心實作專題: RISCV with TinyEMU](https://hackmd.io/@johnnylord/HkeMrEARE) > [buildRoot study - 建立自己的作業系統](http://fichugh.blogspot.com/2016/02/buildroot-study.html) 要讓一個系統從==開機到完全啟動==,至少需要以下三個部分: * **bootloader** * **kernel** * **root file system** :::warning 注意用詞: * dirctory 是「目錄」 * folder 是「檔案夾」 在 POSIX 相容系統 (包含 Linux),應該用「目錄」 :notes: jserv ::: Buildroot 專案就能用來建構出上面三個部分,其中: * **toolchain/** 目錄中包含所有與交叉編譯工具鏈 (e.g., binutils, gcc, gdb、kernel-headers 等) 相關的 makefile 與軟體檔案。 * **arch/** 目錄所有 buildroot 中支援之硬體架構的定義。 * **package/** 目錄包含所有 Buildroot 可以編譯和加進目標 root filesystem 的 user-space 的工具和函式庫的 makefile 和相關檔案,且每個 package 都有一個子目錄。 * **linux/** 目錄包含 Linux 核心的 makefile 和相關檔案。 * **boot/** 目錄包含 buildroot 支援之 bootloader 的 makefile 和相關檔案。 * **system/** 目錄包含系統整合相關檔案,例如: 目標檔案系統、init 程式的選擇。 * **fs/** 目錄包含產生目標 root filesystem 的 makefile 與相關檔案。 上述目錄中都至少包含以下兩個檔案: * something.mk 用來下載、設定、編譯和安裝 package 的 makefile。 * Config.in 工具配置描述檔案的一部分,描述每個 package 中的選項。 上面章節 [利用 Buildroot 建構檔案系統](https://hackmd.io/@sysprog/Skuw3dJB3#%E5%88%A9%E7%94%A8-Buildroot-%E5%BB%BA%E6%A7%8B%E6%AA%94%E6%A1%88%E7%B3%BB%E7%B5%B1) 中複製過來的 \.config 檔案是我們要藉由 buildroot 工具去建構 kernel image + root file system 的設定檔,可透過 `$ make menuconfig` 去調整,下面是客製化 buildroot 的流程。 首先取得 buildroot 程式碼: ```shell $ git clone https://github.com/buildroot/buildroot.git --depth=1 ``` > `--depth=1` 表示只取得最新一筆 commit,在專案很大時可加快下載時間及節省空間。 根據 semu 提供的組態來建構 root file system: ```shell $ cd buildroot $ cp ../semu/configs/buildroot.config .config $ make $ cd .. ``` 安裝必要的開發套件: ```shell $ sudo apt install libncurses5-dev ``` 接著變更設定: ```shell $ make menuconfig ``` 預期會看到以下畫面: ![](https://hackmd.io/_uploads/H1FlJj182.png) 這個介面是用來選擇 build 時的特色 (features) 和參數 (parameters),其中: * **Features** 可使用內建、模組或直接忽略 * **Parameters** 需要以十進制或十六進制的數字或文字輸入 上面介面中: * **Target options** 可選擇 ISA 架構、單精度、ABI 格式、ELF 格式等 * **Toolchain** 可選擇格式 (e.g., buildroot 或外部工具鏈)、變更工具鏈名稱、要引入的函式庫 (e.g., glibc)、kernel headers 版本等 * **Build options** 可選擇建構時的屬性,例如儲存 buildroot config 的位置、是否要加上除錯訊息等 * **System configuration** 可選擇開機提示字、登入是否需要密碼等 * **Target packages** 設定 busybox,包含是否使用原本或自訂的版本 * **Filesystem images** 可建構 root filesystem 的 cpio 檔案,通常被用來初始化 RAM filesystem,並由 bootloader 傳給 kernel * **Bootloader** 用來選擇 bootloader (e.g., opensbi、U-Boot 等) > Busybox 是個工程程式集合,在單一的可執行檔中提供精簡的 UNIX 工具 (e.g., ls 命令),可執行於多款 POSIX 環境的作業系統 (e.g., Linux)。 > cpio 是 UNIX 作業系統的檔案格式,可以從 cpio 或 tar 格式的歸檔包中存入和讀取檔案。其中歸檔包是一種包含其他檔案和有關資訊的檔案 (e.g., 檔名、存取權限等) ### Kilo 是個精簡的程式碼編輯器,支援語法高亮度提示和常見的編輯功能。 首先從 GitHub 下載: ```shell $ git clone https://github.com/antirez/kilo ``` 進行編譯: ```shell $ cd kilo $ make ``` 編譯完成後會出現 kilo 執行檔,使用 kilo 編輯檔案預期會出現以下畫面: ``` $ ./kilo kilo.c ``` ![](https://hackmd.io/_uploads/rkGq-ik8h.png) ### 將 Kilo 加入 Buildroot 中 參考 [Ztex 2019 q1 HW4](https://hackmd.io/@ztex/HyDMZyJtE?type=view) 和當中提到的 [The Buildroot user manual](https://buildroot.org/downloads/manual/manual.html#_getting_started),要對 Buildroot 進行改動前可以先參照上面介紹的 Buildroot 檔案結構,第 18 章 Adding new packages to Buildroot 介紹如何==加入客製化的 package== (i.e., 工具或函式庫) 到 Buildroot 中,步驟如下: 0. 首先找到要加入的 package 位置 1. 在 package 目錄下新增一個自訂的目錄 2. 新增 kilo/Config.in 檔案 3. 將 kilo/Config.in 更新到 package/Config.in 4. 在 kilo/ 下新增一個 makefile 稱為 kilo.mk 5. 以 make menuconfig 確認及選擇 kilo 6. 以 make 建置 buildroot 7. 重新產生 linux kernel image 8. 將 Image 複製到 semu/ 並執行 ==詳細流程==如下: 首先**找到要加入的 package 位置**: ```shell $ make menuconfig ``` 進入選單後 -> Target packages -> Text editors and viewers 確認現有的 package 如下: (預設為 nano 且沒有 kilo) ![](https://hackmd.io/_uploads/Sy1EPdM8h.png) 再來是在 package 目錄下**新增一個自訂的目錄**: (這裡直接以 kilo 為例) ```shell $ cd buildroot/package $ mkdir kilo ``` > 如果要自訂的 package 已經被分類 (e.g., x11r7、qt5 等),則需要在這些目錄下新增子目錄。 **新增 kilo/Config.in 檔案**,Config.in 中會包含 kilo 所需的選項和相關描述: ```shell $ cd kilo $ touch Config.in ``` Config.in 內容如下: ```shell config BR2_PACKAGE_KILO bool "kilo" help This is a comment that explains what libfoo is. The help text should be wrapped. https://github.com/antirez/kilo ``` >其中 bool 是 menu 圖形化介面中會出現的字樣,help 是輸入 shift+? 會出現的提示字。 > > bool、help 和其他 metadata 資訊前需要以 tab 進行縮排,help 中的文字前需要再以兩個 space 進行縮排,單列不能超過 72 個字元,扣掉 tab 剩下 62 個字元,且最後需要空一行並加上專案的來源 (詳細格式請參照第 16 章 Coding style)。 將 kilo/Config.in **更新到 package/Config.in**: ```shell $ cd .. # 移動到 package/ $ vim Config.in # 找到 menu "Text editors and viewers" # 加上 source "package/kilo/Config.in" # 注意加入的地方需要按照字母大小排列,也就是 kilo 需要加在 jxxx package 後 ``` 在 kilo/ 下新增**一個 makefile** 稱為 kilo.mk,這個檔案描述 package 需要如何下載、配置、建購及安裝等操作: ```shell $ cd kilo $ touch kilo.mk ``` 不同型態的 makefile 會對應到不同的寫法以及使用不同的基礎建設,可分為: * **generic package** 不使用 autotools 或 cmake,基於類似 autotools-based package 的基礎建設,但開發者需要做的工作較少,只需要指定如何配置、編譯和安裝 package,這個基礎建設用在不使用 autotools 建置的 package。 * **autotools-based software** 使用 autoconf、automake 等,Buildroot 為這些 package 提供專屬的基礎建設,且這些 package 需要依賴 autotools 來建置。 * **cmake-based software** Buildroot 為這些 package 提供專屬的基礎建設,且這些 package 需要依賴 cmake 來建置。 * **Python modules** Buildroot 為需要 distutils、flit、pep517、setuptools 機制來建置的 python 模組提供專屬的基礎建設。 * **Luna modules** Buildroot 為需要 LuaRocks web site 來建置的 Lua 模組提供專屬的基礎建設。 因 kilo 使用 gcc 即可編譯,所以這裡選擇 generic package 方案,參考[第 18.6 章 Infrastructure for packages with specific build systems](https://buildroot.org/downloads/manual/manual.html#generic-package-tutorial),這個方案的建置方法通常使用手寫的 makfile 或 shell script 而非 autotools 或 cmake。 此外,因 kilo 是從 github 下載,這裡需要依照[第 18.25.4 章 How to add a package from GitHub](https://buildroot.org/downloads/manual/manual.html#_tips_and_tricks) 和參考 [stackoverflow](https://stackoverflow.com/questions/8014991/how-do-i-add-a-a-package-to-buildroot-which-is-available-in-a-git-repository) 來對預設的 makefile 進行修改,其中包含 ```shell # Use a tag or a full commit ID FOO_VERSION = 1.0 FOO_SITE = $(call github,<user>,<package>,v$(FOO_VERSION)) FOO_SITE_METHOD = git ``` > FOO_VERSION 可以是 tag 或完整的 commit ID 修改後的 kilo.mk 內容如下: ```shell ################################################################################ # # kilo # ################################################################################ #KILO_VERSION = 69c3ce609d1e8df3956cba6db3d296a7cf3af3de KILO_VERSION = 62b099af00b542bdb08471058d527af258a349cf KILO_SITE = https://github.com/antirez/kilo.git KILO_SITE_METHOD = git define KILO_BUILD_CMDS $(MAKE) CC="$(TARGET_CC)" LD="$(TARGET_LD)" -C $(@D) endef define KILO_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/kilo $(TARGET_DIR)/usr/bin endef $(eval $(generic-package)) ``` > define 內的敘述必須以兩個 tab 縮排 :::warning 不知道為何使用最新一筆 commit ID 無法下載 repo。 參考 [Ztex 同學](https://hackmd.io/@ztex/HyDMZyJtE) 使用的 commit ID 可解決。 ::: 完成後回到 buildroot/ 下輸入 `make menuconfig` 來確認及選擇 kilo: ```shell $ cd buildroot $ make menuconfig ``` ![](https://hackmd.io/_uploads/BymFvuGLn.png) 可看到圖中出現 kilo 選項,選擇並保存後會更新到 buildroot/.config 中 更新完 buildroot/.config 後,以 make **重新建置 buildroot**: ```shell $ make -j4 ``` 建置完成後,預期在 buildroot/output/target/usr/bin/ 下看到 kilo。 ```shell $ cd buildroot/output/target/usr/bin $ ls ... kilo ... ``` buildroot 建置完成後,至 linux/ 目錄下**重新產生 Linux Kernel Image**: ```shell $ cd linux $ make -j4 Image ``` 建置完成後,將 linux/arch/riscv/boot/Image 複製到 semu/ 並執行: ```shell $ cd arch/riscv/boot $ cp -f Image ~/Documents/semu/Image $ cd ~/Documents/semu $ ./semu Image ``` 預期看到以下畫面: ![](https://hackmd.io/_uploads/Hy4cvOfLh.png) :::danger 文字訊息不要用圖片展現! :notes: jserv ::: #### ISSUE * kilo 無法進入編輯模式 ![](https://hackmd.io/_uploads/BJz5F_zIn.png) > 推測與下方 TODO 改進鍵盤輸入事件有關 ### initramfs 運作原理及如何產生 CPIO 檔案 > refer to: > [鳥哥私房菜 - 第十九章 Linux 的開機流程分析](https://linux.vbird.org/linux_basic/centos7/0510osloader.php#startup) > [深入理解 Linux 2.6 的 initramfs 機制](http://xstarcd.github.io/wiki/Linux/ShengRuLiJie_linux_2.6_initramfs.html) #### initramfs 介紹 Linux 核心可透過動態載入核心模組 (i.e., **driver**),這些核心模組就放在 `/lib/modules` 內,由於模組放置到磁碟根目錄內,**因此在開機的過程中,核心必須要掛載根目錄,這樣才能讀取核心模組提供載入驅動程式的功能**。 一般來說,非必要的功能 (e.g., USB、SATA 等磁碟裝置驅動程式) 會被編譯成為模組等待載入,假設 Linux 是安裝在 SATA 磁碟上,可以透過 boot loader 與 kernel image 來開機,然後 kernel 會開始接管系統並且偵測硬體及嘗試掛載根目錄來取得額外的驅動程式,但**問題是 kernel 在掛載 SATA 相關模組前不認得 SATA 磁碟**,所以若不載入 SATA 磁碟的驅動程式就無法掛載根目錄,但 SATA 驅動程式在 `/lib/modules` 中,若不掛載根目錄也無法讀到 `/lib/modules` 中的驅動程式,在這個情況下 Linux 無法開機。 ==虛擬檔案系統== (Initial RAM Disk 或 Initial RAM Filesystem) 一般記為 `/boot/initrd` 或 **`/boot/initramfs`**,這個檔案的特色是也能夠透過 boot loader 載入進記憶體中,然後這個檔案會被解壓縮並在記憶體中**模擬成一個根目錄**,且此模擬在記憶體中的檔案系統能夠提供一支**載入開機過程中所需要的 kernel module (e.g., USB、SATA 等)** 的程式,載入完成後,會幫助 kernel 重新呼叫 systemd 來開始後續的開機流程。 ![](https://hackmd.io/_uploads/Syuxa49I2.png) 如上圖所示,boot loader 可以載入 kernel 與 initramfs,然後讓 initramfs 解壓縮成根目錄,kernel 就能藉此載入需要的驅動程式,最後釋放虛擬檔案系統,完成後 **kernel 會掛載真正的 root file system** 並執行 \/sbin\/init 程式。 #### 實作客製化 initramfs 新增一個目錄名為 `initramfs-workspace` ```shell $ mkdir initramfs-workspace $ cd initramfs-workspace ``` 將上面以 git clone 下載的 linux 檔案複製到 initramfs-space/ 下 ```shell $ cp -r ~/Downloads/linux ./ ``` 新增一個存放 kernel + initramfs 的目錄,建立 `init.c`,最後再透過 semu 進行模擬 ```shell $ mkdir -p hello-initramfs $ cd hello-initramfs $ cat > init.c <<EOF > #include <stdio.h> > int main() > { > printf("Hello semu !!!\n"); > return 0; > } > EOF ``` 以 buildroot 中的 toolchain 編譯 `init.c` ```shell $ riscv32-buildroot-linux-gnu-gcc -static -o init init.c ``` > 這裡印出的 Hello semu !!! 就是 Early userspace 因執行時期需要 tty (terminal),所以需要在 hello-initramfs/ 下一併建立 /dev/console 的 character device ```shell $ mkdir -p dev $ sudo mknod dev/console c 5 1 ``` 接下來開始準備 kernel ```shell $ cd ../linux # 回到 initramfs-workspace/ $ make menuconfig ``` > 開啟 menuconfig 選擇 General setup -> 開啟 Initial RAM filesystem and RAM disk support -> 將下方的 Initramfs source file 路徑改成能找到 hello-initramfs 的路徑,保存後退出。 開始建構核心 ```shell $ make -j$(nproc) Image ``` 將核心複製到 semu 下並開始執行 ```shell $ cp arch/riscv/boot/Image ~/project/riscv/semu $ cd ~/project/riscv/semu $ ./semu Image ``` 預期看到 ```shell ... [ 0.000000] Machine model: semu [ 0.000000] earlycon: ns16550 at MMIO 0xf4000000 (options '') [ 0.000000] printk: bootconsole [ns16550] enabled [ 0.000000] Zone ranges: [ 0.000000] Normal [mem 0x0000000000000000-0x000000001fffffff] ... [ 1.460666] Run /init as init process Hello semu !!! ... ``` > 可以看到在執行 init 時出現 Hello semu !!!,但因為沒有掛載其他檔案,所以無法進入 shell #### buildroot 產生 CPIO 的機制 在 linux/usr 下可找到 `gen_init_cpio.c`,在當中的 Usage 函式中可看到 ```shell fprintf(stderr, "Usage:\n" "\t%s [-t <timestamp>] [-c] <cpio_list>\n" "\n" "<cpio_list> is a file containing newline separated entries that\n" "describe the files to be included in the initramfs archive:\n" ``` 上文中的 `archive` 就是透過 **cpio** 工具產生的封裝檔案,Linux kernel 提供一個整合性工具,可一次處理目錄與檔案的封裝,封裝過後的檔案 (cpio + gzip) 即是一個==完整的 initramfs image==。 #### 實作客製化 CPIO 回到 initramfs-workspace/ 目錄下,複製 hello-initramfs ```shell $ cd ~/initramfs-workspace $ cp -af hello-initramfs hello2-initramfs ``` 進入 hello-initramfs/ 並修改 `init.c` 中的內容 ```c #include <stdio.h> int main() { printf("Yat Another Hello semu !!!\n"); return 0; } ``` 以 buildroot 工具鏈編譯 `init.c` ```shell $ riscv32-buildroot-linux-gnu-gcc -static -o init init.c ``` 新增一個描述檔 desc_initramfs ```shell dir /dev 0755 0 0 nod /dev/console 0600 0 0 c 5 1 file /init /home/doublemama/initramfs-workspace/hello2-initramfs/init 0755 0 0 ``` 以 linux 中的 cpio 工具進行封裝 ```shell $ ../linux/usr/gen_init_cpio desc_initramfs > my_initramfs.cpio # 這裡因會在 make menuconfig 下直接讀取 cpio 檔,所以不進行壓縮 ($ gzip my_initramfs.cpio) ``` > usr/gen_init_cpio 工具會建構對應的 dir + device node + file 的封裝,最後以 gzip 壓縮起來,於是可得到 my_initramfs.cpio 這個新的 initramfs image 進入 initramfs-workspace/linux 修改讀取 cpio 的路徑並編譯核心 ```shell $ cd ../linux $ make menuconfig # 操作同上,將讀取 cpio 位置改成 .../hello2-initramfs/my_initramfs.cpio $ make -j$(nproc) Image ``` 複製 image 至 semu/ 下並進行測試 ```shell $ cp arch/riscv/boot/Image ~/project/riscv/semu/ $ cd ~/project/riscv/semu $ ./semu Image ``` 預期看到 ```shell ... [ 0.000000] Machine model: semu [ 0.000000] earlycon: ns16550 at MMIO 0xf4000000 (options '') [ 0.000000] printk: bootconsole [ns16550] enabled [ 0.000000] Zone ranges: [ 0.000000] Normal [mem 0x0000000000000000-0x000000001fffffff] [ 0.000000] Movable zone start for each node ... [ 1.449265] Run /init as init process Yat Another Hello semu !!! ... ``` > 一樣因為沒有掛載其他檔案,所以無法進入 shell 上述做法都只有印出訊息,下面以==整合 busybox 進行測試==,首先安裝 busybox ```shell $ sudo apt-get install busybox-static # 測試是否安裝成功 $ file /bin/busybox # 預期看到 /bin/busybox: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=36c64fc4707a00db11657009501f026401385933, for GNU/Linux 3.2.0, stripped ``` 回到 initramfs-workspace/ 目錄下,新增兩個 busybox 子目錄,並將 busybox 執行檔複製過來、擷取當中命令 ```shell $ cd ~/initramfs-workspace $ mkdir -p busybox-initramfs/bin $ mkdir -p busybox-initramfs/proc $ cd busybox-initramfs/bin $ cp /bin/busybox . ./busybox --help | ruby -e 'STDIN.read.split(/functions:$/m)[1].split(/,/).each{|i|`ln -s busybox #{i.strip}` unless i=~/busybox/}' $ cd .. $ echo -e '#!/bin/busybox sh\nmount -t proc proc /proc\nexec busybox sh\n' > init ; chmod +x init # 這裡一樣先不進行壓縮,也就是不執行最後的 gzip $ find . | cpio -o -H newc | gzip > ../busybox.initramfs.cpio.gz ``` 進入 linux/ 下修改讀取 cpio 檔的路徑並進行建置 ```shell $ cd ~/initramfs-workspace/linux $ make menuconfig # 將 initramfs source file 路徑選擇 ~/initramfs-workspace/busybox.initramfs.cpio $ make -j$(nproc) Image ``` > ISSUE: > * \/init 無法執行 > * \/bin\/sh 無法執行 > * Kernel pacin - not syncing: No working init found > > 錯誤訊息如下: > ``` > [ 3.494816] Run /init as init process > [ 3.497068] Failed to execute /init (error -8) > [ 3.497243] Run /sbin/init as init process > [ 3.498035] Run /etc/init as init process > [ 3.498744] Run /bin/init as init process > [ 3.500600] Starting init: /bin/init exists but couldn't execute it (error -8) > [ 3.500844] Run /bin/sh as init process > [ 3.502728] Starting init: /bin/sh exists but couldn't execute it (error -8) > [ 3.502987] Kernel panic - not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance. > ``` :::warning :top: 利用 Buildroot 產生的完整 cpio,在其之上添加自訂程式,重新打包並編譯 Linux 核心以建立對應的映像檔案。熟悉 Buildroot 的關鍵在於掌握易於測試的環境,先熟悉工具再來排除上述問題。 :notes: jserv ::: ## TODO: 正確處理鍵盤輸入事件 現行 semu 所有的鍵盤輸入皆要在按下 Enter 後才發揮作用,這使得 shell 本身的 tab completion (見 Busybox 的 `FEATURE_EDITING`) 無法作用,按下 Enter 後,會輸出額外的 `'\n'` 字元,且在 vi 中無法正確的輸入命令字元 (即按下 Esc),這是因為目前沒有正確處理輸入緩衝區。 解法:另行維護 UART 專用的緩衝區,並確保輸入事件不影響模擬器的運作 (可能需要建立執行緒) 一旦鍵盤事件能夠正確處理,就可實作 Ctrl-a x (離開模擬器) 這樣的按鍵組合。 參見: * [mini-rv32ima](https://github.com/cnlohr/mini-rv32ima): 執行 `make testdlimage` 可模擬完整的 Linux 系統,鍵盤處理無誤 * [kvm-host](https://github.com/sysprog21/kvm-host/blob/master/src/serial.c) * [xv6 branch](https://github.com/jserv/semu/blob/xv6/semu.c) * [yarisc](https://github.com/fgssfgss/yariscv/blob/main/src/console.c) * [rvc](https://github.com/PiMaker/rvc/blob/master/src/uart.h) * [poll(2)](https://man7.org/linux/man-pages/man2/poll.2.html) 上述 [描述程式碼原理](https://hackmd.io/@sysprog/Skuw3dJB3#TODO-%E6%8F%8F%E8%BF%B0%E7%A8%8B%E5%BC%8F%E7%A2%BC%E5%8E%9F%E7%90%86) 有簡單介紹過 semu 中 uart 的運作模式,模擬器每隔 64 個單位時間就會去偵測 uart 是否需要處理,若 uart 未達需要處理的情況就進行 polling ```c void u8250_check_ready(u8250_state_t *uart) { if (uart->in_ready) return; poll(&pfd, 1, 0); if (pfd.revents & POLLIN) uart->in_ready = true; } ``` 而若此時 uart 需要處理,就發出中斷 ```c static void emu_update_uart_interrupts(vm_t *vm) { u8250_update_interrupts(&data->uart); if (data->uart.pending_ints) data->plic.active |= IRQ_UART_BIT; else data->plic.active &= ~IRQ_UART_BIT; plic_update_interrupts(vm, &data->plic); } ``` 參照「背景知識 - PLIC」,外部裝置 (e.g., uart) 發出的中斷會傳送到 PLIC,再經由 PLIC 判斷先處理哪種中斷 (i.e., 先將哪種中斷傳給 hart 做處理)。 因 semu 原本接收鍵盤輸入的方式會在按下 Enter 後輸入額外換行字元,所以無法使用 tab completion 且無法使用 vi 進行編輯,參考 [mini-rv32ima](https://github.com/cnlohr/mini-rv32ima) 中的做法使用 [termios(3)](https://man7.org/linux/man-pages/man3/termios.3.html) 來解決,這個功能是用來控制終端介面非同步通訊的通訊埠,在 `tcgetattr` 取得該 fd 對應的設定後,取消標準輸入 (ICANON) 模式和 ECHO,完成後以 `tcsetattr` 做設定。 uart 處理鍵盤輸入的地方在 `u8250_handle_in()`,在當中加上以下程式碼即可使用 tab completion 且可使用 vi 進行編輯 ([commit e8f183f](https://github.com/JiggerChuang/semu/commit/e8f183f32b5360b93c5fa34aca22809420466b57)): ```c struct termios term; tcgetattr(0, &term); term.c_lflag &= ~(ICANON | ECHO); // Disable echo as well tcsetattr(0, TCSANOW, &term); ``` 以 `vi test.c` 進行測試,進入 shell 後以 vi 編輯檔案 ```shell # vi test.c Hello Semu (保存後退出) # cat te // 這裡按下 tab 可自動補齊 test.c # cat test.c Hello Semu ``` 在能正確處理 UART 的鍵盤輸入後,就能實現組合按鍵,實作使用組合按鍵離開 semu,而非以 Ctrl-c 結束整個程式如下 ([commit 90c34f9](https://github.com/JiggerChuang/semu/commit/90c34f9f1d5a70093270c1f7e2608dfbae09147c)): ```shell if (value == 1){ /* start of heading (Ctrl-a) */ if (getchar() == 120){ /* keyboard x */ printf("\n"); /* end emulator with newline */ exit(0); } } ``` :::warning 準備提交 pull request。參照 [Linux 核心專題: 系統虛擬機器開發和改進](https://hackmd.io/@sysprog/rkro_FeSh)的「TODO: 用 epoll 和 eventfd 改進 UART 實作」描述 :notes: jserv ::: #### TODO * 若出現大量的鍵盤輸入,可能導致模擬器花費大量的時間在處理 IO,可以使用多執行緒解決。 ## TODO: 改進網路處理 參照 [RVVM](https://github.com/LekKit/RVVM) 的實作,支援 Linux 核心的 TAP 和跨平台的 userspace TAP: * [src/devices/tap_api.h](https://github.com/LekKit/RVVM/blob/staging/src/devices/tap_api.h) * [src/devices/tap_linux.c](https://github.com/LekKit/RVVM/blob/staging/src/devices/tap_linux.c) * [src/devices/tap_user.c](https://github.com/LekKit/RVVM/blob/staging/src/devices/tap_user.c)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully