I try to implement my own OS, and now I try to implement paging mechanism.
I created a page directory, and created identity mapping of the kernel code.However, after storing the physical address of the first page table and enabling paging, my code seems lost - meaning, the virtual address not mapped to a physical address, or mapped to a wrong one.I can see this behaviour in GDB: my code opcodes turn to add [eax], al
.
My structs are:
#define MAX_PAGES_PER_TABLE 1024#define MAX_TABLES_PER_DIR 1024typedef struct { uint32_t present : 1; // Page present in memory uint32_t rw : 1; // Read-only if clear, readwrite if set uint32_t user : 1; // Supervisor level only if clear uint32_t accessed : 1; // Has the page been accessed since last refresh? uint32_t dirty : 1; // Has the page been written to since last refresh? uint32_t unused : 7; // Amalgamation of unused and reserved bits uint32_t frame : 20; // Frame address (shifted right 12 bits)} page_t;typedef struct { page_t pages[MAX_PAGES_PER_TABLE];} page_table_t;typedef struct { page_table_t* page_tables[MAX_TABLES_PER_DIR]; // Array of pointers to pagetables. /* Array of pointers to the pagetables above, but gives their *physical* location, for loading into the CR3 register. */ uint32_t page_tables_physical[MAX_TABLES_PER_DIR]; /* The physical address of page_tables_physical. This comes into play when we get our kernel heap allocated and the directory may be in a different location in virtual memory. */ uint32_t physical_address;} page_directory_t;
Here is my enabling paging code:
page_directory_t* current_page_dir;void switch_page_directory(page_directory_t* new_page_directory){ current_page_dir = new_page_directory; // let the cpu know the physical address of the page tables asm volatile("mov %0, %%cr3" : : "r"(&new_page_directory->page_tables_physical)); uint32_t cr0; asm volatile("mov %%cr0, %0" : "=r"(cr0)); cr0 |= 0x80000000; // set the PG flag of cr0 - enable paging asm volatile("mov %0, %%cr0" : : "r" (cr0));}
I double checked that the page directory looks ok, but maybe I am wrong so here is the identity mapping code:
void alloc_frame(page_t *page, int is_kernel, int is_writeable){ if (page->frame != 0) { // there is already a frame associated with the page return; } uint32_t first_free_frame_address = first_not_set(frames); if (first_free_frame_address == frames.len + 1) { panic("no free pages"); } set_bit(first_free_frame_address, frames); page->frame = first_free_frame_address * ALIGNMENT; page->present = 1; page->rw = is_writeable ? 1: 0; page->user = is_kernel ? 0 : 1;}page_t* get_page(uint32_t address, int create, page_directory_t* dir){ // when dividing the address by alignment, the index of the page is received uint32_t address_index = address / ALIGNMENT; // according to the index of the address, // the index of the table, and the index of the page in the table are calculated uint32_t table_index = address_index / MAX_PAGES_PER_TABLE; uint32_t page_index = address_index % MAX_PAGES_PER_TABLE; if (dir->page_tables[table_index]) { // page table exists return &dir->page_tables[table_index]->pages[page_index]; } else if (create) { // page table doesn't exist - creating it uint32_t physical_address; dir->page_tables[table_index] = (page_table_t*) kmalloc_internal(sizeof(page_table_t), 1, &physical_address); // set a first entry in the table memory_set((uint8_t*)dir->page_tables[table_index], 0, ALIGNMENT); // give the first page, the attributes: Present, Read/Write, Kernel page physical_address |= 0x3; // save the physical address of the table dir->page_tables_physical[table_index] = physical_address; return &dir->page_tables[table_index]->pages[page_index]; } else { //page table doesn't exist - page cannot be retrieved return 0; }}void initialize_paging(){ initialize_frames(); page_directory_t* page_dir = (page_directory_t*) kmalloc(sizeof(page_directory_t)); memory_set((uint8_t*) page_dir, 0, sizeof(page_directory_t)); // set the physical addresses of the page tables to 0 with attributes: // kernel tables, rw, not present int j; uint8_t* tmp = (uint8_t*) page_dir->page_tables_physical; for (j = 0; j < 1024; j++) { memory_set(tmp, 2, 1); tmp++; memory_set(tmp, 0, 3); tmp += 3; } current_page_dir = page_dir; // We need to identity map (phys addr = virt addr) from // 0x0 to the end of used memory, so we can access this // transparently, as if paging wasn't enabled. // NOTE that we use a while loop here deliberately. // inside the loop body we actually change free_physical_address // by calling kmalloc(). A while loop causes this to be // computed on-the-fly rather than once at the start. uint32_t free_physical_address = get_current_physicall_address(); uint32_t i = 0; while (i <= free_physical_address) { // Kernel code is readable but not writeable from userspace. alloc_frame(get_page(i, 1, page_dir), 1, 1); i += ALIGNMENT; // get_page() may allocate more space for new tables free_physical_address = get_current_physicall_address(); }
That's a lot of code so I will sum up:alloc_frame()
finds the next free frame - using a bitset (I don't put the bitset code here because it works correctly.get_page()
, returns the page entry corresponding to the given address, and creates the page table if it doesn't exist.initialize_paging()
creates the page directory and does the identity mapping.
The following functions are not here but that's their summary:kmalloc()
allocates memory aligned to 4096.get_current_physicall_address()
returns the address which kmalloc will allocate on the next run.memory_set()
is my implementation of memset()
.
Any help will be appreciated!
Thanks in advance!