
struct page는 만들어 둔다.VM_UNINIT 페이지로 분류한다.
load_segment 수정하기// 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) {
// 생략
// 페이지 단위로 vm_alloc_page_with_initializer 호출.
while (read_bytes > 0 || zero_bytes > 0) {
// 현재 페이지에서, 파일에서 읽을 바이트 수 / 0으로 채울 바이트 수
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
// [구현 A] lazy_load_segment에서 파일 로딩할 때 필요한 정보를, 구조체로 묶어 aux로 보내기
void *aux = NULL;
if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux))
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
// [구현 A]
}
return true;
}
가상주소 upage에 file의 ofs부터 시작하는 세그먼트를 로드.
read_bytes: upage부터 read_bytes만큼 file의 ofs에서 로드한다. (실제 파일 읽기)zero_bytes: upage + read_bytes부터 zero_bytes`만큼 0으로 채운다. (zero padding)writable이 true면 읽기/쓰기 가능, false면 읽기 전용
load_segment는 페이지 단위로, vm_alloc_page_with_initializer를 반복 호출
VM_ANON: anonymous page로 설정upage: 가상주소 upage에 해당하는 페이지를 초기화writable: 초기화할 페이지의 쓰기 가능여부 설정lazy_load_segment, aux: 이후 page fault가 발생하면 lazy_load_segment에서 파일 로드 시도. 이때 필요한 정보를 aux로 전달해야 함!![구현 A] lazy_load_segment에 파일 관련 정보 보내기
struct file *file 필요off_t ofs 필요ofs += PGSIZE로 갱신해야 할듯page_read_bytes, page_zero_bytes 필요.vm_alloc_page_with_initializer 수정하기// vm/vm.c
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux) {
struct supplemental_page_table *spt = &thread_current()->spt;
// 현재 SPT에 존재하지 않는 페이지일 경우
if (spt_find_page(spt, upage) == NULL) {
// [구현 B-1] struct page 할당 -> malloc OR calloc
// [구현 B-2] uninit_new에 알맞은 매개변수 보내, struct page 필드 초기화
// !!이때 vm_type에 따라 알맞은 initializer를 전달해야 함에 유의!!
// [구현 B-3] SPT에 struct page 삽입 -> spt_insert_page
}
err:
return false;
}
(1) struct page 생성 -> malloc
(2) uninit_new 호출해, 생성한 struct page의 필드 초기화
void uninit_new(struct page *page, void *va, vm_initializer *init, enum vm_type type, void *aux, bool (*initializer)(struct page *, enum vm_type, void *))page: (1)에서 생성한 struct pageva: 매개변수 upage, 즉 가상 주소를 전달init, type, aux: 기존 매개변수 init, type, aux 그대로 전달initializer: VM_TYPE(vm_type) == VM_ANON이면 anon_initializer, VM_TYPE(vm_type) == VM_FILE이면 file_backed_initializer. VM_TYPE 함수를 쓰는 이유는, type 매개변수를 VM_ANON | VM_MARKER_0 식으로 설정할 때도 있기 때문. 이때 VM_ANON만 끄집어내는 역할.uninit_new 밖에서 초기화.cf. 이후 uninit_new에선, page의 필드는 아래와 같이 초기화됨
load_segment에서 호출했을 때 기준)// vm/uninit.c 내 uninit_new 함수
// uninit_new는 struct page의 field를 초기화함.
*page = (struct page){.operations = &uninit_ops,
.va = va, // upage
.frame = NULL, // 당연히 지금은 아직 물리 프레임을 할당 안 했으니까?
.uninit = (struct uninit_page){
.init = init, // lazy_load_segment
.type = type, // VM_ANON
.aux = aux, // 파일 정보 구조체
.page_initializer = initializer, // anon_initializer
}
};
(3) uninit_new에서 초기화하지 못한 필드가 있는 경우, 초기화
struct page에 writable 필드를 만들어 두고 사용해야 하려나 싶음. 얘만 버려진 상황이라.(4) SPT에 struct page 삽입 -> spt_insert_page.
도중에 실패 시 goto err

page_fault// userprog/exception.c
static void page_fault(struct intr_frame *f) {
/* fault가 발생한 가상주소 */
fault_addr = (void *) rcr2();
/* Turn interrupts back on (they were only off so that we could
be assured of reading CR2 before it changed). */
intr_enable();
if ((f->error_code & PF_U) != 0) {
// [구현 C] 강제 종료 시키면 절 대 안 됨
exit(-1);
}
/* Determine cause. */
not_present = (f->error_code & PF_P) == 0;
write = (f->error_code & PF_W) != 0;
user = (f->error_code & PF_U) != 0;
#ifdef VM
/* For project 3 and later. */
if (vm_try_handle_fault(f, fault_addr, user, write, not_present))
return;
#endif
/* Count page faults. */
page_fault_cnt++;
/* If the fault is true fault, show info and exit. */
printf("Page fault at %p: %s error %s page in %s context.\n", fault_addr,
not_present ? "not present" : "rights violation", write ? "writing" : "reading",
user ? "user" : "kernel");
kill(f);
}
exit(-1) 없애주기!!!vm_try_handle_fault를 호출해, 해결 시도. 매개변수로 다음 값 전달.fault_addr: page fault가 발생한 가상주소not_present: 메모리에 존재하지 않는 페이지 -> 1. 메모리에 존재하는 페이지 -> 0. (spt 아니라 ㄹㅇ 물리메모리 적재 여부.)write: 쓰기 -> 1. 읽기. -> 0.user: 사용자 모드 -> 1, 커널 모드 -> 0.vm_try_handle_fault 성공 시 true. 실패 시 false. true일시 바로 반환.vm_try_handle_fault 수정true, 실패하면 false 반환.false 반환!// vm/vm.c
// 페이지 폴트 핸들러
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;
struct page *page = NULL;
// [구현 D] 비정상적인 page fault이면, 바로 false 반환하기
return vm_do_claim_page(page);
}
user == true이며 is_kernel_vaddr(addr) == true 인 경우 (A) - 사용자모드일 때 커널영역의 주소에 접근시도
not_present == false, write == true인 경우 (B) - 읽기 전용 영역에 쓰기 시도
SPT에서 addr에 대응되는 struct page 검색 -> spt_find_page
NULL 반환. -> (C) 사용자 영역의 invalid 주소에 접근 시도page에 저장.위 세 경우에 해당하지 않는 경우, 정상적인 page fault.
vm_do_claim_page(page)에서 진행됨.위 세 경우에 해당되는 경우, process terminate
return false 해주면, page_fault에서 알아서 kill해줄 거임.vm_do_claim_page 수정true, 실패하면 false 반환.// vm/vm.c
struct frame *frame = vm_get_frame();
/* Set links */
frame->page = page;
page->frame = frame;
vm_get_frame으로 프레임 할당, struct frame 생성pml4_set_page(thread_current()->pml4, page->va, frame->kva, 1)
pml4_set_page로 페이지와 프레임 매핑1로 고정되어 있음.struct frame에 저장해 둔 writable 사용해, 읽기/쓰기 여부 저장해 두면 될듯...return swap_in(page, frame->kva);
swap_in(page, frame->kva)???swap_in은 사실 uninit_initialise를 호출.uninit_new에서 struct page의 operations 필드를 &uninit_ops로 설정함// vm/uninit.c
static const struct page_operations uninit_ops = {
.swap_in = uninit_initialize,
.swap_out = NULL,
.destroy = uninit_destroy,
.type = VM_UNINIT,
};
swap_in은 uninit_initialize.uninit_initializeuninit_new에서 struct page에 설정한 필드 값들을 참고하여, 페이지 초기화를 진행하는 함수.true, 실패하면 false 반환.// vm/uninit.c
// struct page의 필드값을 참고하여, 페이지 초기화 함수들을 호출.
static bool uninit_initialize(struct page *page, void *kva) {
struct uninit_page *uninit = &page->uninit;
// 주소값을 미리 왜 저장해 주냐면, uninit->page_initializer에서 값이 사라질 수 있기 때문임.
vm_initializer *init = uninit->init;
void *aux = uninit->aux;
/* TODO: You may need to fix this function. */
// 라고는 하는데, 잘은 모르겠음.
return uninit->page_initializer(page, uninit->type, kva) && (init ? init(page, aux) : true);
}
struct uninit_page *uninit// uninit_new에서 이렇게 바꿨었죠
.uninit = (struct uninit_page){
.init = init, // lazy_load_segment
.type = type, // VM_ANON
.aux = aux, // 파일 정보 구조체
.page_initializer = initializer, // anon_initializer
}
init을 vm_initializer *init에 저장load_segment에서 페이지 할당을 한 경우, lazy_load_segmentaux를 void *aux에 저장load_segment에서 페이지 할당을 한 경우, 앞서 본 파일 구조체load_segment에서 페이지 할당한 경우 기준으로uninit->page_initializer엔 anon_initializer가 담김.page(가상페이지 주소), uninit->type(VM_ANON), kva(물리프레임 주소)를 보냄.init에는 lazy_load_segment가 담김.page(가상페이지 주소)와 aux(파일 구조체)를 보냄.anon_initializertrue, 실패하면 false 반환.// uninitialized page -> anonymous page
bool anon_initializer(struct page *page, enum vm_type type, void *kva) {
// operations 필드를, anonymous page 용도의 operations로 변경.
page->operations = &anon_ops;
struct anon_page *anon_page = &page->anon;
// [구현 F] 성공/실패 여부에 따라 반환값 저장
}
struct anon_page엔 아무 필드도 없음. 여기에 어떤 필드를 넣어야 하는지는 솔직히 모르겠음.return true만 있어도 되지 않을까? 나중에 고쳐보자lazy_load_segmentload_segment에서 만든 파일 정보 구조체를 사용하게 됨! 우와!true, 실패하면 false 반환.static bool lazy_load_segment(struct page *page, void *aux) {
// [구현 G] aux 내 파일 정보를 사용하여, page에 해당하는 주소에 로딩할 것.
}
page에 aux의 파일 정보를 이용하여 로딩.struct page의 struct frame의 kva 멤버로 주소를 구할 수 있을 듯함.file_read_at 사용을 추천하고 있음.// filesys/file.c
off_t file_read_at(struct file *file, void *buffer, off_t size, off_t file_ofs)
file 의 file_ofs 위치부터 size만큼 buffer로 읽는다.
page_read_bytes) 모두 aux에 저장되어 있을 것. 활용하면 됨buffer는 앞서 구한 프레임 주소로 하면 될듯.file_read_at은 읽은 바이트 수를 반환. page_read_bytes와 동일한지 아닌지로 성공, 실패 여부 확인 가능이후 zero padding이 필요한 경우, 뒷부분은 0으로 채워야 함.
프레임주소 + page_read_bytes부터 page_zero_bytes만큼 0으로 채우면 됨. memset 사용하도록 하자.true 실패 시 false 반환 함수.setup_stack은 원래는 palloc_get_page를 사용해서 바로 페이지 영역을 할당받음.setup_stack 변경하기// userprog/process.c
// VM 미사용 시 setup_stack
static bool setup_stack(struct intr_frame *if_) {
uint8_t *kpage;
bool success = false;
kpage = palloc_get_page(PAL_USER | PAL_ZERO);
if (kpage != NULL) {
success = install_page(((uint8_t *)USER_STACK) - PGSIZE, kpage, true);
if (success)
if_->rsp = USER_STACK;
else
palloc_free_page(kpage);
}
return success;
}
// VM 사용 시 setup_stack
static bool setup_stack(struct intr_frame *if_) {
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
// [구현 H-1] stack_bottom에 스택 할당 요청
// [구현 H-2] 이후 바로 page claim
// [구현 H-3] 스택포인터 위치 변경
/* 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 */
return success;
}
vm_alloc_page_with_initializer로 할당 요청 보내기VM_MARKER_0을 사용하라고 함.type -> VM_ANON | VM_MARKER_0??? upage -> stack_bottom??? writable -> true, init -> NULL(파일 로드할거 아니니까), aux -> NULL???vm_claim_page 사용하면 될듯?if_->rsp를 USER_STACK으로 설정fork 계열 빼고 통과한다고 함.