OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Sep 19, 2019 11:15 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 12 posts ] 
Author Message
 Post subject: Switching from 4MB to 4KB paging
PostPosted: Fri Aug 16, 2019 7:12 pm 
Offline
Member
Member

Joined: Sun Sep 16, 2018 6:46 am
Posts: 42
I have 4MB paging enabled from the bootloader to load my higher-half kernel at 0xC0000000.

I created two Page Tables where I identity page the first 4MB and the higher-half kernel.
Code:
uint32_t *first4MBpt = kmalloc();
uint32_t virt = 0;
for (i = 0; i < 1024; i++, virt += PAGE_SIZE)
    first4MBpt[i] = virt | 0x000000003;

uint32_t *kernelPT = kmalloc();

// Mapping kernel space - should be from the 768th pde and the 256th pte (for KERNEL_VIRTUAL_BASE == 0xC00000000)
for (i = 0x100000, virt = KERNEL_VIRTUAL_BASE; virt <= (_half_maxStack + KERNEL_VIRTUAL_BASE); i += PAGE_SIZE, virt += PAGE_SIZE)
    kernelPT[PAGE_TABLE_INDEX(i)] = virt | 0x000000003;


Then I created a Page Directory to hold them and set as current.

Code:
pageDirectory[0] = (uint32_t)(first4MBpt) | 0x000000003;
pageDirectory[PAGE_DIRECTORY_INDEX(KERNEL_VIRTUAL_BASE)] = (uint32_t)(kernelPT) | 0x000000003;

_cur_pageDirectory = pageDirectory;


Now I have to switch the type of paging from 4MB to 4KB and set the new Page Directory.
This is my entire .asm code:
Code:
section .text
; Needs a parameter: the physical address of the Page Directory
extern _cur_pageDirectory
global switch_to4kb

switch_to4kb:
    pusha

    ; Load PDBR (CR3), it must contains the physical address of the Page Directory
    mov ecx, [_cur_pageDirectory]
    mov cr3, ecx

    ; Reset PSE bit in CR4 to enable 4KB pages
    mov ecx, cr4
    and ecx, 0xffffffef
    mov cr4, ecx

    popa
    ret

CR3 get loaded, but then Bochs throws this error: bx_dbg_read_linear: physical memory read error (phy=0x00000000c000527a, lin=0x00000000c010527a).
The code is executed at that address, so what should I do?
I tried to only switch cr4 and it worked, but the new PD is not loaded so it's useless.

Can anyone help me? Thanks.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Fri Aug 16, 2019 10:17 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 240
There is no need to disable PSE. You can keep your 4MB pages for the kernel around and use 4kB pages for everything else.

Also, you are mapping virtual addresses to virtual addresses. This will not work. You need to write the physical addresses of the kernel into the page tables. What bochs is saying here is, you tried to execute physical address 0xc000XXXX, which is usually an IO memory hole. So it complains about that.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Sat Aug 17, 2019 3:48 am 
Offline
Member
Member

Joined: Sun Sep 16, 2018 6:46 am
Posts: 42
nullplan wrote:
There is no need to disable PSE. You can keep your 4MB pages for the kernel around and use 4kB pages for everything else.

Can you please explain this further?

nullplan wrote:
Also, you are mapping virtual addresses to virtual addresses. This will not work. You need to write the physical addresses of the kernel into the page tables.

Right, my bad!

nullplan wrote:
What bochs is saying here is, you tried to execute physical address 0xc000XXXX, which is usually an IO memory hole. So it complains about that.

Ok, but EIP is executing there, so what should I do?


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Sat Aug 17, 2019 5:00 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 1636
You can have a mixture of 4kB and 4MB pages. For each page directory entry, bit 7 determines whether the entry points to a single 4MB page or a page table containing 1024 4kB pages.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Sat Aug 17, 2019 6:37 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 240
Thpertic wrote:
nullplan wrote:
What bochs is saying here is, you tried to execute physical address 0xc000XXXX, which is usually an IO memory hole. So it complains about that.

Ok, but EIP is executing there, so what should I do?

Don't mix up physical and virtual addresses. EIP is set to 0xc000XXXX, which is a virtual address. The physical backing store is, I presume, at the 1MB line, right? So there is no issue. Just, you should never map the physical address 0xc000XXXX as executable and then run it. Unless the BIOS told you there is RAM there. And you loaded something there to be executed.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Sat Aug 17, 2019 7:42 am 
Offline
Member
Member

Joined: Sun Sep 16, 2018 6:46 am
Posts: 42
Octocontrabass wrote:
You can have a mixture of 4kB and 4MB pages. For each page directory entry, bit 7 determines whether the entry points to a single 4MB page or a page table containing 1024 4kB pages.

This will be useful, thank you!

nullplan wrote:
Don't mix up physical and virtual addresses. EIP is set to 0xc000XXXX, which is a virtual address. The physical backing store is, I presume, at the 1MB line, right? So there is no issue. Just, you should never map the physical address 0xc000XXXX as executable and then run it. Unless the BIOS told you there is RAM there. And you loaded something there to be executed.

The kernel is executing there because I set it up in the higher-half but it's mapped to 1MB physical, I'm not sure what do you mean.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Sat Aug 17, 2019 10:31 pm 
Offline
Member
Member

Joined: Wed Mar 09, 2011 3:55 am
Posts: 315
Thpertic wrote:
nullplan wrote:
Don't mix up physical and virtual addresses. EIP is set to 0xc000XXXX, which is a virtual address. The physical backing store is, I presume, at the 1MB line, right? So there is no issue. Just, you should never map the physical address 0xc000XXXX as executable and then run it. Unless the BIOS told you there is RAM there. And you loaded something there to be executed.

The kernel is executing there because I set it up in the higher-half but it's mapped to 1MB physical, I'm not sure what do you mean.


The error from Bochs indicates that your code tried reading or executing from 0xc000xxxx *physical*. Your higher half setup has your code at 1MB physical, which is mapped at 0xc000xxxx *virtual*.

So the mapping that you meant to have is:

Code:
virtual          physical
0-1MB ---->   0-1MB
0xc0000xxxx ---> 1MB


But you also (or perhaps instead) have a mapping:

Code:
virtual           physical
????????    ---> 0xc000xxxx


There often isn't any RAM at physical 0xc000xxxx, either because the computer has less than 3 GiB of RAM, or because the motherboard chipset maps some bit of hardware there, so it isn't safe to use physical addresses that high until you've asked the firmware for a list of usable addresses.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Sun Aug 18, 2019 5:35 pm 
Offline
Member
Member

Joined: Sun Sep 16, 2018 6:46 am
Posts: 42
I think I may have fixed it, but it still triple faults. The kernel mapping starts from 0xC0000000 + 0x100000 using this macro to find the exact index:
Code:
#define PAGE_TABLE_INDEX(x) (((x) >> 12) & 0x3ff)

Code:
for (i = KERNEL_VIRTUAL_BASE + 0x100000, addr = 0x100000; (uint32_t)i <= _end_addr; i += PAGE_SIZE, addr += PAGE_SIZE)
        kernelPT[PAGE_TABLE_INDEX(i)] = addr | 0x000000003;


Then, I also changed something in the switch assembly, now it just invalidate the TLB and load the new page directory:
Code:
pusha
; Invalidate the TLB
mov ecx, cr3
invlpg [ecx]

; Load PDBR (CR3), it must contains the physical address of the Page Directory
mov ecx, [_cur_pageDirectory]
mov cr3, ecx

popa
ret

The Bochs debugger doesn't complain anymore about the physical-to-virtual address mapping, but still reboots.
Can you help me?


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Mon Aug 19, 2019 9:24 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 240
Thpertic wrote:
Code:
#define PAGE_TABLE_INDEX(x) (((x) >> 12) & 0x3ff)

Code:
for (i = KERNEL_VIRTUAL_BASE + 0x100000, addr = 0x100000; (uint32_t)i <= _end_addr; i += PAGE_SIZE, addr += PAGE_SIZE)
        kernelPT[PAGE_TABLE_INDEX(i)] = addr | 0x000000003;


Looks about right.

Thpertic wrote:
Then, I also changed something in the switch assembly, now it just invalidate the TLB and load the new page directory:
Code:
pusha
; Invalidate the TLB
mov ecx, cr3
invlpg [ecx]

; Load PDBR (CR3), it must contains the physical address of the Page Directory
mov ecx, [_cur_pageDirectory]
mov cr3, ecx

popa
ret

What did I just say? Don't mix up physical and virtual addresses. CR3 contains a physical address. "invlpg" requires a virtual one.

This isn't necessary anyway, since loading CR3 already causes a TLB flush (except in global pages, but that is a feature you aren't using yet). The pusha and popa aren't necessary either if you'd just follow the ABI. In your case, you only clobber ecx, which is volatile, so you don't need to save it.

Also, CR3 contains the physical address of the page directory. And the page directory must contain the physical addresses of the page tables. So, what you need to do is:

Code:
pageDirectory[0] = v2p(first4MBpt) | 3;
pageDirectory[PAGE_DIRECTORY_INDEX(KERNEL_VIRTUAL_BASE)] = v2p(kernelPT) | 0x000000003;
loadPageDir(v2p(pageDirectory));


And then think how you get from virtual to physical address. I don't know where your kmalloc() is allocating from. Maybe something like

Code:
uintptr_t v2p(void* addr) {
  uintptr_t x = (uintptr_t)addr;
  if (x - KERNEL_VIRTUAL_BASE < (uintptr_t)_end_linmap - KERNEL_VIRTUAL_BASE)
    return x - KERNEL_VIRTUAL_BASE;
  uint32_t pde = _cur_pageDirectory[PAGE_DIRECTORY_INDEX(x)];
  assert(pde & 1);
  if (pde & 128)
    return (pde & 0xffc00000) + (x & 0x003fffff);
  uint32_t *pt = p2v(pde & 0xfffff000);
  assert(pt);
  uint32_t pte = pt[PAGE_TABLE_INDEX(x)];
  assert(pte & 1);
  return (pte & 0xfffff000) + (x & 0x00000fff);
}

void *p2v(uint32_t paddr) {
  if (paddr < _end_linmap - KERNEL_VIRTUAL_BASE)
    return (void*)(paddr + KERNEL_VIRTUAL_BASE);
  return 0; //XXX: Or panic instead? Are the page tables always mapped?
}


This depends on assertions. I suggest you panic if an assertion fails (stop executing, write a crashlog to console, stop all other cores).

This also depends on a new global variable, _end_linmap, which determines the current end of the linear mapping from 1MB to 3GB. Basically, this is where the cat chases its own tail: You need virtual and physical addresses of the page tables always to hand, and can't look it up in the page tables. If you only have a physical address, you need a simple way to find the corresponding virtual address.

This is usually accomplished by putting the paging structures inside the area that is linearly mapped. Then there is always a very simple relationship between physical and virtual address for the page tables. Alternatively, another linear area can be set aside. Problem is that it constrains the possible locations of the page tables in physical address space. Thus, you might run into the problem that you are out of space for such things, even if there'd be room for other stuff (linear mapping area is full and can't be expanded since either the physical or virtual memory following it is already in use).

On AMD64 you don't have this problem, since all of physical address space can be mapped at once.

Oh, and also:
Code:
uint32_t loadPageDir(uint32_t); /*loads CR3 with argument and returns old CR3. */

Code:
loadPageDir:
  mov ecx, [esp + 4]
  mov eax, cr3
  mov cr3, ecx
  ret


HTH.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Tue Aug 20, 2019 12:22 pm 
Offline
Member
Member

Joined: Sun Sep 16, 2018 6:46 am
Posts: 42
Thank you, this is helping!

I only have a few questions: since kmalloc already returns physical page-aligned addresses, can I skip the translation step, at least for v2p()? And can you please explain the use of _end_linmap further?


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Tue Aug 20, 2019 9:32 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 240
Wait, so kmalloc() returns a physical address? Then you need to map the page returned into virtual address space before you can access it. Physical addresses are no pointers, you cannot dereference them directly. So if you can take the return value of kmalloc() and write in it, then it is a virtual address. I generally try to keep them apart by passing physical addresses as "paddr_t", and virtual addresses as "void*". For x86, you could do:

Code:
#ifdef CONFIG_PAE
typedef uint64_t paddr_t;
#else
typedef uint32_t paddr_t;
#endif


One more thing that is easier on AMD64: PAE is always enabled there.

Regarding _end_linmap: The idea is that you reserve "some" space behind the kernel for things where you have to tell the physical address from the virtual one quickly / without page tables. This is mostly just all the page tables. If you need a physical address for a device driver, those already have paging set up.

So, how much memory to reserve here? Well, IBM's recommendation for the PowerPC page table was to use 1/512 of the memory size, which is as good of an estimate as any. So you could do something like:

Code:
uint32_t _end_linmap;
void init_linmap(void) {
  _end_linmap = ((size_t)_end_kernel + (count_ram_size() >> 9) + 4095) & -4096;

  /* now trim _end_linmap back if the physical memory area the kernel is in ends before the current _end_linmap */
  kmalloc_mark_used((uint32_t)_end_kernel - KERNEL_VIRTUAL_BASE, _end_linmap - KERNEL_VIRTUAL_BASE);
  for (uint32_t addr = (uint32_t)_end_kernel; addr < _end_linmap; addr += 4096)
    mmap((void*)addr, addr - KERNEL_VIRTUAL_BASE, PROT_READ | PROT_WRITE | PROT_KERNEL);
}


Memory for page tables is then exclusively allocated from that area. If you run out of space you can start swapping things out. You are going to have to save the virtual memory mappings of each process in your own data structure (usually a binary tree). So what you can do is, you can just leave the things you currently don't need out of the page tables. If a page fault happens, and the address was really mapped, throw out the least recently used page tables till you have the space to add this memory mapping. Which is actually easier on x86, since you definitely will have a page directory table, so you could only ever be missing a page table. So only a single page to free and then allocate again.


Top
 Profile  
 
 Post subject: Re: Switching from 4MB to 4KB paging
PostPosted: Fri Aug 23, 2019 12:37 pm 
Offline
Member
Member

Joined: Sun Sep 16, 2018 6:46 am
Posts: 42
Okay, so I have to map pages those pages.

Does kmalloc_mark_used() maps pages? Or you just map them through mmap()?
nullplan wrote:
  /* now trim _end_linmap back if the physical memory area the kernel is in ends before the current _end_linmap */
  kmalloc_mark_used((uint32_t)_end_kernel - KERNEL_VIRTUAL_BASE, _end_linmap - KERNEL_VIRTUAL_BASE);
  for (uint32_t addr = (uint32_t)_end_kernel; addr < _end_linmap; addr += 4096)
    mmap((void*)addr, addr - KERNEL_VIRTUAL_BASE, PROT_READ | PROT_WRITE | PROT_KERNEL);


Would it be worse if I don't use _end_linmap at all and just map where kmalloc() returns with the proper function?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group