728x90

➡️ 과제 설명(Gitbook)

 

Memory Mapped Files · GitBook

In this section, you will implement memory-mapped pages. Unlike anonymous pages, memory-mapped pages are file-backed mappings. The contents in the page mirror data in some existing file. If a page fault occurs, a physical frame is immediately allocated and

casys-kaist.github.io

 

  • 메모리 매핑 페이지를 구현한다.
  • memory-mapped 페이지는 anonymous 페이지와 달리, 파일 기반 매핑이다.
  • 페이지의 콘텐츠는 일부 기존 파일의 데이터를 미러링한다.
  • 페이지 폴트가 발생하면 물리적 프레임이 즉시 할당되며, 파일에서 메모리로 내용이 복사된다.

 

 


  • 시스템 호출 mmap , munmap 을 구현한다.
  • VM 시스템은 mmap 영역에서 파일을 lazy load 해야한다.
  • 매핑된 파일은 그 자체로 매핑을 위한 저장소로 사용되어야한다.

do_mmapdo_munmap (_vm/file.c) 을 이용해, 이 두 시스템 콜을 구현해야한다.

 

 


void *mmap (void *addr, size_t length, int writable, int fd, off_t offset);
  • fd로 열린 파일의 offset 바이트로 부터 length 바이트 만큼을 프로세스의 가상 주소 공간의 주소 addr에 매핑한다.
  • 전체 파일은 addr 부터 시작하는 연속된 가상 페이지에 매핑된다.

 

  • 파일 길이가 PGSIZE(4kb)의 배수가 아니면 끝에 매핑된 페이지의 일부가 파일의 끝을 넘어서 “stick out”이 된다.
  • 페이지 폴트가 발생하면 이 바이트를 0으로 설정하고 페이지를 디스크에 다시 쓸 때 버린다.

 

  • return
    • 성공 시, 파일이 매핑된 va
    • 실패 시, NULL
  • 실패 하는 경우(NULL)
    • CASE 1. addr 가 0인 경우
    • CASE 2. addr 가 커널 가상 주소인 경우
    • CASE 3. addr 가 page-aligned 되지 않은 경우
    • CASE 4. 기존에 매핑된 페이지 집합(stack, 페이지)과 겹치는 경우
    • CASE 5. 읽으려는 파일의 offset 위치가 PGSIZE 보다 큰 경우
    • CASE 6. 읽으려는 파일의 길이가 0인 경우
    • CASE 7. STDIN, STDOUT 인 경우
    • CASE 8. 파일 객체가 존재하지 않는 경우
    • CASE 9. fd로 열린 파일의 길이가 0인 경우

 

  • NULL의 처리
    • 💡 Linux 커널은 매핑을 생성할 적절한 주소를 찾는다. → 다시 addr에서 mmap 시도

 

 


void munmap (void *addr);
  • addr 의 주소 매핑을 해제한다.
  • 해당 주소는 이전에 mmap 에 의해 호출되고 리턴된 va여야 한다.

 

  • 프로세스가 exit되면 모든 매핑이 묵시적으로 해제된다.
  • 매핑 해제시, 프로세스에서 쓴 모든 페이지 → 다시 파일 기록
  • 기록되지 않은 페이지 → 다시 기록

⚠️ 파일 닫기 또는 제거는 매핑 해제를 의미하지는 않는다.

⭐ 매핑 해제의 발생은 munmmap 의 호출, 프로세스 종료 에만 가능하다!

  • 각 파일에 대한 독립적인 매핑 정보를 얻으려면 file_reopen 을 사용하라.

 

  • 둘 이상의 프로세스가 동일한 파일을 매핑하는 경우
  • mmap 에는 페이지를 공유 또는 보호 여부를 결정할 수 있도록 하는 파라미터가 존재한다.

 

  • 필요에 따라 vm_file 관련 함수들을 수정할 수 있다.

 

 


✨ Summary

  • 메모리 매핑 페이지를 구현하라.

 

👷 구현 순서

  1. mmap
  2. munmap

 

 


👋 실제 리눅스 시스템에서의 mmap 🆚 munmap

  • mmap
    • mmap()은 호출 프로세스의 가상 주소 공간 안에 새 매핑을 만든다. addr에는 새 매핑의 시작 주소를 지정한다. length 인자는 매핑의 길이(0보다 커야 함)를 나타낸다.
  • munmap
    • munmap() 시스템 호출은 지정한 주소 범위에 대한 매핑을 삭제하고 그 범위 내 주소에 대한 이후 참조가 비유효 메모리 참조를 일으키도록 한다. 프로세스가 종료할 때도 자동으로 해제한다. 반면 파일 디스크립터를 닫는 것으로는 맵을 해제하지 않는다.
    • 주소 addr은 페이지 크기의 배수여야 한다. (length는 그럴 필요가 없다.) 지정한 범위의 일부라도 담은 모든 페이지들이 해제되며 이후 그 페이지들에 대한 참조가 SIGSEGV를 일으키게 된다. 지정한 범위 안에 맵 된 페이지가 없더라도 오류가 아니다.

_ linux man page

 

  • mmap을 그럼 실제 리눅스 시스템에서 언제쓰는가?
    • 사용자가 물리 메모리에 직접 데이터를 쓰고 싶을 때 쓴다.

 

1. mmap

void *mmap (void *addr, size_t length, int writable, int fd, off_t offset);
  • fd로 열린 파일의 offset 바이트로 부터 length 바이트 만큼을 프로세스의 가상 주소 공간의 주소 addr에 매핑한다.
  • 전체 파일은 addr 부터 시작하는 연속된 가상 페이지에 매핑된다.

 

  • do_mmap 을 사용해서 mmap을 구현해야한다.
    • 따라서, 시스템 콜 핸들러에서 do_mmap 으로 이어지도록 구현한다.
    • Gitbook에 나와있는 mmap을 할 수 없는 경우에 대해 예외처리를 userprog/syscall.c 에 구현한다.
    • 실패 하는 경우(Return NULL을 해야하는 경우) _by Gitbook
      • CASE 1. addr 가 0인 경우
      • CASE 2. addr 가 커널 가상 주소인 경우
      • CASE 3. addr 가 page-aligned 되지 않은 경우
      • CASE 4. 기존에 매핑된 페이지 집합(stack, 페이지)과 겹치는 경우
      • CASE 5. 읽으려는 파일의 offset 위치가 PGSIZE 보다 큰 경우
      • CASE 6. 읽으려는 파일의 길이가 0인 경우
      • CASE 7. STDIN, STDOUT 인 경우
      • CASE 8. 파일 객체가 존재하지 않는 경우
      • CASE 9. fd로 열린 파일의 길이가 0인 경우

 

/* userprog/syscall.c */

void *mmap (void *addr, size_t length, int writable, int fd, off_t offset){

    /* Return NULL의 경우 
     * CASE 1. `addr` 가 0인 경우
     * CASE 2. `addr` 가 커널 가상 주소인 경우
     * CASE 3. `addr` 가 page-aligned 되지 않은 경우
     * CASE 4. 기존에 매핑된 페이지 집합(stack, 페이지)과 겹치는 경우
     * CASE 5. 읽으려는 파일의 offset 위치가 PGSIZE 보다 큰 경우
     * CASE 6. 읽으려는 파일의 길이가 0보다 작거나 같은 경우
     * CASE 7. STDIN, STDOUT 인 경우
     * CASE 8. 파일 객체가 존재하지 않는 경우
     * CASE 9. fd로 열린 파일의 길이가 0인 경우 */

    // CASE 1 - 6
  if (addr == NULL || is_kernel_vaddr(addr) || is_kernel_vaddr(pg_round_up(addr)) || pg_round_down(addr) != addr || spt_find_page(&thread_current()->spt, addr) \
    || offset > PGSIZE \
    || (long) length <= 0) // ? 형 변환 안하면 통과 못함 (Input : 음수)
      return NULL;

  struct file *file = fdt_get_file(fd);

    // CASE 7 - 9
  if (fd <= STDOUT_FILENO || file == NULL || file_length(file) == 0)
      return NULL;

    // do_mmap의 4번째 인자가 파일 객체이므로 fd로 부터 파일 객체를 얻은 값을 넣어준다.
  return do_mmap(addr, length, writable, file, offset);
}

 

/* vm/file.c */

void *
do_mmap (void *addr, size_t length, int writable,
        struct file *file, off_t offset) {
    struct file *re_file = file_reopen(file);

  void * mmap_addr = addr; // 페이지 확장 시, 리턴 주소 값 변경 방지
  size_t read_bytes = length > file_length(file) ? file_length(file) : length; // 실제 읽어올 바이트 수
  size_t zero_bytes = PGSIZE - (read_bytes % PGSIZE); // 페이지에서 read_bytes를 제외한 공간은 0으로 채운다.

    // 읽으려는 바이트 수만큼 페이지를 할당한다.
    while (read_bytes > 0 || zero_bytes > 0) {
        size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
        size_t page_zero_bytes = page_read_bytes == PGSIZE ? 0 : PGSIZE - page_read_bytes;

    struct segment_aux *segment_aux = (struct segment_aux*)malloc(sizeof(struct segment_aux));
    segment_aux->file = re_file;
    segment_aux->offset = offset;
    segment_aux->page_read_bytes = page_read_bytes;

        // vm_alloc_page가 안되는 이유? -> aux(segment_aux) 값으로 파일에 대한 정보를 넘겨줘야한다.
        if (!vm_alloc_page_with_initializer (VM_FILE, mmap_addr, writable, lazy_load_segment, segment_aux)) {
            free(mmap_addr); // 안전장치
            return NULL;
    }
        read_bytes -= page_read_bytes;
        zero_bytes -= page_zero_bytes;

        // 페이지 하나 할당에 따른 주소값, 오프셋 변경
        mmap_addr += PGSIZE;
        offset += page_read_bytes;
    }
    return addr;
}
  • 읽으려는 바이트 수만큼 페이지를 할당한다.
  • PGSIZE 단위로 페이지를 할당하는데, 맨 마지막 페이지의 경우에는 빈 곳을 0으로 채울 수 있게끔 zero_byte 값을 추적한다.

 

 

2. munmap

void munmap (void *addr);
  • addr 의 주소 매핑을 해제한다.
  • 해당 주소는 이전에 mmap 에 의해 호출되고 리턴된 va여야 한다.

 

⭐ 매핑 해제의 발생은 munmmap 의 호출, 프로세스 종료 에만 가능하다!

 

/* userprog/syscall.c */

void munmap (void *addr){
    do_munmap(addr);
}

 

/* vm/file.c */

void
do_munmap (void *addr) {
    while (true) {
        struct page* page = spt_find_page(&thread_current()->spt, addr);

        if (page == NULL)
            break;

        struct segment_aux * segment_aux = (struct segment_aux *) page->uninit.aux;

        // dirty bit(사용된 적이 있으면) -> 파일에 다시 쓰고 dirty bit를 0으로 만들어줌
        if(pml4_is_dirty(thread_current()->pml4, page->va)) {
            file_write_at(segment_aux->file, addr, segment_aux->page_read_bytes, segment_aux->offset); // ? i-node에 내가 쓰던 파일이 해제됨을 알린다고 보면 될 듯?
            pml4_set_dirty (thread_current()->pml4, page->va, 0);
        }

        pml4_clear_page(thread_current()->pml4, page->va);
        addr += PGSIZE;
    }
}

 

 

 

728x90

'🌱 Dev Diary > 📄 TIL' 카테고리의 다른 글

알쓸 정규표현식  (32) 2024.05.07
Project 3. Stack Growth(2)  (0) 2023.01.19
Project 3. Stack Growth(1)  (0) 2023.01.19
Project 3. Anonymous Page(2)  (0) 2023.01.19
Project 3. Anonymous Page(1)  (0) 2023.01.19