Pintos Project3 : ~ anonymous page 페이지 초기화 흐름 정리

HiroPark·2023년 5월 16일
0

문제해결

목록 보기
9/11

현재 anonymous까지 구현을 마치고, 일부 돌아가지 않는 테스트들에 맞게 코드를 수정하고 있는 단계이다.

핀토스는 하나의 프로세스를 만들고, 이를 fork해서 사용하는 방식이다.
이때, 첫 부분(하나의 프로세스를 만들기)에서 uninit 페이지를 생성하고, 이후 fault에 따라서 실제 값을 불러오고 페이지 종류를 변경하는 과정에서 계속 에러가 발생해서 디버깅을 하며 흐름을 파악했다.

이참에 Pintos 운영체제 안에서 페이지들의 생성 흐름을 정리하고자 한다.

일단 전반적인 흐름은 다음과 같다.

  • 처음 프로세스를 하나 생성할때, load_segment에서 read_byte크기를 (최대) 페이지 크기로 잘라가며 while문을 돌며 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) {
	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;

		/* create vm_entry(use malloc)*
		/* setting vm_entry members, offset and size of file to read when virtual page is required, zero byte to pad at the end */
		/* vm_entry를 hash table에 삽입 */
		// aux에 file과 offset과 , read byte, zero byte 등록
		// vm_alloc_page_with_initializer호출하는데 , 이때 aux 사용 

		struct info_aux *aux = (struct aux*)calloc(1, sizeof(struct info_aux));
		aux->file = file;
		aux->offset = ofs;
		aux->read_bytes = page_read_bytes;
		aux->zero_bytes = page_zero_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;

		ofs += page_read_bytes; // 다음 파일을 위해 파일을 읽은 바이트만큼 오프셋 이동
	}
	return true;
}
  • vm_alloc_page_with_initializer 를 통해서 생성된 모든 페이지는 uninit 타입의 페이지로, 실제 페이지 테이블(pml4)에 매핑돼 있지 않다. (spt에는 들어있다)
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(va) is already occupied or not. */
	if (spt_find_page (spt, upage) == NULL) { 
		struct page *page = (struct page*)malloc(sizeof(struct page));

		bool (*initializerFunc)(struct page *, enum vm_type, void *); 

		switch(type) {  
			case VM_ANON:
				initializerFunc = anon_initializer;
				break;

			case VM_FILE:
				initializerFunc = file_backed_initializer;
				break;
		}
	
		uninit_new(page, upage, init, type, aux, initializerFunc); 
		

		page->writable = writable;

		if (type == (VM_ANON | VM_MARKER_0)) {
			page->stack = true;
		}
        
		return spt_insert_page(spt, page);
	}
err:
	return false;
}
  • 이후, 해당 페이지에 접근해서 page fault가 나게된다. exception.c 에서 이를 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;

	if(is_kernel_vaddr(addr) || addr == NULL)
        return false;
	

    if (f->rsp - 8 <= addr && addr <= USER_STACK && USER_STACK - 0x100000 <= addr) {

        vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
        return true;
    }

	if (not_present) {
        page = spt_find_page(spt, addr);

        if (page == NULL)
            return false;

        if (vm_do_claim_page (page) == false)

            return false;
    }

	
	if (write && !page->writable)
		return false;

    return true;
}
  • 우선, vm_do_claim_page를 통해서 페이지와 프레임을 연결해주고, pml4에 이를 기록한다
static bool
vm_do_claim_page (struct page *page) {
	struct frame *frame = vm_get_frame ();
	struct thread *t = thread_current ();

	ASSERT(frame != NULL);

	frame->page = page;
	page->frame = frame;

	if(pml4_get_page(t->pml4, page->va) == NULL && pml4_set_page(t->pml4, page->va, frame->kva, page->writable)){
        
        if(page->stack == true)
            return true;
        else
            return swap_in (page, frame->kva);
    }
    else
        return false;
}
  • 다음으로, vm_do_claim_page안의 swap_in을 호출해 호출 흐름을 타고가다보면, uninit_initialize를 호출하게 된다.

  • 이 uninit initialize는
    - lazy_load_segment를 통해서 파일의 읽어와야 할 부분을 읽어서 물리 프레임을 저장한다.
    - 페이지 타입에 맞는 이니셜라이저를 호출한다.(페이지 타입을 변경한다)
    ex) anon_initializer

bool
anon_initializer (struct page *page, enum vm_type type, void *kva) {
    // new anon page: uninit 필드를 0으로 초기화
    struct uninit_page *uninit = &page->uninit;
    memset(uninit, 0, sizeof(struct uninit_page));
    /* Set up the handler */
    page->operations = &anon_ops;
    struct anon_page *anon_page = &page->anon;
}

페이지의 operation(swap_in, out, destroy)를 anon 페이지의 오퍼레이션으로 바꾸고, union 구조체를 anon으로 채운다

union {
		struct uninit_page uninit;
		struct anon_page anon;
		struct file_page file;
#ifdef EFILESYS
		struct page_cache page_cache;
#endif
	};

lazy_load_segment

static bool lazy_load_segment (struct page *page, void *aux) {

	struct info_aux *info_aux = (struct info_aux*)aux;

	struct file *file = info_aux->file;
	off_t ofs = info_aux->offset;
	uint32_t read_bytes = info_aux->read_bytes;
	uint32_t zero_bytes = info_aux->zero_bytes;

	file_seek (file, ofs);
	
	if (file_read(file, page->frame->kva, read_bytes) != (int) read_bytes) {
		palloc_free_page(page->frame->kva);

		return false;
	}
	else {
       memset (page->frame->kva + read_bytes, 0, zero_bytes);

       return true;
	}
}
  • 이제는 가상 페이지와 물리 프레임이 연결돼있고, 원하는 파일의 값이 물리 프레임에 적재된다.

왜 anonymous로 load?

근데 위의 load_segment를 보면, vm_alloc_page_with_initializer를 호출할때, 타입 인자를 VM_ANON으로 넘겨서, 모든 uninit 페이지가 page fault이후 Anonymous로 생성되게 만들어 놓은 것을 볼 수 있다.

왜 file_backed 도 있는데 항상 Anonymous 페이지로 할당받도록 설계한 것일까??

  • load_segment는 elf형식으로 저장된 응용프로그램을 로드하는데 사용하는데,
    현재 로드하는 섹션이 .text인지, .bss인지, .data인지 알기 위해서는 추가적인 작업이 필요하다.

  • 그리고, 로드하는 세그먼트의 종류에 따라 로딩 방식을 달리한다면, 처리 방식을 분기해줘야되는데, 이에 따른 구현의 복잡도와 성능 손실이 크다.

  • 따라서, 운영체제의 입장에서, 현재 로드하고 있는 섹션이 무엇인지를 정확히 구분하는 이점이 없다.

  • 이미 현재의 구현 방식으로도 각 섹션의 본래 목적에 맞게 로딩을 처리할 수 있다.

  • 예를 들어, bss 와 같이 파일로 부터 읽을 데이터가 없는 경우는 anonymous를 사용하는 것이 맞고,
    .text처럼 파일로부터 읽어와야 할 데이터가 있는 경우에는 lazy_loading을 통해 추후에 파일을 불러온다.

  • 실행파일은 disk에서 stack과 BSS 섹션은 디스크와 연동되어서는 안되기 때문에 anonymous사용

굳이 각 섹션의 목적에 따라 페이지의 종류를 다르게 할당 받을 필요가 없다는 것은 확인했다. 그렇다면 왜 file-backed가 아닌 anonymous를 사용할까??

  • file-backed 페이지는, 해당 영역에 데이터를 쓰면 파일에 자동으로 반영되는 페이지이다.

  • 우리는 응용 프로그램으로부터 .data 같은 영역을 불러와서, 실행중에 해당 영역의 값이 바뀐다고 하더라도, 실제 파일을 바꾸지는 않을 것이므로, file-backed를 아직까지는 사용하지 않는다.

  • swap in/out 시에도 실행중 변경된 값을 그대로 복원하기를 원하기에, swap disk를 사용하는 anonymous 페이지를 사용한다.

  • 따라서 anonymous를 사용한다.

profile
https://de-vlog.tistory.com/ 이사중입니다

0개의 댓글