물리 메모리의 활용을 극대화하기 위한 회수 기법. 현재 사용되지 않고 있는 메모리 프레임들을 디스크로 스왑 아웃 하는 것.
이는 일부 메모리 자원들을 해제 시켜서 다른 어플리케이션들이 이 자원들을 사용할 수 있게 해준다.
퇴거 대상
코드
// vm/anon.h
struct anon_page
{
uint32_t slot_no; // swap out될 때 이 페이지가 저장된 slot의 번호
};
// vm/vm.h
struct slot
{
struct page *page;
uint32_t slot_no;
struct list_elem swap_elem;
};
/* 스왑 관련된 구조체 초기화 */
struct list swap_table;
struct lock swap_table_lock;
vm_anon_init
, anon_initializer
함수 수정
스왑 디스크 설정 필요. 또한 스왑 디스크에서 사용 가능한 영역 and 사용된 영역 관리하기 위한 데이터 구조 필요.
코드
// vm/anon.c
void
vm_anon_init (void)
{
/* TODO: Set up the swap_disk. */
// 1. 스왑 디스크 설정
swap_disk = disk_get(1, 1);
// 2. 스왑 테이블 초기화
list_init(&swap_table);
// 3. 스왑 테이블 락 초기화
lock_init(&swap_table_lock);
// swap_disk 크기만큼 slot을 만들어서 swap_table에 넣어둔다.
// 1 slot에 1 page를 담을 수 있는 slot 개수 구하기
// : **1** sector = 512bytes, **1** page = 4096bytes -> **1** slot = **8** sector
// 스왑 슬룻 생성 및 초기화
disk_sector_t swap_size = disk_size(swap_disk) / 8;
// 각 슬룻은 **8개**의 섹터 사용(각 섹터는 512 바이트, 한 슬룻은 4096 바이트)
for (disk_sector_t i = 0; i < swap_size; i++)
{
struct slot *slot = (struct slot *)malloc(sizeof(struct slot));
slot->page = NULL;
slot->slot_no = i;
lock_acquire(&swap_table_lock);
list_push_back(&swap_table, &slot->swap_elem);
lock_release(&swap_table_lock);
}
}
페이지를 익명 페이지로 초기화
코드
bool
anon_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
// 1. 페이지 작업 구조체 설정
page->operations = &anon_ops;
// 2. 익명 페이지 구조체 가져오기
struct anon_page *anon_page = &page->anon;
// 3. 슬룻 번호 초기화
anon_page->slot_no = -1; // 초기화 함수가 호출되는 시점은 page가 매핑된 상태이므로 swap_slot을 차지x
return true;
}
스왑 디스크에서 페이지를 읽어와 메모리에 로드
이 함수들을 작동하여 익명 페이지의 스왑 인/스왑 아웃 및 제거 처리.
스왑 공간을 효율적 관리 + 페이지 폴트가 발생시 필요한 데이터를 디스크 → 메모리로 가져옴
코드
// vm/anon.c
static bool
anon_swap_in (struct page *page, void *kva)
{
struct anon_page *anon_page = &page->anon;
// 1. 스왑 슬룻 번호 가져오기
disk_sector_t page_slot_no = anon_page->slot_no; // page가 저장된 slot_no
struct list_elem *e;
struct slot *slot;
lock_acquire(&swap_table_lock);
// 2. 스왑 슬룻 검색
for (e = list_begin(&swap_table); e != list_end(&swap_table); e = list_next(e))
{
slot = list_entry(e, struct slot, swap_elem);
// 2. `slot_no`를 가진 슬룻을 찾기
if(slot->slot_no == page_slot_no)
{
// 3 페이지가 저장된 슬룻을 찾으면, 8개의 섹터를 읽어와 메모리에 로드
for (int i = 0; i < 8; i++)
{
// 디스크, 읽을 섹터 번호, 담을 주소(512bytes씩 읽는다. disk 관련은 동기화 처리가 되어 있어서 lock 불필요)
disk_read(swap_disk, page_slot_no * 8 + i, kva + DISK_SECTOR_SIZE * i);
}
// 4. 슬룻 업데이트
slot->page = NULL; // 빈 slot으로 업데이트.
anon_page->slot_no = -1; // 이제는 이 page는 swap_slot을 차지하지 않는다.
// 5. 락 해제 및 반환
lock_release(&swap_table_lock);
return true;
}
}
// 5. 락 해제 및 반환
lock_release(&swap_table_lock);
return false;
}
익명 페이지 -> 스왑 디스크에 저장.
코드
// vm/anon.c
static bool
anon_swap_out (struct page *page)
{
// 1. 유효성 검사
if (page == NULL)
{
return false;
}
struct anon_page *anon_page = &page->anon;
struct list_elem *e;
struct slot *slot;
lock_acquire(&swap_table_lock);
// 2. 스왑 테이블을 **순회**하며 **빈 슬룻**을 찾는다.
for (e = list_begin(&swap_table); e != list_end(&swap_table); e = list_next(e))
{
slot = list_entry(e, struct slot, swap_elem);
if (slot->page == **NULL**)
{
// 3. 빈 슬룻을 찾으면, 페이지를 **8개의 섹터**에 나눠 스왑 디스크에 저장.
for (int i = 0; i < 8; i++)
{
disk_write(swap_disk, slot->slot_no * 8 + i, page->va + DISK_SECTOR_SIZE * i);
}
// 4. 슬룻 업데이트
anon_page->slot_no = slot->slot_no; // page의 `slot_no`를 저장.
slot->page = page; // 페이지 필드 업데이트
// 5. page and frame의 연결을 끊는다.
page->frame->page = NULL;
page->frame = NULL;
// 6. 락 해제 및 반환
pml4_clear_page(thread_current()->pml4, page->va);
lock_release(&swap_table_lock);
return true;
}
}
lock_release(&swap_table_lock);
PANIC("insufficient swap space"); // 디스크에 더 이상 빈 슬룻이 없는 경우
}
익명 페이지를 destroy.
코드
static void
anon_destroy (struct page *page) {
// 1. 익명 페이지 접근
struct anon_page *anon_page = &page->anon;
// anonymous page에 의해 유지되던 리소스 해제.
// page_struct를 명시적으로 해제할 필요는 없으며, 호출자가 이를 수정해야 한다.
struct list_elem *e;
struct slot *slot;
// 3. 스왑 테이블 잠금 흭득
lock_acquire(&swap_table_lock);
// 4. 스왑 테이블에서 **슬룻** 찾기
for (e = list_begin(&swap_table); e != list_end(&swap_table); e = list_next(e))
{
slot = list_entry(e, struct slot, swap_elem);
// 각 슬룻의 'slot_no'가 익명 페이지의 'slot_no'와 **일치**하는지 확인
if (slot->slot_no == anon_page->slot_no)
{
slot->page = NULL; // 해당 슬룻의 'page' 필드를 NULL로 설정하여 슬룻이 더 이상 사용되지 않음을 나타낸다.
break;
}
}
// 5. 스왑 테이블 잠금 해제
lock_release(&swap_table_lock);
}
file_backed_swap_in
, file_backed_swap_out
구현
파일에서 내용을 읽어와 페이지를 스왑 인. 'lazy_load_segment' 함수를 호출하여 페이지의 내용을 파일에서 읽음
코드
static bool
file_backed_swap_in (struct page *page, void *kva) {
struct file_page *file_page UNUSED = &page->file;
return lazy_load_segment(page, file_page);
}
페이지의 내용을 파일에 다시 기록 + 페이지 and 프레임의 연결을 끊기
코드
static bool
file_backed_swap_out (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
// 1. 페이지가 수정된 경우, **내용**을 파일(디스크)에 기록
if (pml4_is_dirty(thread_current()->pml4, page->va))
{
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs ); // 페이지의 내용을 파일에 기록
pml4_set_dirty(thread_current()->pml4, page->va, 0); // 페이지의 수정 플래그 초기화
}
// 2. 페이지와 프레임의 **연결 끊기** - 이는 페이지가 더 이상 해당 프레임 사용x
page->frame->page = NULL;
page->frame = NULL;
// 3. 페이지 테이블에서 페이지 제거
pml4_clear_page(thread_current()->pml4, page->va);
return true;
}
파일로 백업된 페이지 파괴. 페이지를 메모리에서 해제 and 페이지가 수정된 경우 해당 내용을 파일에 다시 기록.
코드
static void
file_backed_destroy (struct page *page)
{
// 1. 'file_page' 구조체 가져오기
struct file_page *file_page UNUSED = &page->file;
// 2. 페이지가 수정되었는지 확인
if (pml4_is_dirty(thread_current()->pml4, page->va))
{
// 3. 페이지가 수정('dirty 상태인 경우')된 경우 파일에 쓰기
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, 0); // 페이지의 'dirty' 상태 해제
}
// 4. 페이지를 페이지 테이블에서 제거
pml4_clear_page(thread_current()->pml4, page->va);
}
vm_get_victim
, vm_evict_frame
, vm_get_frame
구현
Second Chance 알고리즘
(원형 큐) 구조를 사용하여 페이지 프레임을 관리.
원형 큐의 각 위치는 프레임을 나타내며, 프레임마다 참조 비트가 있다. 알고리즘은 포인터를 사용하여 현재 프레임 가리킨다.
Dirty Bit
페이지 교체 알고리즘에 따라 희생할 프레임을 선택하는 역할. 접근 비트를 이용한 Second Chance 알고리즘 사용
코드
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();
// 1. 프레임 테이블 접근을 보호하기 위해 락을 흭득
lock_acquire(&frame_table_lock);
struct list_elem *start = list_begin(&frame_table);
// 2. 프레임 테이블을 순회하며 희생할 프레임을 찾음.
for (start; start != list_end(&frame_table); start = list_next(start))
{
// 3. 프레임 선택
victim = list_entry(start, struct frame, frame_elem);
// 4. 할당되지 않은 프레임 확인.
if (victim->page == NULL)
{
lock_release(&frame_table_lock);
return victim; // 프레임이 할당되지 않은 경우, 해당 프레임을 바로 반환
}
// 5. 접근 비트 확인
if (pml4_is_accessed(curr->pml4, victim->page->va))
{
// 6. 접근 비트 초기화
pml4_set_accessed(curr->pml4, victim->page->va, 0);
}
// 7. 접근되지 않은 프레임 선택
else
{
// 8. 락 해제 or 반환
lock_release(&frame_table_lock);
return victim;
}
}
lock_release(&frame_table_lock);
return victim;
}
가상 메모리 시스템에서 페이지 교체. 메모리 > 디스크로 내보내느 과정. 새로운 페이지를 메모리에 불러올 때 사용. 희생 프레임 선택 > 선택된 프레임을 스왑 아웃 후 > 반환.
코드
static struct frame *
vm_evict_frame (void) {
// 1. 희생 프레임 선택(페이지 교체 정책에 따라 프레임 선택)
struct frame *victim = vm_get_victim();
/* TODO: swap out the victim and return the evicted frame. */
// 2. 희생 프레임의 페이지가 존재하는 경우
if (victim->page)
{
swap_out(victim->page); // 스왑아웃
}
// 3. 희생 프레임 반환 - 이제 물리 메모리에서 해제된 상태이므로, 새로운 페이지를 이 프레임에 매핑할 수 있다.
return victim;
}
페이지 할당 실패 처리가 NULL이었는데 > 페이지 교체 알고리즘을 사용하여 희생 프레임을 선택 and 반환.
코드
// 3. 페이지 할당 실패 처리
if(kva == NULL)
{
struct frame *victim = vm_evict_frame();
victim->page = NULL;
return victim;
}
파일로 백업된 페이지 '파괴'... 보링한 코딩 씬에서 보기 어려운 실로 그윽한 단어 선택이다. 이런, 아이사츠를 잊을 뻔. 이는 실로 무례한 행동으로 그 즉시 무라하치.
도모, oasisgorilla 입니다.
(무라하치는 음습한 사회적 린치를 말한다. 무서움!)