Page 1 of 1

Paging troubleshooting

Posted: Mon Sep 07, 2015 1:40 pm
by TheLittleWho
I am working on my paging code for a long time. I still have problems with it. So, first I need to know I really know paging, so am I right?

1. Paging is a memory management scheme which organize memory in pages
2. The MMU translates virtual addresses to physical addresses
3. A page is 32 bits (4 bytes) large. Structure of a page is:
  • present byte
  • read/write byte
  • user byte
  • accessed byte
  • dirty byte
  • 7 special bytes
  • 20 bytes to store frame address
4. A page table is a collection of 1024 pages
5. A page directory is a collection of 1024 page tables. Page directory is loaded into CR3 register that MMU can use its page tables to translate addresses
6. Page tables and page directory are 4096 bits aligned
7. Virtual address of a page is translated to physical using its frame address.
8. A frame is a physical memory area 32 bit (0x20 in hex) (4 bytes) large
9. TLB (translate lookaside buffer) is a cache that improve translation speed
10. If we don't use a page anymore, we need to use invlpg instruction to invalidate its address
11. Paging is enabled when paging bit in CR0 register is set

This was the theory that I know on paging, now how my memory manager should work:

1. I get memory size and I divide it to frame size so I can get number of memory frames, after I create a array of all frames.
2. Next I set all frames to free
3. Then I allocate space for a kernel directory and its page tables using a placement address based allocator
4. After I use memset to clean kernel directory
5. Then I map 1:1 from 0x0 to kernel end + 1 MiB (I know it is better to map 0x0 -> 3 GiB ... kernel end -> 3 GiB + kernel size, but I just want to test if paging works, I will go to a high half kernel later). Mapping algorithm is:
  • find a free frame
  • get index of page table where our page will be by dividing virtual address by 1024.
  • get index of the page in page table by dividing virtual address to 4096
  • get a pointer of the page
  • set present byte, rw byte and user byte
  • set frame bytes to frame address
6. After mapping the kernel and one more MiB, load the physical addresses of the page tables into CR3
7. Set paging byte in CR0 and enable paging.

This was how my memory manager should work, it is ok? Any improvements to do?
Here is a part of my code: (it is crashing)

Structures:

Code: Select all

typedef struct page {
    uint32_t present    : 1;
    uint32_t rw         : 1;
    uint32_t us         : 1;
    uint32_t accessed   : 1;
    uint32_t dirty      : 1;
    uint32_t unused     : 7;
    uint32_t frame      : 20;
} page_t;

typedef struct page_table {
    page_t pages[1024];
} page_table_t __attribute__((aligned(4096)));

typedef struct page_directory {
    page_table_t *tables[1024];
    uint32_t phys_tables[1024];
} page_directory_t __attribute__((aligned(4096)));
Frame allocator:

Code: Select all

#define FRAME_SIZE 32
#define FRAME_INDEX(x) (x / FRAME_SIZE)
#define PAGE_ALIGN(x) (x & 0xFFFFF000)
#define FRAME_ADDR_FROM_INDEX(x) (x * FRAME_SIZE)

void init_pmm(uint32_t mem_size)
{
    nframes = (mem_size * 1024) / FRAME_SIZE;
    frames = kmalloc(sizeof(uint32_t) * nframes, 0, 0);
    for (uint32_t i = 0; i < nframes; i++) {
        free_frame(FRAME_ADDR_FROM_INDEX(i));
    }
}

uint32_t find_frame()
{
   for (uint32_t i = 0; i < nframes; i++) {
        if (!frames[i]) {
	    uint32_t frame_addr = FRAME_ADDR_FROM_INDEX(i);
            use_frame(frame_addr);
	    return frame_addr;
	}
   }
   printk("[ERROR] No free frames!");
   keep_running();
}

void use_frame(uint32_t addr)
{
    int frame_index = FRAME_INDEX(addr);
    frames[frame_index] = 1;
}

void free_frame(uint32_t addr)
{
    int frame_index = FRAME_INDEX(addr);
    frames[frame_index] = 0;
}
And there is actual paging code:

Code: Select all

void init_vmm(uint32_t mem_size)
{
    // Register interrupt handler
    register_interrupt_handler(14, &page_fault_handler);

    // Alloc space for kernel directory
    kernel_directory = kmalloc(sizeof(page_directory_t), 1, 0);
    
    // Empty it
    memset(kernel_directory, 0, sizeof(page_directory_t));

    // Set current directory to kernel diretory
    current_directory = kernel_directory;

    // Map the memory
    uint32_t mem_addr = 0x0;
    uint32_t frame_addr = 0x0;
    while (mem_addr < (size_t) &kernel_end + 0x10000) {
        frame_addr = find_frame();
	    map(mem_addr, frame_addr, 1, 1);
	    mem_addr += 32;
    }
    // Switch current directory
    switch_page_directory(kernel_directory);

    // Enable paging
    enable_paging();
}

void switch_page_directory(page_directory_t *dir)
{
    current_directory = dir;
    write_cr3((uint32_t) &(dir->phys_tables));
}

void enable_paging()
{
    uint32_t cr0 = read_cr0();
    cr0 |= 0x80000000;
    write_cr0(cr0);
    paging_enabled = 1;
}

void map(uint32_t va, uint32_t pa, uint32_t rw, uint32_t us)
{
    page_t *page = get_page(va, current_directory);
    create_page(page, pa, rw, us);
}

void unmap(uint32_t va)
{
    page_t *page = get_page(va, current_directory);
    free_frame(page->frame);
    memset(page, 0, sizeof(page_t));
    asm volatile ("invlpg (%0)" : : "a" (va));
}

void create_page(page_t *page, uint32_t frame, uint32_t rw, uint32_t us)
{
    page->present = 1;
    page->rw = rw;
    page->us = us;
    page->frame = frame;
}

page_t *get_page(uint32_t address, page_directory_t *dir)
{
    int page_index = address / 0x1000;
    int table_index = page_index / 1024;

    if (dir->tables[table_index] != 0) {
        return &(dir->tables[table_index]->pages[page_index]);
    } else {
        uint32_t phys_table_addr;
        dir->tables[table_index] = kmalloc(sizeof(page_table_t), 
	                                   1, &phys_table_addr);
        memset((void*) dir->tables[table_index], 0, sizeof(page_table_t));
	dir->phys_tables[table_index] = phys_table_addr | 0x7;
	return &(dir->tables[table_index]->pages[page_index]);
    }
    return NULL;
}
At last, I understand paging good? If no, can you explain me what I understand wrong? Can you say me why my code is crashing, what I do wrong? (It triple fault after enable paging)
You can see all my code at https://github.com/JustBeYou/PhotonOS/tree/devel/.

Thanks.

Re: Paging troubleshooting

Posted: Tue Sep 08, 2015 3:11 am
by BrightLight
A page is 4096 bytes-aligned, not bits.
It does seem you have a good understanding of paging. :)

Re: Paging troubleshooting

Posted: Tue Sep 08, 2015 8:10 am
by Ready4Dis
I didn't take much time, but is your page directory supposed to be a list of pointers AND a list of 32-bit integers? Was that supposed to be a union? Like I said, I didn't spend much time looking, but it just struck me as odd.

Re: Paging troubleshooting

Posted: Tue Sep 08, 2015 11:20 am
by BASICFreak
Ready4Dis wrote:I didn't take much time, but is your page directory supposed to be a list of pointers AND a list of 32-bit integers? Was that supposed to be a union? Like I said, I didn't spend much time looking, but it just struck me as odd.
I'll agree with Ready4Dis.

Code: Select all

typedef struct page_directory {
    page_table_t *tables[1024];
    uint32_t phys_tables[1024];
} page_directory_t __attribute__((aligned(4096)));
One or the other... either user the pointer or the uint not both as the size of this is 8kb and supposed to be 4kb

EDIT: though in theory it should work as the TBL only will see the first 4kb.

I use __attribute__((packed)) as there is no need to align if your PMM is done properly.

Re: Paging troubleshooting

Posted: Tue Sep 08, 2015 11:47 pm
by Techel
Check the paging docs again:
The entries in the PD have nearly the same structure as these in the PT, and are not just pointers.

Re: Paging troubleshooting

Posted: Wed Sep 09, 2015 12:08 pm
by eryjus
I recognize this code to be close to the James Malloy tutorial. If you have not already, please be sure you have reviewed the known issues. I know I had HUGE issues with paging code when I started.

Known Bugs

Re: Paging troubleshooting

Posted: Thu Sep 10, 2015 7:18 am
by TheLittleWho
Thanks for answers. I will make a redesign on structures and PMM to be sure that it is ok. (I think here is the problem that triple fault). Yea, it is similar to JM because I read his tutorial. I used his code, but I has very big problems so I deleted code and rewrite it. I checked already the bugs, they aren't present in my code.
One or the other... either user the pointer or the uint not both as the size of this is 8kb and supposed to be 4kb

EDIT: though in theory it should work as the TBL only will see the first 4kb.
I load only phys_tables into cr3 register. Where phys_tables are physical addresses of my tables pointers.

Re: Paging troubleshooting

Posted: Thu Sep 10, 2015 12:49 pm
by BASICFreak
One more thing that might be your issue:
write_cr3((uint32_t) &(dir->phys_tables));

I have found (not always but mostly) &(dir->phys_tables) != &(dir->phys_tables[0])
The first one you are locating the pointer to dir->phys_tables (which is an array [or pointer])
The latter you are locating the first entry's address

^ I've had to chase pointer issues like this more than I want to admit, so just check to make sure you have the value you think you have.

In fact (uint32_t)(dir->phys_tables) == (uint32_t)(&dir->phys_tables[0])?
An array is a pointer - so getting a pointer of a pointer may not be the best approach

NOTE: I'm not saying it is an error - but it could very well be.

Re: Paging troubleshooting

Posted: Thu Sep 10, 2015 1:37 pm
by TheLittleWho
I tried a lot of variants. It steel doesn't work. Like : phys_tables[0] , &(phys_tables[0]), phys_tables, &phys_tables, physical_address, &(physical_address)...

Re: Paging troubleshooting

Posted: Fri Sep 11, 2015 1:49 am
by Combuster
I tried a lot of variants
Shotgun debugging doesn't tend to work in complex scenarios where everything needs to be in proper condition to work. For each of the attempts you listed, have you printed the actual value passed so that you know:

1: What each piece of code actually does
2: Which piece produces the correct value
3: Which piece should therefore be present in your code

To make sure this part is no longer an issue, can you at least give us the code you chose in step 3 and which value it produces?

Re: Paging troubleshooting

Posted: Fri Sep 11, 2015 10:36 am
by TheLittleWho
Thanks for your help all. At last I decided to do not use James Molly design for paging. I changed design and rewrite some things and now all seems to be fine.

vmm.h

Code: Select all

#ifndef _vmm_h
#define _vmm_h

#include <stddef.h>
#include <stdint.h>
#include <system.h>

#define PAGE_PRESENT 0x1
#define PAGE_READ_WRITE 0x2
#define PAGE_USER 0x4
#define PAGE_WRITE_THROUGH 0x8
#define PAGE_CACHE_DISABLE 0x16
#define PAGE_ACCESSED 0x32
#define PAGE_DIRTY 0x64
#define PAGE_GLOBAL 0x128

typedef struct page_table {
    uint32_t pages[1024];
} page_table_t;

typedef struct page_directory {
    uint32_t phys_tables[1024];
    page_table_t *virt_tables[1024];
} page_directory_t;

void init_vmm();
void switch_page_directory(page_directory_t *dir);
void enable_paging();
void map(uint32_t va, uint32_t pa, uint32_t flags);
void unmap(uint32_t va);
void page_fault_handler(registers_t *regs);

#endif
vmm.c

Code: Select all

#include <stddef.h>
#include <stdint.h>
#include <io.h>
#include <system.h>
#include <pmm.h>
#include <vmm.h>
#include <string.h>
#include <kheap.h>

extern void write_cr3();
extern void write_cr0();
extern uint32_t read_cr3();
extern uint32_t read_cr0();
extern uint32_t *frames;
extern uint32_t nframes;
extern int paging_enabled;
extern uint32_t placement_addr;
extern size_t kernel_end;

page_directory_t *kernel_directory;
page_directory_t *current_directory;

static void map_area(uint32_t from_va, uint32_t to_va)
{
    for (uint32_t va = from_va; va < to_va; va += 0x1000) {
        uint32_t pa = alloc_frame();
        map(va, pa, PAGE_READ_WRITE | PAGE_PRESENT);
    }
}

void init_vmm()
{
    register_interrupt_handler(14, page_fault_handler);

    kernel_directory = kmalloc(sizeof(page_directory_t), 1, 0);
    memset(kernel_directory, 0, sizeof(page_directory_t));
    
    for (int i = 0; i < 1024; i++) {
        kernel_directory->phys_tables[i] = 0x0 | PAGE_READ_WRITE;
    }
    
    current_directory = kernel_directory;
    
    // Map BIOS data
    map_area(0x0, 0x10000);
    
    // Map Kernel
    map_area(0x10000, (uint32_t) &kernel_end);
    
    switch_page_directory(kernel_directory);
    enable_paging();
}

void switch_page_directory(page_directory_t *dir)
{
    current_directory = dir;
    write_cr3(current_directory);
}

void enable_paging()
{
    uint32_t cr0 = read_cr0();
    cr0 |= 0x80000000;
    write_cr0(cr0);
    paging_enabled = 1;
}

void map(uint32_t va, uint32_t pa, uint32_t flags)
{
    uint32_t page_num = (va / 4096) % 1024;  
    uint32_t table_num = (va / 4096) / 1024;
    
    if (!current_directory->virt_tables[table_num]) {
        uint32_t phys;
        current_directory->virt_tables[table_num] = kmalloc(sizeof(page_table_t), 
                                                    1, 
                                                    &phys);
                        
        current_directory->phys_tables[table_num] = phys | PAGE_READ_WRITE | PAGE_PRESENT;
    }   
    current_directory->virt_tables[table_num]->pages[page_num] = pa | flags;
}

void unmap(uint32_t va)
{
    uint32_t page_num = (va / 4096) % 1024;  
    uint32_t table_num = (va / 4096) / 1024;
    
    current_directory->virt_tables[table_num]->pages[page_num] = 0x0;

    asm volatile ("invlpg (%0)" : : "a" (va));
}

void page_fault_handler(registers_t *regs)
{
    uint32_t cr2;
    asm volatile ("mov %%cr2, %0": "=r" (cr2));
    printk("Page fault at %x, faulting address %x\n", regs->eip, cr2);
    printk("Error code %x\n", regs->err_code);
    panic("Core dumb.\n", __LINE__, __FILE__);
}
Now seems to work. Thanks all.

Re: Paging troubleshooting

Posted: Fri Oct 09, 2015 5:56 am
by kenod
It would be better to chance

Code: Select all

#define PAGE_PRESENT 0x1
#define PAGE_READ_WRITE 0x2
#define PAGE_USER 0x4
#define PAGE_WRITE_THROUGH 0x8
#define PAGE_CACHE_DISABLE 0x16
#define PAGE_ACCESSED 0x32
#define PAGE_DIRTY 0x64
#define PAGE_GLOBAL 0x128
to

Code: Select all

#define PAGE_PRESENT 0x1
#define PAGE_READ_WRITE 0x2
#define PAGE_USER 0x4
#define PAGE_WRITE_THROUGH 0x8
#define PAGE_CACHE_DISABLE 0x10
#define PAGE_ACCESSED 0x20
#define PAGE_DIRTY 0x40
#define PAGE_GLOBAL 0x100
or

Code: Select all

#define PAGE_PRESENT 1
#define PAGE_READ_WRITE 2
#define PAGE_USER 4
#define PAGE_WRITE_THROUGH 8
#define PAGE_CACHE_DISABLE 16
#define PAGE_ACCESSED 32
#define PAGE_DIRTY 64
#define PAGE_GLOBAL 256
because now you're using hexadecimal notation while intending to use decimal (0x16 gives 10110 in binary not 10000).
I also changed PAGE_GLOBAL from 0x80/128 to 0x100/256 because the bit you are changing is reserved. The global bit is one bit higher.
The reason that you haven't gotten any errors is because everything till the WRITE_THROUGH bit still works, and you normally only use PRESENT, READ_WRITE and USER (in a basic hobby os at least).