#include #include #include #include #include "frame.h" #include "page.h" #include "threads/malloc.h" #include "threads/vaddr.h" #include "threads/synch.h" #include "devices/swap.h" /* Hash table that maps every active frame's kernel virtual address to its corresponding 'frame_metadata'.*/ struct hash frame_table; /* Linked list of frame_metadata whose pages are predicted to currently be in the working set of a process. They are not considered for eviction, but are considered for demotion to the 'inactive' list. */ struct list active_list; /* Linked list of frame_metadata whose pages are predicted to leave the working set of their processes soon, so are considered for eviction. Pages are considered for eviction from the tail end, and are initially demoted to 'inactive' at the head. */ struct list inactive_list; /* Synchronisation variables. */ /* Protects access to the 'inactive' list. */ struct lock inactive_lock; struct frame_metadata { void *frame; /* The kernel virtual address holding the frame. */ void *upage; /* The user virtual address pointing to the frame. */ struct thread *owner; /* Pointer to the thread that owns the frame. */ struct hash_elem hash_elem; /* Tracks the position of the frame metadata within 'frame_table', whose key is the kernel virtual address of the frame. */ struct list_elem list_elem; /* Tracks the position of the frame metadata in either the 'active' or 'inactive' list, so a victim can be chosen for eviction. */ }; hash_hash_func frame_metadata_hash; hash_less_func frame_metadata_less; static struct frame_metadata *get_victim (void); /* Initialize the frame system by initializing the frame (hash) table with the frame_metadata hashing and comparison functions, as well as initializing the active & inactive lists. Also initializes the system's synchronisation primitives. */ void frame_init (void) { hash_init (&frame_table, frame_metadata_hash, frame_metadata_less, NULL); list_init (&active_list); list_init (&inactive_list); lock_init (&inactive_lock); } /* Attempt to allocate a frame for a user process, either by direct allocation of a user page if there is sufficient RAM, or by evicting a currently active page if memory allocated for user processes is fulled and storing it in swap. If swap is full in the former case, panic the kernel. */ void * frame_alloc (enum palloc_flags flags, void *upage, struct thread *owner) { flags |= PAL_USER; void *frame = palloc_get_page (flags); /* If a frame couldn't be allocated we must be out of main memory. Thus, obtain a victim page to replace with our page, and swap the victim into disk. */ if (frame == NULL) { /* TODO: Deal with race condition wherein a page may be evicted in one thread while it's in the middle of being evicted in another. */ struct frame_metadata *victim = get_victim (); if (victim == NULL) return NULL; size_t swap_slot = swap_out (victim->frame); page_set_swap (victim->owner, victim->upage, swap_slot); /* If zero flag is set, zero out the victim page. */ if (flags & PAL_ZERO) memset (victim->frame, 0, PGSIZE); frame = victim->frame; } struct frame_metadata *frame_metadata = malloc (sizeof (struct frame_metadata)); frame_metadata->frame = frame; frame_metadata->upage = upage; frame_metadata->owner = owner; /* Newly faulted pages begin at the head of the inactive list. */ lock_acquire (&inactive_lock); list_push_front (&inactive_list, &frame_metadata->list_elem); lock_release (&inactive_lock); /* Finally, insert frame metadata within the frame table, with the key as its allocated kernel address. */ hash_replace (&frame_table, &frame_metadata->hash_elem); return frame; } /* Attempt to deallocate a frame for a user process by removing it from the frame table as well as active/inactive list, and freeing the underlying page memory. Panics if the frame isn't active in memory. */ void frame_free (void *frame) { struct frame_metadata key_metadata; key_metadata.frame = frame; struct hash_elem *e = hash_delete (&frame_table, &key_metadata.hash_elem); if (e == NULL) PANIC ("Attempted to free a frame without a corresponding " "kernel address!\n"); struct frame_metadata *frame_metadata = hash_entry (e, struct frame_metadata, hash_elem); list_remove (&frame_metadata->list_elem); free (frame_metadata); palloc_free_page (frame); } /* Obtain a pointer to the metadata of the frame we should evict next. */ static struct frame_metadata * get_victim (void) { lock_acquire (&inactive_lock); if (list_empty (&inactive_list)) { return NULL; } else { struct list_elem *victim_elem = list_pop_back (&inactive_list); lock_release (&inactive_lock); return list_entry (victim_elem, struct frame_metadata, list_elem); } } /* Hash function for frame metadata, used for storing entries in the frame table. */ unsigned frame_metadata_hash (const struct hash_elem *e, void *aux UNUSED) { struct frame_metadata *frame_metadata = hash_entry (e, struct frame_metadata, hash_elem); return hash_bytes (&frame_metadata->frame, sizeof (frame_metadata->frame)); } /* 'less_func' comparison function for frame metadata, used for comparing the keys of the frame table. Returns true iff the kernel virtual address of the first frame is less than that of the second frame. */ bool frame_metadata_less (const struct hash_elem *a_, const struct hash_elem *b_, void *aux UNUSED) { struct frame_metadata *a = hash_entry (a_, struct frame_metadata, hash_elem); struct frame_metadata *b = hash_entry (b_, struct frame_metadata, hash_elem); return a->frame < b->frame; }