Rottweiler OS161
Stella Pantella - Roger Zurawicki
// max_pid is 64 = 2^6
struct cm_elt_t {
uint32_t tlb_hi;
// = TLB_HI[1/2] 32 bits
// |-- VPN 20 bits
// |-- pid 12 bits
// unsigned lockbit; (used for multipage allocation)
}
We assume one level constant size pagetable
// 64-bit struct
struct pt_elt_t {
uint32_t tlb_lo;
// = TLB_LO[1/2] 32 bits
// |-- PPN 20 bits
// |-= mode 4 bits
// | |-- nocache 1 bit
// | |-- dirty 1 bit
// | |-- valid 1 bit
// | |-- global (kernel) 1 bit
// |-= TLB reserved 8 bits
// | |-- swapped 1 bit
// | |-- (unused) 3 bit
// | |-- cpu_id 4 bits
ppn_t ppn; // 32 bits
}
struct pagetable_t {
// dynamic array, start at size USERPRAM_SZ/PAGE_SZ
pt_elt_tarray pt_elts;
struct lock *pt_lk;
}
struct addrspace {
// TODO handle the stack boundary?
vaddr_t stack_min;
vaddr_t heap_max;
struct pagetable_t pt;
}
## Kernel-dependent structures
```c
// Corepage size is fixed at boot-time = USERRAM_SZ/PAGE_SZ
// sizeof(struct coremap)
// 512 KB RAM -> 512+ B coremap
// 512 MB RAM -> 512+ KB coremap
struct coremap_t {
struct cm_elt_t *cm_elts;
size_t cm_sz;
// we have a total of k buckets with cm_sz/k elts each
struct lock *cm_lks[COREMAP_LKS];
// lets use the clock LFU approximation algorithm
// points to the index of the map that will be a candidate for eviction
// if used we evict it and update the clock_hand
int clock_hand;
}
// Bitmap size is dynamic and increases at run-time
// sizeof(struct swapdisk_bitmap_t)
// 1 MB disk -> 128+ b = 16+ B bitmap max size
// 4 GB disk -> 1+ Mb = 128+ KB bitmap max size
struct swapdisk_bitmap_t {
struct bitmap bm;
struct lock *bm_lk;
struct vnode *bm_vn;
}
Note: MACROS use the actual struct, NOT a pointer.
Note: All functions are thread-unsafe, less noted otherwise.
#define CME_PPN(cme) ((ppn_t)(&cme - &CME[0]))
#define CME_VPN(cme) ((vpn_t)((cme).tlb_hi >> 12))
#define CME_PID(cme) ((cme).tlb_hi & 0xfff)
#define PTE_PPN(pte) ((ppn_t)((pte).tlb_lo >> 12))
#define PTE_ISNOCACHE(pte) ((pte).tlb_lo >> 11 & 1)
#define PTE_ISDIRTY(pte) ((pte).tlb_lo >> 10 & 1)
#define PTE_ISVALID(pte) ((pte).tlb_lo >> 9 & 1)
#define PTE_ISKERNEL(pte) ((pte).tlb_lo >> 8 & 1)
#define PTE_ISSWAPPED(pte) ((pte).tlb_lo >> 7 & 1)
#define PTE_CPU(pte) ((pte).tlb_lo & 0xf)
typedef uint64_t tlb_t; // tlb_t should be 64 bits
typedef uint32_t ppn_t; // Physcial page number type
typedef uint32_t vpn_t; // Virtual Page Number tyle
typedef uint32_t spn_t; // Swap Page Number Type
#define OFFSET_BITS 20
#define PN_OF_ADDR(ptr) ((ptr) >> OFFSET_BITS)
#define ADDR_OF_PN(pn) ((pn) << OFFSET_BITS)
#define CME(ppn) (coremap->cm_elts[(ppn)])
/*
* Helper functions
*/
/** Gets the pagetable entry for a given process and virtual address
*
*/
inline struct pt_elt_t *pte_proc_vaddr(struct proc *p, vaddr_t ptr) {
return &p->p_as.pt.pt_elts[PN(ptr)];
}
/** Looks up pagetable entry from coremap entry
* @param cme[in] coremap entry to lookup
* @return pagetable entry of page
* @pre page is mapped in RAM by an active process
*/
inline struct pt_elt_t *cme_to_pte(struct cm_elt_t *cme) {
struct proc *p = procvector->procs[cme->owner];
struct pagetable_t *pt = p->p_as.pt;
return pt.pt_elts[CME_VPN(cme)];
}
/** Looks up coremap entry from pagetable entry
* @param[in] pte pagetable entry to lookup
* @return coremap entry of page
* @pre page is mapped in RAM by an active process
*/
inline struct cm_elt_t *pte_to_cme(struct pt_elt_t *pte) {
return CME(PTE_PPN(*pte));
}
/** Convert page mapping to TLB entry
* @param[in] cme coremap entry of page
* @param[in] pte pagetable entry of page
* @return TLB entry from @pte and @cme
* @pre the page is active and mapped by @pte to @cme
*/
tlb_t tlb_entry(struct cm_elt_t *cme, struct pt_elt_t *pte) {
tlb_t retval;
uint32_t tlb_lo = pte->tlb_lo & ~(0xff); // clears the reserve bits
uint32_t tlb_hi = cme->tlb_hi & ~(0x3f); // clears the reserve bits
// TODO we might want to handle the ASID
join32to64(&tlb_hi, &tlb_lo, &retval);
return retval;
}
/*
* VM mapping functions
*/
/** Gets an open coremap page, evicitng if necessary
* @return coremap entry of available page or NULL
*
* Will evict page as necessary
* May block on I/O
*/
struct cm_elt_t *alloc_cme() {
// TODO lock coremap
// TODO use clock to find nearest free page
// TODO if clock wraps around and cannot find free page
// else manually evict
page_deactivate(cme, pte);
// TODO update coremap and return cme
}
/** Updates coremap to map physical page entry to virtual page
* @param[in,out] coremap entry to update
* @param[in] ptr virtual address of page to map
* @param[in] owner written to coremap entry
* @return physical address that @ptr maps to
* @pre @ptr is not mapped in RAM
* @pre @cme is inactive
* @post coremap refects new mapping
*/
inline paddr_t cm_map(struct cm_elt_t *cme, vaddr_t ptr, pid_t owner) {
cme->tlb_hi = PN_OF_ADDR(ptr);
cme->tlb_hi <<= 12;
cme->tlb_hi |= (owner & 0xfff);
return CME_PPN(cme) | (ptr & 0xfff);
}
/** Grabs physical memory to map virtual page
* @param[in] pid user process id, 0 means kernel
* @param[in] ptr virtual address of page to map
* @param[in] writeable false if page is readonly
* @return physical address that @ptr maps to
* @pre @ptr is not mapped in RAM
* @pre @ptr is not swapped to disk
* @pre Process @pid is active or equals 0
* @post coremap refects new mapping
* @post Process @pid's pagetable reflect new mapping
*
* Will evict page as necessary
* May block on I/O
*/
paddr_t page_create(pid_t pid, vaddr_t ptr, int writable) {
struct pt_elt_t *pte = pte_proc_vaddr(PROC(pid), ptr);
struct cm_elt_t *cme = alloc_cme();
ppn_t ppn = CM_PPN(cme);
pte->tlb_lo = ppn << 12
// set valid bit
pte->tlb_lo |= (1 << 9);
if (writeable) {
pte->tlb_lo |= (1 << 10);
}
if (pid == 0) {
// set global bit
pte->tlb_lo |= (1 << 8);
}
// no need to save CPU, that info is in the proccess's kthread
pte->owner = pid;
return cm_map(cme, ptr, pid);
}
/** Sets up coremap to map virtual page
* @param[in] pid user process id, 0 means kernel
* @param[in] ptr virtual address of page to map
* @return physical address that @ptr maps to
* @pre @ptr is not mapped to RAM
* @pre @ptr is swapped to disk
* @pre Process @pid is active or equals 0
* @post coremap refects new mapping
* @post Process @pid's pagetable reflect new mapping in RAM
*
* Will evict page as necessary
* May block on I/O
*/
paddr_t page_reactivate(pid_t pid, vaddr_t ptr) {
return cm_map(alloc_cme(), ptr, pid);
}
/** Removes a page from memory, writing to swap as neccessary
* @param[in,out] cme coremap entry of page to deactivate
* @param[in,out] pte coremap entry of page to deactivate
* @pre @pte belongs to active process
* @pre @cme's page is active and mapped by owner's pagetable
* @post physical page is filled with deadbeef
*
* May block on I/O
*/
void page_deactivate(struct cm_elt_t *cme, struct pt_elt_t *pte) {
if (PTE_ISDIRTY(pte)) {
KASSERT(PTE_ISSWAPPED(pte));
swapout(CME_PPN(&cme), pte->spn);
}
pte->tlb_lo = 0;
pte->spn = 0;
cme->tlb_hi = 0;
ppn_t ppn = CME_PPN(cme);
fill_deadbeef(cme, PAGE_SZ);
}
We interact wth synchonization primitvies by the following order. We order by increasing acquisition pressure so that the critical section for high pressure synchronization primitives have smaller critical sections.
- Locks (sorted by ascending acquisiton pressure)
- I/O locks (TBD, this might be too impractical to enforce)
- Pagetable Locks
- Coremap locks ...
- Big Locks
- In case of tie, ascending address in memory
- Spinlocks
- Ordered by ascending address in memory
- Atomic CAS, TAS, and other instructions
We can only acquire lock in ascending order (top-down). We free locks as necessary to enforce this invariant.
If we use a lock for the coremap, it is necessary to acquire the lock for writes. It is sufficient to acquire the lock for reads, but is it necessary?
We might explore how CAS might help us avoid allocating an entire lock.
Because each process has its own pagetable, this is not quite the smae synchonization problem as airballons.
Roger: We could have a biglock for all pagetables. I just don't know if that is preferable or not.
A race condition we need to watch out for is the following:
Assume TLB and memory are full
Page X and Y have the same VPN.
Process A has a TLB miss for an active, valid page (Page X) (in coremap and pagetable)
Process B has a TLB miss for an inactive, valid page (Page Y) swapped to disk.
Process B evicts page X from memory to swap disk (write to A's pagetable & coremap).
Process B installs page Y in memory (write to B's pagetable & coremap).
Process B installs the TLB entry for page Y (read B's pagetable).
Process A sets the TLB to map page X (read A's pagetable). (Is page X active?)
Process B now issues a read to page X when it should have read page Y.
Process A
Following from A2, we use p->p_lk to protect process sturcutres (excluding) VM.
We lock our vnode I/O with st_lk.
On context switch we call the function vm_tlbshootdown_all
(defined in "vm.h"
)
'''c
/** Invalidates all tlb entries in the current cpu.
-
@post all tlb entries are invalidated for the current cpu
-
Atomic at Machine level */ void vm_tlbshootdown_all(void) { // TODO inter-CPU shootdown - Is it necessary? int spl = splhigh(0);
for (int i = 0; i < NUM_TLB; i++) tlb_write(TLBHI_INVALID, TLBLO_INVALID, i); splx(spl); }
## TLB Fault
```c
/** Handles TLB fault
* @pre page is not in TLB
* @param[in] faulttype type of fault
* @param[in] ptr pointer to virtual address
* @return 0 on success, errno descriptor on failure
*
* Atomic at thread level
*/
int vm_fault(int faulttype, vaddr_t ptr) {
int err = 0;
switch (faulttype) {
case VM_FAULT_READONLY:
case VM_FAULT_READ:
case VM_FAULT_WRITE:
struct pt_elt_t *pte = pte_proc_vaddr(curproc, ptr);
if (pte_to_cme(pte) == NULL)
// bring page to memory
err = page_handler(pte);
KASSERT(pte_to_cme(pte) != NULL);
if TLB does not have empty slot
evict random page from TLB
insert new page into that slot with tlb_write
break;
default:
err = EINVAL;
}
return print_err(err); // TODO standardize error handling
}
/** Handles Page fault
* @param[in] pte page table entry
* @pre page is not in TLB
* @return 0 on success, errno descriptor on failure
*
* Atomic at thread level
*/
int page_handler(struct pt_elt_t *pte) {
int err;
// grab coremap lock
// search coremap for empty slot
// if no empty slot, run paging daemon
// reserve the slot
// release core map lock
err = swapcopyin(cm_slot, sidx);
// update VPN -> PPN in Page Table
return print_err(err);
}
We are using a dynamically allocated page table array for every address space. The odd indexes of the array correspond to heap v. addresses and the even indexes of the array correspond to stack v. addresses. That way we do not need to use 2-level page tables.
vpn_to_pt_idx(x) = if even then (x/2) else (n-(x/2 + 1)
pt_idx_to_vpn(x) = if x < HALF then (2x) else (2(x+n)-1)
( 0 , 1 , 2 , ... , n-2 , n-1 )
maps =>
( 0 , n-1 , 1 , n-2 , 2 , ... )
Errors
The main data structure used for swap management is the "swapdisk_bitmap_t"
. It has a global lock and use the "lhdraw0:"
disk.
/** Swapout updates the contents of the swap page on disk
* @pre assumes the page is dirty
* @pre assumes the page has spn @sidx
*
* thread-safe with respect to swapdisk
*/
void
swapcopyout(ppn_t *ppn, spn_t sidx) {
struct uio u;
struct iovec iov;
iov.iov_ubase = ADDR_OF_PN(ppn);
iov.iov_len = PAGE_SZ;
u.uio_iov = &iov;
u.uio_iovcnt = 1;
u.uio_resid = PAGE_SZ; // amount to read from the file
u.uio_segflg = UIO_USERSPACE;
u.uio_rw = UIO_WRITE;
u.uio_space = curproc->p_addrspace;
// set the position to write to based on the idx
u.uio_offset = sidx * PAGE_SZ;
lock_acquire(swapdisk_bitmap->bm_lk);
VOP_WRITE(swapdisk->bm_vn, uio);
lock_release(swapdisk_bitmap->bm_lk0;
}
/** set memory contents to that of swap space
* @pre assumes there is a page to cpy from swap space to @ppn
* @pre @ppn belongs to the process and is already alloced in the coremap
*
* thread-safe with respect to swapdisk
*/
void
swapcopyin(ppn_t *ppn, spn_t sidx) {
struct uio u;
struct iovec iov;
iov.iov_ubase = ADDR_OF_PN(ppn);
iov.iov_len = PAGE_SZ;
u.uio_iov = &iov;
u.uio_iovcnt = 1;
u.uio_resid = PAGE_SZ; // amount to read from the file
u.uio_segflg = UIO_USERSPACE;
u.uio_rw = UIO_WRITE;
u.uio_space = curproc->p_addrspace;
// set the position to write to based on the idx
u.uio_offset = sidx * PAGE_SZ;
lock_acquire(swapdisk_bitmap->bm_lk);
VOP_READ(swapdisk->bm_vn, uio);
lock_release(swapdisk_bitmap->bm_lk0;
}
/** Finds free n contiguous swap pages in disk
* @return -1 for no space, starting index for most pages
* @post updates swap bitmap to reflect new relationship
*/
spn_t
swapalloc(int npages) {
}
/** Frees npages of swap starting at sidx
* @param[in] sidx starting idx
* @param[in] npages number of pages to free
* @return Does not fail
*/
void
swapdealloc(spn_t sidx, int npages) {
// lock swap space
// free pages and set them
}
This is a thread forked on startup and specified in vm_daemon_thread
.
void
On vm_bootstrap
we need to create a coremap that will keep track of the empty and allocated physical pages for us. The coremap will be allocated at the first few spaces of our vm space which will not be virtually mapped.
In order to find the size of the coremap, we need to check how big our physical space is and get the last physical address by calling
lastpaddr = ram_getsize().
Our VM starts at address 0, so the total number of pages we have is:
NPAGES = lastpaddr/PAGE_SIZE // integer division
Our coremap will occupy the first sizeof(struct coremap_elt) * NPAGES
bytes.
// vm.h
// flag to check whether vm has been initialined, our implementation of kmalloc depends on this
static bool vm_initialized;
/** bootstraps ram
*
*/
static
ram_bootstrap() {
+ vm_initialized = false;
}
// vm.h
/** called after ram_bootstrap
* initializes the coremap
* excludes itself from the mapped pages
* @pre firstpaddr and lastpaddr have not been invalidated
* @post firstpaddr and lastpaddr are invalidated
*/
void vm_bootstrap() {
paddr_t lastpaddr = ram_getsize();
paddr_t firstpaddr = ram_getfirstfree();
unsigned total_pages = (firstpaddr - lastpaddr)/PAGE_SZ;
unsigned coremap->cm_sz = total_pages * PAGE_SZ/(PAGE_SZ + sizeof(cm_elt_t))
// initialize corem
coremap->cm_elts = (cm_elt_t *)(firstaddr);
for (i = 0; i < coremap->cm_sz; ++i)
coremap->cm_elts[i] = {};
vm_initalized = true;
// kmalllc uses the core map from this point
for (int i = 0; i < COREMAP_LKS; ++i)
coremap->cm_lks[i] = lock_create(“cm lock”);
if (coremap->cm_lks[i] == NULL)
panic(“could not create coremap lock”);
}
void swapdisk_bootstrap() {
swapdisk_bitmap_t *swapdisk_bitmap;
// use lhdraw0 disk as the swap space
err = vfs_open(“lhdraw0:”, flags, 0644, &swapdisk_bitmap.vn);
if (err)
panic(“swapdisk_bootstrap failed”);
// get the size of the disk
struct stat s;
VOP_STAT(swapdisk_bitmap->bm_vn, &s);
off_t nbits = s.st_size;
// allocate nbits for the bitmap
swapdisk_bitmap->bm = bitmap_create(nbits);
swapdisk_bitmap->bm_lk = lock_create(“bitmap lock”);
if (“swapdisk_bitmap->bm_lk” == NULL)
panic(“swapdisk_bootstrap failed: cannot create bm lock”);
}
// Helper functions for the swapspace found in swap.c?
set_swaping_idx(unsigned pa_idx, unsigned swaping_idx, struct addrspace as);
unsigned get_swaping_idx(unsigned pa_idx, struct addrspace as);
bool has_swaping_idx(unsigned pa_idx, struct addrspace as);
vm_daemon_thread() {
// updates the daemon idx field in coremap struct
next_daemon_idx(coremap);
lock_acquire(swapdisk_bitmap->bm_lk)
pid_t owner = CME(coremap.daemon_idx).owner
unsigned swaping_idx;
if (!has_swaping_idx(coremap.daemon_idx, procvector->procs[owner]->p_addrspace))
bswaping_idx = swapdisk_find_freeidx();
lock_acquire(procvector->procs[owner]->p_addrspace->pt->pt_lk);
// update swaping idx in addrspace and swaping bit in coremap elt
set_swaping_idx(coremap.daemon_idx, sing_idx, procvector->procs[owner]->p_addrspace);
lock_release(procvector->procs[owner]->p_addrspace->pt->pt_lk);
else
swaping_idx =
get_swaping_idx(coremap.daemon_idx,procvector->procs[owner]->p_addrspace)
// TODO: do proper write with uio objects
VOP_WRITE(swapdisk_bitmap->bm_vn, idx * PAGE_SZ);
lock_release(swapdisk_bitmap->bm_lk)
}
int sys_sbreak(intptr_t amount, int *retval) {
if (amount == 0) {
// simple value return
//*retval = curproc->p_as->break
return 0;
}
//TODO
// get npages we need to add to our heap
// Free or create pages as needed
// if we cant find a new page, ENOMEM (retval gets 1)
// if amount + current val < 0, EINVAL
}
/** Initializes addrspace
*/
struct addrspace *as_create(void) {
// initialize pt_elets array at size USERPRAM_SZ/PAGE_SZ
// create the pt_lk
}
/** Copies addrspaces
* @param[in] src
* @param[in] dst
* @return error
*/
int
as_copy(struct addrspace *src, struct addrspace **ret) {
// grap the addrspace lock of the src
// foreach multipage allocation in the page table of src
// create a page in swapdisk for the new proc
// if failure at any point
// release src lock
as_destroy(ret);
}
/** Invalidates all TLB entries
* @param[in] src
* @param[in] dst
* @return error
*/
void as_activate(void) {
vm_tlbshootdown_all();
}
/** Deactivates current TLB entries
*
*/
void as_deactivate(void) {
}
/** Destroys addrspace
* We should free all proces pages that have not been
* freed in swap bitmap and the coremap.
* @param[in] src
* @param[in] dst
* @return void
*/
void as_destroy(struct addrspace *) {
// for each element in the page table, destroy the element
// free the corresponding swap space
// free the core map
}
/** Set up a segment at virtual address VADDR of size MEMSIZE.
* The segment in memory extends from VADDR up to (but not
* including) VADDR+MEMSIZE.
*
* The READABLE, WRITEABLE, and EXECUTABLE flags are set if
* read write, or execute permission should be set on the
* segment.
* @param[in] as addrespace
* @param[in] vaddr
* @param[in] sz
* @param[in] readable permissions
* @param[in] writeable permissions
* @param[in] executable permissions
*/
int as_define_region(struct addrspace *as,
vaddr_t vaddr, size_t sz,
int readable,
int writeable,
int executable) {
// for every entry in the pagetable, set the write bits
// do not set the
}
/** set the permissions of the addrspace table as read and write to prepare for load
* as_complete_load will restore permissions to whatever they were before
* @param[in] as addrespace
* @return error
*/
int as_prepare_load(struct addrspace *as) {
}
/** restore permissions to whatever they were before as_prepare_load
* @param[in] as addrespace
* @return error
*/
int as_complete_load(struct addrspace *as) {
}
/** Sets the top of the stack
* @param[in] as addrespace
* @param[in,out] initstackptr stack ptr to set
* @return error
*/
int as_define_stack(struct addrspace *as, vaddr_t *initstackptr) {
*initstackptr = USERSTACK;
return 0;
}
Kernel allocations over multiple pages are labeled such that the first page has the big set, and subsequent pages of an allocation have the bit unset.
Consider the following kmalloc calls
...
kmalloc(3*4096);
kmalloc(1*4096);
kmalloc(2*4096);
kmalloc(1*4096);
...
Here are the multipage_start bits of the following contiguous physical pages from
cm_elts[i].tlb_hi:multipage_start
... | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | ...
Do we alloc pages before setting up the VM? We shouldn’t!!!
- What if we have space in an existing page?
- Let’s setup a ref count of objects for each page, we can free when its zero
- we should stuff pages when we can
- Do we start a new page?
- Hopefully not, we want to save space
/** Gets npages from virtual memory
* Never shifts pages around. If there are not n contiguous pages in memory we just return null.
* @return Null
*/
static
paddr_t
getppages(unsigned long npages)
{
paddr_t addr;
if (!vm_initialized) {
spinlock_acquire(&stealmem_lock);
addr = ram_stealmem(npages);
spinlock_release(&stealmem_lock);
} else {
// linear greedy search for contiguous free pages
int pages_left = npages;
int start_idx = 0;
//TOOD: locking, which depends on our coremap implementation
for (int i = 0; i < MAX_PPN; i++ && pages_left > 0; i++) {
if ( CME_ISFREE(CME(i)) ) {
pages_left--;
CME_RESERVE(CME(i));
}
else {
pages_left = npages;
start_idx = i;
}
}
// we did not find enough pages
if (pages_left > 0) {
// ENOMEM?
return NULL;
}
// setup page structure
kalloc_startpage(start_idx);
for (int i = start_idx+1; i < start_idx + npages; i++) {
kalloc_multipage(i);
}
return ADDR_OF_PN(start_idx);
}
return addr;
}
- How do we free multiple pages? Do we keep track of next in our core map?
- we know a multipage allocation is contiguous in virtual memory, we can therefore find out all the pages we need to free
- Mark the page as free
- USE THIS:
as_zero_region
coremap_elt->address_space = NULL
- USE THIS:
- do TLB shootdown
kfree is called on a page with the multipage flag unset, return error
Otherwise, kfree will free the current page and all following pages until the multipage flag is set again
void kfree(void *ptr) {
if (ptr == NULL) {
return;
}
unsigned int ppn = PN_OF_ADDR(ptr);
cm_elt_t cme = CME(ppn);
if (!CME_ISMULTIPAGE(cme)) {
return EINVAL;
}
do {
free_kpages(ppn);
cme_deactivate(cme);
ppn++;
cme = CME(ppn);
} while (CME_ISMULTIPAGE(cme) && ppn <= PPN_MAX);
}
- TLB Hit Rate = # TLB Hits / # TLB Accessses (should be ~98%)
https://www.dropbox.com/developers/core/docs#files_put
error handling page after sbrk should be marked as invalid daemon only writes a page to disk if it’s already there if it’s not there just wait for forced eviction to put it there
when destroying the address space, we should delete from swap when we free we should delete from swap
Assuming that a user program just accessed a piece of data at (virtual) address X, describe the conditions under which each of the following can arise. If the situation cannot happen, answer "impossible" and explain why it cannot occur.
TLB miss, page fault
- user/kernel proc is running and the page requested is not in the TLB and the page is not in memory TLB miss, no page fault
- user/kernel proc is running and the page is still in memory but not in the tlb TLB hit, page fault
- impossible
- if the valid bit is not set then it is not a hit TLB hit, no page fault
- this is the best case, the page is in memory and mapped in the TLB
A friend of yours who foolishly decided not to take 161, but who likes OS/161, implemented a TLB that has room for only one entry, and experienced a bug that caused a user-level instruction to generate a TLB fault infinitely—the instruction never completed executing! Explain how this could happen. (Note that after OS/161 handles an exception, it restarts the instruction that caused the exception.)
- We need to have a page in TLB
- instruction pointer memory needs TLB too
la ($a0), $a1 // int a = *0x8000000;
Does kernel needs a TLB entry?
- No all kernel memory is unmapped in our implmentation
How many memory-related exceptions (i.e., hardware exceptions and other software exceptional conditions) can the following MIPS-like instruction raise? Explain the cause of each exception.
load word from $0 (zero register), offset 0x120, into register $3
lw $3, 0x0120($0)
Exceptions at the MIPS level
#define EX_TLBL 2 /* TLB miss on load */ (permissions)
#define EX_ADEL 4 /* Address error on load */
Exceptions at the OS161 level: segfault (accesses illegal memory)
/* This is bad code: it doesn't do any error-checking */
#include <stdio.h>
int main (int argc, char **argv) {
int i;
void *start, *finish;
void *res[10];
start = sbrk(0);
for (i = 0; i < 10; i++) {
res[i] = malloc(10);
}
finish = sbrk(0);
/* TWO */
return 0;
}
How many times does the system call sbrk() get called from within malloc()?
Once, the first malloc
gets the initial page, then other malloc
calls all use the memory allocated by the first page
On the i386 platform, what is the numeric value of (finish - start)? 4 KB (0x1000)
{
void *x;
free(res[8]); free(res[7]); free(res[6]);
free(res[1]); free(res[3]); free(res[2]);
x = malloc(60); /* MARK */
}
Again on the i386, would malloc() call sbrk() when doing that last allocation at the marked line above? What can you say about x?
No, a page is 4KB, and we are using less than 100 bytes.
We know that res[9] < x < sbrk(0)
Question 3. It is conventional for libc internal functions and variables to be prefaced with "__". Why do you think this is so?
If we use the "__" prefix we can directly access protected variables and write code that requires more privilege. It should be helpful to the programmer.
Question 4. The man page for malloc requires that "the pointer returned must be suitably aligned for use with any data type." How does our implementation of malloc guarantee this?
Currently, we assign a new page each time we call malloc
, even to malloc
1 byte.
Each malloc therefore therefore is 4KB aligned.