vm_alloc_page_with_initializer()를 구현하세요. 전달된 vm_type에 따라 적절한 초기화 프로그램을 가져와서 uninit_new를 호출해야 합니다.
bool vm_alloc_page_with_initializer (enum vm_type type, void *va,
bool writable, vm_initializer *init, void *aux);
주어진 유형의 초기화되지 않은 페이지를 생성합니다. 초기화되지 않은 페이지의 swap_in 핸들러는 자동으로 유형에 따라 페이지를 초기화하고 주어진 AUX로 INIT을 호출합니다. 페이지 구조체를 획득한 후에는 해당 페이지를 프로세스의 보충 페이지 테이블에 삽입합니다. vm.h에서 정의된 VM_TYPE 매크로를 사용하는 것이 편리할 수 있습니다.
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)
{
/* TODO: Create the page, fetch the initialier according to the VM type,
* TODO: and then create "uninit" page struct by calling uninit_new. You
* TODO: should modify the field after calling the uninit_new. */
struct page *pg = (struct page *)malloc(sizeof(struct page)); // 페이지 구조체를 생성
if (pg == NULL)
goto err;
void *va_rounded = pg_round_down(upage); // upage로 페이지의 시작 주소 추출
switch (VM_TYPE(type))
{
case VM_ANON:
uninit_new(pg, va_rounded, init, type, aux, anon_initializer); // 익명 페이지로 형태 생성
break;
case VM_FILE:
uninit_new(pg, va_rounded, init, type, aux, file_backed_initializer); // 파일 기반 페이지로 형태 생성
break;
default:
NOT_REACHED();
break;
}
페이지 폴트 핸들러는 호출 체인을 따라가며 최종적으로 swap_in을 호출할 때 uninit_initialize에 도달합니다. 해당 함수에 대한 완전한 구현을 제공하지만, 디자인에 따라 uninit_initialize를 수정해야 할 수 있습니다.
static bool uninit_initialize (struct page *page, void *kva);
첫 번째 폴트에서 페이지를 초기화합니다. 템플릿 코드는 먼저 vm_initializer 및 aux를 가져와서 해당하는 페이지 초기화 프로그램을 함수 포인터를 통해 호출합니다. 디자인에 따라 함수를 수정해야 할 수 있습니다.
static bool
uninit_initialize(struct page *page, void *kva) // 적절한 형태로 만들어진 페이지를 초기화. 별도로 수정하지 않음
{
struct uninit_page *uninit = &page->uninit;
/* Fetch first, page_initialize may overwrite the values */
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);
}
vm/anon.c의 vm_anon_init 및 anon_initializer를 필요에 따라 수정할 수 있습니다.
void vm_anon_init (void);
익명 페이지 서브시스템을 위해 초기화합니다. 이 함수에서는 익명 페이지와 관련된 모든 설정을 수행할 수 있습니다.
void vm_anon_init(void) // 추후에 수정(스왑 구현 시)
{
/* TODO: Set up the swap_disk. */
}
bool anon_initializer (struct page *page,enum vm_type type, void *kva);
이 함수는 먼저 페이지->operations에 대한 익명 페이지 핸들러를 설정합니다. 현재 비어 있는 구조체인 anon_page에서 일부 정보를 업데이트해야 할 수 있습니다. 이 함수는 익명 페이지의 초기화 함수로 사용됩니다 (즉, VM_ANON).
bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
/* Set up the handler */
page->operations = &anon_ops; // 페이지 구조체의 oprations를 설정
struct anon_page *anon_page = &page->anon; // 페이지 타입 설정
anon_page->sec_no = SIZE_MAX;
anon_page->thread = thread_current();
return true;
}
userprog/process.c에 있는 load_segment 및 lazy_load_segment 함수를 구현하세요. 실행 파일로부터 세그먼트를 로드하는 부분을 구현해야 합니다. 이들 페이지는 게으르게 로드되어야 합니다. 즉, 커널이 해당 페이지에 대한 페이지 폴트를 가로채기 전까지만 로드되어야 합니다.
프로그램 로더의 핵심인 userprog/process.c의 load_segment 루프를 수정해야 합니다. 루프 주변에서는 매번 vm_alloc_page_with_initializer를 호출하여 보류 중인 페이지 객체를 생성합니다. 페이지 폴트가 발생하면 여기서 파일에서 세그먼트가 실제로 로드됩니다.
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를 호출하여 보류 중인 객체를 생성합니다.
여러분은 vm_alloc_page_with_initializer에 제공할 aux 인자로 aux 값을 설정해야 합니다. 이진 파일 로딩에 필요한 정보를 담은 구조체를 만드는 것이 좋을 수 있습니다.
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);
off_t dynamic_ofs = ofs;
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;
/* TODO: Set up aux to pass information to the lazy_load_segment. */
struct load_segment_aux *aux = (struct load_segment_aux *)malloc(sizeof(struct load_segment_aux));
lock_acquire(&filesys_lock); // aux에 lazy_load_segment에 대한 정보를 입력
aux->file = file_reopen(file);
lock_release(&filesys_lock);
aux->ofs = dynamic_ofs;
aux->page_read_bytes = page_read_bytes;
aux->page_zero_bytes = page_zero_bytes;
if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, (void *)aux))
{
file_close(aux->file);
free(aux);
return false;
} // lazy_load_segment로 페이지 초기화
/* Advance. */
read_bytes -= page_read_bytes; // aux에 담을 정보 갱신
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
dynamic_ofs += PGSIZE;
}
return true;
}
static bool lazy_load_segment (struct page *page, void *aux);
lazy_load_segment 함수가 **load_segment**에서 **vm_alloc_page_with_initializer**의 네 번째 인자로 제공되는 것을 알아채셨을 것입니다. 이 함수는 실행 파일 페이지의 초기화 함수로, 페이지 폴트가 발생할 때 호출됩니다. 이 함수는 페이지 구조체와 aux를 인자로 받습니다. aux는 **load_segment**에서 설정한 정보입니다. 이 정보를 사용하여 세그먼트를 읽을 파일을 찾고, 최종적으로 세그먼트를 메모리에 읽어들이어야 합니다.
static bool lazy_load_segment(struct page *page, void *aux)
{
/* TODO: Load the segment from the file */
/* TODO: This called when the first page fault occurs on address VA. */
/* TODO: VA is available when calling this function. */
bool success = true;
struct load_segment_aux *info = (struct load_segment_aux *)aux;
if (file_read_at(info->file, page->va, info->page_read_bytes, info->ofs) != (off_t)info->page_read_bytes)
{
vm_dealloc_page(page);
success = false; // 요청한 만큼 읽어오기 실패하면 false
}
else
memset((page->va) + info->page_read_bytes, 0, info->page_zero_bytes);
// 페이지에 파일이 로드되고 남은 부분은 0으로 세팅
file_close(info->file); // 다 읽었으면 파일 닫기
free(aux); // aux 할당 해제
return success;
}
사용자 프로그램(process.c)의 setup_stack 함수를 조정하여 새로운 메모리 관리 시스템에 스택 할당을 맞출 필요가 있습니다. 첫 번째 스택 페이지는 느리게 할당될 필요가 없습니다. 명령 줄 인수로 이를 로드 시간에 할당하고 초기화할 수 있으며, 이를 느리게 구동되기를 기다릴 필요가 없습니다. 스택을 식별하기 위한 방법을 제공해야 할 수 있습니다. **vm_type**의 보조 마커(예: VM_MARKER_0)를 사용하여 페이지를 표시할 수 있습니다.
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 */
success = vm_alloc_page(VM_ANON, stack_bottom, true); // stack_bottom까지 페이지 할당
if (success)
{
if (vm_claim_page(stack_bottom)) // 프레임 할당하고 매핑
if_->rsp = (uintptr_t)USER_STACK; // 스택포인터 조정
}
return success;
}
마지막으로, vm_try_handle_fault 함수를 수정하여 페이지 부재로 인한 오류를 해결할 때 부가 페이지 테이블을 참고하여 해당 주소에 해당하는 페이지 구조체를 찾을 수 있도록 해야 합니다.
위의 요구 사항을 모두 구현한 후, fork를 제외한 프로젝트 2의 모든 테스트가 통과되어야 합니다.
bool vm_try_handle_fault(struct intr_frame *f, void *addr, bool user, bool write, bool not_present)
{
struct supplemental_page_table *spt = &thread_current()->spt;
struct page *page = NULL;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
if (is_kernel_vaddr(addr) && user) // 유효 주소 확인
return false;
page = spt_find_page(spt, addr);
if (vm_do_claim_page(page)) // 프레임 할당 및 매핑 요청
return true; // 성공
return false; // 실패
}
보충 페이지 테이블(Supplemental Page Table)
인터페이스를 재방문하여 복사 및 정리 작업을 지원하도록 구현해야 합니다. 이러한 작업은 프로세스를 생성(구체적으로는 자식 프로세스를 생성)하거나 프로세스를 소멸시킬 때 필요합니다. 아래에 자세한 내용이 나와 있습니다. 위에서 구현한 초기화 함수 중 일부를 사용할 수도 있기 때문에 지금이 보충 페이지 테이블을 다시 살펴보는 이유입니다.
vm/vm.c 파일에 있는 **supplemental_page_table_copy**와 supplemental_page_table_kill 함수를 구현하세요.
bool supplemental_page_table_copy (struct supplemental_page_table *dst,
struct supplemental_page_table *src);
src에서 dst로 보충 페이지 테이블을 복사합니다. 이는 자식이 부모의 실행 컨텍스트를 상속해야 할 때 사용됩니다(즉, fork()). src의 보충 페이지 테이블의 각 페이지를 반복하고 dst의 보충 페이지 테이블에 해당 항목의 정확한 사본을 만들어야 합니다. 초기화되지 않은 페이지를 할당하고 즉시 그 페이지를 차지해야 할 것입니다.
bool supplemental_page_table_copy(struct supplemental_page_table *dst, struct supplemental_page_table *src)
{
struct hash_iterator iter;
hash_first(&iter, &(src->spt));
while (hash_next(&iter))
{
struct page *tmp = hash_entry(hash_cur(&iter), struct page, page_elem);
struct page *cpy = NULL;
switch (VM_TYPE(tmp->operations->type))
{
case VM_UNINIT:
if (VM_TYPE(tmp->uninit.type) == VM_ANON)
{
struct load_segment_aux *info = (struct load_segment_aux *)malloc(sizeof(struct load_segment_aux));
memcpy(info, tmp->uninit.aux, sizeof(struct load_segment_aux));
info->file = file_duplicate(info->file);
vm_alloc_page_with_initializer(tmp->uninit.type, tmp->va, tmp->writable, tmp->uninit.init, (void *)info);
}
break;
case VM_ANON:
vm_alloc_page(tmp->operations->type, tmp->va, tmp->writable);
cpy = spt_find_page(dst, tmp->va);
if (cpy == NULL)
return false;
cpy->copy_writable = tmp->writable;
struct frame *cpy_frame = malloc(sizeof(struct frame));
cpy->frame = cpy_frame;
cpy_frame->page = cpy;
cpy_frame->kva = tmp->frame->kva;
struct thread *t = thread_current();
lock_acquire(&lru_lock);
list_push_back(&lru, &cpy_frame->lru_elem);
lock_release(&lru_lock);
if (pml4_set_page(t->pml4, cpy->va, cpy_frame->kva, 0) == false)
return false;
swap_in(cpy, cpy_frame->kva);
break;
case VM_FILE:
break;
default:
break;
}
}
return true;
}
void supplemental_page_table_kill (struct supplemental_page_table *spt);
보충 페이지 테이블이 보유하고 있던 모든 리소스를 해제합니다. 이 함수는 프로세스가 종료될 때 호출됩니다(userprog/process.c의 process_exit()). 페이지 항목을 반복하고 테이블 내의 각 페이지에 대해 destroy(page)를 호출해야 합니다. 이 함수에서는 실제 페이지 테이블(pml4)과 물리적 메모리(palloc으로 할당된 메모리)에 대해 걱정할 필요가 없습니다. 이러한 요소들은 보충 페이지 테이블이 정리된 후에 호출자가 정리합니다.
void supplemental_page_table_kill(struct supplemental_page_table *spt)
{
/* TODO: Destroy all the supplemental_page_table hold by thread */
lock_acquire(&kill_lock);
hash_destroy(&(spt->spt), spt_destroy_func); // spt 삭제
lock_release(&kill_lock);
/* TODO: writeback all the modified contents to the storage. */
}
static void spt_destroy_func(struct hash_elem *e, void *aux)
{
const struct page *pg = hash_entry(e, struct page, page_elem);
vm_dealloc_page(pg); // 페이지 할당 해제
}
페이지 클린업
vm/uninit.c 파일에 uninit_destroy 함수를, 그리고 vm/anon.c 파일에 anon_destroy 함수를 구현하세요. 이는 초기화되지 않은 페이지에 대한 파괴 작업을 처리하는 핸들러입니다. 초기화되지 않은 페이지가 다른 페이지 객체로 변환되더라도, 프로세스가 종료될 때 여전히 초기화되지 않은 페이지가 존재할 수 있습니다.
static void uninit_destroy (struct page *page);
page 구조체에 의해 보유된 리소스를 해제합니다. 페이지의 vm type을 확인하고 그에 따라 적절히 처리해야 할 것입니다.
static void
uninit_destroy(struct page *page) // 별도로 수정하지 않음.
{
struct uninit_page *uninit UNUSED = &page->uninit;
/* TODO: Fill this function.
* TODO: If you don't have anything to do, just return. */
}
현재는 익명 페이지만 처리할 수 있습니다. 나중에 파일로 지원되는 페이지를 정리하려면 이 함수를 다시 살펴보게 될 것입니다.
static void anon_destroy (struct page *page);
익명 페이지에 의해 보유된 리소스를 해제합니다. 페이지 구조체를 명시적으로 해제할 필요는 없습니다. 이 작업은 호출자가 수행해야 합니다.
static void
anon_destroy(struct page *page)
{
struct anon_page *anon_page = &page->anon;
if (page->frame != NULL)
{
// printf("anon_destroy: %s\n", thread_current()->name);
// printf("remove: %p, kva:%p\n", page->va, page->frame->kva);
// printf("list_size: %d, list: %p\n", list_size(&lru), &lru);
lock_acquire(&lru_lock);
list_remove(&page->frame->lru_elem); // 프레임 리스트에서 삭제
lock_release(&lru_lock);
// printf("anon_destroy: list: %p\n", &lru);
// pte write bit 1 -> free
free(page->frame); // 프레임 구조체 해제
}
if (anon_page->sec_no != SIZE_MAX)
bitmap_set_multiple(disk_bitmap, anon_page->sec_no, 8, false); // 사용 가능한 프레임에 대한 bitmap 설정
}
이제 프로젝트 2의 모든 테스트가 통과되어야 합니다.