From ea2725f60636ac7c678e91cc3417e9fd35a7b5fc Mon Sep 17 00:00:00 2001 From: Themis Demetriades Date: Tue, 26 Nov 2024 15:17:11 +0000 Subject: [PATCH] feat: implement frame table without thread safety --- src/Makefile.build | 1 + src/threads/init.c | 4 ++ src/userprog/process.c | 42 +++++++++++-- src/vm/frame.c | 137 +++++++++++++++++++++++++++++++++++++++++ src/vm/frame.h | 10 +++ 5 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 src/vm/frame.c create mode 100644 src/vm/frame.h diff --git a/src/Makefile.build b/src/Makefile.build index c0d535e..4e57a13 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -62,6 +62,7 @@ userprog_SRC += userprog/gdt.c # GDT initialization. userprog_SRC += userprog/tss.c # TSS management. # Virtual memory code. +vm_SRC += vm/frame.c # Frame table manager. vm_SRC += devices/swap.c # Swap block manager. #vm_SRC = vm/file.c # Some other file. diff --git a/src/threads/init.c b/src/threads/init.c index 2a93d4f..69ac6af 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -32,6 +32,7 @@ #include "tests/threads/tests.h" #endif #ifdef VM +#include "vm/frame.h" #include "devices/swap.h" #endif #ifdef FILESYS @@ -101,6 +102,9 @@ main (void) palloc_init (user_page_limit); malloc_init (); paging_init (); +#ifdef VM + frame_init (); +#endif /* Segmentation. */ #ifdef USERPROG diff --git a/src/userprog/process.c b/src/userprog/process.c index 1f56875..a8f1d10 100644 --- a/src/userprog/process.c +++ b/src/userprog/process.c @@ -24,6 +24,9 @@ #include "threads/vaddr.h" #include "threads/synch.h" #include "devices/timer.h" +#ifdef VM +#include "vm/frame.h" +#endif /* Defines the native number of bytes processed by the processor (for the purposes of alignment). */ @@ -113,7 +116,10 @@ process_execute (const char *cmd) return tid; } +static void *get_usr_kpage (enum palloc_flags flags); +static void free_usr_kpage (void *kpage); static bool install_page (void *upage, void *kpage, bool writable); + static bool process_init_stack (char *cmd_saveptr, void **esp, char *file_name); static void *push_to_stack (void **esp, void *data, size_t data_size); #define push_var_to_stack(esp, var) (push_to_stack (esp, &var, sizeof (var))) @@ -253,7 +259,7 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name) /* Allocate the pages and map them to the user process. */ for (int i = 1; i < pages_needed + 1; i++) { - uint8_t *kpage = palloc_get_page (PAL_USER | PAL_ZERO); + uint8_t *kpage = get_usr_kpage (PAL_ZERO); if (!install_page (((uint8_t *) PHYS_BASE) - PGSIZE * (i + 1), kpage, true)) return false; @@ -704,7 +710,7 @@ load_segment (struct file *file, off_t ofs, uint8_t *upage, if (kpage == NULL){ /* Get a new page of memory. */ - kpage = palloc_get_page (PAL_USER); + kpage = get_usr_kpage (0); if (kpage == NULL){ return false; } @@ -712,7 +718,7 @@ load_segment (struct file *file, off_t ofs, uint8_t *upage, /* Add the page to the process's address space. */ if (!install_page (upage, kpage, writable)) { - palloc_free_page (kpage); + free_usr_kpage (kpage); return false; } @@ -747,18 +753,44 @@ setup_stack (void **esp) uint8_t *kpage; bool success = false; - kpage = palloc_get_page (PAL_USER | PAL_ZERO); + kpage = get_usr_kpage (PAL_ZERO); if (kpage != NULL) { success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage, true); if (success) *esp = PHYS_BASE; else - palloc_free_page (kpage); + free_usr_kpage (kpage); } return success; } +/* Claims a page from the user pool and returns its kernel address, + updating the frame table if VM is enabled. */ +static void * +get_usr_kpage (enum palloc_flags flags) +{ + void *page; +#ifdef VM + page = frame_alloc (flags); +#else + page = palloc_get_page (flags | PAL_USER); +#endif + return page; +} + +/* Frees a page belonging to a user process given its kernel address, + updating the frame table if VM is enabled. */ +static void +free_usr_kpage (void *kpage) +{ +#ifdef VM + frame_free (kpage); +#else + palloc_free_page (kpage); +#endif +} + /* Adds a mapping from user virtual address UPAGE to kernel virtual address KPAGE to the page table. If WRITABLE is true, the user process may modify the page; diff --git a/src/vm/frame.c b/src/vm/frame.c new file mode 100644 index 0000000..b030c59 --- /dev/null +++ b/src/vm/frame.c @@ -0,0 +1,137 @@ +#include +#include +#include + +#include "frame.h" +#include "threads/malloc.h" +#include "threads/synch.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. */ +/* Ensures mutual exclusion to accessing the 'head' and first element of + 'inactive_list', which is accessed every time a frame is allocated. */ +struct lock inactive_head_lock; + +struct frame_metadata + { + void *frame; /* The kernel virtual address holding 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; + +/* 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_head_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) +{ + flags |= PAL_USER; + + void *frame = palloc_get_page (flags); + if (frame == NULL) + { + /* TODO: Find victim page to replace, and swap it with this new page. */ + return NULL; + } + + struct frame_metadata *frame_metadata = + malloc (sizeof (struct frame_metadata)); + frame_metadata->frame = frame; + + /* Newly faulted pages begin at the head of the inactive list. */ + lock_acquire (&inactive_head_lock); + list_push_front (&inactive_list, &frame_metadata->list_elem); + lock_release (&inactive_head_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); + } + +/* 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; + } + diff --git a/src/vm/frame.h b/src/vm/frame.h new file mode 100644 index 0000000..8e52ec2 --- /dev/null +++ b/src/vm/frame.h @@ -0,0 +1,10 @@ +#ifndef VM_FRAME_H +#define VM_FRAME_H + +#include "threads/palloc.h" + +void frame_init (void); +void *frame_alloc (enum palloc_flags); +void frame_free (void *frame); + +#endif /* vm/frame.h */