180 lines
6.0 KiB
C
180 lines
6.0 KiB
C
#include <debug.h>
|
|
#include <hash.h>
|
|
#include <list.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
}
|
|
|