Section 6: Virtual Memory and Pagetables
#
In this section, we'll go over the why, what, and how of virtual
memory and pagetables.
Worksheet: For a version of this page that you can annotate, see
this worksheet.
Discussion: Why use virtual memory?
#
We'll explore a few scenarios in which virtual memory can make our computers safer and more efficient.
DISCUSSION A. Eve wants to force Alice to waste her processor time
by executing an infinite loop. She wants her process to edit the code
segment of Alice's process to only have the instruction eb fe
. How
will virtual memory stop Eve's plan?
In virtual memory, instead of each process directly working with
addresses in physical memory, each process has its own virtual
memory space. Processes work with addresses in their own virtual
memory, and every address in this virtual memory space maps to a
unique address in physical memory. Each process can thus only access
memory at physical addresses that its virtual addresses map to,
effectively stopping it from interfering with any other processes;
this is known as process isolation. That is to say, if Eve tried
to change the memory in a physical address belonging to a different
process, she simply couldn't. Eve's process can only work with
virtual addresses, and all of Eve's virtual addresses map to
physical addresses designated for her own process! Eve could get
herself into an infinite loop, but thanks to process isolation,
there's no way for her to mess with the kernel or other processes.
DISCUSSION B. Consider this implementation of WeensyOS that
doesn't use virtual memory. Process 4 has run out of contiguous
address space, yet there are still empty physical pages.

How can using virtual memory mappings allow us to allocate additional memory for process 4?
When we use virtual memory, the virtual address used by a process
doesn't have to directly map to the same physical address. In other
words, the memory mappings for individual processes do not need to
match physical memory. The memory allocated for a process in virtual
memory may form a contiguous segment, but the actual physical memory
those virtual addresses map to doesn't need to be contiguous. This
means we can allocate noncontiguous physical blocks, but treat them
as contiguous for a process like process 4.
This means that instead of allocating massive contiguous segments
for each process, the kernel can just allocate one page at a time;
when a process needs additional memory, the kernel simply allocates
the next available page of physical memory and maps the desired page
in virtual memory map to this new physical page. Despite the pages
not being contiguous in physical memory, they are one contiguous
block in virtual memory! This prevents us from allocating too little
or too much memory to each process.
QUESTION 1: Fun with Page Tables!
#
Now that we've talked about how virtual memory works at a high level,
it's time to get into the nitty-gritty of how we map virtual to
physical addresses, starting with our favorite data structure—page
tables!
QUESTION 1A. What information does a pagetable store?
Pagetables keep track of the physical address that each virtual address maps to.
QUESTION 1B. How many levels of pagetables are there in the x86-64 architecture?
There are 4 levels of page tables: L4, L3, L2, and L1:
- A L4 pagetable contains 512 entries, each of which can hold the physical address of a L3 pagetable.
- A L3 pagetable contains 512 entries, each of which can hold the physical address of a L2 pagetable.
- A L3 pagetable contains 512 entries, each of which can hold the physical address of a L1 pagetable.
- A L1 pagetable contains 512 entries, each of which can hold the physical address of a page of memory. A page is a large block of memory (typically 4096 bytes/4 KB).
Each page table takes up a single page of memory. Since a page is
4096=2^12 bytes and an address is (8=2^3) bytes, page tables can
store (2^12)/(2^3)=(2^9=512) unique addresses. (That's where
this number comes from!)
QUESTION 1C. We say that page tables are a "sparse" data structure. What does "sparse" mean and why do we want "sparse" data structures?
Pagetables may seem like they take up a lot of space (a process can
have up to (512^3) L1 pagetables), but a lot of space can be saved
by simply not mapping addresses not present in a virtual address
space.
A "sparse" data structure ignores empty data by not tracking it. In
the case of pagetables, this means not storing data corresponding to
unmapped virtual addresses. This is important for pagetables since
for most processes, the majority of virtual address space will not
be mapped, saving memory.
QUESTION 1D. When translating addresses, how does the translation hardware know where to find the current process's L4 page table?
The physical address of a process' L4 pagetable is stored in the
special register %cr3
. Since this register determines the virtual
memory view of a process, only the kernel can modify this
register. When a virtual address needs to be translated into a
physical address, the translation hardware uses the physical address
in the %cr3
register to find the L4 page table to start at.
QUESTION 1E. Consider the following virtual address:
0x 0000 7f46 9ae0 5445
Its binary expansion is:
0b 0000 0000 0000 0000 0111 1111 0100 0110 1001 1010 1110 0000 0101 0100 0100 0101
Translate the virtual address into a physical address, using the page
table contents below! As you do so, it will be helpful to consider how
each of the bits in the above virtual address is used in translating
an 8-byte virtual address to an 8-byte physical address.
L4 page table
index | L3 pagetable address
------+---------------------
0 | 0
... | ...
126 | 0
127 | 0x9020000
128 | 0x5000000
... |
253 | 0x3900000
254 | 0x3000000
255 | 0
... |
L3 page table at 0x3000000
index | L2 pagetable address
------+---------------------
0 | 0
... | ...
140 | 0
141 | 0x2220000
142 | 0x9400000
... |
280 | 0x8100000
281 | 0
282 | 0x9007000
... |
L2 page table at 0x2220000
index | L1 pagetable address
------+---------------------
0 | 0
... | ...
200 | 0x4444000
201 | 0x3400000
202 | 0x1200000
... |
215 | 0x9099000
216 | 0x1240000
217 | 0x1230000
... |
L2 page table at 0x9007000
index | L1 pagetable address
------+---------------------
0 | 0
... | ...
106 | 0x7800000
107 | 0x2210000
108 | 0x2208000
... |
215 | 0x4500000
216 | 0x7800000
217 | 0x7801000
... |
L1 page table at 0x9099000
index | contents
------+---------------------
0 | 0
... | ...
3 | 0b 1000 0000 0000 0000 0000 1011 1101 1111 1111 0101 0111 1000 1101 0000 0000 0101
4 | 0b 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
5 | 0b 1000 0000 0000 0000 0000 1011 1101 1111 1111 0101 0111 1000 1101 0000 0000 0111
... |
129 | 0b 1000 0000 0000 0000 0000 1001 1100 1010 1011 0001 0101 1100 0001 0000 0000 0111
130 | 0b 1000 0000 0000 0000 0000 0010 1100 0110 1111 0001 0100 1000 0101 0000 0000 0011
131 | 0b 1000 0000 0000 0000 0000 0100 1111 0010 1011 0101 0001 1010 0001 0000 0000 0111
... |
L1 page table at 0x4500000
index | contents
------+---------------------
0 | 0
... | ...
3 | 0b 1000 0000 0000 0000 0000 1010 1100 1110 1011 0001 0100 1100 0101 0000 0000 0111
4 | 0b 1000 0000 0000 0000 0000 1010 1100 0010 1011 0001 0100 1111 0101 0000 0000 0111
5 | 0b 1000 0000 0000 0000 0000 1010 1100 0010 1011 0001 0000 1000 0101 0000 0000 0111
... |
129 | 0b 1000 0000 0000 0000 0000 0000 1100 1010 1011 0001 0100 1100 0101 0000 0000 0111
130 | 0b 1000 0000 0000 0000 0000 0110 1100 0010 1111 0001 0100 1100 0101 0000 0000 0111
131 | 0b 1000 0000 0000 0000 0000 1010 1100 0010 1011 0101 0000 1000 0001 0000 0000 0111
... |

Despite virtual addresses consisting of 8 bytes (64 bits total) like
physical addresses, only the lower 48 bits get used during virtual
address translation. 36 of these bits are used to find the right
page of physical memory by indexing into page tables. The remaining
12 bits are the offset into the physical page. They tell us the
address within the page.
Recall that each level of page table has 512 = 2^9 indexes, each
storing an address for another page table (or in the case of the L1
page table, physical page addresses). Each of these indexes can be
expressed using 9 bits total (this enumerates all indexes of 0
through 512).
The 47th through 39th bit (9 total) in the virtual address
correspond to the index within the L4 page table. The 38th through
30th bit correspond to the index within the L3 page table. The 29th
through 21st bit the L2 page table, and the 20th through 12th bit to
the L1 page table.
A single page consists of 2^12=4096 bytes. Each of these bytes
should be accessible. Once we access the right physical page by
using the address in the corresponding index of the L1 page table,
we need to know which byte our virtual address corresponds to. This
is where the 12 final bytes for the virtual address come in. They
tell us which byte within the page (the offset) this virtual address
corresponds to.
The 63rd through 48th bits are unused for virtual address
translation. They may, however, store additional information, such
as if the page of memory this virtual address belongs to is
executable or non-executable.
In order to translate a virtual address to the physical address, the
hardware finds the process' L4 page table using the %cr3
register,
indexes into it using bits 47 through 39 to obtain the L3 page
table, indexes into the L3 page table using bits 38 though 30 to
obtain the L2 page table, indexes into the L2 page table using bits
29 through 21 to obtain the L1 page table, and uses the 20 through
12 bit to obtain the L1 page table. The remaining 11 bits are used
to determine the offset into the resulting page.
In the example:
- The L4 index is 254, and the L3 page table is therefore at
0x3000000
. - The L3 index is 282, so translation uses the L2 page table at
0x9007000
. - The L2 index is 215, so translation uses the L1 page table at
0x4500000
. - The L1 index is 5, so the relevant entry is
0b 1000 0000 0000 0000 0000 1010 1100 0010 1011 0001 0000 1000 0101 0000 0000 0111
. - The top 16 bits of the entry are unused (except for the NX bit in position 63), which leaves
0b 0000 1010 1100 0010 1011 0001 0000 1000 0101 0000 0000 0111
. - The bottom 12 bits hold protection flags and get replaced by the offset into the page from the original virtual address. The offset is
0100 0100 0101
. - Hence, the resulting 48-bit physical address is
0b 0000 1010 1100 0010 1011 0001 0000 1000 0101 0100 0100 0101
, which converted to hex gives 0x0ac2 b108 5445
.
QUESTION 2: Virtual Memory in Ye Olden Days
#
We know the specifics of how virtual address translation works with 4
KB pages and 8 byte pointers. However, some older computers used
32-bit architectures, on which pointers are 4 bytes in size. This
question investigates how virtual address translation would work with
these smaller (4 byte) pointers.
QUESTION 2A. How many unique 4 byte addresses can a 4 KB (4096
byte) page hold?
A 4 KB page is 4096 = 2^12 bytes. Addresses are now 4 = 2^2
bytes. To determine how many unique addresses we can hold in a page,
we can divide the size of a page (in bytes) by the size of an
address (in bytes).
Since (2^12)/(2^2)=2^10, we know that a 4 KB page can hold
2^10 unique 4 byte addresses.
QUESTION 2B. How many bits would we need to index into each address for a page table that stores 4 byte addresses?
We know that a 4 KB page can hold 2^10 unique 4 byte
addresses. In order to access each of these, we need a series of
bits that can take on at least 2^10 different values. We can
keep track of a page table index using 10 bits of a 4 byte (32 bit)
virtual address.
QUESTION 2C. Consider the following 4 byte virtual address:
0x 9ae0 5445
Its binary expansion is:
0b 1001 1010 1110 0000 0101 0100 0100 0101
Using two levels of pagetables, how are each of the bits in the above virtual address used in translating a 4 byte virtual address to the corresponding 4 byte physical address?

Even with 4 byte virtual addresses, virtual address will still
consist of a page offset and indexes for page tables
For page offsets, the size of pages have not changed. They're still
4096 = 2^12 bytes, so in order every byte within the page, we'll
still need to use 12 bits.
We know that it requires 10 bits to access each index of a 4 KB page
table that stores 4 byte addresses. We have 2 levels of page tables,
so this will require 20 bits in total
In a 4 byte virtual address, we only have 32 bits we could possibly
use for virtual address translation. 12 of these bits are used for
calculating the page offset, leaving us with 20 bits for page table
indexing. Each level of page table requires 10 bits, which gives us
just enough bits for 2 whole levels of page tables!