[PintOS] Project3 - Memory Mapped Files(4)

Baedonguri·2022년 6월 28일
0
post-thumbnail

Memory Mapped Files

이전에 다뤘던 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은 메모리를 페이지 단위로 할당받을 수 있는 시스템 콜이다.


TODO

  • Memory mapped files를 위한 syscall인 mmapmunmap을 구현하자.
  • VM 시스템은 mmap 영역에서 페이지를 lazy load하고, mmap된 파일 자체를 mapping의 backing store(백업 저장소)로 사용해야 한다. → 백업 해두면, file close가 되어도 reopen을 통해 file을 열었기 때문에 mapping 되었던 것이 해제가
    안됨. (close 시에도.)
  • 위의 두가지 시스템 콜을 구현하기 위해, vm/file.c에 정의된 do_mmap, do_munmap을 구현하고 사용해야 한다.

Implement

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.cdo_mmap()을 호출한다.
그렇다면 어떤 조건일 경우 실패 조건이 될까? 아래는 깃북의 요구사항이다.

  • addr가 0인 경우, 혹은 유저 가상 주소 공간이 아닐 경우 mmap 실패
  • length가 0인 경우에도 실패
  • 만약 fd로 열린 파일의 길이가 0byte인 경우 mapp 호출이 실패할 수 있음.
  • 주소가 page-aligned 되지 않았거나 매핑된 페이지 범위가 기존에 매핑된 페이지 집합과 겹치는 경우 Fail 한다. (실행파일 로드시 매핑된 스택 또는 페이지를 포함하여)
  • fd가 표준 입출력일 경우.

위 조건에 맞추어 예외처리를 해준 뒤, 해당 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 해주어야 하는데 그 이유는 다음과 같다.

  1. 원본파일(구조체)는 그대로 유지되어야한다.
    user가 read나 write를 할 때 정상적으로 진행되기 위해서는 원본 구조체의 offset이 유지되어야 하기 때문

  2. User가 해당 fd로 close를 하더라도 munmap하지 않았다면 매핑이 유지되어야 하기 때문.
    reopen을 하지 않고 원본을 사용하면 user에 의해 file이 close되었을 때 더이상 매핑을 유지할 수 없음

이후 while문을 돌면서 파일을 페이지 단위로 잘라 file의 정보들을 aux_dt 구조체에 저장해준다.
그리고, vm_alloc_page_with_initializerVM_FILE 타입의 UNINIT 페이지를 만들고,
vm_init으로 lazy_load_segment를 넣어준다.

이렇게 만들어진 페이지는 유저 프로세스가 접근하여 Page fault가 일어나면,
해당 페이지를 초기화 및 claim하여 물리 프레임과 서로 연결시켜 주고, lazy_load_segment()가 호출되어 디스크에 있는 파일을 해당 페이지와 매핑되어 있는 물리 프레임에 복사하게 된다.


mummap system call

ADDR으로부터 연속된 유저 가상 페이지들의 변경 사항을 디스크의 파일에 업데이트하고 매핑 정보를 지운다.

Dirty Bit

해당 페이지가 변경 여부를 저장하는 비트이다. 이 비트는 하드웨어에 의해 기록된다.
페이지가 변경될 때마다 이 비트는 1이 되고, 디스크에 변경 내용을 기록하고 나면 해당 페이지의 더티 비트는 다시 0으로 초기화해야 한다.

페이지 교체 정책에서 Dirty Bit이 1인 페이지들은 디스크에 접근하여 업데이트 후 Unmap해주어야 하기 때문에 비용이 비싸다. 따라서 페이지 교체 알고리즘은 더티 페이지 대신 깨끗한 페이지를 내보내는 것을 선호하기도 한다.

Present Bit

해당 페이지가 물리 메모리에 매핑되어 있는지 아니면 스왑 아웃되었는지를 가리킨다. 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과 매핑된 가상주소로,
    아직 매핑이 해제되지 않은 가상 주소여야 한다.
  • 모든 mapping은 process가 exit할 경우 (exit()으로 나가지 않는 경우를 포함하여) unmap 되어야한다.
  • process에 의해 쓰여진 page들은 file로 write-back을 한다.
  • page들은 virtual page의 process list에서 제거한다.
  • closing되거나 removing된 file은 unmap하지 않는다.
  • 한번 만들어지면, mapping은 munmap 되거나, process exit이 되기 전까지는 계속 유효하다.
    즉, 파일을 닫거나 제거해도 mapping이 해제되지 않는 것을 의미.
  • 각 mapping에 대해 독립적이고 개별적인 참조를 위해 file_reopen 을 실행한다.
  • 한 file에 여러 process가 map하고 있을 때, 같은 data를 바라보지 않아도 된다. 
    mmap system은 사용자가 page를 공유할 것인지를 묻는 argument를 가진다.

vm_file_init

void vm_file_init (void);
  • file-backed page subsystem을 초기화한다.
  • file-backed page와 관련된 것을 setup할 수 있다.

file_backed_initializer

bool file_backed_initializer (struct page *page, enum vm_type type, void *kva);
  • file-backed page를 초기화한다.
  • file-backed page를 위해 page->operation 안의 handler를 setup한다.
  • 페이지 구조체 내의 정보를 업데이트 할 수 있다. (메모리를 백업하는 파일과 같은 일부 정보 등..)

file_backed_destroy

static void file_backed_destroy (struct page *page);
  • 연관된 file을 닫음으로 file backed page를 지울 수 있다.
  • 만약 content가 변질(dirty)되었다면, file로 write-back을 해야한다.
    → 페이지가 수정 사항이 있을 경우
  • 이 함수에서 page를 free할 필요는 없다. (file_backed_destroy의 caller가 이를 수행한다.)
profile
Software Engineer

0개의 댓글