[가상메모리] PintOS의 Swap

Serye·2023년 5월 23일
1

운영체제

목록 보기
7/7
post-thumbnail

서론

이번 주차는 개인적으로 아쉬움이 많이 남는다. 이전까지는 그래도 마지막 날이 됐을 때는 내용이 머릿속에서 구조화가 되었는데, vm은 여전히 머리에서 각각의 조각들이 둥둥 떠다닌다. 우리 조는 가장 중요한 부분이 swap in/out이라 생각하여 해당 부분을 정리해보았다.

Swap

스왑 공간은 가상 메모리의 일부를 디스크에 저장하는 공간입니다. 가상 메모리는 물리적인 메모리와 스왑 공간 간의 데이터 이동을 통해 필요한 메모리를 관리합니다. 이를 통해 유한한 물리적인 메모리와 디스크 공간을 최대한 활용하면서 메모리 부족 상황을 관리할 수 있습니다. 스왑 공간은 메모리 관리와 성능 향상을 위해 중요한 역할을 합니다.

void vm_anon_init (void) {
	swap_disk = disk_get(1, 1);
    size_t swap_size = disk_size(swap_disk) / SECTORS_PER_PAGE;
    swap_table = bitmap_create(swap_size);
}

Pintos 프로젝트에서 스왑 인/아웃을 구현하기 위해서 몇 가지를 추가해야 합니다. 첫 번째로, vm_anon_init() 함수를 수정하여 스왑 디스크와 스왑 테이블을 설정합니다. 스왑 디스크는 익명 페이지를 저장하는 임시 공간으로 사용되며, 스왑 테이블은 스왑 디스크의 사용 가능한 영역과 사용 중인 영역을 관리하는 비트맵 형태로 구현됩니다.

vm_anon_init() 함수는 익명 스왑 영역을 초기화하는 역할을 합니다. Pintos에서는 스왑 디스크를 채널 1, 디바이스 1로 지정하고 있습니다.

다음으로, disk_size() 함수를 사용하여 스왑 디스크의 크기를 페이지 단위로 계산합니다.

마지막으로, bitmap_create() 함수를 사용하여 swap_size만큼의 비트맵을 생성합니다. 이 비트맵은 스왑 테이블로 사용되며, 스왑 영역에서 어떤 페이지가 사용 중인지 여부를 추적하는 데 사용됩니다.

swap_in

'Swap In'은 swap 영역에 있는 페이지를 메모리로 다시 불러오는 과정으로, 프로세스가 swap 영역에 있는 페이지를 요청하면, 운영체제는 그 페이지를 메모리로 불러와서 프로세스에 제공합니다.

bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED,
                         bool user UNUSED, bool write UNUSED, bool not_present UNUSED)
{
    struct supplemental_page_table *spt UNUSED = &thread_current()->spt;
  	...
    void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
    if (not_present)
    {
        if (!vm_claim_page(addr))
        {
            if (rsp_stack - 8 <= addr && USER_STACK - 0x100000 <= addr && addr <= USER_STACK)
            {
                vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
                return true;
            }
            return false;
        }
        else
            return true;
    }
    return false;
}

‘Swap In’의 첫 시작은 vm_try_handle_fault() 입니다. 만약 해당 페이지가 존재하지 않으면 스택 확장 등의 추가 작업을 수행하고 페이지를 확보하기 위해 vm_claim_page 함수를 호출합니다.

bool vm_claim_page(void *va UNUSED)
{
    ...
    return vm_do_claim_page(page);
}

vm_claim_page 함수는 주어진 가상주소 ‘va’에 할당된 페이지를 요청하는 기능을 수행합니다. vm_do_claim 함수의 반환값을 그대로 반환하여 페이지 할당 작업의 성공 여부를 표시합니다.

static bool vm_do_claim_page(struct page *page)
{
    ...

    if (install_page(page->va, frame->kva, page->writable))
    { 
        return swap_in(page, frame->kva);
    }
    return false;
}

해당 함수는 프레임을 가져오기 위해 vm_get_frame 함수를 호출하고 가져온 프레임과 페이지를 연결합니다. 이후 페이지 테이블 엔트리에 페이지의 가상 주소(VA)와 프레임의 물리 주소(PA)를 매핑하기 위해 pml4_set_page 함수를 호출합니다. 만약 매핑에 실패하면 페이지 할당을 해제하고 false를 반환합니다. 그렇지 않으면 swap_in 함수를 호출하여 스왑 영역에서 페이지를 가져와 메모리로 로드합니다.


swap_in 함수는 스왑 영역에 저장된 페이지를 메모리로 로드하는 과정을 수행합니다. 이 과정에서 anon_swap_in 또는 file_backed_swap_in 함수가 호출됩니다.

anon_swap_in은 익명 페이지를 스왑 인하여 메모리에 로드하고, file_backed_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, kva + DISK_SECTOR_SIZE * i);
    }

    bitmap_set(swap_table, page_no, false);
    
    return true;
}
static bool file_backed_swap_in (struct page *page, void *kva) {
	struct file_page *file_page UNUSED = &page->file;

	if (page == NULL)
        return false;

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

    struct file *file = aux->file;
	off_t offset = aux->offset;
    size_t page_read_bytes = aux->page_read_bytes;
    size_t page_zero_bytes = PGSIZE - page_read_bytes;

	file_seek (file, offset);

    if (file_read (file, kva, page_read_bytes) != (int) page_read_bytes) {
        return false;
    }

    memset (kva + page_read_bytes, 0, page_zero_bytes);

    return true;
}

swap_out

다음은 ‘Swap Out’입니다. 'Swap Out'은 메모리가 가득 찼을 때, 운영체제가 선택한 메모리 페이지를 디스크의 'swap' 영역으로 옮기는 과정입니다. 이로써 메모리 공간을 확보하여 새로운 프로세스나 데이터를 불러올 수 있게 됩니다.

static struct frame *vm_get_frame(void)
{
    struct frame *frame = (struct frame *)malloc(sizeof(struct frame));

    frame->kva = palloc_get_page(PAL_USER);
    if (frame->kva == NULL)
    {
        frame = vm_evict_frame();
        frame->page = NULL;

        return frame;
    }
    ...
}

vm_get_frame 함수는 페이지를 할당하기 위해 사용 가능한 프레임을 가져오는 과정을 수행합니다.

static struct frame *vm_evict_frame(void)
{
    struct frame *victim = vm_get_victim();
    swap_out(victim->page);

    return victim;
}

위 함수 내부에 vm_evict_frame 함수를 호출하여 필요에 따라 페이지를 스왑 아웃하고 해당 프레임을 반환합니다.

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;
}

또한 vm_get_victim 함수를 호출하여 스왑 아웃할 페이지의 프레임을 선택합니다.
선택된 프레임을 스왑 아웃하기 위해 스왑 아웃 함수를 호출하고 해당 프레임을 반환합니다.


swap_out 함수는 페이지를 스왑 아웃하여 메모리에서 해제하고, 해당 페이지를 스왑 영역에 저장하는 역할을 합니다.

이 과정에서 anon_swap_out 또는 file_backed_swap_out 함수가 호출되며, 스왑 슬롯을 할당하여 페이지의 내용을 복사하고, 페이지 테이블 엔트리와 스왑 테이블을 업데이트하여 페이지의 스왑 아웃을 표시합니다. 이를 통해 해당 프레임은 다른 페이지에 할당되어 메모리의 사용을 최적화합니다.

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

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

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

    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);
    }

    bitmap_set(swap_table, page_no, true);
    pml4_clear_page(thread_current()->pml4, page->va);

    anon_page->swap_index = page_no;

    return true;
}
static bool file_backed_swap_out (struct page *page) {
	struct file_page *file_page UNUSED = &page->file;
        
    if (page == NULL)
        return false;

    struct container * aux = (struct container *) page->uninit.aux;
    
    if(pml4_is_dirty(thread_current()->pml4, page->va)){
        file_write_at(aux->file, page->va, aux->page_read_bytes, aux->offset);
        pml4_set_dirty (thread_current()->pml4, page->va, 0);
    }

    pml4_clear_page(thread_current()->pml4, page->va);
}
profile
🎤 📷 ❄️ 🌊

0개의 댓글