728x90

➡️ 과제 설명(Gitbook)

 

[Krafton Jungle | TIL_22.01.10] Project 3. Anonymous Page(1)

➡️ 과제 설명(Gitbook) Anonymous Page · GitBook 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

olive-su.tistory.com

 

 

✨ Summary

  • Page Fault 에 대한 처리 & Lazy loading
  • Page Cycle
    • 초기화 → (페이지 폴트 → 지연 로딩 → 스왑 인 → 스왑 아웃 → …) → 삭제
  • UPAGE : 사용자 가상페이지
  • KPAGE: 커널 가상 페이지

 

👷 구현 순서

  1. vm_alloc_page_with_initializer
    1. 새로운 페이지를 만들고 spt에 삽입한다.
    2. VM_TYPE = VM_UNINIT (미결정된 페이지”a.k.a. 껍데기” 로 지정)
  2. VM_UNINIT 에 대한 처리(생성, 초기화, 삭제)
    1. Lazy Loading을 위해 프로그램 시작 시 모든 페이지를 VM_UNINIT으로 만들어버렸으니, VM_UNINIT이 된 페이지들에 대한 작업 수행
  3. page fault handler
  4. Lazy loading 구현
  5. 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
    1. 생성 : uninit_new
    2. 초기화 : uninit_initialize
    3. 삭제 : 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)
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_pagevm_claim_page 로 바꿔줬다.
    • 어차피 vm_claim_page 내부에서 vm_do_claim_page 진행한다.
    • spt_find_page를 통해 페이지가 존재하는 지 확인하는 로직을 안전장치로 함께 추가했다.

 

⛳ 필요에 따라 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
  1. vm_alloc_page_with_initializer
  2. 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

  1. 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 (&current->spt);
    if (!supplemental_page_table_copy (&current->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;
    }
}
  • 함수 설명 : 초기화된 이후의 페이지 타입을 알기위해 사용한다.
  1. 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