728x90
➡️ 과제 설명(Gitbook)
✨ Summary
- Page Fault 에 대한 처리 & Lazy loading
- Page Cycle
- 초기화 → (페이지 폴트 → 지연 로딩 → 스왑 인 → 스왑 아웃 → …) → 삭제
- UPAGE : 사용자 가상페이지
- KPAGE: 커널 가상 페이지
👷 구현 순서
vm_alloc_page_with_initializer
- 새로운 페이지를 만들고 spt에 삽입한다.
- VM_TYPE = VM_UNINIT (미결정된 페이지
”a.k.a. 껍데기”로 지정)
- VM_UNINIT 에 대한 처리(생성, 초기화, 삭제)
- Lazy Loading을 위해 프로그램 시작 시 모든 페이지를 VM_UNINIT으로 만들어버렸으니, VM_UNINIT이 된 페이지들에 대한 작업 수행
- page fault handler
- Lazy loading 구현
- setup_stack
1. vm_alloc_page_with_initializer
[📖 Gitbook]
- initializer가 페이지 구조를 할당하고 페이지 유형에 따라 적절한 initializer를 설정하여 새로운 페이지를 초기화함
- 페이지 구조 할당 → initializer 설정까지의 단계
- 스켈레톤 코드
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) // vm_type은 VM_ANON과 VM_FILE만 가능하다.
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.
* 페이지를 만들고 vm유형에 따라 이니셜을 가져온 다음 uninit_new를 호출하여 uninit 페이지 구조를 만듦
* uninit_new를 호출한 후 필드를 수정해야 함*/
/* TODO: Insert the page into the spt. */
}
err:
return false;
}
- vm_alloc_page_with_initializer의 호출
/* 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) {
...
while (read_bytes > 0 || zero_bytes > 0) {
...
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;
}
return true;
}
- 코드 구현
/* vm/vm.c */
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) // vm_type은 VM_ANON과 VM_FILE만 가능하다.
struct supplemental_page_table *spt = &thread_current ()->spt;
/* Check wheter the upage is already occupied or not. */
if (spt_find_page (spt, upage) == NULL) {
// ↳ upage라는 가상 메모리에 매핑되는 페이지 존재 x -> 새로 만들어야함
/* 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.
* 페이지를 만들고 vm유형에 따라 이니셜을 가져온 다음 uninit_new를 호출하여 uninit 페이지 구조를 만듦
* uninit_new를 호출한 후 필드를 수정해야 함*/
struct page* page = (struct page*)malloc(sizeof(struct page));
// 페이지 타입에 따라 initializer가 될 초기화 함수를 매칭해준다.
typedef bool (*initializer_by_type)(struct page *, enum vm_type, void *);
initializer_by_type initializer = NULL;
// switch-case 문 : VM_TYPE 이 enum이므로
switch(VM_TYPE(type)) { // 페이지 타입에 따라 initiailizer를 설정한다.
case VM_ANON:
initializer = anon_initializer;
break;
case VM_FILE:
initializer = file_backed_initializer;
break;
}
uninit_new(page, upage, init, type, aux, initializer); // UNINIT 페이지 생성
page->writable = writable; // page의 w/r 여부
/* TODO: Insert the page into the spt. */
return spt_insert_page(spt, page); // 새로 만든 페이지를 spt에 삽입한다.
}
err:
return false;
}
- 함수 포인터의 사용
bool anon_initializer (struct page *page, enum vm_type type, void *kva);
bool file_backed_initializer (struct page *page, enum vm_type type, void *kva);
vm_type
에 따라서 initializer의 종류를 결정한다.- 이때, 함수 포인터를 사용하는데, 둘의 리턴 타입, 인자가 같으므로 함수 이름을 포인터처럼 넘겨줄 수 있다.
📢 여기까지 하면 페이지를 생성하고 → spt에 까지 넣어진 상태!
2. VM_UNINIT 에 대한 처리(생성, 초기화, 삭제)
[📖 Gitbook]
- 초기화되지 않은 페이지를 생성, 초기화, 삭제하는 함수(
include/vm/uninit.c
) - Function List
- 생성 :
uninit_new
- 초기화 :
uninit_initialize
- 삭제 :
uninit_destroy
- 생성 :
1. 생성 : uninit_new
uninit_new
의 호출vm_alloc_page_with_initializer
에서 페이지 타입에 따른 initializer 설정하고 호출(UNINIT 타입의 새로운 페이지를 생성)한다. (위 코드 스니펫 참조)
❌ DO NOT MODIFY this function ❌
/* vm/uninit.c */
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 *)) {
ASSERT (page != NULL);
*page = (struct page) {
.operations = &uninit_ops,
// operations 필드로 uninit_ops를 설정해서 추후
// 'VM_UNINIT'에 대한 처리를 해준다. -> VM_ANON or VM_FILE에 따라 initializer 확정
.va = va,
.frame = NULL, /* no frame for now */ // 현재는 물리 메모리와 매핑되지는 않은 상태
.uninit = (struct uninit_page) {
.init = init,
.type = type,
.aux = aux,
.page_initializer = initializer, // 페이지 타입에 따라 인자로 넘어온 initializer 설정
}
};
}
- uninit page로써 새로운 페이지를 만들어준다.
- 껍데기를 만들어 주는 과정
- 함수 인자로 initializer를 받는다.
- initializer : 아직 페이지의 확정은 되지않았지만 “추후에 Lazy loading을 구현하고 해당 페이지는 어떤 페이지가 될 거다.”를 나타낸다.
2. 초기화 : uninit_initialize
uninit_initialize
의 호출- 첫 번째 페이지 폴트(bogus fault)가 발생할 때, swap_in을 하면서 해당 함수가 호출된다.
/* vm/uninit.c */
static const struct page_operations uninit_ops = {
/* 앞선 uninit_new 에서 operations로 uninit_ops를 설정해줬기 때문에
해당 page_operations가 수행된다. */
.swap_in = uninit_initialize,
.swap_out = NULL,
.destroy = uninit_destroy,
.type = VM_UNINIT,
};
⚠️ 필요에 따라 수정한다.
/* vm/uninit.c */
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; // lazy_load_segment
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);
// 페이지 타입에 따라 page_initializer 수행 and lazy_load_segment 수행
}
- uninit->page_initializer (page, uninit->type, kva) 의 초기화 함수 실행
- before : VM_UNINIT (
init_ops
) - after : (eg.) VM_ANON (
anon_ops
)
- before : VM_UNINIT (
bool
anon_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
page->operations = &anon_ops;
struct anon_page *anon_page = &page->anon;
}
☁️ 리마인드!
- 앞선 단계를 수행하며
struct page
에 담긴 정보들
*page = (struct page) {
.operations = &uninit_ops,
.va = va,
.frame = NULL,
.uninit = (struct uninit_page) {
.init = init, // lazy_load_segment(_userprog/process.c)
.type = type, // 페이지 타입
.aux = aux, // 여기선 몰라도 된다.(load_segment에서 사용할 예정)
.page_initializer = initializer, // 페이지 타입에 따라 인자로 넘어온 initializer 설정
}
3. 삭제 : uninit_destroy
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. */
if (page->uninit.aux != NULL)
free (page->uninit.aux);
return;
}
3. page fault handler
/* userprog/exception.c */
static void
page_fault (struct intr_frame *f) {
bool not_present; /* True: not-present page, false: writing r/o page. */
bool write; /* True: access was write, false: access was read. */
bool user; /* True: access by user, false: access by kernel. */
void *fault_addr; /* Fault address. */
...
#ifdef VM
/* For project 3 and later. */
// VM 구현 이후부터 페이지 폴트는 vm_try_handle_fault가 처리한다.
if (vm_try_handle_fault (f, fault_addr, user, write, not_present))
return;
#endif
exit(-1); // TC "bad-*" 통과를 위함(exit를 안해주면 kill로 그냥 삭제됨)
/* 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);
}
page_fault
핸들러는 페이지 폴트에 대한 제어권을vm_try_handle_fault
으로 넘긴다.
vm_try_handle_fault
스켈레톤 코드
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;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
return vm_do_claim_page (page);
}
/* 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;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
/* page_fault로 부터 넘어온 인자
* not_present : 페이지 존재 x (bogus fault)
* user : 유저에 의한 접근(true), 커널에 의한 접근(false)
* write : 쓰기 목적 접근(true), 읽기 목적 접근(false)
*/
// page fault 주소에 대한 유효성 검증
// 커널 가상 주소 공간에 대한 폴트 처리 불가, 사용자 요청에 대한 폴트 처리 불가
if (is_kernel_vaddr (addr) && user) // real fault
return false;
void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
if (not_present){
if (!vm_claim_page(addr)) return false;
else return true;
}
// return vm_do_claim_page (page);
return false;
}
vm_claim_page
에서 페이지 폴트가 난 부분에 대해서 물리적 주소인 프레임과 연결하는 작업을 수행한다.- 기존 스켈레톤 코드의
vm_do_claim_page
를vm_claim_page
로 바꿔줬다. - 어차피
vm_claim_page
내부에서vm_do_claim_page
진행한다. spt_find_page
를 통해 페이지가 존재하는 지 확인하는 로직을 안전장치로 함께 추가했다.
- 기존 스켈레톤 코드의
vm_do_claim_page
→swap_in
- swap_in 함수를 호출한다.
- swap_in이 uninit_initialize 로 연결되므로
이후, 페이지 타입에 따라 초기화 함수를 달리하여 페이지 초기화를 진행한다.
⛳ 필요에 따라 vm/anon.c
에 있는 vm_anon_init
이나 anon_initializer
를 수정할 수 있다!
📢 여기까지 하면 “bogus page fault” 로 인한 새 페이지 할당 구현 완료
4. Lazy loading 구현
1. load_segment
[📖 Gitbook]
- aux를 사용해서 세그먼트를 읽을 파일을 찾고 최종적으로 세그먼트를 메모리에서 읽어야한다.
- lazy_load_segment의 두 번째 인자인 aux로 넘겨주는 정보를 aux인자에 넣어줘야한다.
- load_segment 주석 설명
/* Loads a segment starting at offset OFS in FILE at address
* UPAGE. In total, READ_BYTES + ZERO_BYTES bytes of virtual
* memory are initialized, as follows:
*
* - READ_BYTES bytes at UPAGE must be read from FILE
* starting at offset OFS.
*
* - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
*
* The pages initialized by this function must be writable by the
* user process if WRITABLE is true, read-only otherwise.
*
* Return true if successful, false if a memory allocation error
* or disk read error occurs. */
/* 파일의 UPAGE(유저 가상 페이지)주소에서부터 OFS 오프셋에서 시작하는 세그먼트를 로드한다.
* 가상 메모리의 READ_BYTES + ZERO_BYTES는 다음과 같이 초기화된다.
*
* - UPAGE의 READ_BYTES는 FILE에서 오프셋 OFS에서 시작하도록 해서 읽어야 한다.
* - ZERO_BYTES(UPAGE + READ_BYTES)는 반드시 0이어야 한다.
*
* 해당 함수로 초기화된 페이지는 반드시 유저 프로세스에 의해 쓰기가 가능해야 하며,
* 그렇지 않은 경우는 read-only여야한다.
*
* 성공하면 true를 리턴하고 만약 메모리 할당 에러 또는 디스크 읽기 오류 발생시,
* false를 리턴한다. */
- 코드
/* 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;
/* TODO: Set up aux to pass information to the lazy_load_segment. */
/* TODO: lazy_load_segment에 정보를 전달하도록 aux를 설정한다. */
struct segment_aux *aux = (struct segment_aux *)malloc(sizeof(struct segment_aux));
aux->file = file; // 세그먼트를 읽어올 파일
aux->offset = ofs; // 시작 오프셋
aux->page_read_bytes = page_read_bytes; // 총 읽어올 바이트
// 이후, lazy_load_segment로 aux값을 넘겨준다.
ofs += page_read_bytes;
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;
}
return true;
}
- aux에 세그먼트 로드에 필요한 정보들을 구조체 안에 담아서
lazy_load_segment
로 넘긴다.
load_segment
에서는 실제 “Lazy_loading”이 일어나는 순간(첫 번째 페이지 폴트 이후)에 가져와야할 코드 데이터들이 위치한 파일에 대한 정보를 넘겨준다.- ❗️ 즉, 실제 세그먼트 로드는 여기서 이뤄지는 건 아님! → 실제 세그먼트 로드는 Lazy_loading으로
lazy_load_segment
에서 이뤄진다.
2. lazy_load_segment
- 실행가능 파일의 페이지들을 초기화하고 페이지 폴트가 발생할 때 호출된다.
lazy_load_segment
의 호출uninit_initialize
에서 첫 번째 페이지 폴트가 난 뒤lazy_load_segment
가 수행된다.
/* userprog/process.c */
static bool
lazy_load_segment (struct page *page, void *aux) {
if (page == NULL) // 페이지 주소에 대한 유효성 검증
return false;
/* TODO: Load the segment from the file */
// 인자로 넘긴 aux에 대한 encapsulation을 진행한다.
struct segment_aux* segment_aux = (struct segment_aux *) aux;
struct file *file = ((struct segment_aux *)aux) -> file;
off_t offset = ((struct segment_aux *)aux) -> offset;
size_t page_read_bytes = ((struct segment_aux *)aux) -> page_read_bytes;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* TODO: This called when the first page fault occurs on address VA. */
file_seek(file, offset); // 파일의 오프셋을 설정한다.
// file_read : 읽어온 바이트 수를 리턴
// 만약 읽어온 바이트 수가 page_readbytes 와 다르다면 false
if (file_read(file, page->frame->kva, page_read_bytes) != (int)page_read_bytes) { // 파일을 읽어온다.
palloc_free_page(page->frame->kva);
return false;
}
/* TODO: VA is available when calling this function. */
// 페이지에 대한 초기화 작업 수행
memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);
return true;
}
5. setup_stack
- 첫 번째 스택 페이지는 지연 할당될 필요가 없다.
- 🤔 Why? - 아마 함수 호출을 통해 시스템을 운용하려면 시스템 운영의 메인 스트림이 되는 첫 번째 스택은 메모리를 할당해야해서 그런거 같다.
/* userprog/process.c */
static bool
setup_stack (struct intr_frame *if_) {
bool success = false;
void *stack_bottom = (void *) (((uint8_t *) USER_STACK) - PGSIZE);
// 스택은 아래로 커지니까 새로 추가하려는 페이지 사이즈 만큼 빼준 뒤, 해당 공간으로부터 페이지를 할당한다.
// ↳ [Ref. Gitbook Project 2 - Introduction]
/* 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)) { // = vm_alloc_page_with_initializer ((type), (upage), (writable), NULL, NULL)
// ↳ 1(true)이면 writable
success = vm_claim_page(stack_bottom); // 페이지와 프레임 매핑
if (success) {
if_->rsp = USER_STACK; // 스택을 위한 공간 할당했으니까 rsp 위치 지정
thread_current()->stack_bottom = stack_bottom; // stack_bottom 지정
}
}
return success;
}
- stack_bottom
- 스택의 맨 아래 위치를 가리키는 포인터
- 메모리를 상향으로 증가하기 때문에 페이지크기 만큼 빼준 뒤 공간을 할당해주는 것이다!
- 아래 그림 참조! (Ref)
- 페이지 생성 함수
Create the pending page object with initializer. If you want to create a page, do not create it directly and make it through this function or ’vm_alloc_page’.
📃 Initializer를 사용하여 보류 중인 페이지 개체를 만든다. 페이지를 생성하려면 직접 생성하지 말고 이 함수 또는 'vm_alloc_page'를 통해 생성한다.
_ vm/vm.c
- vm_alloc_page_with_initializer
- vm_alloc_page (✅)
- stack_bottom : 스택의 맨 아래 포인터
- vm_alloc_page
/* include/vm/vm.c */
#define vm_alloc_page(type, upage, writable) \
vm_alloc_page_with_initializer ((type), (upage), (writable), NULL, NULL)
- 결국엔
vm_alloc_page_with_initializer
를 호출하는 함수
☁️ 함수 리마인드
vm_alloc_page
(=vm_alloc_page_with_initializer
)- 새로 (페이지 타입에 따라) 페이지를 만들고 spt에 넣어주는 작업 수행
vm_claim_page
- 페이지(논리 단위)와 프레임(물리 단위)를 매핑
- 현재 실행중인 스레드의 페이지 테이블에 넣는 작업도 포함
🎉 중간 점검
- 여기까지 구현을 완료하면 Project 1, 2에서 fork를 제외한 나머지 모든 코드들이 통과한다.
6. Supplemental Page Table - Revisit
- supplemental_page_table_copy
- 프로세스 생성 시, spt 복사를 수행한다.
- 정확히는 자식 프로세스 생성
- src의 spt에 있는 각 페이지를 반복하고, dst의 spt 엔트리에 정확한 복사본을 만든다.
- supplemental_page_table_copy 의 호출
/* userprog/process.c */
static void
__do_fork (void *aux) {
...
#ifdef VM
supplemental_page_table_init (¤t->spt);
if (!supplemental_page_table_copy (¤t->spt, &parent->spt))
... (중략) ...
- 코드
/* vm/vm.c */
bool
supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED,
struct supplemental_page_table *src UNUSED) {
struct thread *curr = thread_current(); // (현재 실행중인)자식 프로세스
struct hash_iterator i; // 부모의 해쉬 테이블을 순회하기 위한 iterator
hash_first (&i, &src->spt_hash);
while (hash_next (&i)) {
struct page *parent_page = hash_entry (hash_cur (&i), struct page, hash_elem); // 복사하려는 부모 페이지
enum vm_type parent_type = parent_page->operations->type; // 부모 페이지의 타입에 따라 조건문을 분기한다.
// CASE 1. UNINIT 페이지인 경우 -> ANON 또는 FILE로 페이지 타입 결정
// ↳ 페이지만
if(parent_type == VM_UNINIT){
if(!vm_alloc_page_with_initializer(parent_page->uninit.type, parent_page->va, \
parent_page->writable, parent_page->uninit.init, parent_page->uninit.aux))
return false;
}
// CASE 2. UNINIT 페이지가 아닌 경우
// ↳ 페이지 + 프레임
else {
// CASE 2-1. 스택 페이지인 경우, 자식 프로세스에게도 스택 설정을 해줌
// setup_stack : 페이지 할당 + 프레임 할당 + stack_bottom 설정
if (parent_type & VM_MARKER_0)
setup_stack(&thread_current()->tf); // setup_stack's param : intr_frame
// CASE 2-2. 스택 페이지 이외의 경우
// 페이지 할당 + 프레임 할당
else
if(!vm_alloc_page(parent_type, parent_page->va, parent_page->writable)) // 페이지 할당
return false;
if(!vm_claim_page(parent_page->va)) // 프레임 할당
return false;
// 부모의 프레임을 자식 프레임으로 복사한다.
struct page* child_page = spt_find_page(dst, parent_page->va);
memcpy(child_page->frame->kva, parent_page->frame->kva, PGSIZE); // 부모 프레임 그대로 복사
}
}
return true;
}
- 부모의 페이지 타입이 UNINIT 인 경우와 페이지 타입이 UNINIT 아닌 경우로 나누어서 생각한다.
- 두 경우를 나눠서 페이지 할당 및 프레임 할당을 진행한다.
1. VM_UNINIT 인 경우
- UNINIT인 경우, 실제 페이지 타입은 결정되지 않았으며 추후에 어떤 페이지 타입을 가질 지에 대한 정보는 parent_page->uninit 구조체 안에 들어있다.
- 따라서 자식 프로세스에서도 vm_alloc_page_with_initializer 을 해줄 때, parent_page->uninit 구조체 멤버들을 인자로 넘겨준다.
- parent_page->uninit.type ⇒ ANON or FILE
vm_alloc_page_with_initializer(parent_page->uninit.type, parent_page->va, \
parent_page->writable, parent_page->uninit.init, parent_page->uninit.aux))
2. VM_UNINIT이 아닌 경우
- VM_UNINIT이 아닌 경우(VM_ANON or VM_FILE), 실제 페이지 타입이 결정된 상태이다.
- 따라서 vm_alloc_page_with_initializer 를 이용해서 UNINIT에 대한 초기화를 해줄 필요는 없고 페이지 할당과 프레임 할당만 해주면된다.
- 그런데 스택의 경우, 따로 스택임을 표시해줘야하므로 setup_stack을 이용하여 적용한다.
- setup_stack
- 자식의 스택 페이지를 할당 + 스택에 해당하는 프레임 할당을 해준다.
- 해당 함수 내에 vm_alloc_page , vm_claim_page 가 포함되어 있다.
- 이후, 프레임이 할당된 <CASE 2. UNINIT 페이지가 아닌 경우>에만 memcpy를 이용해서 부모 프레임을 자식 프레임으로 복사한다.
- page_get_type
enum vm_type
page_get_type (struct page *page) {
int ty = VM_TYPE (page->operations->type); // flag를 제외한 실제 페이지 타입
switch (ty) {
case VM_UNINIT:
// UNINIT 타입의 경우, 실제 페이지 타입은 바뀌지 않고 곧 바뀌게 될 페이지 타입을
// page->uninit.type에 담는다.
return VM_TYPE (page->uninit.type);
default:
return ty;
}
}
- 함수 설명 : 초기화된 이후의 페이지 타입을 알기위해 사용한다.
- supplemental_page_table_kill
- 프로세스 삭제 시, spt 삭제를 수행한다.
- spt에 의해 유지되던 모든 자원들을 해제한다.
- 코드
/* vm/vm.c */
void
supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
/* TODO: Destroy all the supplemental_page_table hold by thread and
* TODO: writeback all the modified contents to the storage. */
/* TODO: 스레드별로 소유하고 있는 모든 spt를 삭제하고 수정된 모든 내용을 저장소에 다시 기록
*/
// hash 엔트리 각각에 대해 메모리를 해제한다.
hash_destroy(&spt->spt_hash, destructor);
}
- 추가함수 destructor
/* vm/vm.c */
static void
destructor(struct hash_elem *e, void* aux) {
const struct page *p = hash_entry(e, struct page, hash_elem);
free(p);
}
- hash_destroy 주석 설명
void
hash_destroy (struct hash *h, hash_action_func *destructor) {
if (destructor != NULL)
hash_clear (h, destructor);
free (h->buckets);
}
/* Destroys hash table H.
If DESTRUCTOR is non-null, then it is first called for each
element in the hash. DESTRUCTOR may, if appropriate,
deallocate the memory used by the hash element. However,
modifying hash table H while hash_clear() is running, using
any of the functions hash_clear(), hash_destroy(),
hash_insert(), hash_replace(), or hash_delete(), yields
undefined behavior, whether done in DESTRUCTOR or
elsewhere. */
/* 해시 테이블 H를 파괴한다.
DESTURCTOR가 null이 아닌 경우, 해시의 각 요소들이 호출된다.
DESTURCTOR는 적절히 해시 요소에 의한 메모리를 할당해제할 수 있다. */
7. Edit System Calls
- 시스템 콜 구현 부분에서 check_address 를 수정해준다.
- before
#ifndef VM
static void
check_address(void *addr) {
struct thread *curr = thread_current();
if (!is_user_vaddr(addr) || pml4_get_page(curr -> pml4, addr) == NULL || addr == NULL) // 유저 영역인지 NULL 포인터인지 확인
exit(-1);
}
#endif
- after
#ifdef VM
struct page* check_address(void *addr){
struct thread *curr = thread_current();
if (addr == NULL || is_kernel_vaddr(addr))
exit(-1);
return spt_find_page(&curr->spt, addr);
}
#endif
728x90
'🌱 Dev Diary > 📄 TIL' 카테고리의 다른 글
Project 3. Stack Growth(2) (0) | 2023.01.19 |
---|---|
Project 3. Stack Growth(1) (0) | 2023.01.19 |
Project 3. Anonymous Page(1) (0) | 2023.01.19 |
Project 3. Memory Management (0) | 2023.01.19 |
Project 3. Introduction (0) | 2023.01.19 |