Chapter 4
Introduction
We don’t want a fixed physical addr for allocation, rather, we want a unified abstract interface for dynamic memory layout for app storage. We call it Virtual Address
Safety: Every app will be allocated in its own virtual memory space, so each can’t interfere others.
Efficiency: Every app can coexist in same time without demand of reading outer peripherals to switch app.(With development of memory size)
We need MMU(Memory Management Unit) to achieve Address Translation for interview from virtual to physical.
Different designs occur.
Design
Segment Design
Each app exist in one fixed slot for one segment as $[0,bound)$, with a linear map by MMU.
Problem: Wasteful and inflexible
We may want a different linear map for each app, for example, its allocation for heap, data, code etc… So we can dispatch memory in more finer style, but it can’t resolve the problem because now even slot is dynamically allocated, it may still exist some free memory too small to reuse, cause the External Fragment rather than Internal Fragment which is the problem due to fixed slot.
Paging Design
We could set a Protection bit as r
for read, w
for write, x
for execution.
Another way is to inverse our mind, rather take a slot on memory, we take slot on MMU, it can map its slot(now we call it Page) for real physical memory layout. To adjust, we can take slice for Page to form Frame which is a smaller unit to suit physical memory layout, each app can take many Page Number for a Page Table, record the map.
Page Design
SV39 only use lower 39 bits rather whole 64 bits in design even bit width is 64 bits(it’s a fairly large amount!)
In a address, $[11:0]$ called Page Offset, and $[38:12]$ is the VPN which will be used for location of page and use offset to direct in one page(with 4KiB in one page).
We should modify satp
to open Paging for S and U-level memory.
1 | const PAGE_SIZE: usize = 4096 |
Page Size and offset to slice physical addr.
1 | // os/src/mm/address.rs |
Page Entry to bundle permission and physical page.
1 | pub fn ppn(&self) -> PhysPageNum { |
Usually, a simple design for page manager would be a linear map from base addr and follow up. But actually it will take a huge amount of memory due to the storage of offset by base addr for each app.
A finer design is from Trie. We will take index slice for each 8 bits(it will fit in to 4KB just right!), it means for each slice has 512 states, and link those state up, form a tree. Usually with 3-level for Page Index. Total 27 bits.
Virtual Page will reserve 27 bits for the index slice and 12 bits for offset for certain page. Total 39 bits.
When we transform a virtual addr to physical one, we will do the following exposition reversely.
Page Management Design
A simple allocation for page(rather management!) is stack style.
1 | pub struct StackFrameAllocator { |
Based on Allocation, we can design Page Table.
1 | // os/src/mm/frame_allocator.rs |
It means for one physical page, we will record each allocation by vector of FrameTracker as a representation of real Frame after the root.
We should design transformation from virtual addr to physical addr.
1 | // for each idxs represent index slices of virtual addr. |
Page Map Design
Based on our abstraction, we need a design for MapArea
to given a map from continuous virtual address(no matter their corresponding page!) to physical address.
1 | // os/src/mm/memory_set.rs |
Based on continuous virtual address map, we can define discontinuous map for one app.
1 | // os/src/mm/memory_set.rs |
In Each MapArea
allocated for some key-value for virtual-physical addr, it will allocate the same for PageTable
for Frame.
Allocation Space
To open SV39, we should write instruction for satp
:
1 | // os/src/mm/page_table.rs |
Therefore, it will fill current root of physical page number as activation.
Notice that we should make instruction contigeous for SV39
open in physical address to amend the gap of transformation of address before and after open.
Kernel
Initiation for Kernel memory layout.
1 | let mut memory_set = Self::new_bare(); |
Initiation for App memory layout.
Previously, we will cut off elf
part of binary of apps, now we load it and extract useful infos, s.t. Permissions.
MemorySet
should allocate storage of execution code with its permissions, allocate user stack and trap context at top of the memory layout.
Output MemorySet
, user stack top, entry point addr.
1 | let mut max_end_vpn = VirtPageNum(0); |
App
A problem is that separation of Kernel and App will also isolate Trap, the process need info from App to Kernel but App can’t see it. Therefore, we need a transfer operation. We achieve this by storing related info in TrapContext
.
(Because there’s no more register to store these without breaking state like sscratch
designed for kernel stack.)
1 | // os/src/trap/context.rs |
Notice that we also need to modify below to trigger satp
and specify corresponding(U-level, S-level satp
) physical page number to change state.
1 | # __alltraps: |
To amend the problem of contigeous instructions after switch, we need to adjust memory layout for trampoline
which is in the same location in U-level and S-level(unified for all app to trap!). It will be placed at highest virtual page.
1 | # os/src/linker.ld |
We modify rather raw handler and restore, due to virtual address, we need to adjust it for trampoline
rather the address we had code!(it’s virtual!)
1 |
|
Then map the virtual address for it up to the physical address for unifying.
1 | // os/src/config.rs |
We should adjust TaskControlBlock
for the same reason, record each Trap
.
1 | // os/src/task/task.rs |
It will read data getting from elf, get trap contexr ppn, its kernel stack bottom and top, and then initiate trap context.
Here the part of task control initiation:
1 | impl TaskContext { |