이때, 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를 부르는 호출자는 크게
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개의 테스트가 통과가 되지 않고 있다.