운영체제(OS)/with PintOS

(Project3-Virtual Memory) - Swap In/Out

스탠딩 2023. 12. 26. 21:24

스왑 인/아웃

메모리 스왑은 물리적 메모리의 사용을 최대화하기 위한 메모리 회수 기술 중 하나입니다. 메인 메모리의 프레임이 할당되어 있을 때 시스템은 더 이상 사용자 프로그램의 메모리 할당 요청을 처리할 수 없습니다. 이 문제를 해결하기 위한 한 가지 방법은 현재 사용되지 않는 메모리 프레임을 디스크로 스왑아웃하는 것입니다. 이렇게 하면 일부 메모리 자원이 해제되어 다른 응용 프로그램에 사용할 수 있게 됩니다.
스왑은 운영 체제에 의해 수행됩니다. 시스템이 메모리 부족 상태임을 감지하지만 메모리 할당 요청을 받으면, 현재 사용되지 않는 페이지를 스왑 디스크로 이동시킵니다. 그런 다음, 해당 메모리 프레임의 정확한 상태가 디스크에 복사됩니다. 프로세스가 스왑아웃된 페이지에 액세스하려고 할 때, 운영 체제는 페이지를 복구하여 정확한 내용을 다시 메모리로 가져옵니다.
퇴출되는 페이지는 익명 페이지 또는 파일 지원 페이지 중 하나일 수 있습니다. 이 섹션에서는 각 경우를 처리할 것입니다.
스왑 인/아웃 작업은 명시적으로 호출되는 것이 아니라 함수 포인터로서 발생합니다. 이러한 함수 포인터는 각 페이지의 이니셜라이저로 등록될 page_operations 구조체의 멤버입니다.

 

익명 페이지(Anonymous Page)

vm/anon.c 파일의 vm_anon_init 및 anon_initializer를 수정하십시오. 익명 페이지는 해당 페이지에 대한 배경 저장소가 없습니다. 익명 페이지의 스왑을 지원하기 위해 우리는 swap 디스크라고 불리는 임시 백업 저장소를 제공합니다. 익명 페이지의 스왑을 구현하기 위해 swap 디스크를 활용하게 됩니다.

void vm_anon_init (void);

이 함수에서는 스왑 디스크를 설정해야 합니다. 또한 스왑 디스크의 빈 영역과 사용 중인 영역을 관리하는 데이터 구조가 필요합니다. 스왑 영역은 PGSIZE(4096 바이트)의 단위로 관리될 것입니다.

void vm_anon_init(void)
{
    /* TODO: Set up the swap_disk. */
    swap_disk = disk_get(1, 1); // anon_page를 위한 스왑 디스크
    disk_bitmap = bitmap_create((size_t)disk_size(swap_disk)); // 스왑 디스크를 관리할 비트맵
    lock_init(&bitmap_lock); // 비트맵 락 초기화
}
bool anon_initializer (struct page *page, enum vm_type type, void *kva);

이것은 익명 페이지의 이니셜라이저입니다. 스왑을 지원하기 위해 익명 페이지에 몇 가지 정보를 추가해야 할 것입니다.

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->sec_no = SIZE_MAX;
    anon_page->thread = thread_current();

    return true;
}

 

이제 vm/anon.c에 있는 익명 페이지에 대한 스왑인(anon_swap_in) 및 스왑아웃(anon_swap_out)을 구현하세요. 페이지를 스왑인하려면 먼저 스왑아웃을 구현하는 것이 좋습니다. 데이터 콘텐츠를 스왑 디스크로 이동하고 안전하게 메모리로 다시 가져와야 합니다.

static bool anon_swap_in (struct page *page, void *kva);

이 함수는 익명 페이지를 스왑 디스크로부터 읽어와 메모리로 스왑인합니다. 페이지가 스왑아웃될 때 해당 데이터의 위치는 페이지 구조체에 저장되어 있어야 합니다. 스왑 테이블을 업데이트하는 것을 잊지 마세요 (스왑 테이블 관리 참조).

static bool
anon_swap_in(struct page *page, void *kva)
{
    // printf("anon_swap_in\n");
    struct anon_page *anon_page = &page->anon;

    if (anon_page->sec_no == SIZE_MAX)
        return false;

    lock_acquire(&bitmap_lock);
    bool check = bitmap_contains(disk_bitmap, anon_page->sec_no, 8, false);
    lock_release(&bitmap_lock);
    if (check)
    {
        return false;
    }

    for (int i = 0; i < 8; i++) // 스왑 디스크에서 데이터를 읽어옴
    {
        disk_read(swap_disk, anon_page->sec_no + i, kva + i * DISK_SECTOR_SIZE);
    }

    lock_acquire(&bitmap_lock);
    bitmap_set_multiple(disk_bitmap, anon_page->sec_no, 8, false); // 읽기 성공하면 false로 설정
    lock_release(&bitmap_lock);

    return true;
}
static bool anon_swap_out (struct page *page);
이 함수는 메모리에서 데이터 내용을 디스크로 복사하여 익명 페이지를 스왑 디스크로 스왑아웃합니다. 먼저, 스왑 테이블을 사용하여 디스크에서 빈 스왑 슬롯을 찾은 다음, 페이지의 데이터를 해당 슬롯으로 복사합니다. 데이터의 위치는 페이지 구조체에 저장되어야 합니다. 디스크에 더 이상 빈 슬롯이 없다면 커널을 패닉할 수 있습니다.
static bool anon_swap_out(struct page *page)
{
    struct anon_page *anon_page = &page->anon;

    lock_acquire(&bitmap_lock);
    disk_sector_t sec_no = (disk_sector_t)bitmap_scan_and_flip(disk_bitmap, 0, 8, false); // 스왑 가능한 섹터 탐색
    lock_release(&bitmap_lock);
    if (sec_no == BITMAP_ERROR)
        return false;

    anon_page->sec_no = sec_no;

    for (int i = 0; i < 8; i++)
    {
        disk_write(swap_disk, sec_no + i, page->frame->kva + i * DISK_SECTOR_SIZE); // 스왑 디스크에 쓰기
    }

    pml4_clear_page(anon_page->thread->pml4, page->va); // 페이지 테이블에서 해당 가상 주소에 대한 매핑 제거
    pml4_set_dirty(anon_page->thread->pml4, page->va, false); // 더티 비트 (0)false 설정(초기화)
    page->frame = NULL;

    return true;
}

파일 매핑된 페이지

파일 지원 페이지의 내용은 파일에서 가져오기 때문에 mmap된 파일은 백업 저장소로 사용되어야 합니다. 즉, 파일 지원 페이지를 퇴출하면 해당 페이지는 해당 파일로 다시 기록됩니다. vm/file.c에서 file_backed_swap_in과 file_backed_swap_out을 구현하세요. 설계에 따라 file_backed_init 및 file_initializer를 수정할 수 있습니다.

static bool file_backed_swap_in (struct page *page, void *kva);

주어진 가상 주소(kva)의 페이지를 파일로부터 읽어와 스왑인합니다. 파일 시스템과 동기화해야 합니다.

static bool
file_backed_swap_in (struct page *page, void *kva) {    
    struct file_page *file_page UNUSED = &page->file; // load_segmet와 비슷함
   
    if (page == NULL) {
        return false;
    }
   
    struct aux_for_lazy_load *aux = (struct aux_for_lazy_load *)page->uninit.aux;
   
    struct file *file = aux->mapped_file;
    off_t offset = aux->ofs;
    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;
}
static bool file_backed_swap_out (struct page *page);

주어진 페이지를 파일로 다시 기록하여 스왑아웃합니다. 페이지가 dirty 상태인지 먼저 확인하는 것이 좋습니다. Dirty 상태가 아니라면 파일 내용을 수정할 필요가 없습니다. 페이지를 스왑아웃한 후에는 해당 페이지의 dirty 비트를 해제하는 것을 잊지 마세요.

static bool
file_backed_swap_out (struct page *page) {
    struct file_page *file_page UNUSED = &page->file;
 
    if (page == NULL) {
        return false; // 예외 처리
    }
   
    struct aux_for_lazy_load *aux = (struct aux_for_lazy_load *)page->uninit.aux;
   
    if (pml4_is_dirty(thread_current()->pml4, page->va)) { // 페이지가 수정되었다면 (더티 비트로 확인)
       
        lock_acquire(&file_lock);
        file_write_at(aux->mapped_file, page->va, aux->page_read_bytes, aux->ofs); // 디스크에 반영
        lock_release(&file_lock);
       
        pml4_set_dirty(thread_current()->pml4, page->va, 0); // 페이지의 더티 비트 0(false)으로 초기화
    }
   
    pml4_clear_page(thread_current()->pml4, page->va); // 페이지 테이블 매핑 해제
}