[PintOS] Project3: Anonymous page & Lazy Loading

ktkdgh·2022년 6월 28일
0

Development Log

목록 보기
38/45

Anonymous page & Lazy Loading

Anonymous page

  • 파일으로부터 매핑되지 않은, 커널로부터 할당된 페이지를 뜻한다.
  • 익명 페이지는 힙을 거치지 않고 할당받은 메모리 공간
  • 스택, 힙과 같은 실행 파일에서 사용됨

Lazy Loading(Demanding Paging)

lazy loading은 메모리 로딩이 필요한 시점까지 지연되는 디자인

가상 page만 할당해두고 필요한 page를 요청하면 page fault가 발생하고 해당 page를 type에 맞게 초기화하고 frame과 연결하고 userprogram으로 제어권을 넘긴다.

지연로딩 순서

  1. 커널이 새 page를 요청하면 vm_alloc_page_with_initializer 호출
  2. initializer는 페이지 구조를 할당하고 페이지 type에 따라 적절한 initalizer를 할당하고 SPT 페이지에 추가후 userprogram으로 반환함
  3. 해당 페이지에 access 호출이 오면 내용이 없는 페이지 임으로 page fault가 발생
  4. uninit_initialize 를 호출하고 이전에 설정한 initializer를 호출한다.
  5. page를 frame가 연결하고 lazy_load_segment 를 호출하여 필요한 데이터를 물리메모리에 올린다.

생명주기

initialize->(page_fault->lazy-load->swap-in>swap-out->...)->destroy

  1. Unintalized page 구현
 vm_alloc_page_with_initializer
  • 인자로 주어진 type으로 초기화되지 않은 page를 만든다.
  • SPT에 새로 만든 page를 추가하여 준다.
  • 제일 처음 만들어지는 페이지는 uninit page이다.
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;

	if (spt_find_page(spt, upage) == NULL)
	{
		struct page *new_page = malloc(sizeof(struct page));

		if (type == VM_ANON)
		{
			uninit_new(new_page, upage, init, type, aux, anon_initializer);
		}
		else if (type == VM_FILE)
		{
			uninit_new(new_page, upage, init, type, aux, file_backed_initializer);
		}
		new_page->writable = writable;
		spt_insert_page(spt, new_page);
		return true;
	}
err:
	return false;
}
  1. Anonymous Page구현

ANON page 타입에 대한 함수들 수정

Anonymous Page에서 중요한 점은 물리메모리와 맵핑될 때 초기화할 때 모든 데이터를 Zeroing해주어야한다.

anon_initializer
  • 기존 page안에 있는 union 안의 uninit_page에 대한 데이터를 모두 0으로 만들어준다.
  • operations을 anon_ops로 변경하고 anon_page에 대한 정보를 바꿔준다.
bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
	/* page struct 안의 Union 영역은 현재 uninit page이다.
		 ANON page를 초기화해주기 위해 해당 데이터를 모두 0으로 초기화해준다.
		 Q. 이렇게 하면 Union 영역은 모두 다 0으로 초기화되나? -> 맞다. */
	struct uninit_page *uninit = &page->uninit;
	memset(uninit, 0, sizeof(struct uninit_page));

	/* Set up the handler */
	/* 이제 해당 페이지는 ANON이므로 operations도 anon으로 지정한다. */
	page->operations = &anon_ops;

	struct anon_page *anon_page = &page->anon;
	anon_page->swap_index = -1;

	return true;
}
  • 이 부분은 구현에서 전혀 작성하지 못한 부분이다.
    union에 대한 값 초기화와 anon_page에 대한 값을 생각하지 못하였다.
  1. load_segment 구현 (수정)

project1, 2까지의 pintos는 디스크에서 실행시킬 파일 전체를 물리메모리에 올리고 페이지 테이블에 맵핑해주었다.

이제는 load_segment 를 수정하여 page를 만들고 file에 대한 정보만을 initializer로 넘겨준다. 디스크에 있는 데이터를 물리메모리에 바로 올리지 않는다.

load_segment
  • file_information 구조체를 추가하여 file을 load할때 필요한 file, offset, read_bytes를 저장하고 initializer를 호출하고 aux 인자로 넘겨준다.
  • 파일을 page 단위로 끊어서 uninit 페이지로 만들고 file 정보를 page에 저장하고 SPT에 추가한다.
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);

	/* upage 주소부터 1페이지 단위씩 UNINIT 페이지를 만들어 프로세스의 spt에 넣는다(vm_alloc_page_with_initializer).
		 이 때 각 페이지의 타입에 맞게 initializer도 맞춰준다. */
	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;

		struct file_information *file_inf = (struct file_information *)malloc(sizeof(struct file_information));
		file_inf->file = file;
		file_inf->ofs = ofs;
		file_inf->read_bytes = page_read_bytes;

		if (!vm_alloc_page_with_initializer(VM_ANON, upage,
																				writable, lazy_load_segment, file_inf))
			return false;
		/* Advance. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		upage += PGSIZE;
		ofs += page_read_bytes;
	}
	return true;
}

Q. anon 페이지로 모든 page를 만들고 있는데 file에서 올리는 거면 왜 anon 페이지로 만들어 주지?

lazy_load_segment() 구현

  • 프로세스가 uninit_page로 처음 접근하여 page_fault가 발생하면 해당 함수가 호출된다.
  • 호출된 page를 frame과 맵핑하고 해당 page에 연결된 물리메모리에 file 정보를 load 해준다.
static bool
lazy_load_segment(struct page *page, void *aux)
{
	/* TODO: Load the segment from the file */
	struct file *file = ((struct file_information *)aux)->file;
	off_t offset = ((struct file_information *)aux)->ofs;
	size_t page_read_bytes = ((struct file_information *)aux)->read_bytes;
	size_t page_zero_bytes = PGSIZE - page_read_bytes;
	file_seek(file, offset); // file의 오프셋을 offset으로 바꾼다. 이제 offset부터 읽기 시작한다.

	/* 페이지에 매핑된 물리 메모리(frame, 커널 가상 주소)에 파일의 데이터를 읽어온다. */
	/* 제대로 못 읽어오면 페이지를 FREE시키고 FALSE 리턴 */
	if (file_read(file, page->frame->kva, page_read_bytes) != (int)page_read_bytes)
	{
		palloc_free_page(page->frame->kva);
		return false;
	}
	/* 만약 1페이지 못 되게 받아왔다면 남는 데이터를 0으로 초기화한다. */
	memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);

	return true;
}

setup_stack() 재구현

기존의 setup_stack()를 SPT가 추가된 상황에 맞게 수정한다.

setup_stack은 page를 할당하고 바로 물리 메모리와 맵핑하여 준다. → page_fault가 발생전에 물리메모리에 바로 할당

  • stack_bottom을 page의 va(가상주소 시작위치)로 할당하기 위해 인자로 넘겨준다.
  • stack_bottom을 추가 할당을 위하여 thread_current에 stack_bottom저장 변수를 만들고 넣어준다.
static bool
setup_stack(struct intr_frame *if_)
{
	bool success = false;
	void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
	/* ANON 페이지로 만들 UNINIT 페이지를 stack_bottom에서 위로 PGSIZE만큼(1 PAGE) 만든다.
		 이 때 TYPE에 VM_MARKER_0 flag를 추가함으로써 이 페이지가 STACK에 있다는 것을 표시한다. */
	if (vm_alloc_page_with_initializer(VM_ANON, stack_bottom, true, NULL, NULL))
	{
		success = vm_claim_page(stack_bottom);
	};

	if (success)
	{
		if_->rsp = USER_STACK;
		thread_current()->stack_bottom = stack_bottom;
	}
	return success;
}
/*   구현 후 스택의 모습
		 ------------------------- <---- USER_STACK == if_->rsp
		 |                       |
		 |       NEW PAGE        |
		 |                       |
		 |                       |
		 ------------------------- <---- stack_bottom
*/

Q. 왜 스택은 lazy loading을 하지 않고 바로 물리메모리와 맵핑해 주는 것인가??

  • setup stack 이후 파일 실행에 필요한 argument를 바로 stack에 넣어 주어야한다.
  • stack은 바로 사용하기 때문에 uninit page로 두지 않고 생성 후 바로 frame과 맵핑시켜준다.
/* Set up stack. */
	if (!setup_stack(if_))
		goto done;

	if_->rip = ehdr.e_entry;
	argument_stack(if_, argv_cnt, argv_list);

check_address() 수정

  • 기존의 check_address는 syscall의 인자로 받은 주소값이 유저의 영역인지 pml4에 있는지를 확인하였고 없다면 exit(-1)로 프로세스를 종료하였다.
  • SPT가 추가되면서 유효한 주소이지만 pml4에는 없을 수 있기에 함수를 수정하여 pml4에 없는 경우 프로세스를 바로 종료하지 않게 해야한다.
수정전
void check_address(void *addr)
{
	struct thread *cur = thread_current();
	/* 주소 addr이 유저 가상 주소가 아니거나 pml4에 없으면 */
	if (addr == NULL || !is_user_vaddr(addr) || pml4_get_page(cur->pml4, addr) == NULL)
	{
		exit(-1);
	}
	/* addr이 유저 가상 주소이고 동시에 pml4에 있으면 페이지를 리턴해야 한다. */
}

수정후
struct page* check_address(void *addr)
{
	/* 주소 addr이 유저 가상 주소가 아니거나 pml4에 없으면 프로세스 종료 */
	if (addr == NULL || !is_user_vaddr(addr))
	{
		exit(-1);
	}
	/* 유저 가상 주소면 SPT에서 페이지 찾아서 리턴 */
	return spt_find_page(&thread_current()->spt, addr);
}
  • 유저 가상 주소이면 SPT에서 찾고 page를 반환하여 주게 수정한다.

check_buffer

 Supplemental Page Table - Revisit(fork, exec)

SPT 테이블에 대한 copy와 clean up을 위한 수정이 필요함

child를 만들때 copy, process를 종료할 때 destroy가 필요

supplemental_page_table_copy

  • 부모의 STPd의 page는 같게 frame은 다르게 복사한다.
  • 부모의 물리메모리에 있는 데이터를 자식에게 복사하여 준다.

구현 요구사항

  1. src에서 dst로 SPT를 복사한다.
  2. child가 parent의 실행 컨텍스트를 상속해야 할때 사용된다.
  3. 각 page를 반복하고 dst의 SPT에 있는 항목의 복사본을 만든다.
  4. unitit page를 할당하고 claim을 바로한다.
  • 구현 코드
    /* Copy supplemental page table from src to dst */
    bool supplemental_page_table_copy(struct supplemental_page_table *dst UNUSED,
    																	struct supplemental_page_table *src UNUSED)
    {
    
    	struct hash_iterator parnet_hast_iter;
    
    	hash_first(&parnet_hast_iter, &src->hash);
    	while (hash_next(&parnet_hast_iter))
    	{
    		struct page *page = hash_entry(hash_cur(&parnet_hast_iter), struct page, hash_elem);
    
    		if (!vm_alloc_page_with_initializer(VM_ANON, page->va, page->writable, NULL, NULL))
    		{
    			return false;
    		}
    		struct page *child_page = spt_find_page(dst, page->va);
    
    		if (!vm_claim_page(page->va))
    		{
    			return false;
    		}
    		if (page->frame != NULL)
    		{
    			memcpy(child_page->frame->kva, page->frame->kva, PGSIZE);
    		}
    	}
    	return true;
    }

supplemental_page_kill

  • 프로세스가 종료 될 때 함수를 호출하고 SPT에 모든 리소스를 해제한다.
  • 모든 페이지에 대해서 destroy(page) 로 리소스 해제

uninit_destroy

  • 페이지의 type에 따라 페이지 구조가 보유한 리소스 해제
  • 현재까지는 anon_page만 처리하고 나중에 file_backed_page를 정리할 수 있다.

anon_destroy

  • 익명 페이지가 보유한 리소스 해제
  • page struct를 명시적으로 해제할 필요가 없고 호출자가 수행한다.


PintOS Project3 GIthub 주소 PintOS

0개의 댓글