Anonymous Page
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 - anon_page
in
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
the 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
handling procedure, 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
initialize->(page_fault->lazy-load->swap-in>swap-out->...)->destroy.
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 VM_UNINIT
in
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 (page_fault
in userprog/exception.c
)
transfers control to vm_try_handle_fault
in 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
implement lazy_load_segment
in userprog/process.c
.
Implement vm_alloc_page_with_initializer()
.
You should fetch an appropriate initializer according to the passed vm_type and
call 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_TYPE
macro defined invm.h
can 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
your design.
static bool uninit_initialize (struct page *page, void *kva);
Initializes the page on the first fault. The template code first fetches
vm_initializer
andaux
and calls the corresponding page_initializer through a function pointer. You may need to modify the function depending on your design.
You may modify vm_anon_init
and anon_initializer
in vm/anon.c
according
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 inanon_page
, which is currently an empty struct. This function is used as initializer for anonymous pages (i.e.VM_ANON
).
Implement load_segment
and lazy_load_segment
in userprog/process.c
.
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
load_segment
of userprog/process.c
. Each time around the loop, it makes a
call to 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
file.
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_initializer
to create a pending object. You need to set up the auxiliary values asaux
argument that you will provide tovm_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_segment
is supplied as the fourth argument ofvm_alloc_page_with_initializer
inload_segment
. This function is the initializer for executable's pages and is invoked in times of page faults. It receives a page struct andaux
as arguments.aux
is the information you set up inload_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 setup_stack
in 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
in vm_type
of vm/vm.h
(e.g. VM_MARKER_0
) to mark the page.
Finally, modify vm_try_handle_fault
function to resolve the page struct
corresponding to the faulted address by consulting to the supplemental page
table through spt_find_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.
Implement supplemental_page_table_copy
and supplemental_page_table_kill
in
vm/vm.c
.
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 (
process_exit()
inuserprog/process.c
). You need to iterate through the page entries and calldestroy(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.
Page Cleanup
Implement uninit_destroy
in vm/uninit.c
and anon_destroy
in
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.