I'm progressing along developing my OS, and I recently implemented a virtual memory manager that handles paging. I used 2 Page Tables: (1) to identity map the first 4 MB and (2) to map the kernel (starts at 1MB) to 3GB. I have also both assigned their proper positions within the Page Directory. After I 'jump' into the higher half I then try to delete the identity-mapping page table from the page directory. However, as soon as I do this, my OS boot-loops.
My questions are:
- Am I constructing the Page Tables and their respective entries correctly?
- Am I correctly 'jumping' into the higher half of kernel memory?
This is the most relevant function along with all macros/enums used within it:
/* PHYSICALLY allocates a block of memory */
void *alloc_page();
typedef enum {
PAGE_STRUCT_ENTRY_PRESENT = 0b00000000000000000000000000000001,
PAGE_STRUCT_ENTRY_WRITEABLE = 0b00000000000000000000000000000010,
PAGE_STRUCT_ENTRY_USER_ACCESS = 0b00000000000000000000000000000100,
PAGE_STRUCT_ENTRY_WRITE_THROUGH = 0b00000000000000000000000000001000,
PAGE_STRUCT_ENTRY_CACHE_DISABLE = 0b00000000000000000000000000010000,
PAGE_STRUCT_ENTRY_ACCESSED = 0b00000000000000000000000000100000,
PAGE_STRUCT_ENTRY_DIRTY = 0b00000000000000000000000001000000,
PDE_PAGE_SIZE = 0b00000000000000000000000010000000,
PAGE_STRUCT_GLOBAL = 0b00000000000000000000000100000000,
PAGE_STRUCT_PAGE_FRAME = 0b11111111111111111111000000000000,
} PAGE_STRUCT_ENTRY_MASKS;
typedef uint32_t pt_entry_t;
typedef uint32_t pd_entry_t;
typedef uint32_t vaddr_t;
typedef uint32_t paddr_t;
#define ENTRY_ADD_ATTRIBUTE(entry, attrib)\
((entry) |= (uint32_t)(attrib))
#define ENTRY_DEL_ATTRIBUTE(entry, attrib)\
((entry) &= ~(uint32_t)(attrib))
#define ENTRY_GET_ATTRIBUTE(entry, attrib)\
((entry) & (uint32_t)(attrib))
#define ENTRY_SET_FRAME(entry, frame)\
((entry) |= (uint32_t)(PAGE_STRUCT_PAGE_FRAME) & (uint32_t)(frame))
#define ENTRIES_PER_STRUCT 1024
#define PAGE_DIR_INDEX(vaddr)\
(((vaddr) >> 22) & 0x3FF)
#define PAGE_TABLE_INDEX(vaddr)\
(((vaddr) >> 12) &0x3FF)
#define PTE_FROM_PDIR(page)\
(*page & ~0xFFF)
#define PTABLE_ADDRESS_SPACE 0x400000
#define DTABLE_ADDRESS_SPACE 0x100000000
#define PAGE_SIZE 4096
typedef struct {
pt_entry_t entries[ENTRIES_PER_STRUCT];
} ptable_t;
typedef struct {
pd_entry_t entries[ENTRIES_PER_STRUCT];
} pdirectory_t;
static pdirectory_t *curr_pd;
static void switch_pd(pdirectory_t *new){
curr_pd = new;
asm volatile("movl %0, %%cr3" :: "r"(new) : "memory");
}
void VMM_init() {
/* default pt */
ptable_t *table1 = alloc_page();
/* used for identity mapping */
ptable_t *table2 = alloc_page();
memset(table1, 0, sizeof(ptable_t));
memset(table2, 0, sizeof(ptable_t));
/* linker symbols to denote start and end of kernel */
extern uint8_t _begin, _end;
size_t kernel_size = &_end - &_begin;
/* identity map the first 4MB */
for (int i = 0; i < 1024; ++i){
pt_entry_t *entry = &table2->entries[i];
ENTRY_ADD_ATTRIBUTE(*entry, PAGE_STRUCT_ENTRY_PRESENT);
ENTRY_SET_FRAME(*entry, i * 4096);
}
/* map physical 1MB to virtual 3GB */
for (int i = 0, paddr = 0x00100000; i < 1024; ++i, paddr += 4096){
pt_entry_t *entry = &table1->entries[i];
ENTRY_ADD_ATTRIBUTE(*entry, PAGE_STRUCT_ENTRY_PRESENT);
ENTRY_SET_FRAME(*entry, paddr);
}
/* default dt */
pdirectory_t *pdir = alloc_page();
memset(pdir, 0, sizeof *pdir);
/* 1MB -> 3GB Page Table */
pd_entry_t *pd_entry = &pdir->entries[PAGE_DIR_INDEX(0xC0000000)];
ENTRY_ADD_ATTRIBUTE(*pd_entry, PAGE_STRUCT_ENTRY_PRESENT);
ENTRY_ADD_ATTRIBUTE(*pd_entry, PAGE_STRUCT_ENTRY_WRITEABLE);
ENTRY_SET_FRAME(*pd_entry, table1);
/* 0-4MB -> 0-4MB Page Table */
pd_entry = &pdir->entries[PAGE_DIR_INDEX(0x0)];
ENTRY_ADD_ATTRIBUTE(*pd_entry, PAGE_STRUCT_ENTRY_PRESENT);
ENTRY_ADD_ATTRIBUTE(*pd_entry, PAGE_STRUCT_ENTRY_WRITEABLE);
ENTRY_SET_FRAME(*pd_entry, table2);
switch_pd(pdir);
asm volatile (
"movl %%cr0, %%eax\n"
"orl $0b10000000000000000000000000000000, %%eax\n"
"movl %%eax, %%cr0\n"
"jmp 1f\n" // where 'jump' occurs
"1:\n"
::: "eax");
pdir->entries[PAGE_DIR_INDEX(0x0)] = 0;
switch_pd(pdir); // boot loop
}
Thanks so much for reading. I've been trying to figure this out for hours and I just don't get it, hopefully one of you more experienced developers will be able to help me. Have a great day!
jmp 1fis a no-op, it'll assemble with therel8displacement = 0. The assembler + linker don't know that you want1:to move. You wantjmp 1f + 3*1024*1024*1024or something. Or1f + 3*... - 1024*1024since the distance is 3GiB - 1MiB if I'm reading you correctly that your kernel image starts at 1MiB initially, then will start at 3GiB.1f + (3*1024*1024*1024 - 1024*1024)? But, wouldn't I also have to change the address of the stack frame as that would be using the identity mapped addresses? IF I mentioned what you intended in thejmpblock above, it still results in a bootloop.# Jump to higher half with an absolute jump.withlea 4f, %ecx/jmp *%ecx? The jump target is in a different.section; it's not just a simple jump. Look at the linker script. (It's also an indirect jump, using an absolute address.lea 4f, %ecxis using the absolute address the linker thinks the code should be loaded at. Even if they had put everything in the same section, it would work if everything before thejmpwas position-independent. It's not, though, they use.bss.)