mmap
이란?void *mmap(void addr[.length], size_t length, int prot, int flags, int fd, off_t offset);
리눅스의 mmap
은 파일이나 I/O 장치를 가상 메모리에 매핑할 때 사용되는 시스템콜이다.
위 코드의 인자들을 살펴보면 가상주소공간의 addr
부터 length
바이트 길이만큼 매핑하겠다는 의미이다. 파일을 매핑하는 경우 fd
에 매핑하고자 하는 파일의 디스크립터를 넣어주면 되고, 파일의 중간부터 매핑을 하려면 offset
에 메모리 매핑을 시작할 지점을 넣어주면 된다.
이제 이 시스템 콜을 pintos에서 직접 구현해보자.
do_mmap
do_mmap
은 load_segment
와 유사한 방식으로 구현할 수 있다. 파일의 내용을 읽어와서 페이지를 초기화한다는 점에서 거의 동일하기 때문이다.
파일이 여러 페이지에 걸쳐 매핑되었다면 나중에 이 페이지들을 모두 해제해줘야 하기 때문에, 몇 페이지에 걸쳐 파일이 매핑이 되었는지 알 수 있도록 우리 조는 page_info
에 page_count
필드를 추가해주었다.
그리고 mmap
에서 file
과 관련해서 유의해야 할 점은 파일을 닫은 후에도 메모리 매핑이 유지되어야 한다는 점이다. 우리는 파일이 닫힌 후에도 mmap
이 문제 없이 파일을 읽고 쓸 수 있도록 file_reopen()
함수를 사용했다. 그리고 파일의 중간부터 매핑하는 경우를 위해 file_seek()
함수를 사용해서 파일울 읽기 시작하는 지점을 offset
으로 변경하였다.
void *do_mmap(void *addr, size_t length, int writable, struct file *file, off_t offset)
{
uint8_t *upage;
off_t ofs;
uint32_t read_bytes, zero_bytes;
int pg_count;
struct supplemental_page_table *spt = &thread_current()->spt;
if (spt_find_page(spt, addr) != NULL)
return NULL;
struct file *reopened_file = file_reopen(file);
file_seek(reopened_file, offset);
upage = addr;
ofs = offset;
read_bytes = file_length(reopened_file);
zero_bytes = (ROUND_UP(read_bytes, PGSIZE) - read_bytes);
pg_count = (length / PGSIZE);
while (read_bytes > 0 || zero_bytes > 0)
{
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
struct page_info *page_info = (struct page_info *)malloc(sizeof(struct page_info));
page_info->file = reopened_file;
page_info->ofs = ofs;
page_info->upage = upage;
page_info->read_bytes = page_read_bytes;
page_info->zero_bytes = page_zero_bytes;
page_info->writable = writable;
page_info->page_count = pg_count;
/* Allocate page */
if (!vm_alloc_page_with_initializer(VM_FILE, upage, writable, lazy_load_segment, page_info))
return false;
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return addr;
}
mmap
과 lazy loadingdo_mmap
또한 lazy loading 방식을 사용한다. 즉, mmap
은 가상 메모리와 파일을 매핑만 해놓고, 메모리에 로드하는 작업은 따로 하지 않는 것이다. 파일의 내용을 실제로 메모리에 로드하는 것은 파일의 지점에 엑세스를 하려고 할 때 발생한다.
mmap
이후 어떻게 데이터가 로드되는지 코드의 흐름을 살펴보면, mmap
된 가상주소를 통해 파일을 읽으려고 접근하면 아직 물리 메모리가 없어서 page fault가 발생하게 된다. 그러면 page_fault()
는 vm_do_claim_page()
함수를 호출해서 물리 메모리를 할당 받고 vm_do_claim_page()
은 file_backed_swap_in()
을 호출해서 가상 주소에 파일의 내용을 읽어오게끔 한다.
/* Swap in the page by read contents from the file. */
static bool file_backed_swap_in(struct page *page, void *kva)
{
struct file_page *file_page = &page->file;
file_read_at(file_page->file, page->va, file_page->read_bytes, file_page->offset);
return true;
}