<vm.c?>
/* Return true on success */
bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED,
bool not_present UNUSED) {
struct supplemental_page_table *spt UNUSED = &thread_current()->spt;
if (spt == NULL) return false;
return vm_claim_page(addr);
}
이렇게 vm_claim_page(addr); 하면 해당 함수에서 spt에서 va에 해당하는 page 찾아서 return vm_do_claim_page(page);
이렇게 vm_do_claim_page함수를 호출한다.
Anonymous page 구현 들어가기 전에 page 초기화 흐름을 살펴보자
출처: 링크텍스트
anonymous page 시작해보자.
bool
vm_alloc_page_with_initializer (enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux) {
ASSERT (VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current ()->spt;
/* Check wheter the upage is already occupied or not. */
if (spt_find_page (spt, upage) == NULL) {
struct page *p = (struct page *)malloc(sizeof(struct page));
bool (*page_initializer)(struct page *, enum vm_type, void *);
switch (VM_TYPE(type))
{
case VM_ANON:
page_initializer = anon_initializer;
break;
case VM_FILE:
page_initializer = file_backed_initializer;
break;
}
uninit_new(p, upage, init, type, aux, page_initializer);
p->writable=writable; // 순서 중요. 필드 수정을 uninit_new 이후에 해야함.
return spt_insert_page(spt, p);
err:
return false;
}
inside of the load function(fuc loads the executable file to the current thread) when the process is executed.
upage
.Need a structure that includes the information for the loading to write load_segment.
"include/vm/file.h"
struct lazy_load_arg {
struct file* file;
uint32_t read_bytes;
uint32_t zero_bytes;
off_t ofs;
void *open_addr;
void *close_addr;
};
"userprog/process.c"
static bool load_segment(struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes,
bool writable) {
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(upage) == 0);
ASSERT(ofs % PGSIZE == 0);
while (read_bytes > 0 || zero_bytes > 0) {
/* Do calculate how to fill this page.
* We will read PAGE_READ_BYTES bytes from FILE
* and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
struct lazy_load_arg *lazy_load_arg =
(struct lazy_load_arg *)malloc(sizeof(struct lazy_load_arg));
lazy_load_arg->file = file;
lazy_load_arg->ofs = ofs; // location will start to read
lazy_load_arg->read_bytes = page_read_bytes; // bytes the page need to read
lazy_load_arg->zero_bytes = page_zero_bytes; // bytes need to be filled with 0
void *aux = NULL;
if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable,
lazy_load_segment, aux))
return false;
/* Advance. update for the repeat */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
When the first page fault is called
lazy_load_arg
, which is the information that needs to find the file static bool lazy_load_segment(struct page *page, void *aux) {
void *kva_ = page->frame->kva;
struct lazy_load_arg *lla = (struct lazy_load_arg *)aux;
// set ofs as file's position
file_seek(lla->file, lla->ofs);
// reading(loading) the content to the physical frame
if (file_read(lla->file, kva_, lla->read_bytes) != (int)(lla->read_bytes)) {
palloc_free_page(kva_);
return false;
}
memset(kva_ + lla->read_bytes, 0, lla->zero_bytes);
return true;
}
Inside of load function when the process is running
Creates a page of stack at the point downwards as PGSIZE from USER_STACK where the stack's start point.
Note that the lazy loading is not necessary for the first stack page. Becuase when the process is executed, this address is accessed right away after this function is called in order to add commmand line args
to the stack.
Therefore, we don't have to wait for the fault, but get a physical frame right away.
When a page is allocated, suppliment marker can be used to indicate the page is stack page by using VM_MARKER_0
with the type.
Lastly, change the rsp to USER_STACK
so that the argument_stack
function can push the args from the rsp.
"userprog/process.c"
static bool setup_stack(struct intr_frame *if_) {
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
/* TODO: Map the stack on stack_bottom and claim the page immediately.
* TODO: If success, set the rsp accordingly.
* TODO: You should mark the page is stack. */
/* TODO: Your code goes here */
if(vm_alloc_page(VM_ANON|VM_MARKER_0, stack_bottom, 1)) {
// map the physical frame to the allocated page.
success = vm_claim_page(stack_bottom);
if (success) {
if_->rsp = USER_STACK;
}
}
return success;
}
when the page fault is occured, this function gets the control.
이 함수에서 할당된 물리 프레임이 존재하지 않아서 발생한 예외일 경우에는 매개변수인 not_present에 true를 전달받는다.
그럴 경우, SPT에서 해당 주소에 해당하는 페이지가 있는지 확인해서 존재한다면 해당 페이지에 물리 프레임 할당을 요청하는 vm_do_claim_page 함수를 호출한다.
이 함수에서는 위에서 설정한대로 각 페이지에 맞는 초기화 함수가 호출된다.
not_present 값이 false인 상황을 살펴보면,
물리 프레임이 할당되어 있지만 page fault가 일어난 것이므로 그 경우는 read-only page에 write를 한 경우가 된다. 따라서 not_present가 false인 경우는 예외로 처리하면 된다. (return false)
not_present가 true인 경우에도, read-only page에 write 요청을 할 경우가 생길 수 있으므로 이에 대한 예외 처리를 추가한다.
/* Return true on success */
bool vm_try_handle_fault(struct intr_frame * f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED,
bool not_present UNUSED) {
struct supplemental_page_table *spt UNUSED = &thread_current()->spt;
if (addr == NULL | is_kernel_vaddr(addr)) return false;
// if the physical page doesn't exit
if (not_present) {
page = spt_find_page(spt, addr);
if (page == NULL) return false;
if (write == 1 &&
page->writable ==
0) { // if it's asking to write in unwritable page
return false;
}
return vm_do_claim_page(page);
}
return false;
}