이번 글에서는 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 페이지가 더 이상 필요 없을 때, 다음과 같은 정리 작업을 수행합니다:
- dirty 상태이면 write-back
- 페이지가 변경되었는지 PML4의 dirty 비트를 통해 확인
- 변경되었다면 해당 내용을 파일에 다시 저장
- 매핑된 물리 페이지가 있다면 해제
- 물리 프레임의 메모리 할당도 해제(palloc + malloc 모두 해제)
- 사용자 가상 주소 공간에서 매핑 제거
- 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에서 가상주소 매핑 제거 ]
'PintOS > Project 3 : VIRTUAL MEMORY' 카테고리의 다른 글
| [PintOS] Project 3 : VM - Swap In/out(File-Mapped Page) (0) | 2025.06.11 |
|---|---|
| [PintOS] Project 3 : VM - Swap In/out(anon) (2) | 2025.06.11 |
| [PintOS] Project 3 : VM - Memory Mapped Files - mmap & munmap 구현(with lazy loading) (0) | 2025.06.11 |
| [PintOS] Project 3 : VM - Stack Growth (7) | 2025.06.09 |
| [PintOS] Project 3 : VM - Anonymous Page (1) | 2025.06.07 |