Skip to content

Commit 61ce821

Browse files
committed
virtual to physical address mapping with page table
1 parent 7768241 commit 61ce821

File tree

3 files changed

+65
-6
lines changed

3 files changed

+65
-6
lines changed

writing_os_in_1000_lines/kernel.c

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "kernel.h"
22
#include "common.h"
33

4-
extern char __bss[], __bss_end[], __stack_top[], __free_ram[], __free_ram_end[];
4+
extern char __bss[], __bss_end[], __stack_top[], __free_ram[], __free_ram_end[], __kernel_base[];
55

66
struct process procs[PROCS_MAX]; // Note: All elements are zero-initialized so the state is PROC_STATE_UNUSED
77

@@ -140,6 +140,43 @@ paddr_t alloc_pages(uint32_t const n) {
140140
return paddr;
141141
}
142142

143+
// Register the mapping between the virtual address and the physical address in the page table
144+
void map_page(uint32_t *table1, uint32_t vaddr, paddr_t paddr, uint32_t flags) {
145+
// Sv32 is a two-phase lookup table. It means a tree structure where depth is 3.
146+
// Depth-1 is VPN[1], depth-2 is VPN[0], depth-3 is the page offset in the 4KiB page.
147+
//
148+
// Example of Sv32 page mapping
149+
// ┌──────────────────────────────────┐
150+
// │ 8001123 │ Virtual address
151+
// └──────────────────────────────────┘
152+
// ┌──────────┬──────────┬────────────┐
153+
// │0000100000│0000000001│000100100011│ Page table entry
154+
// └──────────┴──────────┴────────────┘
155+
// VPN[1] VPN[0] page offset
156+
// (10 bits) (10 bits) (12 bits)
157+
// =32 =1 =0x123
158+
159+
if (!is_aligned(vaddr, PAGE_SIZE)) {
160+
PANIC("unaligned vaddr %x", vaddr);
161+
}
162+
163+
if (!is_aligned(paddr, PAGE_SIZE)) {
164+
PANIC("unaligned paddr %x", paddr);
165+
}
166+
167+
uint32_t const vpn1 = (vaddr >> (10 + 12)) & 0x3ff; // Extract the first page table index
168+
if ((table1[vpn1] & PAGE_V) == 0) {
169+
// The second page table does not exist. Create it.
170+
uint32_t const pt_paddr = alloc_pages(1);
171+
table1[vpn1] = ((pt_paddr / PAGE_SIZE) << 10) | PAGE_V;
172+
}
173+
174+
// Insert the new page entry to the second page table
175+
uint32_t const vpn0 = (vaddr >> 12) & 0x3ff; // Extract the second page table index
176+
uint32_t *table0 = (uint32_t *)((table1[vpn1] >> 10) * PAGE_SIZE);
177+
table0[vpn0] = ((paddr / PAGE_SIZE) << 10) | flags | PAGE_V;
178+
}
179+
143180
__attribute__((naked)) void switch_context(uint32_t *prev_sp, uint32_t *next_sp) {
144181
__asm__ __volatile__(
145182
// Save ra and saved registers on current process's stack
@@ -211,9 +248,17 @@ struct process *create_process(uint32_t const pc) {
211248
*--sp = 0; // s0
212249
*--sp = pc; // ra
213250

251+
// Create kernel page mapping (from __kernel_base to __free_ram_end) so that kernel can access
252+
// to both static area (e.g. .text) and dynamically allocated area by alloc_pages().
253+
uint32_t *page_table = (uint32_t *)alloc_pages(1);
254+
for (paddr_t paddr = (paddr_t)__kernel_base; paddr < (paddr_t)__free_ram_end; paddr += PAGE_SIZE) {
255+
map_page(page_table, paddr, paddr, PAGE_R | PAGE_W | PAGE_X);
256+
}
257+
214258
proc->pid = i + 1; // PID starts from 1
215259
proc->state = PROC_STATE_RUNNABLE;
216260
proc->sp = (uint32_t)sp;
261+
proc->page_table = page_table;
217262
return proc;
218263
}
219264

@@ -236,21 +281,26 @@ void yield(void) {
236281
return;
237282
}
238283

239-
// Save the next process's stack bottom address to sscratch register for exception handling.
240284
__asm__ __volatile__(
285+
// Switch the page table by registering VPN[1] to satp register.
286+
// The sfence.vma instruction is a memory barrier to
287+
// - ensure modifying the page table finishes
288+
// - remove the cache of the page table entry (TLB)
289+
"sfence.vma\n"
290+
"csrw satp, %[satp]\n"
291+
"sfence.vma\n"
292+
// Save the next process's stack bottom address to sscratch register for exception handling.
241293
"csrw sscratch, %[sscratch]\n"
242294
:
243-
: [sscratch] "r"((uint32_t)&next->stack[sizeof(next->stack)]));
295+
: [satp] "r"(SATP_SV32 | ((uint32_t)next->page_table / PAGE_SIZE)),
296+
[sscratch] "r"((uint32_t)&next->stack[sizeof(next->stack)]));
244297

245298
// Switch context from the current process to the next runnable process
246299
struct process *prev = current_proc;
247300
current_proc = next;
248301
switch_context(&prev->sp, &next->sp);
249302
}
250303

251-
struct process *proc_a;
252-
struct process *proc_b;
253-
254304
void kernel_main(void) {
255305
// There is no guarantee that bootloader initializes bss with zeros
256306
memset(__bss, 0, (size_t)__bss_end - (size_t)__bss);

writing_os_in_1000_lines/kernel.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
#define PROC_STATE_RUNNABLE 1 // Process is runnable
99
#define PROC_KERNEL_STACK_SIZE 8192 // Stack size of process in bytes
1010

11+
#define SATP_SV32 (1u << 31) // The satp register bit to indicate to enable Sv32 mode paging
12+
#define PAGE_V (1 << 0) // The page is enabled
13+
#define PAGE_R (1 << 1) // The page is readable
14+
#define PAGE_W (1 << 2) // The page is writable
15+
#define PAGE_X (1 << 3) // The page is executable
16+
#define PAGE_U (1 << 4) // The page is accessible from user land
17+
1118
struct sbiret {
1219
long error;
1320
long value;
@@ -17,6 +24,7 @@ struct process {
1724
int pid; // Process ID
1825
int state; // ...
1926
vaddr_t sp; // Stack pointer on context switch
27+
uint32_t *page_table; // Page table for virtual address mapping
2028
uint8_t stack[PROC_KERNEL_STACK_SIZE]; // Kernel stack
2129
};
2230

writing_os_in_1000_lines/kernel.ld

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ENTRY(boot)
1111

1212
SECTIONS {
1313
. = 0x80200000; /* base address */
14+
__kernel_base = .;
1415

1516
.text :{
1617
KEEP(*(.text.boot)); /* Start with .text.boot section */

0 commit comments

Comments
 (0)