[xv6 學習紀錄 01] Lab: Xv6 and Unix utilities

Lab 連結: Lab: Xv6 and Unix utilities Boot xv6(Easy) 題目敘述: 這部份的詳細內容都寫在 lab util 中,會需要一個 linux 系統(windows使用者可以用虛擬機),Xv6 會跑在 linux 所架設的虛擬機上。 下載原始碼 git clone git://g.csail.mit.edu/xv6-labs-2022 cd xv6-labs-2022 安裝架設虛擬機的套件 我自己是用 Debian,如果你用的是 ubuntu 的話下載步驟應該也是一樣的,至於是其他系統的使用者,可以看這裡 sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu compile程式碼並且讓他跑在虛擬機上 $ make qemu ... (一大串訊息) ... xv6 kernel is booting hart 2 starting hart 1 starting init: starting sh $ 到這裡,Xv6已經成功開機了! 嘗試打個指令 $ ls . 1 1 1024 .. 1 1 1024 README 2 2 2059 xargstest.sh 2 3 93 cat 2 4 24120 echo 2 5 22944 forktest 2 6 13184 grep 2 7 27424 init 2 8 23680 kill 2 9 22904 ln 2 10 22744 ls 2 11 26312 mkdir 2 12 23040 rm 2 13 23032 sh 2 14 41856 stressfs 2 15 23904 usertests 2 16 148312 grind 2 17 38008 wc 2 18 25232 zombie 2 19 22280 console 3 20 0 沒意外的話,會出現以上的畫面 ...

September 24, 2025

[xv6 學習紀錄 02] Lab: system calls

Lab 連結: Lab: system calls 大綱 xv6 有哪些 system call,以及他們的作用為何 ? 以程式碼的觀點來理解 xv6 的 system call 流程 Using gdb System call tracing 1. xv6 有哪些 system call,以及他們的作用為何 ? 0. kernel/syscall.h 定義 syste mcall 的編號 // System call numbers #define SYS_fork 1 #define SYS_exit 2 #define SYS_wait 3 #define SYS_pipe 4 #define SYS_read 5 #define SYS_kill 6 #define SYS_exec 7 #define SYS_fstat 8 #define SYS_chdir 9 #define SYS_dup 10 #define SYS_getpid 11 #define SYS_sbrk 12 #define SYS_sleep 13 #define SYS_uptime 14 #define SYS_open 15 #define SYS_write 16 #define SYS_mknod 17 #define SYS_unlink 18 #define SYS_link 19 #define SYS_mkdir 20 #define SYS_close 21 2. 以程式碼的觀點來理解 xv6 的 system call 流程 以下使用 user/cat.c 為例,來探討 xv6 的 system call 流程,流程中有 3 大步驟 ...

September 24, 2025

[xv6 學習紀錄 03-1] Virtual Memory 程式碼解析

xv6 中的 virtual memory memory layout 每一個 process 都會有自己的一個 page table,kernel 則是有它自己獨立的一個 page table 一般來說 kernel page table 會 map 所有的 physical address,這樣在 kernel mode 的時候就可以針對所有 pa 做動作 Kernel address space 接著依序講解 MAXVA, PHYSTOP, KERNBASE MAXVA 如同先前提及,在 Sv39 中 virtual address 由 39 bits 組成 // kernel/riscv.h // one beyond the highest possible virtual address. // MAXVA is actually one bit less than the max allowed by // Sv39, to avoid having to sign-extend virtual addresses // that have the high bit set. #define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) 最大的 virtual address MAXVA 是 0b100 0000 0000 0000 0000 0000 0000 0000 0000 0000(38 0s): 可以得知 Sv39 並沒有把 39 個 bits 用滿,實際上最大的 address 只會到 MAXVA - 1 = 0b011 1111 1111 1111 1111 1111 1111 1111 1111 1111 = 0x3fffffffff ...

October 13, 2025

[xv6 學習紀錄 03] Lab: page tables

Lab 連結: lab pgtbl Speed up system calls (easy) 題目敘述: When each process is created, map one read-only page at USYSCALL (a virtual address defined in memlayout.h). At the start of this page, store a struct usyscall (also defined in memlayout.h), and initialize it to store the PID of the current process. For this lab, ugetpid() has been provided on the userspace side and will automatically use the USYSCALL mapping. You will receive full credit for this part of the lab if the ugetpid test case passes when running pgtbltest. ...

October 2, 2025

[xv6 學習紀錄 04-1] 使用 GDB 追蹤 xv6 以了解 RISC-V 的 Calling Convention 與 Stack Frames

課程影片連結: 6.S081 Fall 2020 Lecture 5: RISC-V Calling Convention and Stack Frames 這篇文章目的在於整理這個影片的重點,主要有以下的重點 如何用 GDB 追蹤 user program RISC-V 的 Calling Convention RISC-V 的 register (Caller and Callee saved) RISC-V 的 Stack Frame 影片中的範例程式並沒有附在 lab 中,要自己新增: 新增影片中的範例程式 kernel/defs.h // demos.c void demo1(void); void demo2(void); void demo3(void); void demo4(void); void demo5(void); void demo6(void); void demo7(void); // asmdemo.S int sum_to(int); int sum_then_double(int); kernel/demos.c #include "types.h" #include "riscv.h" #include "defs.h" #include "date.h" #include "param.h" #include "memlayout.h" #include "spinlock.h" #include "proc.h" #include "sysinfo.h" void demo1() { printf("Result: %d\n", sum_to(5)); } void demo2() { printf("Tesult: %d\n", sum_ten_double(5)); } void _demo3(char a, char b, char c, char d, char e, char f, char g, char h, char i, char j) { printf("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", a, b, c, d, e, f, g, h, i, j); } void demo3() { _demo3('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'); } int dummymain(int argc, char *argv[]) { int i = 0; for (; i < argc; i++) { printf("Argument %d: %s\n", i, argv[i]); } return 0; } void demo4() { char *args[] = {"foo", "bar", "baz"}; int result = dummymain(sizeof(args)/sizeof(args[0]), args); if (result < 0) panic("Demo 4"); } int _dummy(int n) { int div = 5; int sum = 0; for (int i=0; i < n; i++) { if (i % div == 0) sum += i; } return sum; } void demo5() { printf("Demo\n"); int top = 50; int result = _dummy(top); printf("Result: %d\n", result); } void demo6() { int sum = 0; for (int i = 1; i < 10; i++) { sum += 3 *i; printf("%d\n", sum_to(i)); } printf("Sum: %d\n", sum); } struct Person { int id; int age; // char *name; }; void printPerson(struct Person *p) { printf("Person %d (%d)\n", p->id, p->age); // printf("Name:%s: %d (%d)\n", p->name, p->id, p->age); } void demo7() { // Structs struct Person p; p.id = 1215; p.age = 22; // p.name = "Nick"; printPerson(&p); } kernel/asmdemo.S .section .text .global sum_to /* int sum_to() { int acc = 0; for (int i = 0; i <= n; i++) { acc += i; } return acc; } */ sum_to: mv t0, a0 # t0 <- a0 li a0, 0 # a0 <- 0 loop: add a0, a0, t0 # a0 <- a0 + t0 addi t0, t0, -1 # t0 <- t0 - 1 bnez t0, loop # if t0 != 0: pc <- loop ret .global sum_then_double sum_then_double: addi sp, sp, -16 sd ra, 0(sp) call sum_to li t0, 2 mul a0, a0, t0 ld ra, 0(sp) addi sp, sp, 16 ret Makefile OBJS = \ $K/entry.o \ $K/kalloc.o \ $K/string.o \ ... $K/plic.o \ $K/virtio_disk.o \ $K/demos.o \ $K/asmdemo.o 新增 system call demo() user/usys.pl 加上 entry(demo); ...

October 9, 2025

[xv6 學習紀錄 04-2] 如何使用 gdb 追蹤 xv6 的 system call 過程

本文目標:照著以下步驟就可以看到整個 system call 的過程 整個過程大致上都是照著這個影片做的,但其中有幾個步驟稍稍的不同。 2. 用 gdb-multiarch debug xv6 的方式 這裡會需要開啟 2 個終端機, 先在其中一個終端機輸入 make qemu-gdb # 這是被 debug 的對象 在另一個終端機輸入 gdb-multiarch # 開啟 debuger 會開始針對上面的那個終端機中的程式進行除錯 gdb-multiarch 是透過 .gdbinit 這個檔案找到 debug 的對象的 # .gdbinit set confirm off set architecture riscv:rv64 target remote 127.0.0.1:26000 # 透過這個來找到 qemu symbol-file kernel/kernel set disassemble-next-line auto set riscv use-compressed-breakpoints yes 動機:$ 的出現 make qemu 後 xv6 開機時,會顯示的畫面 ...

September 27, 2025

[xv6 學習紀錄 04] Lab: Traps

Lab 連結: lab traps 課程影片連結: 6.S081 Fall 2020 Lecture 5: RISC-V Calling Convention and Stack Frames 6.S081 Fall 2020 Lecture 6: Isolation & System Call Entry/Exit RISC-V assembly (easy) It will be important to understand a bit of RISC-V assembly, which you were exposed to in 6.1910 (6.004). There is a file user/call.c in your xv6 repo. make fs.img compiles it and also produces a readable assembly version of the program in user/call.asm. Read the code in call.asm for the functions g, f, and main. The instruction manual for RISC-V is on the reference page. Here are some questions that you should answer (store the answers in a file answers-traps.txt): ...

October 9, 2025

[xv6 學習紀錄 05-1] Page Fault

課程影片連結: 6.S081 Fall 2020 Lecture 8: Page Faults Page Fault 回顧 virtual memory 的特性 isolation level of indirection 在原先 xv6 的設計中 pagetable 的設計是 static 現在在 page fault 的時候做一些動作,可以設計為 dynamic 的 mapping Information neede 造成 page fault 的 virtual memory stval register The type of page fault scause: R/W/Instruction Virtual address of instruction that cause page fault 這是為了在處理完 page fault 之後,可以回到原本的地方繼續執行 trampframe->epc Allocation: sbrk() 原先的方式:eager allocatoin 可變更為:Lazy Allocation 1. Lazy Allocation 先把 sys_sbrk() 變更為: ...

October 11, 2025

[xv6 學習紀錄 05] Lab: xv6 lazy page allocation

Lab 連結: Lab: xv6 lazy page allocation Eliminate allocation from sbrk() (easy) Your first task is to delete page allocation from the sbrk(n) system call implementation, which is the function sys_sbrk() in sysproc.c. The sbrk(n) system call grows the process’s memory size by n bytes, and then returns the start of the newly allocated region (i.e., the old size). Your new sbrk(n) should just increment the process’s size (myproc()->sz) by n and return the old size. It should not allocate memory – so you should delete the call to growproc() (but you still need to increase the process’s size!). ...

October 11, 2025

[xv6 學習紀錄 06-1] Interrupts

課程影片連結: 6.S081 Fall 2020 Lecture 9: Interrupts Terms PLIC UART Interrupts 的意義 跟 syscall, traps 很像 Interrupts 從哪裡來? 一些 CPU 的 fig programming device, memory mapped I/O UART store/load to registers Case Study: $ ls 的出現 kerenl/main.c kernel/console.c: consoleinit() kernel/uart.c: uartinit() kernel/plic.c: plicinit() kernel/plic.c: plicinithart() kernel/proc.c: scheduler() void main() { if(cpuid() == 0){ consoleinit(); // <-- printfinit(); printf("\n"); printf("xv6 kernel is booting\n"); printf("\n"); kinit(); // physical page allocator kvminit(); // create kernel page table kvminithart(); // turn on paging procinit(); // process table trapinit(); // trap vectors trapinithart(); // install kernel trap vector plicinit(); // set up interrupt controller plicinithart(); // ask PLIC for device interrupts binit(); // buffer cache iinit(); // inode table fileinit(); // file table virtio_disk_init(); // emulated hard disk userinit(); // first user process __sync_synchronize(); started = 1; } else { while(started == 0) ; __sync_synchronize(); printf("hart %d starting\n", cpuid()); kvminithart(); // turn on paging trapinithart(); // install kernel trap vector plicinithart(); // ask PLIC for device interrupts } scheduler(); } UART kernel/console.c: consoleinit() void consoleinit(void) { initlock(&cons.lock, "cons"); uartinit(); // <-- // connect read and write system calls // to consoleread and consolewrite. devsw[CONSOLE].read = consoleread; devsw[CONSOLE].write = consolewrite; } kernel/uart.c: uartinit() void uartinit(void) { // disable interrupts. WriteReg(IER, 0x00); // special mode to set baud rate. WriteReg(LCR, LCR_BAUD_LATCH); // LSB for baud rate of 38.4K. WriteReg(0, 0x03); // MSB for baud rate of 38.4K. WriteReg(1, 0x00); // leave set-baud mode, // and set word length to 8 bits, no parity. WriteReg(LCR, LCR_EIGHT_BITS); // reset and enable FIFOs. WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR); // enable transmit and receive interrupts. WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE); initlock(&uart_tx_lock, "uart"); } 在這裡可以設定一些 UART 的參數,像是 baud rate 等等 ...

October 16, 2025