Learning Objectives
At the end of this lecture, you should be able to:
- Traverse a page table for a given process in the xv6 kernel.
- Define when a page table entry is a page directory or is a leaf page.
- Implement a function called
vmprintto print a given process’s page table.
Topics
In this lecture, we will cover the following topics:
- Page Tables.
- Page Table Traversal in xv6.
Notes
Some Definitions
- We will first explore the xv6 code that is used to manipulate a process’s page table.
- For the RISC-V architecture used in xv6, all addresses are 64 bits. However,
only 39 bits of those 64 are actually used.
- The top 25 bits are unused, and are assumed to be 0.
- The next set of 9 bits are a first level page directory index.
- The next set of 9 bits are a second level page directory index.
- The next set of 9 bits are a third level page directory index.
- Finally, the last set of 12 bits are the page offset in the final physical frame.
- Moreover, each page table entry (PTE) is 64 bits wide.
- The top 10 bits are unused.
- The next 44 bits are the physical frame number.
- They can represent the address of a physical data page, or
- they can represent the address of another level page table.
- The last set of 10 bits are reserved for protection bits.
-
This means that each page table contains 512 PTEs.
-
The file
kernel/riscv.hcontains a bunch of useful macros for manipulating a virtual address, a physical address, and a PTE. - In xv6, a page table is represented by 64 bit address as follows:
typedef uint64 *pagetable_t; - Similarly, a PTE is also represented as a 64 bit address:
typedef uint64 pte_t;
Page Table Manipulation
The walkaddr Function
- In xv6, the function
walkaddrinkernel/vm.ctakes a page table and a virtual addressva, and returns a corresponding physical addresspa.- The function first walks the page table looking for the virtual address’s
PTE using
pte = walk(pagetable, va, 0);. We will look at thewalkfunction shortly. - After obtaining the corresponding PTE, the function checks for some
conditions:
- If the PTE is 0, then the page is not mapped in the page table.
- If the PTE is not valid (i.e., if
*pte & PTE_V == 0, then the page is not mapped in the page table. - If the PTE is not user accessible (i.e., if
*pte & PTE_U == 0, then the page is not accessible by the user and an invalid physical address is returned.
- From the corresponding PTE, you can use the
PTE2PAmacro to obtain the corresponding physical frame address from the PTE.pa = PTE2PA(*pte); - Checkout the
kernel/riscv.hfile for the definition of thePTE2PAmacro.
- The function first walks the page table looking for the virtual address’s
PTE using
The walk Function
- The
walkfunction inkernel/vm.cis the building block ofwalkaddrand any traversal of a give page table. - It accepts a page table and a virtual page address as arguments (let’s ignore
the
allocargument for now), and returns the corresponding leaf page table entry for the virtual page address. Actually, it returns the address of that PTE. walkstarts from the level 2 page directory index and traverses the page table level by level until it hits a valid leaf PTE.- To get a PTE from a page table and a virtual address
va,walkuses thePX(level, va)macro that extracts the page directory index fromvaat the correspondinglevel.- It then indexes into the corresponding page table and obtains the PTE.
pte_t *pte = &pagetable[PX(level, va)];
- It then indexes into the corresponding page table and obtains the PTE.
- If the PTE is valid, then it maps into a lower level page table (or a leaf physical frame or it’s the last level).
- The statement
pagetable = (pagetable_t)PTE2PA(*pte);first extracts a physical frame number from a given PTE using thePTE2PAmacro defined inkernel/riscv.h, and then casts that address into another page table. - Once it reaches level 0,
walkwill return the address of the PTE in the last level page table as follows:return &pagetable[PX(0, va)];
Other Relevant Functions
-
Here is a list of some other relevant functions in
kernel/vm.c:-
mappagesmaps a memory region of sizesizethat starts at virtual addressvainto a page tablepagetable. It creates PTEs in each level of the page table by setting theallocargument ofwalkto 1, and then marks each mapped PTE as valid. -
uvmunmapunmapsnpagespages starting a virtual addressvafrom the input page table. If thedo_freeargument is set, then the function will also free allocated frames for each mapped page. -
uvmcreatecreates an empty page table. -
freewalkrecursively frees all the pages that have been allocated for a page table. Note that this function should never encounter leaf frames. -
uvmcopycopies a parent’s page table into a child’s page table by copying all of the mapped pages from the parent to the child. This function is at the core of the implementation of thefork()system call, and is one that we will extensively revisit in lab 5.
-
Implementing vmprint
- In this activity, we would like to implement the function
vmprintthat you find at the end of thekernel/vm.cfile. - This function simply accepts a page table as an argument and prints that page level by level.
- We will use the recursive
printptefunction defined right before it as a helper in print out all levels of a given page table.
Obtaining the Source Code
- From the xv6 source code, checkout the
vmprintbranch as follows:$ git fetch $ git checkout vmprint $ git pull - You can check the branch that you are on using
git branch. -
Make sure that you can see the empty
vmprintandprintptefunctions at the end of thekernel/vm.cfile. - Currently, the code will call the
vmprintfunction every time the process withpid = 1isexec‘ed. You can check out the code for that inkernel/exec.cin theexec()function:if(p->pid == 1) vmprint(p->pagetable);
Implementing vmprint
- Your task for the rest of this activity is to implement the code in
vmprintandprintpte. - You will find a lot of inspiration in the
freewalkfunction. - When running xv6 with
vmprintimplemented, your output should look like the following:
$ make qemu
...
xv6 kernel is booting
hart 1 starting
hart 2 starting
page table 0x0000000087f6c000
..0: pte 0x0000000021fda001 pa 0x0000000087f68000
.. ..0: pte 0x0000000021fd9c01 pa 0x0000000087f67000
.. .. ..0: pte 0x0000000021fda41b pa 0x0000000087f69000
.. .. ..1: pte 0x0000000021fd9817 pa 0x0000000087f66000
.. .. ..2: pte 0x0000000021fd9407 pa 0x0000000087f65000
.. .. ..3: pte 0x0000000021fd9017 pa 0x0000000087f64000
..255: pte 0x0000000021fdac01 pa 0x0000000087f6b000
.. ..511: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. .. ..510: pte 0x0000000021fdd007 pa 0x0000000087f74000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
init: starting sh
$
- Note that the physical mapped addresses that you see above might be different in your case, but your output should look similar.
Possible Implementation Plan
- Here’s a possible plan of attack:
- Look at
freewalkfor inspiration, the code there will prove to be very helpful. - For each level, traverse all 512 PTE of the corresponding page table.
- If the PTE is valid, but is neither readable, write, nor executable, then
the PTE points to an inner level page table. This is when you need to make
the recursive call to
printpte. - If the PTE is valid and is either readable, writable, or executable, then the PTE points to a leaf data frame. In that case, you simple need to print the mapping.
- If the PTE is valid, but is neither readable, write, nor executable, then
the PTE points to an inner level page table. This is when you need to make
the recursive call to
- The
prefixargument of theprintptefunction might prove to be useful in printing the..that you can see at each level of the page table in the output above.
- Look at