Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How does paging work?

117 views
Skip to first unread message

smilinggoomba

unread,
May 12, 2012, 3:29:10 PM5/12/12
to
Hello all,

I am in the process of writing a simple bootloader in assembly, to
load a simple kernel. So far I have code to enable the A20 line, load
the kernel to 0x10000 in RAM, and load a temporary GDT. What I still
want to do is to map the kernel to 0xC0000000 using paging. The thing
is, I have no idea on how to implement paging. Some questions that I
have are:


1) Firstly, and most simply, how does one map the kernel to
0xC0000000?

2) Secondly, I see code in various paging tutorials (and Linus' head.s
in early v0.11) involving some sort of loop. I am very fluent in C
but not so much in assembly, so I would like to know what every
instruction does, and how it fits into the structure of the page
tables/directories.

3) Thirdly, how are processes running in userland able to use paging?

I know that these are very "n00bish" questions, but my operating
system project cannot continue without the answers to them.

Thanks!
_______________________________________________________________________________
Fake signature w00t!
Message has been deleted

smilinggoomba

unread,
May 12, 2012, 3:51:09 PM5/12/12
to
I have enough knowledge of ASM to get through the bootloader (except
for this part) and all ASM-required parts of the OS (I know about
reading/writing to disk in protected mode, receiving keyboard input
through ports, et cetera), but my problem is more conceptual than
related to my knowledge of ASM. (And yes, I did read the FAQ.)
Also, if you want the code so far, just reply saying so.
Thanks!
___________________________________________________________________________
____
Fake signature :-)

James Harris

unread,
May 12, 2012, 4:52:12 PM5/12/12
to
On May 12, 8:29 pm, smilinggoomba <jhd0...@gmail.com> wrote:
> Hello all,
>
> I am in the process of writing a simple bootloader in assembly, to
> load a simple kernel.  So far I have code to enable the A20 line, load
> the kernel to 0x10000 in RAM, and load a temporary GDT.  What I still
> want to do is to map the kernel to 0xC0000000 using paging.  The thing
> is, I have no idea on how to implement paging.  Some questions that I
> have are:
>
> 1) Firstly, and most simply, how does one map the kernel to
> 0xC0000000?

You can map the memory that the kernel occupies (or is about to
occupy) to hex c000_0000 using the page tables. If you want the kernel
to run there you will have to make sure it is prepared to accept that
it is running at that address. Depending on your compiler the jumps in
x86 code may be relative so that jumps to within the kernel need no
relocation. Data references, on the other hand, may need fixup unless
the kernel is either compiled to run at your target address or its
addresses are hard coded.

> 2) Secondly, I see code in various paging tutorials (and Linus' head.s
> in early v0.11) involving some sort of loop.  I am very fluent in C
> but not so much in assembly, so I would like to know what every
> instruction does, and how it fits into the structure of the page
> tables/directories.

Not sure if you mean "every instruction" in the assembler sequence or
in Linus' code. Check out the x86 manuals for the defined-as-correct
sequence to enter protected mode and enable paging. Feel free to post
specifics here.

The structure of the page tables is purely data and will apply whether
you are using assembler or C. For simplicity you might want to start
with a 4Mbyte block of RAM which is 4Mbyte-aligned and use it as an
array of page table entries. An array would be easy to manipulate in
C. You can also set up a 4k page directory on a 4k boundary and point
CR3 at it.

You may well need some data structures other than the page tables in
order to help you manage memory.

> 3) Thirdly, how are processes running in userland able to use paging?

Once in paging mode almost everything is paged including access to the
page tables! Accesses by user processes would be too. It would be
unusual for user processes to manipulate the page tables but if you
wanted them some limited access to do that it should be through the
kernel. In other words you could provide kernel calls for page table
manipulation.

> I know that these are very "n00bish" questions, but my operating
> system project cannot continue without the answers to them.

They are good questions. Feel free to ask more.

James
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted

smilinggoomba

unread,
May 13, 2012, 11:01:45 AM5/13/12
to
On May 12, 4:52 pm, James Harris <james.harri...@gmail.com> wrote:
- Hide quoted text -

- Hide quoted text -
> On May 12, 8:29 pm, smilinggoomba <jhd0...@gmail.com> wrote:
> > Hello all,
> > I am in the process of writing a simple bootloader in assembly, to
> > load a simple kernel. So far I have code to enable the A20 line, load
> > the kernel to 0x10000 in RAM, and load a temporary GDT. What I still
> > want to do is to map the kernel to 0xC0000000 using paging. The thing
> > is, I have no idea on how to implement paging. Some questions that I
> > have are:
> > 1) Firstly, and most simply, how does one map the kernel to
> > 0xC0000000?
> You can map the memory that the kernel occupies (or is about to
> occupy) to hex c000_0000 using the page tables. If you want the kernel
> to run there you will have to make sure it is prepared to accept that
> it is running at that address. Depending on your compiler the jumps in
> x86 code may be relative so that jumps to within the kernel need no
> relocation. Data references, on the other hand, may need fixup unless
> the kernel is either compiled to run at your target address or its
> addresses are hard coded.

I somewhat don't understand how page tables/directories "work", which
is what I meant in my 2nd question (a.k.a how Linus' code identity
maps 16MB)
> > 2) Secondly, I see code in various paging tutorials (and Linus' head.s
> > in early v0.11) involving some sort of loop. I am very fluent in C
> > but not so much in assembly, so I would like to know what every
> > instruction does, and how it fits into the structure of the page
> > tables/directories.
> Not sure if you mean "every instruction" in the assembler sequence or
> in Linus' code. Check out the x86 manuals for the defined-as-correct
> sequence to enter protected mode and enable paging. Feel free to post
> specifics here.

I meant in Linus' code. Well, here it is (which I think I somewhat
understand (identity pages 16k)):
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page
tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,_pg_dir /* set present bit/user r/w
*/
movl $pg1+7,_pg_dir+4 /* --------- " " ---------
*/
movl $pg2+7,_pg_dir+8 /* --------- " " ---------
*/
movl $pg3+7,_pg_dir+12 /* --------- " " ---------
*/
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w
user,p) */
std
stosl /* fill pages backwards - more
efficient :-) */
subl
$0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-
queue */
- Hide quoted text -
> The structure of the page tables is purely data and will apply whether
> you are using assembler or C. For simplicity you might want to start
> with a 4Mbyte block of RAM which is 4Mbyte-aligned and use it as an
> array of page table entries. An array would be easy to manipulate in
> C. You can also set up a 4k page directory on a 4k boundary and point
> CR3 at it.
> You may well need some data structures other than the page tables in
> order to help you manage memory.
> > 3) Thirdly, how are processes running in userland able to use paging?
> Once in paging mode almost everything is paged including access to the
> page tables! Accesses by user processes would be too. It would be
> unusual for user processes to manipulate the page tables but if you
> wanted them some limited access to do that it should be through the
> kernel. In other words you could provide kernel calls for page table
> manipulation.

Wow, that was so obvious. I can't believe that I missed that.
Thank you so much!
(P.S. I apologize for not being clear about what I was asking. I
hope
I was clearer this time ;-))
--
"If you want to be a hacker, keep reading. If you want to be a
cracker, go read the alt.2600 newsgroup and get ready to do five to
ten in the slammer after finding out you aren't as smart as you think
you are. And that's all I'm going to say about crackers."-Eric S.
Raymond

James Harris

unread,
May 14, 2012, 4:36:52 PM5/14/12
to
On May 13, 4:01 pm, smilinggoomba <jhd0...@gmail.com> wrote:

...

> > > 2) Secondly, I see code in various paging tutorials (and Linus' head.s
> > > in early v0.11) involving some sort of loop.  I am very fluent in C
> > > but not so much in assembly, so I would like to know what every
> > > instruction does, and how it fits into the structure of the page
> > > tables/directories.

> > Not sure if you mean "every instruction" in the assembler sequence or
> > in Linus' code. Check out the x86 manuals for the defined-as-correct
> > sequence to enter protected mode and enable paging. Feel free to post
> > specifics here.
>
> I meant in Linus' code.  Well, here it is (which I think I somewhat
> understand (identity pages 16k)):

That Linux code seems overly cryptic. I don't have any paging code but
AFAICT identity mapping should be fairly easy. As each page is 4k long
the key seems to be to write the following values into the paging
table

page_table[0] = 0;
page_table[1] = 4096;
page_table[2] = 8192;

and so on so that each page table entry points to the corresponding 4k
page. In addition to that you need to set the low two bits of each
entry to say that the entry is present (bit 0) and writeable (bit 1).
That leads to the following untested code which you might prefer to
implement in assembly. I just write it in C as I think it is easier to
understand in this case.

uint32_t *page_table;
for (uint32_t a = 0; a < 1024; a++) {
page_table[a] = (a << 12) | 3;
}

That is intended to identity map the first 1024 pages or 4Mbytes. In
addition you'll need to set the first page directory entry to point to
the start of the page table with something like the following.
Assembly is easier here.

mov eax, page_table ;Where page_table is the address
mov [page_dir + 0], eax

Finally, check the Intel manuals for the Pentium Pro or later for the
correct initialisation sequence to set CR3 to point to page_dir.

For anyone who has paging working, corrections to the above are
welcome....

James

smilinggoomba

unread,
May 14, 2012, 6:19:19 PM5/14/12
to
On May 14, 4:36 pm, James Harris <james.harri...@gmail.com> wrote:
> On May 13, 4:01 pm, smilinggoomba <jhd0...@gmail.com> wrote:
>
> ...
>
> > > > 2) Secondly, I see code in various paging tutorials (and Linus' head.s
> > > > in early v0.11) involving some sort of loop.  I am very fluent in C
> > > > but not so much in assembly, so I would like to know what every
> > > > instruction does, and how it fits into the structure of the page
> > > > tables/directories.
> > > Not sure if you mean "every instruction" in the assembler sequence or
> > > in Linus' code. Check out the x86 manuals for the defined-as-correct
> > > sequence to enter protected mode and enable paging. Feel free to post
> > > specifics here.
>
> > I meant in Linus' code.  Well, here it is (which I think I somewhat
> > understand (identity pages 16k)):
>
> That Linux code seems overly cryptic. I don't have any paging code but
> AFAICT identity mapping should be fairly easy. As each page is 4k long
> the key seems to be to write the following values into the paging
> table
>
> page_table[0] = 0;
> page_table[1] = 4096;
> page_table[2] = 8192;
>
> and so on so that each page table entry points to the corresponding 4k
> page.

So, if I wanted to map 0x10000 to 0xC00000000, I would use the
following:

page_table[0] = 0xC0000000;
page_table[1] = 0xC0004096;
page_table[2] = 0xC0008192;

(and also "or" the bits by 3 as mentioned below)?

> In addition to that you need to set the low two bits of each
> entry to say that the entry is present (bit 0) and writeable (bit 1).
> That leads to the following untested code which you might prefer to
> implement in assembly. I just write it in C as I think it is easier to
> understand in this case.
>
>   uint32_t *page_table;
>   for (uint32_t a = 0; a < 1024; a++) {
>     page_table[a] = (a << 12) | 3;
>   }

Yeah, I've decided to do the paging routine in C. I understand
everything but the bit shift. What is its purpose?

> That is intended to identity map the first 1024 pages or 4Mbytes. In
> addition you'll need to set the first page directory entry to point to
> the start of the page table with something like the following.
> Assembly is easier here.
>
>   mov eax, page_table      ;Where page_table is the address
>   mov [page_dir + 0], eax

I think that one way to do it in C is like this, but I'm not really
sure:

&(page_dir + 0) = page_table;

> Finally, check the Intel manuals for the Pentium Pro or later for the
> correct initialisation sequence to set CR3 to point to page_dir.
>

I'm pretty sure it's this:

mov eax, [page_dir]
mov cr3, eax


Thank you so much!

smilinggoomba

unread,
May 14, 2012, 7:38:23 PM5/14/12
to
Actually, I realize what the bit shift does (multiplies by 4096).

Wow, I can't believe I made a new post for such a trivial thing (one
line).

James Harris

unread,
May 15, 2012, 1:13:58 AM5/15/12
to
On May 14, 11:19 pm, smilinggoomba <jhd0...@gmail.com> wrote:

...

> So, if I wanted to map 0x10000 to 0xC00000000, I would use the
> following:
>
> page_table[0] = 0xC0000000;
> page_table[1] = 0xC0004096;
> page_table[2] = 0xC0008192;
>
> (and also "or" the bits by 3 as mentioned below)?

If I understood correctly you wanted your kernel to appear at hex
c000_0000. Dividing that by 4096 gets 786432 so wouldn't you would
need to set page_table[786432] and later to refer to the physical
location of the kernel?

Be careful not to mix up hex and decimal.

...

> > Finally, check the Intel manuals for the Pentium Pro or later for the
> > correct initialisation sequence to set CR3 to point to page_dir.
>
> I'm pretty sure it's this:
>
> mov eax, [page_dir]
> mov cr3, eax

There is a little more to it than that if you want your code to run
properly on all CPUs. For example see the requirements for an early
CPU at 10.4.4 at

http://pdos.csail.mit.edu/6.828/2007/readings/i386/s10_04.htm

I think the requirements stabilised round about the Pentium or PPro
era.

James

James Harris

unread,
May 15, 2012, 2:05:17 AM5/15/12
to
On May 15, 6:13 am, James Harris <james.harri...@gmail.com> wrote:

... (discussions on how to enable paging)

> I think the requirements stabilised round about the Pentium or PPro
> era.

I found the references that I vaguely remembered. First, from the
Pentium (P54C) manual.

---
The following guidelines for setting the PG bit (as with the PE bit)
should be adhered to
maintain both upwards and downwards compatibility:

1. The instruction setting the PG bit should be followed immediately
with a JMP
instruction. A JMP instruction immediately after the MOV CR0
instruction changes the
flow of execution, so it has the effect of emptying the Intel386 and
Intel486 processor of
instructions which have been fetched or decoded. The Pentium
processor, however, uses
a branch target buffer (BTB) for branch prediction, eliminating the
need for branch
instructions to flush the prefetch queue. For more information on the
BTB, see the
Pentium® Processor Family Developer’s Manual, Volume 1, order number
241428.

2. The code from the instruction which sets the PG bit through the JMP
instruction must
come from a page which is identity mapped (i.e., the linear address
before the jump is
the same as the physical address after paging is enabled).

The 32-bit Intel architectures have different requirements for
enabling paging and switching
to protected mode. The Intel386 processor requires following steps 1
or 2 above. The
Intel486 processor requires following both steps 1 and 2 above. The
Pentium processor
requires only step 2 but for upwards and downwards code compatibility
with the Intel386 and
Intel486 processors, it is recommended both steps 1 and 2 be taken.
---

Second, from the PPro manual.

---
For backward and forward compatibility with all Intel Architecture
processors, Intel
recommends that the following operations be performed when enabling or
disabling paging:

1. Execute a MOV CR0, REG instruction to either set (enable paging) or
clear (disable
paging) the PG flag.

2. Execute a near JMP instruction.

The sequence bounded by the MOV and JMP instructions should be
identity mapped (that is,
the instructions should reside on a page whose linear and physical
addresses are identical).
---

James

Marven Lee

unread,
May 15, 2012, 5:22:23 AM5/15/12
to
James Harris <james.h...@gmail.com> wrote:
> 1. Execute a MOV CR0, REG instruction to either set (enable paging)
> or clear (disable paging) the PG flag.
>
> 2. Execute a near JMP instruction.

I just had a look through my paging initialization code and I
don't do a jump. I can't remember if I ever did the jump in older versions
of my kernel. I guess I should add a jmp to the inline assembly
that loads cr0.

--
Marv

smilinggoomba

unread,
May 15, 2012, 8:20:04 PM5/15/12
to
On May 15, 1:13 am, James Harris <james.harri...@gmail.com> wrote:
> On May 14, 11:19 pm, smilinggoomba <jhd0...@gmail.com> wrote:
>
> ...
>
> > So, if I wanted to map 0x10000 to 0xC00000000, I would use the
> > following:
>
> > page_table[0] = 0xC0000000;
> > page_table[1] = 0xC0004096;
> > page_table[2] = 0xC0008192;
>
> > (and also "or" the bits by 3 as mentioned below)?
>
> If I understood correctly you wanted your kernel to appear at hex
> c000_0000. Dividing that by 4096 gets 786432 so wouldn't you would
> need to set page_table[786432] and later to refer to the physical
> location of the kernel?
>
> Be careful not to mix up hex and decimal.

Yeah, that was careless, as I also did page_table[1] = 0xC0004096
instead of page_table[1] = 0xC0001000.

Also, the address contained by page_table[i] is equal to the location
of the page in physical memory; while where page_table[i]'s address is
equal to the address to be mapped, right? (Probably not)

> > > Finally, check the Intel manuals for the Pentium Pro or later for the
> > > correct initialisation sequence to set CR3 to point to page_dir.
>
> > I'm pretty sure it's this:
>
> > mov eax, [page_dir]
> > mov cr3, eax
>
> There is a little more to it than that if you want your code to run
> properly on all CPUs.

I forgot the part when you "or" cr0 by 0x80000000 to set the paging
bit.

Thanks for everything!

smilinggoomba

unread,
May 17, 2012, 4:58:09 PM5/17/12
to
Is this thread dead...
I didn't mean to make it sound like I had my answer when I said:

> Thanks for everything!

Oh well.

Marven Lee

unread,
May 19, 2012, 11:14:42 AM5/19/12
to
smilinggoomba wrote:
> Is this thread dead...
> I didn't mean to make it sound like I had my answer when I said:

I'm not sure if it will be of help to you but I'll explain the basics
of my VM initialization code.

What I do is split my kernel code into 2 parts so that the initialization
routine that enables paging is mapped with a physical address
and virtual address 0x00100000. The real part of the kernel
is mapped with a physical address of 0x00400000 but with
a virtual address of 0xF0000000.

This is done with a linker script and using the AT directive
to set the physical address. It looks something like this:


OUTPUT_FORMAT("elf32-i386")
OUTPUT_ARCH(i386)
SEARCH_DIR("/cross/lib/gcc-lib/i386-elf/3.3.3")

ENTRY(Entry)
SECTIONS
{

. = 0x00100000; /* Physical and virtual address of Init code */

.inittext :
{
_init_stext = .;
kernel/i386/init/entry.o(.text)
kernel/i386/init/*.o(.text)
}

.initrodata :
{
kernel/i386/init/*.o(.rodata)
kernel/i386/init/*.o(.rodata.*)
kernel/i386/init/*.o(.rodata1)
_init_etext = .;
}

.initdata ALIGN (0x1000) :
{
_init_sdata = .;
kernel/i386/init/*.o(.data)
_init_edata = .;
}

.initbss :
{
_init_sbss = .;
kernel/i386/init/*.o(.bss)
kernel/i386/init/*.o(COMMON)
_init_ebss = .;
}


. = 0xF0000000; /* Virtual address of kernel */

.text : AT (_stext - (0xF0000000 - 0x00400000)) /* Kernel loaded at 4MB
physical addr */
{
_stext = .;
_stext_phys = _stext - (0xF0000000 - 0x00400000);
*(.text)
}

.rodata : AT (_srodata - (0xF0000000 - 0x00400000))
{
_srodata = .;
*(.rodata)
*(.rodata.*)
*(.rodata1)
_etext = .;
_etext_phys = _etext - (0xF0000000 - 0x00400000);
}

.data ALIGN (0x1000) : AT (_sdata - (0xF0000000 - 0x00400000))
{
_sdata = .;
_sdata_phys = _sdata - (0xF0000000 - 0x00400000);
*(.data)
_edata = .;
}

.bss : AT (_sbss - (0xF0000000 - 0x00400000))
{
_sbss = .;
_sbss_phys = _sbss - (0xF0000000 - 0x00400000);
*(.bss)
*(COMMON)
_ebss = .;
_ebss_phys = _ebss - (0xF0000000 - 0x00400000);
}
}


So the init part gets loaded by GRUB at 0x00100000 and the
main kernel part gets loaded at 0x00400000. One of the first
functions called is InitVM() that sets up all of the memory
management data structures and enables paging, identity mapping
the init code to 0x00100000 but remapping the kernel to
0xF0000000.

A simple heap allocator is used to allocate kernel data tables
and structures above the end of the kernel's data. These
structures include those for managing free pages, managing
free areas of a process's memory and importantly a fixed
sized pool of pagetables, allocated as a proportion of the
total amount of physical memory, I think I use about 1/32
and have a minimum limit. So the physical address space
looks a bit like this:

-- end of kernel heap
pagetable_pool
pmap_desc_array - used to allocate pagetables
pageframe_array
memregion_array
process_array
-- beginning of kernel heap
&__ebss_phys
kernel .data
kernel .text
&__stext_phys
0x00400000
...
Init. data
Init .text
0x00100000

The kernel and the heap above it get mapped to 0xF000000 when
paging is enabled.

A function MapMem() is use to initialize the page tables. A separate set
of functions that manipulate the page tables are used once the kernel
is initialized, these are the PmapInit(), PmapEnter() and PmapRemove()
functions.

MapMem() is called several times, for 0-1mb area, the init code, init data,
kernel code and kernel data. vbase and vceiling are the virtual addresses
to map, pbase is the physical address of the base of the region and pte_bits
are used to set the attributes of the page table entry.

root_pagedirectory is the first page of the pagetable pool. This is the
page directory of the root process. current_pagetable is incremented by
PAGE_SIZE when a page-table needs to be allocated.

Initially :
current_pagetable = pagetable_pool;
root_pagedirectory = current_pagetable;
current_pagetable = (uint32 *)((uint8 *)current_pagetable + PAGE_SIZE);


void MapMem (vm_addr vbase, vm_addr vceiling, vm_addr pbase, uint32
pte_bits)
{
vm_addr pa, va;
uint32 *pd, *pt;
uint32 pde_idx, pte_idx;

if (CPUSupportsGlobalPaging() == FALSE)
pte_bits &= ~PG_GLOBAL;

for (pa = pbase, va = vbase; va < vceiling; pa+= PAGE_SIZE, va +=PAGE_SIZE)
{
pde_idx = (va >> PDE_SHIFT) & PDE_MASK;
pte_idx = (va >> PTE_SHIFT) & PTE_MASK;

pd = root_pagedirectory;

/* Allocate a page table if needed and get the page table pointer */

if ((*(pd + pde_idx) & PG_PRESENT) == 0)
{
pt = current_pagetable;
current_pagetable = (uint32 *)((uint8 *)current_pagetable + PAGE_SIZE);
*(pd + pde_idx) = ((uint32)pt & PDE_ADDR_MASK) | PG_USER | PG_READWRITE |
PG_PRESENT;
}
else
{
pt = (uint32 *)(*(pd + pde_idx) & PDE_ADDR_MASK);
}

*(pt + pte_idx) = (pa & PTE_ADDR_MASK) | pte_bits | PG_PRESENT;
}
}


Once the areas are mapped using this function paging is enabled. Then all of
the
other data structures are initialized.

A proper allocator is later used to allocate and free the pagetables for the
rest
of the system. Basically a separate array is used to maintain a list of free
page tables I'm sure a bitmap could be use just as well.

Also not that once paging is enabled it is easy enough to convert from
the virtual address of page tables and page directories to their physical
addresses using

phys_addr = virt_addr - (0xF0000000 - 0x00400000)
virt_addr = phys_addr + (0xF0000000 - 0x00400000)

CR3, page directory and page table entries need physical addresses, but
in order to access them you use the virtual addresses.

So that's basically the initialization. There's a lot more to it though,
the memory management is divided into two parts, a high level
description of address spaces and a hardware specific portion.

The high level portion comprises of AddressSpace, MemRegion
and Pageframe structures to describe a process's address space.
A cpu specific part called the Pmap is used to manage the
actual page tables.

An old post describes the scheme, except back then the kernel
was identity mapped with the init code at 0x00100000.

http://groups.google.com/group/alt.os.development/msg/862869c76fdfd5cf

I don't think I've described the Pmap pagetable routines anywhere
so I'll briefly mention them here. Basically Pmap is a structure that
points to a page directory and is part of my AddressSpace structure.

Whenever a page table entry needs to be changed in the kernel or user
processes the following calls are used.

bool PmapEnter (struct Pmap *pmap, vm_offset va, vm_offset pa, uint32 prot);
bool PmapProtect (struct Pmap *pmap, vm_offset va, uint32 prot);
bool PmapRemove (struct Pmap *pmap, vm_offset va);

These are similar to MapMem() above but only change a single pagetable
entry at a time. A flush of the TLBs by reloading CR3 needs to be performed
once the page tables have been updated.

I've posted the PmapEnter() code below to give you an idea of what they
look like:


bool PmapEnter (struct Pmap *pmap, vm_offset va, vm_offset pa, uint32 prot)
{
uint32 *pd, *pt;
uint32 pde_idx, pte_idx;
struct PmapDesc *pt_desc, *pd_desc;
uint32 pde_bits;

SpinLock (&pmap_slock);

pde_idx = (va >> PDE_SHIFT) & PDE_MASK;
pte_idx = (va >> PTE_SHIFT) & PTE_MASK;

pd = pmap->page_directory;

if ((*(pd + pde_idx) & PG_PRESENT) == 0)
{
if ((pt = PmapAllocPagetable (pmap, PMAP_THROWAWAY)) == NULL)
{
SpinUnlock (&pmap_slock);
return FALSE;
}

*(pd + pde_idx) = (uint32) (PmapToPhys((vm_addr) pt) & PDE_ADDR_MASK) |
PG_USER | PG_READWRITE | PG_PRESENT;

pd_desc = pmapdesc + (((vm_addr)pd - (vm_addr)pagetable)/PAGE_SIZE);
pd_desc->reference_cnt ++;

pt_desc = pmapdesc + (((vm_addr)pt - (vm_addr)pagetable)/PAGE_SIZE);
pt_desc->pde_idx = pde_idx;
pt_desc->parent_pdesc = pd_desc;
}
else
{
pt = (uint32 *)PmapToVirt((vm_addr)(*(pd + pde_idx) & PDE_ADDR_MASK));
pt_desc = pmapdesc + (((vm_addr)pt - (vm_addr)pagetable)/PAGE_SIZE);
}

pt_desc->reference_cnt ++;

pde_bits = PG_USER | PG_PRESENT;

if (prot & VM_PROT_WRITE)
pde_bits |= PG_READWRITE;

*(pt + pte_idx) = (pa & PTE_ADDR_MASK) | pde_bits;

SpinUnlock (&pmap_slock);
return TRUE;
}


The Pmap idea is a simplification of the Mach VM system, I think that
was written by John Dyson and then later ported to FreeBSD by others.
VM is quite complicated especially in mainstream OSes. I hope my
brief description of my page table code above will help or at least give
you some ideas.

I've actually been thinking of simplifying my memory management,
well the AddressSpace and MemRegion stuff in the post I linked to.


--
Marv

Marven Lee

unread,
May 19, 2012, 12:50:58 PM5/19/12
to

"Marven Lee" <marv...@gmail.com> wrote in message:
> The Pmap idea is a simplification of the Mach VM system, I think that
> was written by John Dyson and then later ported to FreeBSD by others.

Doh. I meant to say the Pmap interface in Mach was ported to FreeBSD
by Dyson, I think others might have extended it.


--
Marv

smilinggoomba

unread,
May 24, 2012, 4:27:34 PM5/24/12
to
Actually, with help of a tutorial and all of the posts here, I fully
understand paging. Thanks everybody!

P.S. Marv, if your OS is open source/free software, you should
definitely upload it somewhere.
0 new messages