이제 남은 구현은 Swap In/Out 이 남았다. 메모리 스와핑은 물리 메모리의 활용을 극대화하기 위한 메모리 회수기법이다. 메인 메모리의 프레임들이 모두 할당되면 시스템은 유저 프로그램이 요청하는 메모리 할당 요청을 더 이상 처리할 수 없다. 이에 대한 해결 방법은 현재 사용되지 않고 있는 메모리 프레임들을 디스크로 스왑 아웃 하는 것이다. 이는 일부 메모리 자원들을 해제시켜서 다른 애플리케이션들이 이 자원들을 사용할 수 있게 해 준다.
메모리는 고갈되었는데 메모리 할당 요청을 받았다는 것을 운영체제가 감지하면 swap 디스크로 퇴서(evict)시킬 페이지를 골라낸다. 그리고 메모리 프레임의 상태를 동일하게 디스크에 복사해 둔다.(스왑 아웃), 프로세스가 스왑 아웃된 페이지에 접근하려고 할 때, 운영체제는 디스크에 복사해둔 내용을 그대로 다시 메모리에 가져옴으로써 페이지를 다시 복원시킨다.
스왑 디스크란?
스왑 디스크(Swap Disk) 또는 스왑 공간(Swap Space)은 운영체제가 메인 메모리(RAM)가 부족할 때 임시로 데이터를 저장하는 디스크 공간을 말한다.
Pintos에서의 스왑 디스크
- 스왑 공간은 일반적으로 블록 장치(block device)로서 관리된다.
- pintos에서는 devices/block.h 를 통해 스왑 장치(swap_block)를 초기화하고, 페이지 단위로 블록에 쓰고 읽는 코드를 구현한다.
- bitmap 자료구조를 활용하여 어떤 스왑 슬롯이 비어 있는지 추적한다.
anon.c 구현
- vm/anon.c
#include "devices/disk.h"
#include "kernel/bitmap.h"
struct bitmap *swap_table;
struct lock bitmap_lock;
- bitmap은 스왑 공간을 추적하는 전역 비트다.
- swap_table 접근 시 동기화를 위한 락을 선언한다.
vm_anon_init 구현
- vm/anon.c
/* Initialize the data for anonymous pages */
void
vm_anon_init (void) {
/* TODO: Set up the swap_disk. */
swap_disk = disk_get(1,1);
if (swap_disk == NULL){
PANIC("No swap disk found!");
}
swap_table = bitmap_create(disk_size(swap_disk) / 8);
if (swap_table == NULL)
PANIC("Failed to create swap bitmap!");
lock_init(&bitmap_lock);
}
vm_anon_init 은 익명 페이지의 스왑 공간을 초기화하는 함수다.
swap_disk = disk_get(1,1);
- 스왑 디스크 장치를 가져온다.
- disk_get(CHANNEL, DEVICE)에서 (1, 1)은 보통 IDE 채널 1번의 슬레이브 디바이스를 의미한다.
- Pintos에서 이 위치는 보통 스왑 디스크로 예약되어 있다.
- 결과적으로 swap_disk는 이 디스크를 조작하는 데 사용하는 핸들이 된다.
swap_table = bitmap_create(disk_size(swap_disk) / 8);
- 스왑 디스크의 크기를 바탕으로 페이지 단위 비트맵을 생성한다.
- 디스크는 512바이트 sector 단위이므로:
- 페이지 1개 = 4KB = 8 섹터
- 전체 스왑 슬롯 개수 = disk_size(swap_disk) / 8
- 이 수만큼의 비트맵을 만들어, 각 스왑 슬롯이 사용 중인지 추적한다.
lock_init(&bitmap_lock);
- swap_table 접근 시 동시성을 보장하기 위해 락을 초기화한다.
- 여러 스레드가 동시에 swap 작업을 할 수 있으므로 필수다.
anon_initializer 수정
bool
anon_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
page->operations = &anon_ops;
struct anon_page *anon_page = &page->anon;
anon_page->swap_idx = BITMAP_ERROR;
return true;
}
struct anon_page *anon_page = &page->anon;
anon_page->swap_idx = BITMAP_ERROR;
- page->anon은 이 페이지가 VM_ANON일 때 사용되는 union 구조체의 멤버이다.
- anon_page->swap_idx: 이 페이지가 스왑 아웃되었을 때 디스크의 어느 스왑 슬롯(index)에 저장됐는지를 기록하는 필드다.
- BITMAP_ERROR는 일반적으로 유효하지 않은 인덱스(-1)를 의미하며, 현재 이 페이지는 스왑 되지 않았음을 나타낸다.
anon_swap_out 구현
/* Swap out the page by writing contents to the swap disk. */
static bool
anon_swap_out (struct page *page) {
struct anon_page *anon_page = &page->anon;
lock_acquire(&bitmap_lock);
size_t swap_idx = bitmap_scan_and_flip(swap_table, 0, 1, false);
lock_release(&bitmap_lock);
if (swap_idx == BITMAP_ERROR){
return false;
}
anon_page->swap_idx = swap_idx;
for (int i = 0; i < 8; i++){
disk_write(swap_disk, swap_idx * 8 + i, page->frame->kva + DISK_SECTOR_SIZE * i);
}
page->frame->page = NULL;
page->frame = NULL;
pml4_clear_page(thread_current()->pml4, page->va);
return true;
}
anon_swap_out 함수는 현재 메모리에 존재하는 익명 페이지를 스왑 디스크로 내보내는 작업을 수행한다. 이 함수는 페이지 교체가 필요할 때 호출되어 메모리 공간을 확보한다.
lock_acquire(&bitmap_lock);
size_t swap_idx = bitmap_scan_and_flip(swap_table, 0, 1, false);
lock_release(&bitmap_lock);
- bitmap_lock을 획득하여 swap_table에 동기적으로 접근한다.
- bitmap_scan_and_flip() 함수는 비어 있는(값이 false) 슬롯을 찾아 true로 설정하고 인덱스를 반환한다.
- 사용 가능한 스왑 슬롯이 없으면 BITMAP_ERROR를 반환한다.
- 락을 해제하고 다음 단계로 넘어간다.
if (swap_idx == BITMAP_ERROR){
return false;
}
anon_page->swap_idx = swap_idx;
- 스왑 슬롯이 없는 경우에는 false를 반환하고 함수 실행을 중단한다.
- 스왑 슬롯을 정상적으로 얻었다면, 해당 페이지의 anon_page 구조체에 스왑 인덱스를 저장한다.
for (int i = 0; i < 8; i++){
disk_write(swap_disk, swap_idx * 8 + i, page->frame->kva + DISK_SECTOR_SIZE * i);
}
- 1개의 페이지는 4KB이며, 디스크 섹터 크기는 512바이트이므로, 총 8개의 섹터로 구성된다.
- 루프를 통해 페이지 내용을 스왑 디스크의 해당 인덱스에 순차적으로 기록한다.
- 각 섹터의 위치는 swap_idx * 8 + i로 계산하며, 메모리 내 주소도 섹터 단위로 이동하며 쓴다.
page->frame->page = NULL;
page->frame = NULL;
- 페이지와 연결된 프레임 정보를 제거한다.
- 프레임이 다른 페이지와 연결되도록 준비시키거나, 해제 후 재사용 가능하게 만든다.
pml4_clear_page(thread_current()->pml4, page->va);
return true;
}
- 현재 스레드의 페이지 테이블에서 해당 페이지의 가상 주소 매핑을 제거한다.
- 이로써 해당 주소를 접근하면 page fault가 발생하게 되며, 다시 불러올 필요가 생긴다.
anon_swap_in 구현
/* Swap in the page by read contents from the swap disk. */
static bool
anon_swap_in (struct page *page, void *kva) {
struct anon_page *anon_page = &page->anon;
size_t swap_idx = anon_page->swap_idx;
if (!bitmap_test(swap_table, swap_idx)) {
return false;
}
if (swap_idx == BITMAP_ERROR) {
PANIC("swap_in idx is crazy");
return false;
}
for (int i = 0; i < 8; i++) {
disk_read(swap_disk, swap_idx * 8 + i, kva + i * DISK_SECTOR_SIZE);
}
page->frame->kva = kva;
lock_acquire(&bitmap_lock);
bitmap_set(swap_table, swap_idx, false);
lock_release(&bitmap_lock);
return true;
}
anon_swap_in 함수는 이전에 디스크로 스왑 아웃되었던 익명 페이지를 스왑 디스크에서 다시 메모리로 불러오는 작업을 수행한다. 페이지 폴트가 발생했을 때 해당 페이지가 스왑에 저장되어 있었다면 이 함수가 호출되어 내용을 복원한다.
struct anon_page *anon_page = &page->anon;
size_t swap_idx = anon_page->swap_idx;
- anon_page를 통해 익명 페이지 내부 데이터를 참조한다.
- 이전에 anon_swap_out() 시 저장된 swap_idx 값을 읽는다.
- 이 인덱스는 이 페이지의 내용이 디스크 상의 어느 위치에 있는지를 나타낸다.
if (!bitmap_test(swap_table, swap_idx)) {
return false;
}
- 해당 스왑 슬롯이 현재 사용 중인지 확인한다.
- 사용 중이 아니라면 잘못된 접근이므로 실패로 간주하고 false를 반환한다.
if (swap_idx == BITMAP_ERROR) {
PANIC("swap_in idx is crazy");
return false;
}
만약 스왑 인덱스가 BITMAP_ERROR (일반적으로 -1)인 경우, 이는 명백한 논리 오류이므로 즉시 커널 패닉을 발생시킨다.
for (int i = 0; i < 8; i++) {
disk_read(swap_disk, swap_idx * 8 + i, kva + i * DISK_SECTOR_SIZE);
}
- 페이지 하나는 4KB이며, 디스크는 512바이트 섹터 단위로 읽기 때문에 8번 반복해서 읽는다.
- swap_idx * 8 + i는 디스크의 섹터 번호를 계산한 것.
- 읽어온 데이터를 kva에 순차적으로 저장한다.
page->frame->kva = kva;
- 페이지 구조체 내의 프레임 포인터에 현재 복원된 커널 가상 주소를 연결한다.
- 이 작업으로 페이지와 프레임이 다시 연결된다.
lock_acquire(&bitmap_lock);
bitmap_set(swap_table, swap_idx, false);
lock_release(&bitmap_lock);
- 스왑 슬롯을 더 이상 사용하지 않으므로, 비트맵에서 해당 인덱스를 false로 설정하여 해제한다.
- 동시 접근을 방지하기 위해 락을 사용한다.
anon_destory 구현
/* Destroy the anonymous page. PAGE will be freed by the caller. */
static void
anon_destroy (struct page *page) {
struct anon_page *anon_page = &page->anon;
// 스왑 테이블에서 스왑 인덱스 해제
if (anon_page->swap_idx != BITMAP_ERROR) {
lock_acquire(&bitmap_lock);
bitmap_reset(swap_table, anon_page->swap_idx);
lock_release(&bitmap_lock);
}
// 프레임이 존재하면 프레임을 리스트에서 제거하고 해제
if (page->frame) {
list_remove(&page->frame->elem);
page->frame->page = NULL;
palloc_free_page(page->frame->kva);
free(page->frame);
page->frame = NULL;
}
pml4_clear_page(thread_current()->pml4, page->va);
}
anon_destroy 함수는 익명 페이지를 메모리와 스왑에서 정리하는 작업을 수행한다. 이 함수는 페이지의 해제가 필요할 때 호출되며, 스왑 슬롯 반납과 프레임 메모리 헤제, 페이지 테이블 정리를 수행한다.
struct anon_page *anon_page = &page->anon;
if (anon_page->swap_idx != BITMAP_ERROR) {
lock_acquire(&bitmap_lock);
bitmap_reset(swap_table, anon_page->swap_idx);
lock_release(&bitmap_lock);
}
- anon_swap_out()을 통해 스왑 디스크에 저장된 적이 있다면, swap_idx는 BITMAP_ERROR가 아닌 유효한 인덱스를 가진다.
- 이 경우 스왑 테이블에서 해당 인덱스를 false로 되돌려 해제한다.
- 동시 접근을 방지하기 위해 bitmap_lock을 사용한다.
if (page->frame) {
list_remove(&page->frame->elem);
page->frame->page = NULL;
palloc_free_page(page->frame->kva);
free(page->frame);
page->frame = NULL;
}
- 페이지가 메모리에 존재하는 경우, 프레임이 연결되어 있다.
- 이 경우 다음을 수행한다:
- 프레임을 프레임 테이블에서 제거한다 (list_remove)
- 프레임과 페이지의 연결을 끊는다
- 실제 페이지 데이터를 담고 있는 물리 페이지(kva)를 반환한다 (palloc_free_page)
- 프레임 구조체 자체도 free()로 해제한다
pml4_clear_page(thread_current()->pml4, page->va);
- 해당 가상 주소가 현재 프로세스의 페이지 테이블에 남아 있다면 매핑을 제거한다.
- 이로써 더 이상 해당 주소로 접근 시 페이지 폴트가 발생한다.
file.c 구현
- vm/file.c
#include "vm/file.h"
#include "filesys/filesys.h"
#include "userprog/syscall.h"
헤더 파일을 추가한다.
file_backed_swap_out 구현
/* Swap out the page by writeback contents to the file. */
static bool
file_backed_swap_out (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
if (pml4_is_dirty(thread_current()->pml4, page->va) && page->writable) {
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, false);
}
page->frame->page = NULL;
page->frame = NULL;
pml4_clear_page(thread_current()->pml4, page->va);
return true;
}
file_backed_swap_out 함수는 파일에 매핑된 페이지를 메모리에서 내보낼 때 호출되며, 필요시 페이지 내용을 원래 파일로 write-back(재기록)하는 역할을 한다. 즉, 파일과 연결된 페이지를 스왑 아웃할 때 해당 내용을 디스크의 파일에 반영하고 메모리 자원을 해제한다.
struct file_page *file_page UNUSED = &page->file;
- 페이지는 union 형태로 file_page 구조체를 포함한다.
- file_page 구조체에는 다음과 같은 정보가 담겨 있다:
- 원래 읽어왔던 파일 포인터 (file_page->file)
- 파일 내 오프셋 (file_page->ofs)
- 읽은 바이트 수 (file_page->read_bytes)
if (pml4_is_dirty(thread_current()->pml4, page->va) && page->writable) {
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, false);
}
- 해당 페이지가 dirty bit가 설정되어 있고, 쓰기 가능한 페이지일 경우,
- 즉, 메모리에서 페이지 내용이 수정되었고, 이를 파일에 반영할 수 있는 경우라면:
- file_write_at()을 사용해 페이지 내용을 원래 파일에 다시 씀(write-back)
- 대상: file_page->file (원래 읽어온 파일)
- 소스: page->va (페이지의 사용자 가상 주소)
- 크기: file_page->read_bytes (파일에서 읽어온 원래 크기)
- 오프셋: file_page->ofs (파일 내 오프셋 위치)
- 쓰기 후에는 해당 페이지의 dirty bit를 false로 초기화한다.
- file_write_at()을 사용해 페이지 내용을 원래 파일에 다시 씀(write-back)
page->frame->page = NULL;
page->frame = NULL;
- 페이지와 프레임 구조체 간 연결을 끊는다.
- 이때 실제 프레임 구조체나 물리 메모리를 free하지는 않으며, 프레임 테이블이나 외부에서 처리한다는 전제이다.
pml4_clear_page(thread_current()->pml4, page->va);
- 현재 스레드의 페이지 테이블에서 이 가상 주소에 대한 매핑을 제거한다.
- 이후 이 주소에 접근하면 페이지 폴트가 발생한다.
file_backed_swap_in 구현
/* Swap in the page by read contents from the file. */
static bool
file_backed_swap_in (struct page *page, void *kva) {
struct file_page *file_page UNUSED = &page->file;
file_read_at (file_page->file, kva, file_page->read_bytes, file_page->ofs);
return true;
}
file_backed_swap_in 함수는 파일 매핑 페이지가 페이지 폴트 등으로 메모리에 다시 올라올 때 해당 내용을 파일로부터 읽어와 메모리에 복원하는 역할을 한다.
struct file_page *file_page UNUSED = &page->file;
- page 구조체 내부의 file_page를 통해 이 페이지가 매핑된 파일 정보에 접근한다.
- file_page 구조체에는 다음이 포함된다:
- file_page->file: 페이지가 매핑된 파일 포인터
- file_page->ofs: 파일 내에서 읽기 시작할 오프셋
- file_page->read_bytes: 읽을 바이트 수 (일반적으로 최대 한 페이지 크기)
file_read_at (file_page->file, kva, file_page->read_bytes, file_page->ofs);
- 파일에서 read_bytes만큼 데이터를 읽어 kva 주소에 저장한다.
- file_read_at()은:
- file: 읽을 파일 핸들
- kva: 읽은 데이터를 저장할 메모리 위치
- read_bytes: 읽을 길이
- ofs: 파일 내 오프셋 위치를 의미한다.
- 이로써 페이지의 내용이 파일로부터 복원된다.
file_backed_destroy 구현
/* Destory the file backed page. PAGE will be freed by the caller. */
static void
file_backed_destroy (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
// TODO: 해당 페이지가 dirty 상태인지 확인
// - pml4_is_dirty() 또는 page->frame->is_dirty 사용
if (pml4_is_dirty(thread_current()->pml4, page->va) && page->writable){
// TODO: dirty라면, 파일에 해당 내용을 file_write_at()으로 저장
// - page->va, aux->file, aux->offset 등에서 정보 추출
// - writable 여부도 확인
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, 0);
}
hash_delete(&thread_current()->spt.pages, &page->hash_elem);
if (page->frame) {
list_remove(&page->frame->elem);
page->frame->page = NULL;
page->frame = NULL;
free(page->frame);
}
pml4_clear_page(thread_current()->pml4, page->va);
}
file_backed_destroy는 파일에 매핑된 페이지를 삭제할 때 호출된다. 이 함수는 페이지의 메모리와 매핑을 해제하고, 필요한 경우 페이지 내용을 파일에 write-back(재기록) 한다.
struct file_page *file_page UNUSED = &page->file;
- page 내부의 file_page 구조체를 참조하여 파일 정보, 오프셋, 읽은 바이트 수 등을 가져온다.
if (pml4_is_dirty(thread_current()->pml4, page->va) && page->writable){
- 현재 페이지가 dirty 상태인지 확인한다:
- 페이지가 수정되었고(dirty),
- 쓰기 가능한(writable) 페이지일 때만 파일에 다시 쓴다.
- pml4_is_dirty()는 페이지 테이블의 dirty bit를 확인한다.
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, 0);
- 페이지 내용을 해당 파일에 재기록(write-back) 한다:
- file_page->file: 기록 대상 파일
- page->va: 현재 페이지의 가상 주소 (데이터 소스)
- read_bytes: 원래 읽어온 바이트 수 (이만큼만 저장)
- ofs: 파일 내 오프셋
- write-back 후 dirty 비트를 클리어한다.
hash_delete(&thread_current()->spt.pages, &page->hash_elem);
- 현재 프로세스의 supplemental page table(SPT)에서 이 페이지를 제거한다.
- 해시 테이블에서 더 이상 이 페이지를 찾을 수 없게 된다.
if (page->frame) {
list_remove(&page->frame->elem);
page->frame->page = NULL;
page->frame = NULL;
free(page->frame);
}
- 페이지가 메모리에 존재하면, 연결된 프레임을 정리한다:
- 프레임 리스트에서 제거
- 페이지와 프레임 간 연결 해제
- 프레임 구조체 메모리를 free()로 해제
pml4_clear_page(thread_current()->pml4, page->va);
- 프로세스의 페이지 테이블에서 이 가상 주소의 매핑을 제거한다.
- 이 주소는 더 이상 유효하지 않으며, 접근 시 page fault가 발생한다.
spt_remove_page 수정
void
spt_remove_page (struct supplemental_page_table *spt, struct page *page)
{
hash_delete(&spt->pages, &page->hash_elem);
vm_dealloc_page(page);
return true;
}
기존 함수에 hash_delete() 함수를 포함한다. SPT에서 페이지를 제거한다. 즉 해당 페이지를 프로세스의 보조 테이블에서 더 이상 참조하지 않도록 만들기 위한 작업이다.
'크래프톤 정글' 카테고리의 다른 글
[pintos] Week4~5: Virtual Memory - Part.7 페이지 교체 및 구현 완료를 위한 수정 (0) | 2025.06.07 |
---|---|
[pintos] Week4~5: Virtual Memory - Part.5 Memory Mapped Files (1) | 2025.06.07 |
[pintos] Week4~5: Virtual Memory - Part.4 Stack Growth (0) | 2025.06.06 |
[pintos] Week4~5: Virtual Memory - Part.3 Anonymous Page (0) | 2025.06.06 |
[pintos] Week4~5: Virtual Memory - Part.2 Memory Management (1) | 2025.06.06 |