## 1. 虛擬地址 每一個process有自己的pagetable,來完成虛擬地址到物理地址的映射 **虛擬地址 映射至物理地址關係** cpu在使用虛擬地址到物理地址時,內部會有satp的寄存器,每個process儲存的地址不一樣,以實現進程間的隔離,satp會儲存第一層page directory的地址,如圖中所示。 同時以下步驟是依靠MMU(Memory Management Unit)電路來實現,不需要依靠軟件。  1. 虛擬地址實際上儲存的是三個偏移量,而不是真的有2^27次方的虛擬記憶體空間,來達到節省物理內存的目的,因為沒用到就不需要創建在往下層級的page table。 2. 三級列表都是實際存在的物理地址,透過satp和第一個偏移量,來尋找下一級table的物理地址,在使用第二層偏移量,以此類推。 3. 而要將虛擬地址映射到物理地址,需要查找三次物理地址,太過耗費時間,有叫做TLB(Translation Lookaside Buffer)的存在,作用類似cache,但原理不太一樣,cache是直接儲存地址對應資料,TLB是儲存虛擬地址對應的物理地址,可以跳過三級查找的時間。 4. 因此TLB在切換進程時,需要清空,避免翻譯錯誤。 5. user mode 跟 kernel 的虛擬地址應設規則是不同的。 6. 第一個pagetable directory 並沒有被全部使用,只使用38bit,所以trampline的偏移量只到256。 7. guard page 被映射在每個process的stack下面,包含kernel 和 user。 **xv6 內核虛擬記憶體配置** 物理地址記憶體的配置由工程師決定,如果記憶體位置大於0x86.....則向DRAM,如果小於,則走向對應IO的寄存器,這部分需查詢對應開發版的DATASHEET。 作業系統會讀取對應地址從0X100讀取BOOT ROM資料來起動bootloader,bootloader會初始化硬件,並創建c語言的STACK,並跳轉到dram的 0X8000..來啟動作業系統,最後將其映射為對應虛擬地址。  由映射規則可以發現,在phystop上的資料會被映射到kernel data和kernel text 同一塊物理地址,而在phystop以下的虛擬地址,經過三級映射後,會跟物理地址一樣。 kernel 會用 free memory 來儲存user process的 pagetable **user mode虛擬記憶體配置(每個process)** user mode 記憶體配置 跟 kernel mode 配置差不多,但是內容不一樣,有heap和stack這些常見的配置。  **vmprint實驗(這個比較簡單,沒看解答)** 這是user mode 的虛擬內存實驗,因為是p->pid==1,會分配test,data,stack等等資料。 在exec.c加這行指令  代表創建第一個進程會使用vmprint() 根據提示在kernel/vm.c 創建 vmprint()函數 函數參數原本沒有int,但我為了遞歸起來比較漂亮加的,基本上是參考freewalk函數寫出來的。 freewalk()  freewalk根據觀察是walk函數的相反,可將虛擬記憶體的物理記憶體解放 **從這裡的程式可以發現,前兩級pagetable PTE_R PTE_W PTE_X是至0的。** vmprint()  參數int 是為了更好的遞歸,pagetable_t是指向512組pte_t的指標,可以從riscv.h發現  for迴圈作用是找到存在的pte,  根據宏定義PTE2PA則是單純的對指標進行位元操作。   這裡的if語句,則是查看是否為前兩級pte,是的話則進行遞歸,不是的話則終止。  後面實驗有空再補筆記 ## 2.trap 有三種情況會導致發生trap,CPU會執行特殊代碼,第一種是user mode使用ecall,會跳轉到kernel mode處理對應呼叫,第二種是非法情形,除0或是使用不存在的虛擬地址,第三種是中斷,系統中斷去處理硬件的讀寫請求。 trap處理完成後會希望代碼能繼續執行,像中斷一樣越快越好,trap 的處理會交給內核,才能操控硬件。 **risc-v處理trap 的寄存器** **stvec** :kernel 處理trap的address會放在這裡,進trap時,PC寄存器會跳轉到stvec。 **sepc** :用來記錄原先PC的address,因為stvec會寫入到pc寄存器,trap結束後,會使用sret寄存器讓sepc寫回pc寄存器。 **scause**:用來描述trap發生原因的寄存器。 **sscratch**: 內核放置在這裡的值,trap發生後會記住其中一個寄存器的值,讓uservec能操控其中一個寄存器。 **sstatus**: SIE bit 決定是否處理中斷,置0時內核不會處理中斷,直到變為1,SPP bit 則是儲存trap發生時是在user 還是 kernel,決定返回 user mode 還是 kernel mode,幫助sret寄存器判斷返回的位置。 **note: trap的發生除了保存PC寄存器,其他都是由軟件完成,以提供軟件的靈活性。** 在user mode 發生trap時,會使用**uservec來執行指令(stvec寄存器**),會幫助切換satp到kernel的pagetable。 在kernel 和 user 的pagetable 中,**TRAMPOLINE都會指向同一塊物理地址**,用來執行uservec,讓trap的情況下,user跳轉到kernel能繼續執行uservec。 uservec不會改變發生當下所有寄存器的狀態,會用**sscratch**去紀錄a0寄存器,**讓uservec能操控a0寄存器**。 之後uservec 會保存用戶寄存器。在進入用戶空間之前,內核已經設置sscratch 指向每個進程的trapframe,trapframe有空間來保存所有的用戶寄存器(參見 kernel/proc.h:44)。  因為satp 還在user page table ,uservec 要在用user mode中映射trapframe。所以在創建每個process時,Xv6 每個process的trapframe分配一頁,並映射到user mode虛擬地址 TRAPFRAME,(通常在TRAMPOLINE下)。p->trapframe 也指向trapframe,但指向的是物理地址,以便內核通過內核頁面表使用它。 trapframe包含當前進程的內核指針、當前 CPU 的 hartid(硬件線程 ID)、usertrap 的地址以及內核頁表的地址。uservec 會檢索這些值,將 satp 切換到內核頁表,然後調用 usertrap(程式)。 usertrap 的任務是確定陷阱的原因、處理它,並返回(在 kernel/trap.c:37)。如上所述,首先更改 stvec,以便在內核中發生trap時由 kernelvec處理。會保存 sepc(保存的用戶程序計數器),因為sepc可能被覆蓋。如果trap是系統調用,則由 syscall 處理,syscall將保存的用戶程序計數器(PC)加上四,因為在 RISC-V 架構下,syscall發生時,程序計數器指向是 ecall 指令;如果是中斷,則由 devintr 處理;否則是異常,內核將終止故障進程,在結束時,usertrap 檢查進程是否被終止或是應該讓出 CPU(trap是計時器中斷)。 返回用戶空間的第一步是調用 usertrapret,此函數設置 RISC-V寄存器,以為以後user mode的trap做好準備。將 stvec指向uservec,準備uservec依賴的trapframe,並將 sepc 設置為先前保存的user PC。最後,usertrapret 在映射到user和kernel page的 trampoline上調用 userret;因為 userret的匯編代碼將切換page table,切換之後可以繼續運行。 usertrapret 調用 userret,將進程的用戶頁表指針傳遞給 a0,並將 TRAPFRAME 傳遞給 a1(kernel/trampoline.S:88)。userret 將 satp 切換到進程的用戶頁表。 userret 將trapframe中保存的用戶 a0 複製到 sscratch,為稍後與 TRAPFRAME 進行交換做準備。此時,userret 只能使用寄存器和trapframe的內容。接下來,userret 從trapframe恢復保存的用戶寄存器,最後一次交換 a0 和 sscratch 以恢復用戶的 a0 並保存 TRAPFRAME 以供下次trap使用,並返回用戶空間。 **ecall 執行流程** ecall時將參數放置在寄存器 a0 和 a1 中,並將系統調用號放置在 a7 中,這些會存在trapframe中。 之後 uservec->usertrap->syscall syscall從trapframe中 a7 檢索系統調用號,用來找是哪一個syscalls,調用系統調用實現函數。 當系統調用返回時,syscall 將其返回值記錄在 p->trapframe->a0 中。這會導致原始的用戶空間調用返回該值,因為 RISC-V 上的 C 調用約定將返回值放在a0 中。系統調用通常返回負數以表示錯誤,返回零或正數以表示成功。如果系統調用號無效,syscall 會打印錯誤並返回 1。 **利用 page fault的trap** fork創毽子進程和父進程時會共享物理內存,但設置為只讀,當出現修改只讀的物理內存時,會出現錯誤進trap,trap會複製錯誤內容到新的可讀寫的內存給子進程和父進程,當trap結束時不會報錯,而會將資訊寫入新複製的內存。 另外還有延遲分配,並非所有虛擬內存都會映射到物理內存,當使用到invalid的虛擬內存時,會進錯誤trap,在分配對應內存,這個技術就是 lazy allocation。 不同架構使用的gdb工具版本不一樣 stack 地址從高往低,所以每創建一個function,sp指針會做減法  PC寄存器會保存當前是user mode 還是 kernel mode。 user mode 通常是較低的地址 kernel mode 較高 uservec 使用彙編撰寫,可以避免編譯器編譯程式使用到額外的寄存器,導致恢復到user mode時出現錯誤。 usermode 的 pagetabel 中的 trapframe 或 trapline pte_u 的U沒有被置U,代表只能由kernel mode 訪問。 每個process 都有 自己的 stack 和 kernel stack。 **ecall()就做三件事 1.切換到superior mode 2.儲存當前pc,也就是ecall的程式位置到sepc 3.將pc寄存器替換成stvec寄存器(trampline page 的起始位置)** **實驗 backtrace** 透過r_fp()讀取s0寄存器,為當前正在執行的kernel的堆棧地址,使用PGROUNDUP(cur_fp)會讀取頁表的頂端,risc-v的stack通常用一個頁表儲存,所以頁表的頂端也能理解成stack頂端,(fp-0x08)會是上一個函數的return address,(fp-0x10)這裡是16進制,代表-16,會是上一個函數fp的地址,  這裡使用(pte_t*)代表轉成指標類型,在用*使用指標,才能得到對應記憶體的數據,pte_t改成uint64也可以,最後迴圈整個程式,直到到stack top為止。  **alarm實驗** **test0:** 先將sysalarm和sysreturn函數加入syscall。 sysalarm有兩個輸入,分別是幾個cpu_tick要使用function,和要使用的function。   在proc 結構體中添加這三樣:  其中input_count是決定幾個cpu_tick後要使用function,function則是periodic(),tickcount則是用來記錄距離上一次使用alarm過了幾個tick。 接著是設計sigalarm: myproc()是讀取當前進程,argint 和 argaddr 則是將usermode傳入的參數,從a0,a1寄存器中讀取出來,tick_count則為0。  最後則是修改usertrap,這裡的which_dev==2,代表每過一個tick,系統會自己觸發的硬件中斷,所以alarm實際上是通過syscall修改這裡的值,當input_count被賦值時,通過設置p->trapframe->epc,代表中斷返回user mode時,會執行alarm給的function,這裡剛好是0x0000,因為是user mode,也是這個process的一開始,剛好是periodic,syscall則會直接返回periodic的地址。  所以最後結果等for迴圈造成的...跑完後才出現periodic,因為是tick造成的硬件中斷觸發alarm,而不是syscall觸發的。   test 01 and 02 則是在創建一個syscall,當呼叫alarm函數時,可以用sigreturn回到一開始呼叫sigalarm的位置,不然呼叫完periodic,原先的trapframe就被修改了,還會一直觸發alarm,不能達成計算CPU周期的功能。 修改pro.h,增加兩個東西,使用一個為inalam的類似鎖的東西,另一個是alarmframe,用來儲存觸發alarm之前的trapframe,讓sigreturn可以正確使用。  觸發alarm之後,函數有一個syscall,就是sigreturn  所以修改usertrap和sigreturn即可,首先是usertrap,inalarm為0時觸發alarm函數,代表第一次觸發,或是sigreturn成功之後,才能正確計時,並儲存觸發前的trapframe,才進行修改,紅色部分則是需要用指標,代表整個結構體複製,沒用的,只是把alarmframe和trapframe複製到同一塊記憶體,沒起到保存的作用。  最後是sigreturn: syscall後把鎖解開,代表繼續計時,不會計算到periodic的時間,並將trapframe還原,同樣需要用引用,這裡我猜是內核trapframe必須固定是那個位置,不能是別塊記憶體,最後將計數器歸零。  這張實驗滿有意思的,學到中斷和syscall的結合使用,也學到stack。
×
Sign in
Email
Password
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