PintOS/Project 3 : VIRTUAL MEMORY

[PintOS] Project 3 : VM - Memory Mapped Files - File-Backed 페이지 구성(with lazy loading)

넌뭐가그렇게중요해 2025. 6. 11. 11:37

이번 글에서는 PintOS Project 3의 Memory Mapped Files 파트 중,
File-Backed 페이지 구성과 관련된 구현 내용을 정리합니다.

KAIST 공식 문서의  중 아직 남은 함수들인 다음 세 가지를 중점적으로 다룹니다:

  • void vm_file_init(void)
  • bool file_backed_initializer(struct page *page, enum vm_type type, void *kva)
  • static void file_backed_destroy(struct page *page)

File-Backed Control Flow

 [유저 mmap 호출 (mmap syscall)]
             │
             ▼
     do_mmap(start_addr, file, ...)
             │
             ▼
vm_alloc_page_with_initializer(type=VM_FILE, ..., file_backed_initializer, aux)
             │
             ▼
  file_backed_initializer()
   ├─ page->operations = &file_ops
   └─ page->uninit.initializer = lazy_load_segment
             │
             ▼
      [페이지 접근 발생 (Page Fault)]
             │
             ▼
         page_fault()
             │
             ▼
    vm_try_handle_fault() → vm_do_claim_page()
             │
             ▼
   uninit page → lazy_load_segment(aux)
             │
             ▼
     file_read_at(file, kva, read_bytes, offset)
             │
             ▼
  page->frame에 내용 로드 (이제 실제 프레임 존재)
             │
             ▼
       이후 정상적으로 접근 가능
─────────────────────────────────────────────
       [프로세스 종료 or munmap 호출]
             │
             ▼
          do_munmap(start_addr)
             │
             ▼
     spt_find_page(va) → destroy(page)
             │
             ▼
     file_backed_destroy(page)
        ├─ is_dirty(page) → file_write_at()  // dirty하면 write-back
        └─ file_close(file)

주요 연결 함수 요약

구분 함수명 설명
mmap 요청 do_mmap() 유저 mmap syscall 핸들링
초기 페이지 생성 vm_alloc_page_with_initializer() lazy loading용 페이지 생성
초기화 file_backed_initializer() file-backed 타입 설정 및 handler 등록
lazy load lazy_load_segment() fault 시 파일에서 내용 읽어옴
swap 관련 file_backed_swap_in(),
file_backed_swap_out()
필요 시 디스크와 교체
destroy file_backed_destroy() dirty면 write-back + 파일 닫기

1. vm_file_init() - File-Backed 페이지 서브시스템 초기화 함수

// .../vm/file.c
/* The initializer of file vm */
void vm_file_init(void)
{
	/* 현재 스레드의 mmap_list를 초기화
	   mmap_list는 이 스레드가 mmap()을 통해 매핑한 파일 정보를 저장하는 리스트 */
	list_init(&thread_current()->mmap_list);
}

1.1. vm_file_init() 핵심 요약

  • vm_file_init() 함수는 file-backed 페이지 시스템을 초기화하는 역할을 합니다.
  • 구체적으로는 현재 스레드의 mmap_list를 초기화하여, 해당 스레드가 mmap을 통해 매핑한 파일 정보를 추후 관리할 수 있도록 준비합니다.
  • 이 리스트는 프로세스 종료 시 do_munmap() 또는 process_exit()에서 순회하며 매핑된 모든 파일을 정리하는 데 사용됩니다.

1.2. vm_file_init()을 이렇게 구현한 이유

  • PintOS에서는 각 프로세스마다 mmap_list를 별도로 관리하여, 해당 프로세스가 어떤 파일을 mmap 했는지 추적합니다.
  • 이 리스트가 초기화되어 있지 않으면 mmap 이후 do_munmap() 또는 process_exit()에서 순회 시 예외가 발생할 수 있으므로, 프로세스 시작 시점에 안전하게 초기화해 주는 것이 필수입니다.
  • 또한 이 구조는 프로세스 단위의 메모리 매핑 관리가 가능하도록 설계된 것으로, 시스템 전체가 아닌 개별 스레드/프로세스의 관점에서 리소스를 정리할 수 있게 해 줍니다.

2. file_backed_initializer() - File-Backed 페이지 초기화 함수

// .../vm/file.c
/* Initialize the file backed page */
/*	파일 기반 페이지(file-backed page)를 초기화하는 함수
	해당 페이지에 필요한 파일, 오프셋, 읽을 바이트 수 등의 정보를 설정 
	또한 이후 파일에서 데이터를 swap in/out할 수 있도록 관련 정보를 file_page 구조체에 저장함
*/
bool file_backed_initializer(struct page *page, enum vm_type type, void *kva)
{
	/* Set up the handler */
	/* 페이지가 file-backed임을 나타내는 핸들러(operations)를 설정 */
	page->operations = &file_ops;

	/*	aux에 저장된 mmap_info와 Lazy_Load_info 정보를 가져옴
		mmap_info는 mmap()으로 생성된 매핑 정보를 담고 있으며
		그 내부의 info 필드에 lazy_load_info가 들어 있음	
	*/
	struct mmap_info *mapping_info = (struct mmap_info *)page->uninit.aux;
	struct lazy_load_info *info = (struct lazy_load_info *)mapping_info->info;

	/*	lazy_load_info로부터 파일 매핑에 필요한 정보들을 꺼냄 */
	struct file *backup_file = info->file;				// 매핑할 파일
	off_t backup_offset = info->offset;					// 파일 내 오프셋
	size_t read_byte = info->readbyte;					// 파일에서 읽어올 바이트 수
	size_t zero_byte = info->zerobyte;					// 나머지를 0으로 채울 바이트 수
	int mapping_count = mapping_info->mapping_count;	// 매핑 식별용 ID(보통 mmap_id)

	struct file_page *file_page = &page->file;
	/* swap out을 대비해 저장 */
	file_page->file = backup_file;
	file_page->offset = backup_offset;
	file_page->read_byte = read_byte;
	file_page->zero_byte = zero_byte;
	file_page->mapping_count = mapping_count;

	return true;
}

 

2.1 file_backed_initializer() 핵심 요약

  • file_backed_initializer()는 파일 기반 페이지(file-backed page)의 초기 설정을 담당하는 함수입니다.
    특히 lazy loading 방식으로 페이지를 초기화할 때 호출되며, page->uninit.aux에 저장된 파일 정보(lazy_load_info)를 꺼내어 page->file 필드에 정보를 세팅합니다.
  • 또한, 해당 페이지가 file_ops를 사용하는 file-backed 타입임을 명시하여 이후의 swap_in, swap_out, destroy 등의 동작이 올바르게 처리되도록 합니다.

2.2 file_backed_initializer()를 이렇게 구현한 이유

  • Lazy loading 방식을 활용하면 페이지가 실제로 접근될 때까지 메모리에 로딩하지 않기 때문에, 초기화 시에는 필요한 정보를 보관해두기만 하면 됩니다.
  • 파일에서 어떤 부분을 읽을지(offset), 몇 바이트를 읽고 얼마나 0으로 채울지 등의 정보는 이후 lazy_load_segment() → do_lazy_load() → swap_in() 순으로 활용됩니다.
  • 페이지 구조체 안의 file_page 필드를 채워두면 swap이나 destroy 시에도 해당 정보를 활용할 수 있어 메모리와 파일 사이의 동기화 관리가 쉬워집니다.
  • 결국 이 함수는 “파일 기반 페이지를 위한 준비 작업”을 담당하며, 실질적인 로딩은 이후에 page fault가 발생했을 때 수행됩니다.

3. file_backed_destroy() - 파일 기반 페이지 소멸 함수

// .../vm/file.c

/* 
 * 파일 기반(file-backed) 페이지를 소멸시키는 함수입니다.
 *
 * - 해당 페이지가 dirty(변경됨) 상태이면, 파일에 변경 내용을 다시 저장(write-back)합니다.
 * - 이후 페이지가 물리 프레임에 매핑되어 있다면 해당 메모리를 해제합니다.
 * - 마지막으로 사용자 가상 주소 공간에서 이 페이지를 제거합니다.
 *
 * ※ 주의: 페이지 구조체 자체(page)는 호출자가 해제합니다.
 */
static void
file_backed_destroy(struct page *page)
{
	struct file_page *file_page UNUSED = &page->file;
	struct thread *curr = thread_current();

	// 파일이 읽기 전용으로 열렸을 수 있어 write 가능하게 변경
	file_allow_write(file_page->file);

	/* Dirty 검사 및 write-back 수행 */
	if (pml4_is_dirty(curr->pml4, page->va))
	{
		lock_acquire(&filesys_lock);

		off_t written = file_write_at(
			file_page->file,
			page->frame->kva,
			file_page->read_byte,
			file_page->offset);

		lock_release(&filesys_lock);

		ASSERT(written == file_page->read_byte); // 정확히 썼는지 확인
		pml4_set_dirty(curr->pml4, page->va, false); // dirty 비트 초기화
	}

	/* 물리 메모리에 매핑된 경우, 메모리 해제 */
	if (page->frame != NULL && page->frame->ref_cnt < 1)
	{
		palloc_free_page(page->frame->kva);
		free(page->frame);
		page->frame = NULL;
	}

	// 사용자 페이지 테이블(PML4)에서 매핑 제거
	pml4_clear_page(curr->pml4, page->va);
}

3.1. file_backed_destroy() 핵심 요약

이 함수는 file-backed 페이지가 더 이상 필요 없을 때, 다음과 같은 정리 작업을 수행합니다:

  1. dirty 상태이면 write-back
    • 페이지가 변경되었는지 PML4의 dirty 비트를 통해 확인
    • 변경되었다면 해당 내용을 파일에 다시 저장
  2. 매핑된 물리 페이지가 있다면 해제
    • 물리 프레임의 메모리 할당도 해제(palloc + malloc 모두 해제)
  3. 사용자 가상 주소 공간에서 매핑 제거
    • pml4_clear_page()를 통해 페이지 테이블에서 해당 주소 제거

3.2. file_backed_destroy()를 이렇게 구현한 이유

  • 데이터 일관성 보장: mmap 된 파일 페이지가 수정된 경우, dirty bit를 검사하여 변경 사항을 파일에 다시 써야 데이터 손실을 막을 수 있습니다.
  • 메모리 누수 방지: 물리 메모리에 매핑된 페이지가 남아 있으면 메모리 누수가 발생하므로, 매핑 해제 및 할당 해제가 반드시 필요합니다.
  • 가상 주소 공간 안전성 확보: 페이지 매핑을 완전히 제거하여 불필요한 접근이나 보안 문제를 예방합니다.
  • 파일 시스템 동기화: 파일 시스템은 동시 접근에 취약하므로, write 시 락을 걸어 race condition을 방지합니다.
  • 책임 분리 및 유연성: 페이지 구조체의 메모리 해제는 호출자가 하도록 하여, 페이지 타입별로 해제 로직을 유연하게 관리할 수 있게 합니다.

3.3 file_backed_destroy() 흐름도

┌──────────────────────────────────────────┐
│         file_backed_destroy()            │
└──────────────────────────────────────────┘
                │
                ▼
      [ Dirty bit 확인 (PML4) ]
                │
     ┌──────────┴────────────┐
     │                       │
   Yes                     No
     │                       │
     ▼                       ▼
[ file_write_at() 수행 ]    (넘어감)
     │
     ▼
[ dirty 비트 초기화 ]
                │
                ▼
 [ 물리 프레임 존재? + ref_cnt < 1 ]
                │
     ┌──────────┴────────────┐
     │                       │
   Yes                     No
     │                       │
     ▼                       ▼
[ palloc + free 수행 ]     (넘어감)
                │
                ▼
[ PML4에서 가상주소 매핑 제거 ]