寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[email protected] GitHhub:https://github.com/WindDevil (目前啥也沒有 引言 兜兜轉轉又是新的一章的開始,還是首先要看官方手冊里的理論介紹和內容. 這裡主要還是提綱挈領地摘抄裡面 ...
寫在前面
本隨筆是非常菜的菜雞寫的。如有問題請及時提出。
可以聯繫:[email protected]
GitHhub:https://github.com/WindDevil (目前啥也沒有
引言
兜兜轉轉又是新的一章的開始,還是首先要看官方手冊里的理論介紹和內容.
這裡主要還是提綱挈領地摘抄裡面的部分內容,在下麵用更小的標題表現.
本章目的
本章展現了操作系統為實現“理想”而要擴展的一系列功能:
- 通過動態記憶體分配,提高了應用程式對記憶體的動態使用效率
- 通過頁表的虛實記憶體映射機制,簡化了編譯器對應用的地址空間設置
- 通過頁表的虛實記憶體映射機制,加強了應用之間,應用與內核之間的記憶體隔離,增強了系統安全
- 通過頁表的虛實記憶體映射機制,可以實現空分復用 (提出,但沒有實現)
需求
在大多數應用(也就是應用開發者)的視角中,它們會獨占一整個 CPU 和特定(連續或不連續)的記憶體空間。當然,通過上一章的學習,我們知道在現代操作系統中,出於公平性的考慮,我們極少會讓獨占 CPU 這種情況發生。所以應用自認為的獨占 CPU 只是內核想讓應用看到的一種 幻象 (Illusion) ,而 CPU 計算資源被 時分復用 (TDM, Time-Division Multiplexing) 的實質被內核通過恰當的抽象隱藏了起來,對應用不可見。
與之相對,我們目前還沒有對記憶體管理功能進行進一步拓展,僅僅是把程式放到某處的物理記憶體中。在記憶體訪問方面,所有的應用都直接通過物理地址訪問物理記憶體,這使得應用開發者需要瞭解繁瑣的物理地址空間佈局,訪問記憶體也很不方便。在上一章中,出於任務切換的需要,所有的應用都在初始化階段被載入到記憶體中並同時駐留下去直到它們全部運行結束。而且,所有的應用都直接通過物理地址訪問物理記憶體。
這會帶來以下問題:
- 首先,內核提供給應用的記憶體訪問介面不夠透明,也不好用。由於應用直接訪問物理記憶體,這需要它在構建的時候就清楚所運行電腦的物理記憶體空間佈局,還需規劃自己需要被載入到哪個地址運行。為了避免衝突可能還需要應用的開發者們對此進行協商,這顯然是一件在今天看來不夠通用且極端麻煩的事情。
- 其次,內核並沒有對應用的訪存行為進行任何保護措施,每個應用都有電腦系統中整個物理記憶體的讀寫權力。即使應用被限制在 U 特權級下運行,它還是能夠造成很多麻煩:比如它可以讀寫其他應用的數據來竊取信息或者破壞其它應用的正常運行;甚至它還可以修改內核的代碼段來替換掉原本的
trap_handler
函數,來挾持內核執行惡意代碼。總之,這造成系統既不安全、也不穩定。 - 再次,目前應用的記憶體使用空間在其運行前已經限定死了,內核不能靈活地給應用程式提供的運行時動態可用記憶體空間。比如一個應用結束後,這個應用所占的空間就被釋放了,但這塊空間無法動態地給其它還在運行的應用使用。
解決方案
為了簡化應用開發,防止應用胡作非為,本章將更好地管理物理記憶體,並提供給應用一個抽象出來的更加透明易用、也更加安全的訪存介面,這就是基於分頁機制的虛擬記憶體。站在應用程式運行的角度看,就是存在一個從“0”地址開始的非常大的可讀/可寫/可執行的地址空間(Address Space),而站在操作系統的角度看,每個應用被局限在分配給它的物理記憶體空間中運行,無法讀寫其它應用和操作系統所在的記憶體空間。
硬體支持
實現地址空間的第一步就是實現分頁機制,建立好虛擬記憶體和物理記憶體的頁映射關係。此過程需要硬體支持,硬體細節與具體CPU相關,涉及地址映射機制等,相對比較複雜。
需要思考的問題
總體而言,我們需要思考如下問題:
- 硬體中物理記憶體的範圍是什麼?
- 哪些物理記憶體空間需要建立頁映射關係?
- 如何建立頁表使能分頁機制?
- 如何確保 OS 能夠在分頁機制使能前後的不同時間段中都能正常定址和執行代碼?
- 頁目錄表(一級)的起始地址設置在哪裡?
- 二級/三級等頁表的起始地址設置在哪裡,需要多大空間?
- 如何設置頁目錄表項/頁表項的內容?
- 如果要讓每個任務有自己的地址空間,那每個任務是否要有自己的頁表?
- 代表應用程式的任務和操作系統需要有各自的頁表嗎?
- 在有了頁表之後,任務和操作系統之間應該如何傳遞數據?
體驗環節
經典切換代碼到第四章分支,然後一舉運行:
cd ~/App/rCore-Tutorial-v3
git checkout ch4
cd os
make run
這裡註意如果不能切換到ch4
很有可能是因為沒丟棄或者保存分支,嘗試使用git checkout .
指令.
運行結果:
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
.text [0x80200000, 0x8020c000)
.rodata [0x8020c000, 0x80210000)
.data [0x80210000, 0x80266000)
.bss [0x80266000, 0x80577000)
mapping .text section
mapping .rodata section
mapping .data section
mapping .bss section
mapping physical memory
mapping memory-mapped registers
[kernel] back to world!
remap_test passed!
init TASK_MANAGER
num_app = 7
power_3 [10000/300000]
power_3 [20000/300000]
power_3 [30000/300000]
power_3 [40000/300000]
power_3 [50000/300000]
power_3 [60000/300000]
power_3 [70000/300000]
power_3 [80000/300000]
power_3 [90000/300000]
power_3 [100000/300000]
power_3 [110000/300000]
power_3 [120000/300000]
power_3 [130000/300000]
power_3 [140000/300000]
power_3 [150000/300000]
power_3 [160000/300000]
power_3 [170000/300000]
power_3 [180000/300000]
power_3 [190000/300000]
power_3 [200000/300000]
power_3 [210000/300000]
power_3 [220000/300000]
power_3 [230000/300000]
power_3 [240000/300000]
power_3 [250000/300000]
power_3 [260000/300000]
power_3 [270000/300000]
power_3 [280000/300000]
power_3 [290000/300000]
power_3 [300000/300000]
3^300000 = 612461288(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0
power_5 [10000/210000]
power_5 [20000/210000]
power_5 [30000/210000]
power_5 [40000/210000]
power_5 [50000/210000]
power_5 [60000/210000]
power_5 [70000/210000]
power_5 [80000/210000]
power_5 [90000/210000]
power_5 [100000/210000]
power_5 [110000/210000]
power_5 [120000/210000]
power_5 [130000/210000]
power_5 [140000/210000]
power_5 [150000/210000]
power_7 [10000/240000]
power_7 [20000/240000]
power_7 [30000/240000]
power_7 [40000/240000]
power_7 [50000/240000]
power_7 [60000/240000]
power_7 [70000/240000]
power_7 [80000/240000]
power_7 [90000/240000]
power_7 [100000/240000]
power_7 [110000/240000]
power_7 [120000/240000]
power_7 [130000/240000]
power_7 [140000/240000]
power_7 [150000/240000]
power_7 [160000/240000]
power_7 [170000/240000]
power_7 [180000/240000]
power_7 [190000/240000]
power_7 [200000/240000]
power_7 [210000/240000]
power_7 [220000/240000]
power_7 [230000/240000]
power_7 [240000/240000]
7^240000 = 304164893(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
load_fault APP running...
Into Test load_fault, we will insert an invalid load operation...
Kernel should kill this application!
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x100c0, kernel killed it.
store_fault APP running...
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x100c0, kernel killed it.
Test sbrk start.
origin break point = 17000
one page allocated, break point = 18000
try write to allocated page
write ok
10 page allocated, break point = 22000
11 page DEALLOCATED, break point = 17000
try DEALLOCATED more one page, should be failed.
Test sbrk almost OK!
now write to deallocated page, should cause page fault.
[kernel] PageFault in application, bad addr = 0x17000, bad instruction = 0x1124a, kernel killed it.
power_5 [160000/210000]
power_5 [170000/210000]
power_5 [180000/210000]
power_5 [190000/210000]
power_5 [200000/210000]
power_5 [210000/210000]
5^210000 = 527227302(MOD 998244353)
Test power_5 OK!
[kernel] Application exited with code 0
Test sleep OK!
[kernel] Application exited with code 0
All applications completed!
本章框圖
這裡需要和上一章的框圖進行對比才能知道本章做了什麼改進.
這是本章框圖:
這是上章框圖:
可以看到的不同分為兩個部分:
- 硬體需求不同
- 系統架構不同
硬體需求
觀察框圖,可以看到本章的框圖中要求:
- CPU with MMU 要求 CPU 含有MMU,也即記憶體管理單元(Memory Management Unit)是電腦硬體的一部分,主要負責處理記憶體管理和虛擬地址到物理地址的轉換。
- MEM with PageTable,要求記憶體支持頁表,Page Table 是操作系統用於管理虛擬記憶體的一種數據結構,它記錄了虛擬地址與物理地址之間的映射關係。Page Table 需要硬體支持,特別是 MMU(Memory Management Unit)來實現虛擬地址到物理地址的快速轉換。
總而言之就是需要MMU.
系統架構
本章框圖中增加了:
- APP層和OS層的跳板
Trampline
- 每個APP的地址空間
- 內核的地址空間
- 物理頁幀分配
等功能.
這裡摘自官方手冊:
在具體實現上,擴展了 TaskManager 的管理範圍,每個 Task 的上下文 Task Context 還包括該任務的地址空間,在切換任務時,也要切換任務的地址空間。新增的記憶體管理模塊主要包括與內核中動態記憶體分配相關的頁幀分配、堆分配,以及表示應用地址空間的 Apps MemSets 類型和內核自身地址空間的 Kernel MemSet
類型。
MemSet 類型所包含的頁表 PageTable 建立了虛實地址映射關係,而另外一個 MemArea 表示任務的合法空間範圍。
建議
代碼的閱讀順序參考官方手冊.
這裡代碼繁多,尤其是涉及到記憶體的變換之後就會面臨對前面所有的數據結構的大範圍的重寫,對trap
的處理和對__switch
的重構.
建議多看代碼,多理清問題,這樣才能順利度過這最難的一章.