Browse Source

feat: copy on write

fix: not popping the pushed error code before iret
greatbridf 2 years ago
parent
commit
d923498640

+ 1 - 0
CMakeLists.txt

@@ -75,6 +75,7 @@ set(KERNEL_MAIN_SOURCES src/kernel_main.c
                         include/types/size.h
                         include/types/status.h
                         include/types/stdint.h
+                        include/types/list.h
                         include/types/list.hpp
                         include/kernel_main.h
                         )

+ 21 - 3
include/kernel/interrupt.h

@@ -17,6 +17,24 @@ struct regs_32 {
     uint32_t eax;
 };
 
+// present: When set, the page fault was caused by a page-protection violation.
+//          When not set, it was caused by a non-present page.
+// write:   When set, the page fault was caused by a write access.
+//          When not set, it was caused by a read access.
+// user:    When set, the page fault was caused while CPL = 3.
+//          This does not necessarily mean that the page fault was a privilege violation.
+// from https://wiki.osdev.org/Exceptions#Page_Fault
+struct page_fault_error_code {
+    uint32_t present : 1;
+    uint32_t write : 1;
+    uint32_t user : 1;
+    uint32_t reserved_write : 1;
+    uint32_t instruction_fetch : 1;
+    uint32_t protection_key : 1;
+    uint32_t shadow_stack : 1;
+    uint32_t software_guard_extensions : 1;
+};
+
 // external interrupt handler function
 // stub in assembly MUST be called irqN
 #define SET_UP_IRQ(N, SELECTOR)        \
@@ -64,10 +82,10 @@ void int13_handler(
     uint32_t eflags);
 
 void int14_handler(
-    ptr_t addr,
+    linr_ptr_t l_addr,
     struct regs_32 s_regs,
-    uint32_t error_code,
-    ptr_t eip,
+    struct page_fault_error_code error_code,
+    void* v_eip,
     uint16_t cs,
     uint32_t eflags);
 

+ 105 - 3
include/kernel/mem.h

@@ -3,10 +3,16 @@
 #include <types/size.h>
 #include <types/stdint.h>
 
+#define PAGE_SIZE (4096)
+#define KERNEL_IDENTICALLY_MAPPED_AREA_LIMIT ((void*)0x30000000)
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+// in mem.c
+extern struct mm* kernel_mm_head;
+
 // don't forget to add the initial 1m to the total
 struct mem_size_info {
     uint16_t n_1k_blks; // memory between 1m and 16m in 1k blocks
@@ -109,7 +115,7 @@ struct mm_attr {
 };
 
 struct mm {
-    void* start;
+    linr_ptr_t start;
     size_t len;
     struct mm_attr attr;
     struct page* pgs;
@@ -148,16 +154,112 @@ void* k_malloc(size_t size);
 
 void k_free(void* ptr);
 
-// translate physical address to linear address
+// translate physical address to virtual(mapped) address
 void* p_ptr_to_v_ptr(phys_ptr_t p_ptr);
 
 // translate linear address to physical address
-phys_ptr_t v_ptr_to_p_ptr(struct mm* mm_area, void* v_ptr);
+phys_ptr_t l_ptr_to_p_ptr(struct mm* mm_area, linr_ptr_t v_ptr);
+
+// translate virtual(mapped) address to physical address
+phys_ptr_t v_ptr_to_p_ptr(void* v_ptr);
+
+// check if the l_ptr is contained in the area
+// @return GB_OK if l_ptr is in the area
+//         GB_FAILED if not
+int is_l_ptr_valid(struct mm* mm_area, linr_ptr_t l_ptr);
+
+// find the corresponding page the l_ptr pointing to
+// @return the pointer to the struct if found, NULL if not found
+struct page* find_page_by_l_ptr(struct mm* mm_area, linr_ptr_t l_ptr);
+
+static inline page_t phys_addr_to_page(phys_ptr_t ptr)
+{
+    return ptr >> 12;
+}
+
+static inline pd_i_t page_to_pd_i(page_t p)
+{
+    return p >> 10;
+}
+
+static inline pt_i_t page_to_pt_i(page_t p)
+{
+    return p & (1024 - 1);
+}
+
+static inline phys_ptr_t page_to_phys_addr(page_t p)
+{
+    return p << 12;
+}
+
+static inline pd_i_t linr_addr_to_pd_i(linr_ptr_t ptr)
+{
+    return page_to_pd_i(phys_addr_to_page(ptr));
+}
+
+static inline pd_i_t linr_addr_to_pt_i(linr_ptr_t ptr)
+{
+    return page_to_pt_i(phys_addr_to_page(ptr));
+}
+
+static inline page_directory_entry* lptr_to_pde(struct mm* mm, linr_ptr_t l_ptr)
+{
+    return mm->pd + linr_addr_to_pd_i((phys_ptr_t)l_ptr);
+}
+
+static inline page_table_entry* lptr_to_pte(struct mm* mm, linr_ptr_t l_ptr)
+{
+    page_directory_entry* pde = lptr_to_pde(mm, l_ptr);
+    page_table_entry* pte = (page_table_entry*)p_ptr_to_v_ptr(page_to_phys_addr(pde->in.pt_page));
+    return pte + linr_addr_to_pt_i((phys_ptr_t)l_ptr);
+}
+
+static inline page_directory_entry* lp_to_pde(struct mm* mm, linr_ptr_t l_ptr)
+{
+    phys_ptr_t p_ptr = l_ptr_to_p_ptr(mm, l_ptr);
+    page_directory_entry* pde = mm->pd + linr_addr_to_pd_i(p_ptr);
+    return pde;
+}
+
+// get the corresponding pte for the linear address
+// for example: l_ptr = 0x30001000 will return the pte including the page it is mapped to
+static inline page_table_entry* lp_to_pte(struct mm* mm, linr_ptr_t l_ptr)
+{
+    phys_ptr_t p_ptr = l_ptr_to_p_ptr(mm, l_ptr);
+
+    page_directory_entry* pde = lp_to_pde(mm, l_ptr);
+    phys_ptr_t p_pt = page_to_phys_addr(pde->in.pt_page);
+
+    page_table_entry* pte = (page_table_entry*)p_ptr_to_v_ptr(p_pt);
+    pte += linr_addr_to_pt_i(p_ptr);
+
+    return pte;
+}
+
+// map the page to the end of the mm_area in pd
+int k_map(
+    struct mm* mm_area,
+    struct page* page,
+    int read,
+    int write,
+    int priv,
+    int cow);
+
+// allocate a raw page
+page_t alloc_raw_page(void);
+
+// allocate a struct page together with the raw page
+struct page* allocate_page(void);
 
 #define KERNEL_PAGE_DIRECTORY_ADDR ((page_directory_entry*)0x00000000)
 
 void init_mem(void);
 
+#define KERNEL_CODE_SEGMENT (0x08)
+#define KERNEL_DATA_SEGMENT (0x10)
+#define USER_CODE_SEGMENT (0x18)
+#define USER_DATA_SEGMENT (0x20)
+
 #define SD_TYPE_CODE_SYSTEM (0x9a)
 #define SD_TYPE_DATA_SYSTEM (0x92)
 

+ 6 - 1
include/kernel_main.h

@@ -1,6 +1,11 @@
 #pragma once
 
-#define MAKE_BREAK_POINT() asm volatile("xchgw %bx, %bx")
+static inline void __break_point(void)
+{
+    asm volatile("xchgw %bx, %bx");
+}
+
+#define MAKE_BREAK_POINT() __break_point()
 
 #define KERNEL_STACK_SIZE (16 * 1024)
 #define KERNEL_STACK_SEGMENT (0x10)

+ 9 - 0
include/types/list.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#define LIST_LIKE_AT(type, list_like, pos, result_name) \
+    type* result_name = list_like; \
+    {                   \
+        size_t _tmp_pos = (pos); \
+        while (_tmp_pos--) \
+            result_name = result_name->next; \
+    }

+ 1 - 0
include/types/size.h

@@ -13,6 +13,7 @@ typedef int64_t diff_t;
 #endif
 
 typedef ptr_t phys_ptr_t;
+typedef ptr_t linr_ptr_t;
 typedef size_t page_t;
 typedef size_t pd_i_t;
 typedef size_t pt_i_t;

+ 1 - 0
src/asm/interrupt.s

@@ -38,6 +38,7 @@ int14:
     movl %cr2, %eax
     pushl %eax
     call int14_handler
+    popl %eax
     popal
 
 # remove the 32bit error code from stack

+ 2 - 1
src/asm/sys.s

@@ -11,7 +11,8 @@ asm_enable_paging:
     movl %eax, %cr3
 
     movl %cr0, %eax
-    orl $0x80000001, %eax
+    // SET PE, WP, PG
+    orl $0x80010001, %eax
     movl %eax, %cr0
 
     ret

+ 2 - 1
src/boot.s

@@ -199,7 +199,8 @@ load_early_kernel_page_table:
     movl %eax, %cr3
 
     movl %cr0, %eax
-    orl $0x80000001, %eax
+    // SET PE, WP, PG
+    orl $0x80010001, %eax
     movl %eax, %cr0
 
     jmp start_move_kernel

+ 56 - 15
src/kernel/interrupt.c

@@ -4,6 +4,7 @@
 #include <kernel/hw/keyboard.h>
 #include <kernel/hw/timer.h>
 #include <kernel/interrupt.h>
+#include <kernel/mem.h>
 #include <kernel/stdio.h>
 #include <kernel/tty.h>
 #include <kernel/vga.h>
@@ -129,33 +130,73 @@ void int13_handler(
     asm_hlt();
 }
 
+static size_t page_fault_times;
+
 // page fault
 void int14_handler(
-    ptr_t addr,
+    linr_ptr_t l_addr,
     struct regs_32 s_regs,
-    uint32_t error_code,
-    ptr_t eip,
+    struct page_fault_error_code error_code,
+    void* v_eip,
     uint16_t cs,
     uint32_t eflags)
 {
+    MAKE_BREAK_POINT();
     char buf[512];
 
-    tty_print(console, "---- PAGE FAULT ----\n");
+    ++page_fault_times;
+
+    // not present page, possibly mapped but not loaded
+    // or invalid address or just invalid address
+    // TODO: mmapping and swapping
+    if (error_code.present == 0) {
+        goto kill;
+    }
+
+    // kernel code
+    if (cs == KERNEL_CODE_SEGMENT) {
+        if (is_l_ptr_valid(kernel_mm_head, l_addr) != GB_OK) {
+            goto kill;
+        }
+        struct page* page = find_page_by_l_ptr(kernel_mm_head, l_addr);
+
+        // copy on write
+        if (error_code.write == 1 && page->attr.cow == 1) {
+            page_directory_entry* pde = kernel_mm_head->pd + linr_addr_to_pd_i(l_addr);
+            page_table_entry* pte = p_ptr_to_v_ptr(page_to_phys_addr(pde->in.pt_page));
+            pte += linr_addr_to_pt_i(l_addr);
+
+            // if it is a dying page
+            if (*page->ref_count == 1) {
+                page->attr.cow = 0;
+                pte->in.a = 0;
+                pte->in.rw = 1;
+                return;
+            }
+            // duplicate the page
+            page_t new_page = alloc_raw_page();
+            void* new_page_data = p_ptr_to_v_ptr(page_to_phys_addr(new_page));
+            memcpy(new_page_data, p_ptr_to_v_ptr(page_to_phys_addr(page->phys_page_id)), PAGE_SIZE);
+
+            pte->in.page = new_page;
+            pte->in.rw = 1;
+            pte->in.a = 0;
+
+            --*page->ref_count;
+
+            page->ref_count = (size_t*)k_malloc(sizeof(size_t));
+            *page->ref_count = 1;
+            page->attr.cow = 0;
+            page->phys_page_id = new_page;
+            return;
+        }
+    }
 
+kill:
     snprintf(
         buf, 512,
-        "eax: %x, ebx: %x, ecx: %x, edx: %x\n"
-        "esp: %x, ebp: %x, esi: %x, edi: %x\n"
-        "eip: %x, cs: %x, error_code: %x   \n"
-        "eflags: %x, addr: %x              \n",
-        s_regs.eax, s_regs.ebx, s_regs.ecx,
-        s_regs.edx, s_regs.esp, s_regs.ebp,
-        s_regs.esi, s_regs.edi, eip,
-        cs, error_code, eflags, addr);
+        "killed: segmentation fault (eip: %x, cr2: %x, error_code: %x)", v_eip, l_addr, error_code);
     tty_print(console, buf);
-
-    tty_print(console, "----   HALTING SYSTEM   ----");
-
     asm_cli();
     asm_hlt();
 }

+ 83 - 66
src/kernel/mem.c

@@ -8,10 +8,12 @@
 #include <kernel/vga.h>
 #include <kernel_main.h>
 #include <types/bitmap.h>
+#include <types/list.h>
 
 // static variables
 
-static struct mm kernel_mm;
+struct mm kernel_mm;
+struct mm* kernel_mm_head;
 
 // ---------------------
 
@@ -22,23 +24,8 @@ static struct mm kernel_mm;
 
 // ---------------------
 
-// forward declarations
-static page_t alloc_page(void);
-
-// map page to the end of mm_area in pd
-int k_map(
-    struct mm* mm_area,
-    struct page* page,
-    int read,
-    int write,
-    int priv,
-    int cow);
-
-// ---------------------
-
 static void* p_start;
 static void* p_break;
-static segment_descriptor* gdt;
 
 static size_t mem_size;
 static char mem_bitmap[1024 * 1024 / 8];
@@ -187,36 +174,6 @@ void k_free(void* ptr)
     // TODO: fusion free blocks nearby
 }
 
-static inline page_t phys_addr_to_page(phys_ptr_t ptr)
-{
-    return ptr >> 12;
-}
-
-static inline pd_i_t page_to_pd_i(page_t p)
-{
-    return p >> 10;
-}
-
-static inline pt_i_t page_to_pt_i(page_t p)
-{
-    return p & (1024 - 1);
-}
-
-static inline phys_ptr_t page_to_phys_addr(page_t p)
-{
-    return p << 12;
-}
-
-static inline pd_i_t phys_addr_to_pd_i(phys_ptr_t ptr)
-{
-    return page_to_pd_i(phys_addr_to_page(ptr));
-}
-
-static inline pd_i_t phys_addr_to_pt_i(phys_ptr_t ptr)
-{
-    return page_to_pt_i(phys_addr_to_page(ptr));
-}
-
 void* p_ptr_to_v_ptr(phys_ptr_t p_ptr)
 {
     if (p_ptr <= 0x30000000) {
@@ -224,13 +181,14 @@ void* p_ptr_to_v_ptr(phys_ptr_t p_ptr)
         return (void*)p_ptr;
     } else {
         // TODO: address translation
+        MAKE_BREAK_POINT();
         return (void*)0xffffffff;
     }
 }
 
-phys_ptr_t v_ptr_to_p_ptr(struct mm* mm, void* v_ptr)
+phys_ptr_t l_ptr_to_p_ptr(struct mm* mm, linr_ptr_t v_ptr)
 {
-    if (mm == &kernel_mm && v_ptr < 0x30000000) {
+    if (mm == kernel_mm_head && v_ptr < (linr_ptr_t)KERNEL_IDENTICALLY_MAPPED_AREA_LIMIT) {
         return (phys_ptr_t)v_ptr;
     }
     while (mm != NULL) {
@@ -238,7 +196,8 @@ phys_ptr_t v_ptr_to_p_ptr(struct mm* mm, void* v_ptr)
             goto next;
         }
         size_t offset = (size_t)(v_ptr - mm->start);
-        return page_to_phys_addr(mm->pgs[offset / 4096].phys_page_id) + (offset % 4096);
+        LIST_LIKE_AT(struct page, mm->pgs, offset / PAGE_SIZE, result);
+        return page_to_phys_addr(result->phys_page_id) + (offset % 4096);
     next:
         mm = mm->next;
     }
@@ -247,6 +206,11 @@ phys_ptr_t v_ptr_to_p_ptr(struct mm* mm, void* v_ptr)
     return 0xffffffff;
 }
 
+phys_ptr_t v_ptr_to_p_ptr(void* v_ptr)
+{
+    return l_ptr_to_p_ptr(kernel_mm_head, (linr_ptr_t)v_ptr);
+}
+
 static inline void mark_page(page_t n)
 {
     bm_set(mem_bitmap, n);
@@ -287,7 +251,7 @@ static inline void free_addr_range(phys_ptr_t start, phys_ptr_t end)
     free_addr_len(start, end - start);
 }
 
-static page_t alloc_page(void)
+page_t alloc_raw_page(void)
 {
     for (page_t i = 0; i < 1024 * 1024; ++i) {
         if (bm_test(mem_bitmap, i) == 0) {
@@ -298,6 +262,16 @@ static page_t alloc_page(void)
     return GB_FAILED;
 }
 
+struct page* allocate_page(void)
+{
+    // TODO: allocate memory on identically mapped area
+    struct page* p = (struct page*)k_malloc(sizeof(struct page));
+    memset(p, 0x00, sizeof(struct page));
+    p->phys_page_id = alloc_raw_page();
+    p->ref_count = (size_t*)k_malloc(sizeof(size_t));
+    return p;
+}
+
 static inline void make_page_table(page_table_entry* pt)
 {
     memset(pt, 0x00, sizeof(page_table_entry) * 1024);
@@ -334,34 +308,73 @@ static inline void init_mem_layout(void)
     }
 }
 
+int is_l_ptr_valid(struct mm* mm_area, linr_ptr_t l_ptr)
+{
+    while (mm_area != NULL) {
+        if (l_ptr >= mm_area->start && l_ptr < mm_area->start + mm_area->len * PAGE_SIZE) {
+            return GB_OK;
+        }
+        mm_area = mm_area->next;
+    }
+    return GB_FAILED;
+}
+
+struct page* find_page_by_l_ptr(struct mm* mm, linr_ptr_t l_ptr)
+{
+    if (mm == kernel_mm_head && l_ptr < (linr_ptr_t)KERNEL_IDENTICALLY_MAPPED_AREA_LIMIT) {
+        // TODO: make mm for identically mapped area
+        MAKE_BREAK_POINT();
+        return (struct page*)0xffffffff;
+    }
+    while (mm != NULL) {
+        if (l_ptr >= mm->start && l_ptr < mm->start + mm->len * 4096) {
+            size_t offset = (size_t)(l_ptr - mm->start);
+            LIST_LIKE_AT(struct page, mm->pgs, offset / PAGE_SIZE, result);
+            return result;
+        }
+        mm = mm->next;
+    }
+
+    // TODO: error handling
+    return NULL;
+}
+
+void map_raw_page_to_pte(
+    page_table_entry* pte,
+    page_t page,
+    int rw,
+    int priv)
+{
+    // set P bit
+    pte->v = 0x00000001;
+    pte->in.rw = (rw == 1);
+    pte->in.us = (priv == 1);
+    pte->in.page = page;
+}
+
 static void _map_raw_page_to_addr(
     struct mm* mm_area,
     page_t page,
     int rw,
     int priv)
 {
-    // although it's NOT a physical address, we treat it as one
-    phys_ptr_t addr = (phys_ptr_t)mm_area->start + mm_area->len * 4096;
-    page_directory_entry* pde = mm_area->pd + phys_addr_to_pd_i(addr);
+    linr_ptr_t addr = (linr_ptr_t)mm_area->start + mm_area->len * 4096;
+    page_directory_entry* pde = mm_area->pd + linr_addr_to_pd_i(addr);
     // page table not exist
     if (!pde->in.p) {
         // allocate a page for the page table
         pde->in.p = 1;
         pde->in.rw = 1;
         pde->in.us = 0;
-        pde->in.pt_page = alloc_page();
+        pde->in.pt_page = alloc_raw_page();
 
         make_page_table((page_table_entry*)p_ptr_to_v_ptr(page_to_phys_addr(pde->in.pt_page)));
     }
 
     // map the page in the page table
     page_table_entry* pte = (page_table_entry*)p_ptr_to_v_ptr(page_to_phys_addr(pde->in.pt_page));
-    pte += phys_addr_to_pt_i(addr);
-    // set P bit
-    pte->v = 0x00000001;
-    pte->in.rw = (rw == 1);
-    pte->in.us = !(priv == 1);
-    pte->in.page = page;
+    pte += linr_addr_to_pt_i(addr);
+    map_raw_page_to_pte(pte, page, rw, priv);
 }
 
 // map page to the end of mm_area in pd
@@ -437,7 +450,7 @@ static inline void _init_map_page_identically(page_t page)
         pde->in.p = 1;
         pde->in.rw = 1;
         pde->in.us = 0;
-        pde->in.pt_page = alloc_page();
+        pde->in.pt_page = alloc_raw_page();
         _init_map_page_identically(pde->in.pt_page);
 
         make_page_table((page_table_entry*)p_ptr_to_v_ptr(page_to_phys_addr(pde->in.pt_page)));
@@ -460,7 +473,6 @@ static inline void init_paging_map_low_mem_identically(void)
     }
 }
 
-struct mm* mm_head;
 static struct page empty_page;
 static struct page heap_first_page;
 static size_t heap_first_page_ref_count;
@@ -472,7 +484,7 @@ void init_mem(void)
     // map the 16MiB-768MiB identically
     init_paging_map_low_mem_identically();
 
-    mm_head = &kernel_mm;
+    kernel_mm_head = &kernel_mm;
 
     kernel_mm.attr.read = 1;
     kernel_mm.attr.write = 1;
@@ -481,19 +493,19 @@ void init_mem(void)
     kernel_mm.next = NULL;
     kernel_mm.pd = KERNEL_PAGE_DIRECTORY_ADDR;
     kernel_mm.pgs = NULL;
-    kernel_mm.start = KERNEL_HEAP_START;
+    kernel_mm.start = (linr_ptr_t)KERNEL_HEAP_START;
 
     heap_first_page.attr.cow = 0;
     heap_first_page.attr.read = 1;
     heap_first_page.attr.write = 1;
     heap_first_page.attr.system = 1;
     heap_first_page.next = NULL;
-    heap_first_page.phys_page_id = alloc_page();
+    heap_first_page.phys_page_id = alloc_raw_page();
     heap_first_page.ref_count = &heap_first_page_ref_count;
 
     *heap_first_page.ref_count = 0;
 
-    k_map(mm_head, &heap_first_page, 1, 1, 1, 0);
+    k_map(kernel_mm_head, &heap_first_page, 1, 1, 1, 0);
 
     init_heap();
 
@@ -505,6 +517,11 @@ void init_mem(void)
     empty_page.next = NULL;
     empty_page.phys_page_id = phys_addr_to_page(EMPTY_PAGE_ADDR);
     empty_page.ref_count = (size_t*)k_malloc(sizeof(size_t));
+    *empty_page.ref_count = 1;
+
+    k_map(
+        kernel_mm_head, &empty_page,
+        1, 1, 1, 1);
 }
 
 void create_segment_descriptor(

+ 5 - 3
src/kernel_main.c

@@ -129,7 +129,7 @@ void load_new_gdt(void)
 
 void kernel_main(void)
 {
-    MAKE_BREAK_POINT();
+    // MAKE_BREAK_POINT();
     save_loader_data();
 
     load_new_gdt();
@@ -168,11 +168,13 @@ void kernel_main(void)
     INIT_OK();
 
     printkf("Testing k_malloc...\n");
-    char* k_malloc_buf = (char*)k_malloc(sizeof(char) * 128);
-    snprintf(k_malloc_buf, 128, "This text is printed on the heap!\n");
+    char* k_malloc_buf = (char*)k_malloc(sizeof(char) * 4097);
+    snprintf(k_malloc_buf, 4097, "This text is printed on the heap!\n");
     tty_print(console, k_malloc_buf);
     k_free(k_malloc_buf);
 
+    k_malloc_buf[4096] = '\x89';
+
     printkf("No work to do, halting...\n");
 
     while (1) {