Pintos Project3: Swap In/Out

HiroPark·2023년 5월 24일
1

문제해결

목록 보기
11/11

  • 유저 프로그램이 메모리 할당요청
  • 그러나 모든 프레임이 할당돼있음
  • 현재 사용중이지 않은 프레임을 디스크로 swap out
  • 이 swap out된 페이지에 유저프로그램이 접근하기를 시도할 시, OS는 해당 페이지의 내용을 메모리에 돌려놓음으로서 페이지를 복구시킴(swap in)

이때, swap out 할 프레임을 고르는 과정이 필요하다. 일단 swap out이 어떤식으로 호출되는지 부터 살펴보자.

swap_out은 vm_claim_page -> vm_do_claim_page -> vm_get_frame 안에서 palloc_get_page로 빈 물리 공간을 할당받는데 실패했다면 vm_evict_frame 호출 -> vm_get_victim으로 victim될 물리프레임 얻어와서 해당 프레임에 해당하는 page에 대해 swap_out 호출 의 순서로 호출된다.

이때 시작점 역할을 하는 vm_claim_page를 부르는 호출자는 크게

  • vm_stack_growth
  • vm_try_handle_fault
  • supplemental_page_copy가 있다

fork시에 필요한 supplemental_page_copy는 제쳐두고, 나머지 두개를 살펴보자

vm_stack_growth는 앞선 포스팅에서도 살펴보았듯 페이지 폴트가 난 구역이 스택의 증가로 문제가 해결될 수 있는 구역이라면, 스택의 크기를 늘려주는 역할을 한다. 이때 claim_page를 통해서 페이지와 물리 프레임을 연결해준다.

한편, vm_try_handle_fault에서 vm_claim_page가 불리는 경우는 스택을 늘려줘서 해결되는 것이 아닌 다른 경우의 fault가 난 주소에 대해서, 페이지와 물리 프레임을 연결시켜주기 위해서이다.

이때 물리프레임을 연결해주려면, 빈 프레임을 찾아야 하는데, 이를 담당하는 것이 vm_do_claim_page() 안의 vm_get_frame()이다.

static struct frame *
vm_get_frame (void) {
	struct frame *frame = (struct frame*)calloc(1, sizeof(struct frame));
	/* TODO: Fill this function. */

	frame->kva = palloc_get_page(PAL_USER);		// RAM user pool -> (virtual) kernel VA로 1page 할당

	if (frame->kva == NULL) {
		// PANIC("todo");
		frame = vm_evict_frame();		// RAM user pool이 없으면 frame에서 evict, 새로 할당
		frame->page = NULL;

		ASSERT(frame != NULL);
		ASSERT(frame->page == NULL);

		return frame;
	}

	list_push_back(&frame_table, &frame->frame_elem);
	frame->page = NULL;

	ASSERT(frame != NULL);
	ASSERT(frame->page == NULL);

	return frame;
}

이때, palloc으로 공간을 얻어올 수 있다면 문제가 없지만, 그럴 수 없다면(frame->kva가 NULL이라면) evict를 통해서 프레임을 차지하고 있는 페이지를 쫓아내야 한다.

static struct frame *
vm_evict_frame (void) {
	struct frame *victim UNUSED = vm_get_victim ();
	/* TODO: swap out the victim and return the evicted frame. */
	swap_out(victim->page);

	return victim;
}

우선, vm_get_victim을 통해서 쫒아낼 프레임을 고른다.

static struct frame *
vm_get_victim (void) {
	struct frame *victim = NULL;
	    /* TODO: The policy for eviction is up to you. */
    struct thread *curr = thread_current();
    struct list_elem *e = start;

    for (start = e; start != list_end(&frame_table); start = list_next(start)) {
        victim = list_entry(start, struct frame, frame_elem);
        if (pml4_is_accessed(curr->pml4, victim->page->va))
            pml4_set_accessed (curr->pml4, victim->page->va, 0);
        else
            return victim;
    }

    for (start = list_begin(&frame_table); start != e; start = list_next(start)) {
        victim = list_entry(start, struct frame, frame_elem);
        if (pml4_is_accessed(curr->pml4, victim->page->va))
            pml4_set_accessed (curr->pml4, victim->page->va, 0);
        else
            return victim;
    }

	return victim;
}

그리고, 이 프레임과 연결된 페이지에 대해 swap_out()을 진행하는데, 이는 페이지가 file-backed냐, anon이냐에 따라서에 따라서 호출되는 함수가 달라진다.

우선, anoymous의 경우, 디스크에 backing store가 따로 없기때문에, 이를 만들어줘야 한다.

그래서 vm_anon_init에서 다음과 같이 만들어준다.

void
vm_anon_init (void) {
	/* TODO: Set up the swap_disk. */
	swap_disk = disk_get(1, 1); // -> 인자로 1, 1 이 들어가면 swap 영역을 반환받음
	size_t swap_size = disk_size(swap_disk) / SECTORS_PER_PAGE;
	swap_table = bitmap_create(swap_size);
}

swap_table은 swap_disk내에서의 free/used 영역을 관리하기 위한 비트맵이다.

anon 페이지의 swap out을 담당하는 anon_swap_out은 다음과 같이 생겼다.

static bool
anon_swap_out (struct page *page) {
	struct anon_page *anon_page = &page->anon;

	// 비트맵을 순회해 false(해당 swap slot이 비어있는) 비트를 찾음

	int page_no = bitmap_scan(swap_table, 0, 1, false);

    if (page_no == BITMAP_ERROR) {
        return false;
    } 

	// 해당 섹터에 페이지 크기만큼 써줘야 하니, 필요한 섹터수만큼을 disk_write()를 통해서 입력해줌
	for (int i = 0; i < SECTORS_PER_PAGE; ++i) {
		disk_write(swap_disk, page_no * SECTORS_PER_PAGE + i ,page->va + DISK_SECTOR_SIZE * i);
	} 

	// write 작업이 끝나서 해당 스왑 공간에 페이지가 채워졌으니, bitmap_set()으로 slot이 찼다고 표시 
    bitmap_set(swap_table, page_no, true);

	// pml4_clear_page()로 물리 프레임에 올라와있던 페이지를 지움 
	pml4_clear_page(thread_current()->pml4, page->va);

	// 페이지의 swap_index 값을 이 페이지가 저장된 swap slot의 번호로 저장
	anon_page->swap_index = page_no;

	return true;
}

나는 여기서 왜 disk_write를 page_no x SECTORS_PER_PAGE + i 만큼 건너뛰어가며 page->va + DISK_SECTOR_SIZE x i 위치의 buffer를 써주는지가 궁금했다.

이유는 다음과 같다.
일단 disk_write 함수는 DISK_SECTOR_SIZE 바이트만큼을 버퍼에서 디스크의 특정 섹터로 쓴다.

SECTORS_PER_PAGE는 한 페이지에 몇개의 디스크 섹터가 존재하는지를 나타낸다.
따라서, 시작 섹터 번호는 비트맵에서의 순서인 page_no에 SECTOS_PER_PAGE만큼을 곱한 곳이고, 여기서부터 8(4096/512)개의 섹터에 페이지의 값을 쓰면 된다.

그리고 이때 복사를 할 버퍼(가상주소)의 경우에는
해당 주소로부터 DISK_SECTOR_SIZE만큼 8번을 복사해주어야 하므로 세번째 인자에는
page->va + DISK_SECTOR_SIZE x i 가 들어간다

file-backed의 swap_out은 다음과 같다.

static bool
file_backed_swap_out (struct page *page) {
	if (page == NULL) {
		return false;
	}

	struct file_page *file_page UNUSED = &page->file;


	struct info_aux *aux = (struct info_aux*)page->uninit.aux;

	if (pml4_is_dirty(thread_current()->pml4, page->va)) {
		file_write_at(aux->file, page->va, aux->read_bytes, aux->offset);
		pml4_set_dirty(thread_current()->pml4, page->va, 0);
	}

	pml4_clear_page(thread_current()->pml4, page->va);
	return true;
}

file-backed의 경우에는 file이 anon의 swap_disk와 같은 역할을 하기때문에, 파일에다가 va의 값을 써주고, pml4에서 페이지를 정리하면 된다.

swap in은 프로세스가 swap out 된 페이지에 접근하려 할때, 컨텐츠를 메모리에 돌려놓음으로써 페이지를 복구하는 과정이다.

이 또한 do_claim_page에서 호출되는데, 물리 프레임을 claim할 페이지가 pml4 테이블에 없는 경우, pml4에 해당 페이지를 세팅한 이후에, 페이지가 스택에 해당하는 페이지가 아니라면 swap_in을 진행한다.

swap_in도 마찬가지로 file_backed이냐, anon 페이지이냐에 따라서 실행되는 함수가 달라진다.

우선 anon의 swap_in을 살펴보자.

static bool
anon_swap_in (struct page *page, void *kva) {
	struct anon_page *anon_page = &page->anon; 

	int page_no = anon_page->swap_index; 

	if (bitmap_test(swap_table, page_no) == false) {
		return false;
	}

	// 디스크의 값을 물리 프레임에 다시 적재한다
	for (int i = 0; i < SECTORS_PER_PAGE; ++i) {
		disk_read(swap_disk, page_no * SECTORS_PER_PAGE + i, page->va + DISK_SECTOR_SIZE * i);
	}

    bitmap_set(swap_table, page_no, false);

	return true;
}

swap table에서의 위치인 swap index를 꺼내서,
해당 위치의 디스크의 값을 물리프레임에 다시 적재한다.

그리고, swap영역을 관리하는 swap_table에서, 해당 인덱스에 해당하는 값을 false로 설정해준다.

파일의 경우에는 aux에 저장된 파일 정보를 불러와서, 파일의 값을 물리프레임에 다시 적재해주면 된다.

static bool
file_backed_swap_in (struct page *page, void *kva) {
	// 페이지 예외처리
	if (page == NULL) {
		return false;
	}

	struct file_page *file_page UNUSED = &page->file;

	// aux로부터 파일, 오프셋, read_byte, zero_byte 읽어옴
	struct info_aux *aux = page->uninit.aux;
	struct file *file = aux->file;
	off_t offset = aux->offset;
	uint32_t read_bytes = aux->read_bytes;
	uint32_t zero_bytes = aux->zero_bytes;

	// file의 pos값 변경해주고, 파일의 값을 물리프레임에 씀, 이때 읽는 바이트 수 체크해서 예외처리
	file_seek(file, offset);
	if (file_read_at(file, kva, read_bytes, offset) != (int)read_bytes) {
		return false;
	} 

	// zero byte는 0으로 설정
	memset(kva + read_bytes, 0, zero_bytes);

	return true;
}

문제는...여기까지 작성했는데 swap 관련 코드가 swap-fork빼고 통과가 안된다는 것이다.
현재 page-merge 테스트 3개를 포함해 총 6개의 테스트가 통과가 되지 않고 있다.

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

0개의 댓글