핀토스 정리를 다 끝내지 못 한 상태에서 나만무 주간이 시작되어 계속해서 작성을 미뤘다.
개인적인 욕심으로 핀토스만은 마지막까지 정리를 하고 싶었기 때문에 뜨문뜨문한 기억으로 정리를 마무리 지어보려고 한다ㅎ..
거의 세 달이 되어가기 때문에 그 때만큼 자세하게 작성하진 못 할 것 같다.
핀토스 과정을 돌이켜보면 어렵고 힘들긴 했지만 배운 게 많기도 하고, 은근 재미가 있었던 것 같기도 해서(기억미화 일수도) 나중에 한 번 다시 제대로 해보고 싶은 마음이 좀 들긴 한다.
그게 언제가 될 수 있을지는 모르겠다만ㅎ
이전에 Anonymous Page를 구현했었는데, 이번 과제에선 Memory Mapped Page를 구현한다.
Memory Mapped Page? 🤔
이는 Anonymous Page와 반대로 메모리가 매핑된 페이지로, 파일 기반 페이지라고도 한다.
페이지의 콘텐츠는 일부 기존 파일의 데이터를 미러링한다.
프로그램이 접근하려는 메모리 페이지가 현재 메모리에 로드되지 않았을 때 페이지 폴트가 발생한다.
이 때 해야 할 작업은 크게 두 가지가 있다.
해당 작업이 완료되면 프로그램이 할당된 메모리 공간을 통해 파일 데이터를 사용할 수 있게 된다.
메모리 매핑된 페이지의 데이터가 변경되었을 수 있기 때문에 파일의 내용이 업데이트 되어야 한다. 즉, 페이지가 더 이상 필요 없거나 메모리에서 쫒겨나게 되었을 때, 변경된 내용을 파일에 저장한다.
이와 같은 작업들을 위해 우리가 이번 과제에서 해야 할 일은 먼저 메모리 매핑에 대한 시스템 콜을 구현하는 것이다.
VM 시스템은 mmap 영역에서 페이지를 lazy load 하고 mmap된 파일 자체를 매핑을 위한 백업 저장소로 사용해야 하기 때문에 do_mmap 과 do_munmap을 구현해서 사용해야 한다. (+ syscall_handler 수정)
해당 내용(git book)을 기반으로 추가적인 정리를 해보자면
mmap이 이루어질 수 없는 Cases
do_mmap()
이런 조건들로 구현을 하게 되면 총 3개의 파일을 수정하게 된다.
syscall_handler()에 sys_mmap을 추가해준다. 이 때 예외처리 해주는 것을 잊지 말자.
switch (syscall_num)
{
...
case SYS_MMAP:
f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
break;
...
}
void *mmap(void *addr, size_t length, int writable, int fd, off_t offset)
{
/* addr이 NULL이거나 페이지 경계에 있지 않은 경우 */
if (!addr || addr != pg_round_down(addr))
return NULL;
/* offset이 페이지 경계에 있지 않은 경우 */
if (offset != pg_round_down(offset))
return NULL;
/* addr과 addr+length가 user 영역이 아닌 경우 */
if (!is_user_vaddr(addr) || !is_user_vaddr(addr + length))
return NULL;
/* addr에 할당된 페이지가 이미 있는 경우 */
if (spt_find_page(&thread_current()->spt, addr))
return NULL;
struct file *f = process_get_file(fd);
/* fd에 해당하는 파일이 NULL인 경우 */
if (f == NULL)
return NULL;
/* 파일의 길이가 0이거나, 읽어올 length가 0보다 작은 경우 */
if (file_length(f) == 0 || (int)length <= 0)
return NULL;
/* 위처럼 mmap이 이루어질 수 없는 case들을 제외하고는 do_mmap을 호출해 매핑 후 매핑된 가상주소 반환 */
return do_mmap(addr, length, writable, f, offset);
}
page 구조체에 매핑에 필요한 총 페이지 수를 저장할 멤버 변수를 추가해주기
struct page
{
...
int mapped_page_cnt; /* 매핑에 사용한 총 페이지 수 */
...
}
do_mmap 구현하기
/* Do the mmap */
void *
do_mmap (void *addr, size_t length, int writable,
struct file *file, off_t offset) {
/* reopen()으로 해당 파일에 대한 새로운 파일 디스크립터 얻음 */
struct file *f = file_reopen(file);
/* 매핑 성공 시 반환할 가상 주소 */
void *start_addr = addr;
/* 매핑에 쓰인 총 페이지 수 */
int total_page_count;
/* 읽어올 길이가 PGSIZE보다 작을 시 페이지 수 1개 */
if (length <= PGSIZE)
total_page_count = 1;
/* 일어올 길이가 PGSIZE로 나누어 떨어지지 않는다면 남은 부분을 적재하기 위해 나눈 몫에 +1 */
else if (length % PGSIZE != 0)
total_page_count = length / PGSIZE + 1;
/* 일어올 길이가 PGSIZE로 나누어 떨어진다면 페이지 수는 나눈 몫 */
else
total_page_count = length / PGSIZE;
size_t read_bytes = file_length(f) < length ? file_length(f) : length;
size_t zero_bytes = PGSIZE - read_bytes % PGSIZE;
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(addr) == 0); // upage 페이지 정렬 확인
ASSERT(offset % PGSIZE == 0); // offset 페이지 정렬 확인
while (read_bytes > 0 || zero_bytes > 0)
{
/* 페이지 채우기 */
/* page_read_bytes만큼을 읽고, page_zero_bytes만큼을 0으로 채움 */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)malloc(sizeof(struct lazy_load_arg));
lazy_load_arg->file = f;
lazy_load_arg->ofs = offset;
lazy_load_arg->read_bytes = page_read_bytes;
lazy_load_arg->zero_bytes = page_zero_bytes;
/* vm_alloc_page_with_initializer로 lazy loading할 페이지 생성 (type은 file-backed) */
if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_segment, lazy_load_arg))
return NULL;
struct page *p = spt_find_page(&thread_current()->spt, start_addr);
p->mapped_page_cnt = total_page_count;
/* read bytes와 zero_bytes 감소시키고 가상 주소 증가 */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
addr += PGSIZE;
offset += page_read_bytes;
}
return start_addr;
}
이렇게 하면 Memory Mapped Files 파트에서 Mapping에 관한 내용은 끝난다.
다음 글에서 Unmapping에 관한 내용을 다루면서 해당 파트를 끝내보겠다!