In this part of this project, you will implement the non-disk based image called anonymous page.
An anonymous mapping has no backing file or device. It is anonymous because it does not have any named file source (unlike file-backed pages). Anonymous pages are used in executable, such as for stack and heap.
There is a struct to describe an anonymous page -
include/vm/anon.h. It is currently empty, but you may add members to store
necessary information or state of an anonymous page as you implement. Also, see
struct page in
include/vm/page.h, which contains generic information
of a page. Note, for an anonymous page,
struct anon_page anon is included in
its page structure.
Page Initialization with Lazy Loading
Lazy loading is a design where the loading of memory is deferred until the point at which it is needed. A page is allocated, meaning there is page struct corresponding to it, but there is no dedicated physical frame, and the actual content of the page is not yet loaded. The contents will be loaded only at which it is truly needed, which is signaled by a page fault.
Since we have three page types, the initialization routine is different for each
page. Though will be explained again in the sections below, here we provide a
high-level view of the page initialization flow. First,
vm_alloc_page_with_initializer is invoked when the kernel receives a new page
request. The initializer will initialize a new page by allocating a page
structure and setting appropriate initializer depending on its page type, and
return the control back to the user program. As the user program executes, at a
point, a page fault occurs because the program is trying to access a page which
it believes to possess but the page has no contents yet. During the fault
uninit_initialize is invoked and calls the initializer you
set earlier. The initializer will be
anon_initializer for anonymous pages and
file_backed_initializer for file-backed pages.
A page can have a life cycle of
For each transition of the life cycle, the required procedure differs
depending on the page type (or
VM_TYPE), and the previous paragraph
was an example for the initialization. In this project, you will implement
these transition processes for each page type.
Lazy Loading for Executable
In lazy loading, when a process starts it execution, only the memory parts that are immediately needed are loaded onto the main memory. This can reduce the overhead compared to eager loading, which loads all binary image into the memory at once.
To support the lazy loading, we introduce a page type called
include/vm/vm.h. All pages are initially created as
VM_UNINIT pages. We also
provide a page structure for uninitialized pages -
struct uninit_page in
include/vm/uninit.h. The functions for creating, initializing, and destroying
uninitialized pages can be found in
include/vm/uninit.c. You will have to
complete these functions later.
On page fault, the page fault handler (
transfers control to
vm/vm.c, which first checks if
it is a valid page fault. By valid, we mean the fault that accesses invalid.
If it is a bogus fault, you load some contents into the page and return
control to the user program.
There are three cases of bogus page fault: lazy-loaded, swaped-out page, and
write-protected page (See Copy-on-Write (Extra)). For now, just
consider the first case, lazy-loaded page. If it is a page fault for lazy
loading, the kernel calls one of the initializers you previously set in
vm_alloc_page_with_initializer to lazy load the segment. You will have to
You should fetch an appropriate initializer according to the passed vm_type and
uninit_new with it.
bool vm_alloc_page_with_initializer (enum vm_type type, void *va,
bool writable, vm_initializer *init, void *aux);
Create an uninitialized page with the given type. The swap_in handler of uninit page automatically initializes the page according to the type, and calls INIT with given AUX. Once you have the page struct, insert the page into the process's supplementary page table. Using
VM_TYPEmacro defined in
vm.hcan be handy.
The page fault handler follows its call chain, and finally reaches
uninit_intialize when it calls swap_in. We gives the complete implementation
for it. Although, you may need to modify the
uninit_initialize according to
static bool uninit_initialize (struct page *page, void *kva);
Initializes the page on the first fault. The template code first fetches
auxand calls the corresponding page_initializer through a function pointer. You may need to modify the function depending on your design.
You may modify
to your needs.
void vm_anon_init (void);
Initialize for anonymous page subsystem. In this function, you can setup anything related to the anonymous page.
bool anon_initializer (struct page *page,enum vm_type type, void *kva);
The function first sets up the handlers for the anonymous page in
page->operations. You might need to update some information in
anon_page, which is currently an empty struct. This function is used as initializer for anonymous pages (i.e.
Implement segment loading from executables. All of these pages should be
loaded lazily, that is, only as the kernel intercepts page faults for them.
You'll need to modify the core of the program loader, which is the loop in
userprog/process.c. Each time around the loop, it makes a
vm_alloc_page_with_initializer to create a pending page object.
When a page fault occurs, this is when the segment is actually loaded from the
static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable);
Current code calculates the number of bytes to read from a file and the number of bytes to fill with zeros within the main loop. Then, it calls
vm_alloc_page_with_initializerto create a pending object. You need to set up the auxiliary values as
auxargument that you will provide to
vm_alloc_page_with_initializer. You may want to create a structure that contains necessary information for the loading of binary.
static bool lazy_load_segment (struct page *page, void *aux);
You may have noticed that
lazy_load_segmentis supplied as the fourth argument of
load_segment. This function is the initializer for executable's pages and is invoked in times of page faults. It receives a page struct and
auxis the information you set up in
load_segment. Using this information, you have to find the file to read the segment from and eventually read the segment into memory.
You should adjust the
userprog/process.c to fit stack
allocation into the new memory management system. The first stack page need not be
allocated lazily. You can allocate and initialize it with the command line
arguments at load time, with no need to wait for it to be faulted in. You might
need to provide the way to identify the stack. You can use the auxillary markers
VM_MARKER_0) to mark the page.
vm_try_handle_fault function to resolve the page struct
corresponding to the faulted address by consulting to the supplemental page
After implementing all the requirements, all of the tests in project 2 except fork should be passed.
Supplemental Page Table - Revisit
Now, we revisit the supplemental page table interface to support copy and clean up operations. These operations are needed when creating (more specifically generating a child process) or destroying a process. The details are elaborated below. The reason we are revisiting the supplemental page table at this point is because you may want to use some of the initializing functions you implemented from above.
bool supplemental_page_table_copy (struct supplemental_page_table *dst,
struct supplemental_page_table *src);
Copies the supplemental page table from src to dst. This is used when a child needs to inherit the execution context of its parent (i.e.
fork()). Iterate through each page in the src's supplemental page table and make a exact copy of the entry in the dst's supplemental page table. You will need to allocate uninit page and claim them immediately.
void supplemental_page_table_kill (struct supplemental_page_table *spt);
Frees all the resources that were held by a supplemental page table. This function is called when a process exits (
userprog/process.c). You need to iterate through the page entries and call
destroy(page)for the pages in the table. You do not need to worry about the actual page table (pml4) and the physical memory (palloc-ed memory) in this function; the caller cleans them after the supplemental page table is cleaned up.
vm/anon.c. This is handler for
destroy operation on uninitialized page.
Even though uninitialized pages are transmuted to the other page objects, there
still can be uninit page when the process exits.
static void uninit_destroy (struct page *page);
Frees the resource that was held by page struct. You might want to check the vm type of the page and handle accordingly.
For now, you can only handle the anonymous pages. You will later revisit this function to clean up the file-backed pages.
static void anon_destroy (struct page *page);
Frees the resource that was held by the anonymous page. You do not need to explicitly free the page struct, the caller should do it.
Now all of the tests in project 2 should be passed.