파일을 기반으로 매핑하는 페이지. 즉, 이 페이지에 담기는 내용은 실제 디스크에 존재하는 파일의 데이터가 담긴다.
왜 매핑을 해야 하나요?
// 3-4 mmf 추가
case SYS_MMAP:
f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
break;
case SYS_MUNMAP:
munmap(f->R.rdi);
break;
}
매핑을 하면 안되는 경우 8가지
코드
void *mmap(void *addr, size_t length, int writable, int fd, off_t offset)
{
/* 매핑이 이루어질 수 없는 경우(8가지) */
if (!addr || addr != pg_round_down(addr)) // addr이 없거나 addr이 page-aligend 되지 않은 경우
return NULL;
if (offset != pg_round_down(offset)) // offset이 page-aligned되지 않은 경우
return NULL;
if (!is_user_vaddr(addr) || !is_user_vaddr(addr + length)) // addr or addr + length 가 > 유저 영역이 아닌 경우
return NULL;
if (spt_find_page(&thread_current()->spt, addr)) // addr에 할당된 페이지가 이미 존재하는 경우
return NULL;
struct file *f = process_get_file(fd);
if (f == NULL) // fd에 해당하는 파일이 없는 경우
return NULL;
if (file_length(f) == 0 || (int)length <= 0) // file의 길이가 0 or 0보다 작은 경우
return NULL;
// 위의 경우를 제외하고, do_mmap() 호출하여 파일과 가상 주소간의 매핑 진행.
return do_mmap(addr, length, writable, f, offset); // 파일이 매핑된 가상 주소 반환
}
해야할 것
fil-backed page도 anonymous page와 같이 lazy_loading 이용
page가 초기화될 때 파일에서 내용을 읽어올 수 있도록 lazy_load_segment
와 로딩할 때 필요한 데이터를 페이지에 저장
유닉스 규칙
매핑은 매핑을 해제하는 munmap
이 호출 or 프로세스가 종료될 때까지 유효하다(파일을 닫거나 제거해도 매핑은 해제x)
length가 한 페이지 사이즈를 넘어갈 경우에는 한 파일의 내용이 여러 페이지에 걸쳐서 매핑된다.
매핑 진행 시 addr(페이지 가상 주소) + PGSIZE 에 위치하는 다음 페이지에 이어서 매핑하면 된다.
매핑 해제 시 몇 번째 페이지까지 해제해야 하는지 알아야 함.
그렇기 때문에 총 몇 페이지를 사용해서 매핑이 이루어져야 하는지 page 구조체에 함께 저장.
코드
void *
do_mmap (void *addr, size_t length, int writable,
struct file *file, off_t offset)
{
// 1. 파일 재개방('f'에 저장. 이는 원본 파일의 상태 유지하기 위함.)
struct file *f = file_reopen(file);
// 2. 초기 변수 설정
void *start_addr = addr; // 매핑 성공 시 파일이 매핑된 가상 주소 반환하는데 사용.
// 3. 총 페이지 수 계산(PGSIZE보다 작으면 1페이지, 그렇지 않으면 필요한 페이지 수 계산)
int total_page_count = length <= PGSIZE ? 1 : length % PGSIZE ? length / PGSIZE + 1
: length / PGSIZE; // 이 매핑을 위해 사용한 총 페이지 수
// 4 읽기 및 초기화 바이트 계산
size_t read_bytes = file_length(f) < length ? file_length(f) : length;
size_t zero_bytes = PGSIZE - read_bytes % PGSIZE;
// 5. 페이지 정렬 확인
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(addr) == 0); // upage가 페이지 정렬되어 있는지 확인.
ASSERT(offset % PGSIZE == 0); // ofs가 페이지 정렬되어 있는지 확인
// 6. 매핑할 페이지 **생성**
while (read_bytes > 0 || zero_bytes > 0) // 두 값이 모두 0이 될 때까지 루프를 돌며 페이지 할당.
{
/*
6-1
이 페이지를 채우는 방법 계산
파일에서 PAGE_READ_BYTES 바이트를 읽고
최종 PAGE_ZERO_BYTES 바이트를 0으로 채운다.
*/
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
// 6-2 구조체 **초기화**
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)malloc(sizeof(struct lazy_load_arg));
lazy_load_arg->file = f;
lazy_load_arg->ofs = offset;
lazy_load_arg->read_bytes = page_read_bytes;
lazy_load_arg->zero_bytes = page_zero_bytes;
// 6.3 페이지 초기화(페이지를 대기 상태로 생성, 초기화 함수로 lazy_load_segment사용)
if(!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_segment, lazy_load_arg))
{
return NULL;
}
// 6.5 다음 페이지로 이동
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
addr += PGSIZE;
offset += page_read_bytes;
}
해야할 것
파일로부터 백업된 페이지 초기화.
해야할 것
file_backed_initializer
에서 파일에 대한 정보를 page 구조체에 추가코드
bool
file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
// 1. 핸들러 설정
page->operations = &file_ops;
// 2. 파일 페이지 구조체 설정
struct file_page *file_page = &page->file;
// 3. lazy_load_arg 구조체 설정
// 페이지가 초기화되지 않은 상태에서 전달된 추가 인자('aux'를 'lazy_load_arg' 구조체로 변환)
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)page->uninit.aux;
// 4. 파일 페이지 정보 초기화
file_page->file = lazy_load_arg->file;
file_page->ofs = lazy_load_arg->ofs;
file_page->read_bytes = lazy_load_arg->read_bytes;
file_page->zero_bytes = lazy_load_arg->zero_bytes;
}
파일로 백업된 페이지 파괴. 페이지를 메모리에서 해제 and 페이지가 수정된 경우 해당 내용을 파일에 다시 기록
해야할 것
코드
static void
file_backed_destroy (struct page *page)
{
// 1. 'file_page' 구조체 가져오기
struct file_page *file_page UNUSED = &page->file;
// 2. 페이지가 수정되었는지 확인
if (pml4_is_dirty(thread_current()->pml4, page->va))
{
// 3. 페이지가 수정('dirty 상태인 경우')된 경우 파일에 쓰기
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, 0); // 페이지의 'dirty' 상태 해제
}
// 4. 페이지를 페이지 테이블에서 제거
pml4_clear_page(thread_current()->pml4, page->va);
}
매핑된 파일의 메모리 영역을 해제
코드
void
do_munmap (void *addr)
{
// 1. spt 및 페이지 찾기.
struct supplemental_page_table *spt = &thread_current()->spt;
struct page *p = spt_find_page(spt, addr);
// 2. 매핑된 페이지 수 가져오기
int count = p->mapped_page_count;
// 3. 매핑 해제 루프
for (int i = 0; i < count; i++)
{
// 존재할 경우
if(p)
{
destroy(p);
}
addr += PGSIZE;
p = spt_find_page(spt, addr); // spt에서 새로운 주소('addr')에 해당하는 다음 페이지 찾기
}
}
자식 프로세스가 부모 프로세스의 실행 컨텍스트를 상속하는 과정에서 SPT를 복사할 때, file_backed_page는 자식 페이지가 부모의 프레임과 매핑되도록 분기를 추가
코드
/*
자식 프로세스가 부모 프로세스의 실행 컨텍스트를 상속하는 과정에서 SPT를 복사할 때,
file_backed_page는 자식 페이지가 부모의 프레임과 매핑되도록 분기 추가.
*/
// 2. type이 file이면
if(type == VM_FILE)
{
struct lazy_load_arg *file_aux = malloc(sizeof(struct lazy_load_arg));
file_aux->file = src_page->file.file;
file_aux->ofs = src_page->file.ofs;
file_aux->read_bytes = src_page->file.read_bytes;
file_aux->zero_bytes = src_page->file.zero_bytes;
if(!vm_alloc_page_with_initializer(type, upage, writable, NULL, file_aux))
{
return false;
}
struct page *file_page = spt_find_page(dst, upage);
file_backed_initializer(file_page, type, NULL);
file_page->frame = src_page->frame;
pml4_set_page(thread_current()->pml4, file_page->va, src_page->frame->kva, src_page->writable);
continue;
}