이전에 다뤘던 annoymous page는 특정 파일에 매핑되지 않은 페이지를 다뤘다면,
이번에는 특정 파일과 매핑된 페이지를 다룬다. (File-backed page
)
anon page와 달리 mmap page
(Memory Mapped Page)는 file-backed
매핑이다.
File backed page에 있는 내용물들은 모두 디스크에서 존재하고 있는 파일을 복사한 것이다.
Page fault가 발생하면 물리프레임을 즉시 할당
하고, 파일
에서 물리 프레임
으로 내용을 복사한다.
-> 이 때, 디스크 I/O를 통해 데이터를 복사하는 것이 아닌, DMA 방식으로 디스크에서 파일을 바로 읽어와 복사한다.
이 mmap page가 unmap 혹은 swap-out 되면 파일의 변경 사항이 파일에 반영된다.
즉 mmap은 메모리를 페이지 단위로 할당받을 수 있는 시스템 콜이다.
mmap
과 munmap
을 구현하자.vm/file.c
에 정의된 do_mmap
, do_munmap
을 구현하고 사용해야 한다.mmap system call
void* mmap()
fd로 오픈한 파일을 offset byte 위치에서부터 시작해 length 바이트 크기만큼 읽어들여
addr에 위치한 프로세스 가상 주소 공간에 매핑하는 시스템 콜이다.
전체 파일은 페이지 단위로 나뉘어 연속적인 가상 주소 페이지에 매핑된다.
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset){
if (addr == NULL || !(is_user_vaddr(addr)) || (long)length <= 0 || fd < 2
|| addr != pg_round_down(addr) || offset % PGSIZE != 0){
return NULL;
}
struct file *file = find_file_by_fd(fd);
return do_mmap(addr, length, writable, file, offset);
}
mmap은 syscall.c
에 위치하는데, 들어온 인자들을 조건에 맞게 걸러준뒤 모든 조건을 만족했을 경우에만
file.c
의 do_mmap()
을 호출한다.
그렇다면 어떤 조건일 경우 실패 조건이 될까? 아래는 깃북의 요구사항이다.
addr
가 0인 경우, 혹은 유저 가상 주소 공간이 아닐 경우 mmap 실패length
가 0인 경우에도 실패mapp
호출이 실패할 수 있음.위 조건에 맞추어 예외처리를 해준 뒤, 해당 fd로 file을 찾고, do_mmap으로 인자를 전달해준다.
void* do_mmap()
addr부터 시작하는 연속된 유저 가상 메모리 공간에 페이지들을 만들어 FILE의 offset부터 length에 해당하는
파일의 정보를 각 페이지마다 저장한다. 프로세스가 이 페이지에 접근해서 페이지 폴트가 뜨면 물리 프레임과 매핑하여(claim) 디스크에서 파일 데이터를 프레임에 복사한다.
void *
do_mmap (void *addr, size_t length, int writable,
struct file *file, off_t offset) {
struct file *reopen_file = file_reopen(file);
int cnt = length % PGSIZE ? (int)(length / PGSIZE) + 1 : (int)(length / PGSIZE);
for (int i = 0; i < cnt; i++){
if (spt_find_page(&thread_current()->spt, addr)){
return NULL;
}
}
void* origin_addr = addr;
for (int i = 0; i < cnt; i++){
size_t page_read_bytes = length < PGSIZE ? length : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
struct aux_data *aux_dt = calloc(1, sizeof(struct aux_data));
aux_dt->cnt = cnt;
aux_dt->file = reopen_file;
aux_dt->ofs = offset;
aux_dt->read_bytes = page_read_bytes;
aux_dt->zero_bytes = page_zero_bytes;
if (!vm_alloc_page_with_initializer(VM_FILE, addr,
writable, lazy_load_segment, (void *)aux_dt)){
free(aux_dt);
return false;
}
length -= page_read_bytes;
offset += page_read_bytes;
addr += PGSIZE;
}
return origin_addr;
}
mmap에서는 열었던 file을 reopen 해주어야 하는데 그 이유는 다음과 같다.
원본파일(구조체)는 그대로 유지되어야한다.
user가 read나 write를 할 때 정상적으로 진행되기 위해서는 원본 구조체의 offset이 유지되어야 하기 때문
User가 해당 fd로 close를 하더라도 munmap하지 않았다면 매핑이 유지되어야 하기 때문.
reopen을 하지 않고 원본을 사용하면 user에 의해 file이 close되었을 때 더이상 매핑을 유지할 수 없음
이후 while문을 돌면서 파일을 페이지 단위로 잘라 file의 정보들을 aux_dt
구조체에 저장해준다.
그리고, vm_alloc_page_with_initializer
에 VM_FILE
타입의 UNINIT 페이지를 만들고,
vm_init으로 lazy_load_segment
를 넣어준다.
이렇게 만들어진 페이지는 유저 프로세스가 접근하여 Page fault가 일어나면,
해당 페이지를 초기화 및 claim하여 물리 프레임과 서로 연결시켜 주고, lazy_load_segment()
가 호출되어 디스크에 있는 파일을 해당 페이지와 매핑되어 있는 물리 프레임에 복사하게 된다.
mummap system call
ADDR으로부터 연속된 유저 가상 페이지들의 변경 사항을 디스크의 파일에 업데이트하고 매핑 정보를 지운다.
해당 페이지가 변경 여부를 저장하는 비트이다. 이 비트는 하드웨어에 의해 기록된다.
페이지가 변경될 때마다 이 비트는 1이 되고, 디스크에 변경 내용을 기록하고 나면 해당 페이지의 더티 비트는 다시 0으로 초기화해야 한다.
페이지 교체 정책에서 Dirty Bit이 1인 페이지들은 디스크에 접근하여 업데이트 후 Unmap해주어야 하기 때문에 비용이 비싸다. 따라서 페이지 교체 알고리즘은 더티 페이지 대신 깨끗한 페이지를 내보내는 것을 선호하기도 한다.
해당 페이지가 물리 메모리에 매핑되어 있는지 아니면 스왑 아웃되었는지를 가리킨다. Swap in/out에서 더 자세히 다룰 예정. 실질적으로 Present bit이 1이면 해당 페이지가 물리 메모리 어딘가에 존재한다는 뜻이고, 0이라면 디스크 어딘가에 존재한다는 뜻이다. 이렇게 Present Bit이 0인, 물리 메모리에 존재하지 않는 페이지에 접근하는 과정을 페이지 폴트라고 한다.
void
do_munmap (void *addr) {
// spt를 돌면서 동일한 파일을 갖고 있는 페이지들을 모두 프리해줌
struct thread *t = thread_current();
struct page *page = spt_find_page(&t->spt, addr);
struct supplemental_page_table *src = &t->spt;
struct file *file = page->file.file;
int cnt = page->file.cnt;
for (int i = 0; i < cnt; i++){
struct page *p = spt_find_page(src, addr + (PGSIZE * i));
if (pml4_is_dirty(t->pml4, p->va)){
file_write_at(file, p->frame->kva, p->file.read_bytes, p->file.ofs);
pml4_set_dirty(t->pml4, page->va, false);
}
hash_delete(&t->spt.pages, &p->hash_elem);
// free(p);
}
}
addr
에서 지정된 주소 범위만큼 매핑을 unmap하는 함수addr
는 동일한 프로세스에서 이전에 호출한 mmap에 의해 file과 매핑된 가상주소로,unmap
하지 않는다.file_reopen
을 실행한다.vm_file_init
void vm_file_init (void);
file_backed_initializer
bool file_backed_initializer (struct page *page, enum vm_type type, void *kva);
file_backed_destroy
static void file_backed_destroy (struct page *page);