Skip to content

Commit

Permalink
wrap 20, add a few for 21.
Browse files Browse the repository at this point in the history
  • Loading branch information
tobyatgithub committed Oct 25, 2022
1 parent 88f0643 commit 94c21b2
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 2 deletions.
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": ["-s", "0", "-c"]
}
]
}
55 changes: 54 additions & 1 deletion Homework7/chap20/Chap20Simulator.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
hum
# Homework Simulation Chapter 20 Smaller Pages

## Question 1

> With a linear page table, you need a single register to locate the page table, assuming that hardware does the lookup upon a TLB miss. How many registers do you need to locate a two-level page table? A three-level table?
**Answer:**

Based on figure 20.3, I believe only the top level page directory base registor is required (PDBR).

## Question 2

> Use the simulator to perform translations given random seeds 0, 1, and 2, and check your answers using the -c flag. How many memory references are needed to perform each lookup?
**Answer:**

`python paging-multilevel-translate.py -s 0 -c`

Here I used a python debugger with config file to investigate the process (which is pretty much the same calculation as the textbook.)

As described in the function:

```python
def translate(self, pid, virtualAddr):
(valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, True)
if valid == 1:
ptePage = ptPtr
(valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, True)
if valid == 1:
offset = (virtualAddr & self.OFFSET_MASK)
paddr = (pfn << self.pageBits) | offset
# print(' --> pfn: %02x offset: %x' % (pfn, offset))
return paddr
else:
return -2
return -1
```

here we first call `self.getPageDirEntry`, if valid then call the `self.getPageTableEntry`, and if valid, calculate the offset and actually page address in the end.

And the memory involved methods are:
```python
pageDir = self.pdbr[pid]
pde = self.memory[pdeAddr]
pte = self.memory[pteAddr]
```
so three times.

## Question 3

> Given your understanding of how cache memory works, how do you think memory references to the page table will behave in the cache? Will they lead to lots of cache hits (and thus fast accesses?) Or lots of misses (and thus slow accesses)?
**Answer:**
By the figure 20.3, the top level page directory lookup will work well with temporal locality cache because we will revisit each top-level PFN multiple times. And the page table level lookup will be a little bit harder to cache (i.e. probably more cache misses) and will suit spatial locality cache beter because each PFN is not likely to be revisited but revisit will happen to other PFNs that is close to each other in the memory.
272 changes: 272 additions & 0 deletions Homework7/chap20/paging-multilevel-translate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
#! /usr/bin/env python

from __future__ import print_function
import sys
from optparse import OptionParser
import random
import math

# to make Python2 and Python3 act the same -- how dumb
def random_seed(seed):
try:
random.seed(seed, version=1)
except:
random.seed(seed)
return

def convert(size):
length = len(size)
lastchar = size[length-1]
if (lastchar == 'k') or (lastchar == 'K'):
m = 1024
nsize = int(size[0:length-1]) * m
elif (lastchar == 'm') or (lastchar == 'M'):
m = 1024*1024
nsize = int(size[0:length-1]) * m
elif (lastchar == 'g') or (lastchar == 'G'):
m = 1024*1024*1024
nsize = int(size[0:length-1]) * m
else:
nsize = int(size)
return nsize

def roundup(size):
value = 1.0
while value < size:
value = value * 2.0
return value


class OS:
def __init__(self):
# 4k phys memory (128 pages)
self.pageSize = 32
self.physPages = 128
self.physMem = self.pageSize * self.physPages
self.vaPages = 1024
self.vaSize = self.pageSize * self.vaPages
self.pteSize = 1
self.pageBits = 5 # log of page size

# os tracks
self.usedPages = []
self.usedPagesCount = 0
self.maxPageCount = int(self.physMem / self.pageSize)

# no pages used (yet)
for i in range(0, self.maxPageCount):
self.usedPages.append(0)

# set contents of memory to 0, too
self.memory = []
for i in range(0, self.physMem):
self.memory.append(0)

# associative array of pdbr's (indexed by PID)
self.pdbr = {}

# mask is 11111 00000 00000 --> 0111 1100 0000 0000
self.PDE_MASK = 0x7c00
self.PDE_SHIFT = 10

# 00000 11111 00000 -> 000 0011 1110 0000
self.PTE_MASK = 0x03e0
self.PTE_SHIFT = 5

self.VPN_MASK = self.PDE_MASK | self.PTE_MASK
self.VPN_SHIFT = self.PTE_SHIFT

# grabs the last five bits of a virtual address
self.OFFSET_MASK = 0x1f

def findFree(self):
assert(self.usedPagesCount < self.maxPageCount)
look = int(random.random() * self.maxPageCount)
while self.usedPages[look] == 1:
look = int(random.random() * self.maxPageCount)
self.usedPagesCount = self.usedPagesCount + 1
self.usedPages[look] = 1
return look

def initPageDir(self, whichPage):
whichByte = whichPage << self.pageBits
for i in range(whichByte, whichByte + self.pageSize):
self.memory[i] = 0x7f

def initPageTablePage(self, whichPage):
self.initPageDir(whichPage)

def getPageTableEntry(self, virtualAddr, ptePage, printStuff):
pteBits = (virtualAddr & self.PTE_MASK) >> self.PTE_SHIFT
pteAddr = (ptePage << self.pageBits) | pteBits
pte = self.memory[pteAddr]
valid = (pte & 0x80) >> 7
pfn = (pte & 0x7f)
if printStuff == True:
print(' --> pte index:0x%x [decimal %d] pte contents:0x%x (valid %d, pfn 0x%02x [decimal %d])' % (pteBits, pteBits, pte, valid, pfn, pfn))
return (valid, pfn, pteAddr)

def getPageDirEntry(self, pid, virtualAddr, printStuff):
pageDir = self.pdbr[pid]
pdeBits = (virtualAddr & self.PDE_MASK) >> self.PDE_SHIFT
pdeAddr = (pageDir << self.pageBits) | pdeBits
pde = self.memory[pdeAddr]
valid = (pde & 0x80) >> 7
ptPtr = (pde & 0x7f)
if printStuff == True:
print(' --> pde index:0x%x [decimal %d] pde contents:0x%x (valid %d, pfn 0x%02x [decimal %d])' %
(pdeBits, pdeBits, pde, valid, ptPtr, ptPtr))
return (valid, ptPtr, pdeAddr)

def setPageTableEntry(self, pteAddr, physicalPage):
self.memory[pteAddr] = 0x80 | physicalPage

def setPageDirEntry(self, pdeAddr, physicalPage):
self.memory[pdeAddr] = 0x80 | physicalPage

def allocVirtualPage(self, pid, virtualPage, physicalPage):
# make it into a virtual address, as everything uses this (and not VPN)
virtualAddr = virtualPage << self.pageBits
(valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, False)
if valid == 0:
# must allocate a page of the page table now, and have the PD point to it
assert(ptPtr == 127)
ptePage = self.findFree()
self.setPageDirEntry(pdeAddr, ptePage)
self.initPageTablePage(ptePage)
else:
# otherwise, just extract page number of page table page
ptePage = ptPtr
# now, look up page table entry too, and mark it valid and fill in translation
(valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, False)
assert(valid == 0)
assert(pfn == 127)
self.setPageTableEntry(pteAddr, physicalPage)

# -2 -> PTE fault, -1 means PDE fault
def translate(self, pid, virtualAddr):
(valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, True)
if valid == 1:
ptePage = ptPtr
(valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, True)
if valid == 1:
offset = (virtualAddr & self.OFFSET_MASK)
paddr = (pfn << self.pageBits) | offset
# print(' --> pfn: %02x offset: %x' % (pfn, offset))
return paddr
else:
return -2
return -1

def fillPage(self, whichPage):
for j in range(0, self.pageSize):
self.memory[(whichPage * self.pageSize) + j] = int(random.random() * 31)

def procAlloc(self, pid, numPages):
# need a PDBR: find one somewhere in memory
pageDir = self.findFree()
# print('**ALLOCATE** page dir', pageDir)
self.pdbr[pid] = pageDir
self.initPageDir(pageDir)

used = {}
for vp in range(0, self.vaPages):
used[vp] = 0
allocatedVPs = []

for vp in range(0, numPages):
vp = int(random.random() * self.vaPages)
while used[vp] == 1:
vp = int(random.random() * self.vaPages)
assert(used[vp] == 0)
used[vp] = 1
allocatedVPs.append(vp)
pp = self.findFree()
# print('**ALLOCATE** page', pp)
# print(' trying to map vp:%08x to pp:%08x' % (vp, pp))
self.allocVirtualPage(pid, vp, pp)
self.fillPage(pp)
return allocatedVPs

def dumpPage(self, whichPage):
i = whichPage
for j in range(0, self.pageSize):
print(self.memory[(i * self.pageSize) + j], end='')
print('')

def memoryDump(self):
for i in range(0, int(self.physMem / self.pageSize)):
print('page %3d:' % i, end='')
for j in range(0, self.pageSize):
print('%02x' % self.memory[(i * self.pageSize) + j], end='')
print('')

def getPDBR(self, pid):
return self.pdbr[pid]

def getValue(self, addr):
return self.memory[addr]

# allocate some processes in memory
# allocate some multi-level page tables in memory
# make a bit of a mystery:
# can examine PDBR (PFN of current proc's page directory)
# can examine contents of any page
# fill pages with values too
# ask: when given
# LOAD VA, R1
# what will final value will be loaded into R1?

#
# main program
#
parser = OptionParser()
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
parser.add_option('-a', '--allocated', default=64, help='number of virtual pages allocated',
action='store', type='int', dest='allocated')
parser.add_option('-n', '--addresses', default=10, help='number of virtual addresses to generate',
action='store', type='int', dest='num')
parser.add_option('-c', '--solve', help='compute answers for me', action='store_true', default=False, dest='solve')


(options, args) = parser.parse_args()

print('ARG seed', options.seed)
print('ARG allocated', options.allocated)
print('ARG num', options.num)
print('')

random_seed(options.seed)

# do the work now
os = OS()
used = os.procAlloc(1, options.allocated)

os.memoryDump()

print('\nPDBR:', os.getPDBR(1), ' (decimal) [This means the page directory is held in this page]\n')

for i in range(0, options.num):
if (random.random() * 100) > 50.0 or i >= len(used):
vaddr = int(random.random() * 1024 * 32)
else:
vaddr = (used[i] << 5) | int(random.random() * 32)
if options.solve == True:
print('Virtual Address 0x%04x:' % vaddr)
r = os.translate(1, vaddr)
if r > -1:
print(' --> Translates to Physical Address 0x%03x --> Value: 0x%02x' % (r, os.getValue(r)))
elif r == -1:
print(' --> Fault (page directory entry not valid)')
else:
print(' --> Fault (page table entry not valid)')
else:
print('Virtual Address %04x: Translates To What Physical Address (And Fetches what Value)? Or Fault?' % vaddr)

print('')

exit(0)




Empty file.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ cache replacement: LRU (least recently used) policy, random policy

bottleneck: TLB access can easily become a bottleneck in the CPU pipeline

Cache - spatial locality: the idea is that if a program accesses memory at address x, it will likely soon access memory near x.
Even though this is the first time the program accesses the array, the TLB improves performance due to spatial locality. The elements of the array are packed tightly into pages (i.e., they are close to one another in space), and thus only the first access to an element on a page yields a TLB miss.

Cache - temporal locality: an instruction or data item that has been recently accessed will likely be re-accessed soon in the future

### Chapter 20: Paging with smaller tables

Crux: Simple array-based page tables (usually called linear page tables) are too big, taking up far too much memory on typical systems. How to make page tables smaller?
Expand All @@ -327,4 +332,24 @@ Problem - internal fragmentation (big pages lead to waste within each page.)
Solution 2 - Hybrid Paging + Segmentation:

we use the base not to point to the segment itself but rather to hold the physical address of the page table of that
segment. The bounds register is used to indicate the end of the page table
segment. The bounds register is used to indicate the end of the page table

Solution 3 - Multi level page tables:

First, chop up the page table into page-sized units; then, if an entire page of page-table entries (PTEs) is invalid, don’t allocate that page of the page table at all. To track whether a page of the page table is valid (and if valid, where it
is in memory), use a new structure, called the page directory.

pro - 1. it only allocates page-table space in proportion to the amount of address space you are using. 2. very flexible in address allocating (compared to you will need a continuous physical space in a linear paging design,) we can allocate page-table pages wherever in the physical memory.

con - 1. on a TLB miss, two loads from memory will be required (we trade this time off to get better space performance.) 2. complexity in hardware and os lookup handling.

Solution 4 - Inverted Page Tables:

Here, instead of having many page tables (one per process of the system), we keep a single page table that has an entry for each physical page of the system.

### Chap 21: Beyond Physical Memory: Mechanisms

Motivate: We will now relax these big assumptions, and assume that we wish to support many concurrently-running large address spaces. To support large address spaces, the OS will need a place to stash away portions of address spaces that currently aren’t in great demand.

Crux: How can the OS make use of a larger, slower device to transparently provide the illusion of a large virtual address space?

Binary file modified Textbook/chap20-vm-smalltables.pdf
Binary file not shown.
Binary file modified Textbook/chap21-vm-beyondphys.pdf
Binary file not shown.

0 comments on commit 94c21b2

Please sign in to comment.