PintOS P3 #8 : mmap/munmap
파일을 다루는 페이지들을 다뤄본다. 파일의 일부 내용을 페이지 단위로 잘라서 배치하는데 있어 사용하는 함수들이다. 큰 틀에 있어서 load_segment와 크게 다르지 않지만, 파일을 다룬다는 면에서 추가적으로 해줘야 할 것이 몇몇 존재한다. 한번 다뤄보자.
mmap : VM_FILE을 4KB 단위로 내오는 경우
시스템 콜을 통해 호출되는 mmap은 인자가 사전에 모두 주어진다. 우리는 그것을 정직하게 받아서 반복문에 돌려 쓸 수 있는 형태로 잘 만들어주면 된다. 받아온 내용들은 각 페이지에 배정된 uninit.aux에 사전에 저장해두고, page fault를 통한 lazy load를 통해 실제 물리 메모리에 적재되는 형태를 취하면 된다. 간단히 요약하면 이정도고, 빠진 것이 하나 있긴 하다 :
void* do_mmap(void *addr, size_t length, int writable, struct file *file, off_t offset) {
// 리턴을 위한 주소
void *mapped_addr = addr;
// 파일에서 read할 분량 / zero 채울 분량 계산
size_t file_length_bytes = (size_t) file_length(file);
size_t read_bytes = length < file_length_bytes ? length : file_length_bytes;
size_t zero_bytes = (PGSIZE - (read_bytes % PGSIZE)) % PGSIZE;
size_t total_pages = (read_bytes + zero_bytes) / PGSIZE;
// 이래야 별도의 file descriptor가 되기 때문.
lock_acquire(&g_filesys_lock);
struct file *mapping_file = file_reopen(file);
lock_release(&g_filesys_lock);
// 각 페이지를 매칭
for (size_t i = 0; i < total_pages; i++) {
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
// lazy-load를 위한 aux 설정
struct file_lazy_aux *aux = malloc(sizeof(struct file_lazy_aux));
if (aux == NULL)
return NULL;
aux->file = mapping_file;
aux->ofs = offset;
aux->read_bytes = page_read_bytes;
aux->zero_bytes = page_zero_bytes;
// lazy-load 페이지
if (!vm_alloc_page_with_initializer(
VM_FILE, addr, writable, lazy_load_segment, aux)) {
free(aux);
return NULL;
}
// 다음 페이지로 가자
read_bytes -= page_read_bytes;
offset += page_read_bytes;
addr += PGSIZE;
}
// mmap 시작 주소를 리턴
return mapped_addr;
}
당연한 것이지만 한 페이지에는 한 파일만 들어 갈 수 있다. 근데, 10KB를 배정한다고 할 때, 4KB 4KB 하다 2KB에서 실패하면 남은 8KB의 메모리 해제가 필요하다. 물론 이게 없어도 copy on write를 제외한 테스트 케이스는 잘 동작한다. 하지만 이런 식으로 메모리를 아껴야 테케가 없다고 이런것들이 필요없다는 생각을 너무 극단적으로 가진 않았으면 좋겠다.
munmap :
munmap도 시스템 콜을 이용한 작동이 이루어진다. 인자로 받는 addr은 이 페이지 지워에서의 this를 맡는 내용인데, 내용 자체는 정직하게 이루어지고 있다.
void do_munmap (void *addr) {
struct thread *curr = thread_current();
struct page *page;
struct file_lazy_aux* aux;
while (true)
{
page = spt_find_page(&curr->spt, addr);
if (!page || page_get_type(page) != VM_FILE) break;
if (pml4_is_dirty(curr->pml4, page->va)){
aux = (struct file_lazy_aux *) page->uninit.aux;
lock_acquire(&g_filesys_lock);
file_write_at(aux->file, addr, aux->read_bytes, aux->ofs);
lock_release(&g_filesys_lock);
pml4_set_dirty (curr->pml4, page->va, 0);
}
pml4_clear_page(curr->pml4, page->va);
addr += PGSIZE;
}
}
어쨌든 addr은 의도적으로 조져둔 주소가 아니면 왠만하면 VM_FILE일 것이다. 하지만 정말 아닐 수도 있으니 addr에 대한 FILE 타입이 아닌 경우를 대비는 해둔다. 그리고 FILE이기 때문에 어쨌든 기록 여부를 검사하고 그에 따라 기록을 저장하는 내용도 반영이 되어야 할 필요가 있다. 마지막으로는 진짜 활용하고 있는 페이지 테이블에서 현재 얻어온 내용을 뺀다..?
뭔가 나사빠진 영역이 있는 것 같다면 정상이다. 사실 이것은 연속된 파일 페이지들을 모두 munmap 시키는데, 설마설마 파일 페이지가 연속된다고 한들 같은 파일이라는 보장은 못한다. 그래서 이건 사실 적절한 코드는 아니다. SPT에서 정리하는 코드도 없네. 근데 작동은 한다. PintOS이기 때문에 가능한거지 이거 그대로 상용화된 OS에서 사용할 것이라고 기대하진 말자.. 내가 두고두고 꼭 수정하겠다.