728x90
➡️ 과제 설명(Gitbook)
- 메모리 매핑 페이지를 구현한다.
- memory-mapped 페이지는 anonymous 페이지와 달리, 파일 기반 매핑이다.
- 페이지의 콘텐츠는 일부 기존 파일의 데이터를 미러링한다.
- 페이지 폴트가 발생하면 물리적 프레임이 즉시 할당되며, 파일에서 메모리로 내용이 복사된다.
- 시스템 호출
mmap
,munmap
을 구현한다. - VM 시스템은 mmap 영역에서 파일을 lazy load 해야한다.
- 매핑된 파일은 그 자체로 매핑을 위한 저장소로 사용되어야한다.
⭐ do_mmap
과 do_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인 경우
- CASE 1.
- NULL의 처리
- 💡 Linux 커널은 매핑을 생성할 적절한 주소를 찾는다. → 다시 addr에서 mmap 시도
void munmap (void *addr);
addr
의 주소 매핑을 해제한다.- 해당 주소는 이전에
mmap
에 의해 호출되고 리턴된 va여야 한다.
- 프로세스가 exit되면 모든 매핑이 묵시적으로 해제된다.
- 매핑 해제시, 프로세스에서 쓴 모든 페이지 → 다시 파일 기록
- 기록되지 않은 페이지 → 다시 기록
⚠️ 파일 닫기 또는 제거는 매핑 해제를 의미하지는 않는다.
⭐ 매핑 해제의 발생은 munmmap
의 호출, 프로세스 종료 에만 가능하다!
- 각 파일에 대한 독립적인 매핑 정보를 얻으려면
file_reopen
을 사용하라.
- 둘 이상의 프로세스가 동일한 파일을 매핑하는 경우
mmap
에는 페이지를 공유 또는 보호 여부를 결정할 수 있도록 하는 파라미터가 존재한다.
- 필요에 따라 vm_file 관련 함수들을 수정할 수 있다.
✨ Summary
- 메모리 매핑 페이지를 구현하라.
👷 구현 순서
👋 실제 리눅스 시스템에서의 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인 경우
- CASE 1.
- 따라서, 시스템 콜 핸들러에서
/* 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 |