Page 1 of 1

Confusion about Paging and creating new page tables

Posted: Sat Aug 15, 2015 12:41 am
by Sanchezman
I'm having some trouble with how my page fault handling code works, and I think it's due to some confusion about how I should dynamically create new page tables.

My current situation is as follows:

When a page fault occurs, I read the relevant registers to get the Page Directory Index and the Page Table Entry that caused it. If the fault occurred in a page table that already exists, then I use my physical memory manager to find a free page frame and I map the physical address of that page frame to the virtual address that the fault was generated for. As far as my current testing has gone, this works fine.

If the fault occurred in a page table that doesn't exist, I run into some problems. I get my physical memory manager to give me a free page frame that I will use for the new page table. I then need to fill the page frame with 0's, however, doing this would cause a page fault because the page frame has not been mapped to any virtual address. I would be writing to a physical address that the CPU would think is a virtual address. How do I get around this?

At the moment, I have an "emergency" Page table hard coded in. I always put it as the last page table of the page directory. The idea is that when I need to zero out my new page frame, I can temporarily map it to the 0th entry in this emergency page table. I can then guarantee that I can find this new page frame at virtual address: 1024 (for the last Page Table) * 1 (for the 0th entry in the last page table) * 4096 (for the size of each page frame). I can then zero out this page frame without causing another page fault. I then point the Page Directory to this new Page Table's physical address. Lastly, I remove the mapping of this new Page Table from my emergency page frame.

All that this serves to do is cause a chain of "page table not present" page faults before the VM double-faults. What part of paging am I failing to grasp?

My code for this all is as follows:

Code: Select all

void handle_page_fault(uint32_t errcode){
  
  uint32_t cr2;
  asm volatile("mov %%cr2, %0" : "=r" (cr2));
  //Get the middle 10 bits
  uint32_t pagedir_entry = ((cr2 >> 12) & 0x3FF);
  //Get the upper 10 bits
  uint32_t pagetab_entry = (cr2 >> 22);
  
  bool isPresent = (errcode % 2);
  bool isUser = ((errcode >> 2) % 2);
  
  if(!isPresent){

    printf("Page table not present\n");
    
    //We need to put a new block in the page directory
    uint32_t* newblock = physmm_alloc_block();

    //Point to it with our helper block so we can use it
    insert_Kernel_PTValue(helper_block, HELPER_BLOCK_INSERT_INDEX, addr_to_block_32((void*)newblock));

    //Now we can initialize it full of 0's (Initialize it from the virtual address it is mapped to in the helper block)
    init_KernelPT((uint32_t*) ((HELPER_BLOCK_INSERT_INDEX + 1) * PHYSMM_BLOCK_SIZE * (MAX_TABLE_ENTRY + 1)));

    //Now put this new page table in the page directory
    insert_KernelPTentry(PageDir_Ptr, newblock, pagedir_entry);

    //And now stop pointing to it from helper block
    remove_Kernel_PTValue(helper_block, HELPER_BLOCK_INSERT_INDEX);
  }else{

    printf("Page table was present\n");
    
    uint32_t* tabptr = get_pagetable_ptr(PageDir_Ptr, pagedir_entry);    
    void* toallocate = physmm_alloc_block();
    uint32_t toallocate_block = addr_to_block_32(toallocate);
    if(isUser){
      //Map the block with user privilege
      insert_User_PTValue(tabptr, pagetab_entry, toallocate_block);
    }else{
      //Map the block with kernel privilege
      insert_Kernel_PTValue(tabptr, pagetab_entry, toallocate_block);
    }
  }  
}
Thanks

Re: Confusion about Paging and creating new page tables

Posted: Sat Aug 15, 2015 1:51 am
by iansjack
You should always do an INVLPG after updating a Page Table.

Otherwise, as I understand you are doing the right thing by making a temporary mapping for any physical memory that you need to update. An alternative is to just make a mapping for all physical RAM that the kernel can use. That sounds wasteful but, when you add it up, it doesn't really use too much memory.

Re: Confusion about Paging and creating new page tables

Posted: Sat Aug 15, 2015 2:32 am
by Sanchezman
iansjack wrote:You should always do an INVLPG after updating a Page Table.

Otherwise, as I understand you are doing the right thing by making a temporary mapping for any physical memory that you need to update. An alternative is to just make a mapping for all physical RAM that the kernel can use. That sounds wasteful but, when you add it up, it doesn't really use too much memory.
Thanks! The information on the wiki isn't too clear about the usage of INVLPG. Do I pass it the address of the specific table entry I changed, or do I pass it the address of the block of virtual memory that the table entry represents?

Re: Confusion about Paging and creating new page tables

Posted: Sat Aug 15, 2015 3:03 am
by iansjack
Pass it any address within the page that the table entry points to. So you can pass it the address that faulted.

There's a longish discussion about INVLPG here: http://forum.osdev.org/viewtopic.php?f=1&t=18222

A safe rule of thumb is to use it whenever you have modified the paging structure, even if it is just to mark a page as present for example. It can't do any harm and it avoids undefined situations.

Re: Confusion about Paging and creating new page tables

Posted: Sat Aug 15, 2015 2:06 pm
by Sanchezman
Edit: Solution found
It turns out that although I was allocating a new page table during the fault, that I needed to also put at least one entry in the table. Doing this allowed me to invlpg the table and see have it be recognized as valid.

I had a mistake in my code that converts from Page directory index and Page Table index into virtual addresses. However, instead of double-faulting, my code page faults indefinitely, claiming that there is no page table present. Here is what is displayed (I added diagnostic code):
Image

As you can see, I test my page fault code by attempting to acces 0xD0001000. This throws a page fault because the page is not present. Newblock addr refers to the block retrieved from physical memory that will become the new page table. I map the block temporarily to my helper block, and you can see that the physical address of newblock gets remapped to an address in the 0xFFCXXXXX range. I invlpg this virtual address so I can zero out this new page table. I then have my page directory point to the physical address of this new page table, and then I invlpg the virtual address that caused the page fault to happen. The last line of the first paragraph signifies that we successfully exited the page fault handling function.

However, instead of my test function iterating through a series of addresses, you can see in the second paragraph that it continues to page fault over the original address.

Here is the code I use (without the diagnostic output):

Code: Select all

void handle_page_fault(uint32_t errcode){
  
  uint32_t cr2;
  asm volatile("mov %%cr2, %0" : "=r" (cr2));
  //Get the middle 10 bitsstatic inline void invlpg(void* m)
{
  asm volatile ( "invlpg (%0)" :: "r" (m) : "memory" );
}
  uint32_t pagetab_entry = ((cr2 >> 12) & 0x3FF);
  //Get the upper 10 bits
  uint32_t pagedir_entry = (cr2 >> 22);
  
  bool isPresent = (errcode % 2);
  bool isUser = ((errcode >> 2) % 2);
  
  if(!isPresent){

    //We need to put a new block in the page directory
    uint32_t* newblock = physmm_alloc_block();

    //Point to it with our helper block so we can use it
    insert_Kernel_PTValue(helper_block, HELPER_BLOCK_INSERT_INDEX + insert, addr_to_block_32((void*)newblock));

    index_to_virtual(MAX_TABLE_ENTRY, HELPER_BLOCK_INSERT_INDEX + insert) );

    //invlpg the helper table to ensure that we are allowed to modify this new block
    invlpg( (void*) index_to_virtual(MAX_TABLE_ENTRY, HELPER_BLOCK_INSERT_INDEX + insert));
    
    //Now we can initialize it (Initialize it from the address it is mapped to in the helper block)
    init_KernelPT(index_to_virtual(MAX_TABLE_ENTRY, HELPER_BLOCK_INSERT_INDEX + insert) );
        
    //Now put this new page table in the page directory
    insert_KernelPTentry(PageDir_Ptr, newblock, pagedir_entry);

    ++insert;

    //Invlpg the address that caused the fault
    invlpg( (void*) index_to_virtual(pagedir_entry, pagetab_entry));

  }else{
    
    uint32_t* tabptr = get_pagetable_ptr(PageDir_Ptr, pagedir_entry);    
    void* toallocate = physmm_alloc_block();
    uint32_t toallocate_block = addr_to_block_32(toallocate);
    if(isUser){
      //Map the block with user privilege
      insert_User_PTValue(tabptr, pagetab_entry, toallocate_block);
    }else{
      //Map the block with kernel privilege
      insert_Kernel_PTValue(tabptr, pagetab_entry, toallocate_block);
    }
    
    invlpg(pagedir_entry, pagetab_entry);
  }

}