Stack Growth 마무리
🔹 read 시스템 콜
int read (int fd, void *buffer, unsigned size) {
if (size == 0) return 0;
if (!check_address(buffer)) exit(-1);
#ifdef VM
struct page *page = spt_find_page(&thread_current()->spt, buffer);
if (page != NULL && !page->writable)
exit(-1);
#endif
if (fd == 0) {
…
}
}
pt-grow-stk-sc를 구현하면서 추가했던 코드를 다시 삭제했다.
🔹 vm_try_handle_fault
bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED, bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
// struct page *page = NULL;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
/* Modify this function to resolve the page struct corresponding to the faulted address
* by consulting to the supplemental page table through spt_find_page. */
if (addr == NULL || is_kernel_vaddr(addr) || !not_present)
return false;
struct page *page = spt_find_page(spt, addr);
if (page == NULL) {
/* If you have confirmed that the fault can be handled with a stack growth,
* call vm_stack_growth with the faulted address. */
if (f->rsp - PGSIZE < addr && addr < USER_STACK && f->rsp - PGSIZE >= STACK_LIMIT) {
if (!vm_stack_growth(addr))
return false;
page = spt_find_page(spt, addr);
} else
return false;
}
if (write && !page->writable)
return false;
return vm_do_claim_page(page);
}
다른 블로그와 GPT 등의 코드에서는 스택을 확장하는 조건으로f->rsp - 8을 사용했지만, 그 이유에 대해 명확하게 설명해 주는 경우는 거의 없었다. 깃북에는rsp에 -8을 해주는 것에 대한 내용이 없었기 때문에, 우리 팀은 기존에 설정했던 조건을 그대로 사용하기로 했다.- 깃북에서
rsp - 8에 대해 언급한 내용이 있었다는 것을 뒤늦게 알게 되었다...
However, the x86-64 PUSH instruction checks access permissions before it adjusts the stack pointer, so it may cause
a page fault 8 bytes below the stack pointer.
🔹 vm_stack_growth
static bool
vm_stack_growth (void *addr UNUSED) {
/* Increases the stack size by allocating one or more anonymous pages
* so that addr is no longer a faulted address. Make sure you round down
* the addr to PGSIZE when handling the allocation. */
void *upage = pg_round_down(addr);
int cnt = 0;
while (!spt_find_page(&thread_current()->spt, upage + cnt * PGSIZE))
cnt++;
for (int i = 0; i < cnt; i++) {
if (!vm_alloc_page_with_initializer(VM_ANON | VM_MARKER_0, upage + i * PGSIZE, true, NULL, NULL))
return false;
if (!vm_claim_page(upage + i * PGSIZE))
return false;
}
return true;
}
- 하나 이상의 페이지를 할당하기 위해 반복문을 추가했다.
Memory Mapped Files #
더보기
Anonymous pages와는 다르게, memory-mapped pages는 파일 기반 매핑(file-backed mapping)이다.
- mmap 페이지의 컨텐츠는 어떤 존재하는 파일의 데이터를 미러링한다.
- 페이지 폴트가 발생하면 물리 프레임이 즉시 할당되고 컨텐츠가 파일에서 메모리로 복사된다.
- mmap 페이지가 매핑이 해제되거나 swap out되면 컨텐츠의 변경사항이 파일에 반영된다.
mmap과 munmap 시스템 콜
메모리 매핑된 파일들을 위한 시스템 콜 mmap과 mummap을 구현해야 한다: VM 시스템은 mmap 영역에서 페이지를 지연 로딩해야 하고, mmap된 파일 자체를 매핑을 위한 backing용 저장소로 사용해야 한다. 시스템 콜 구현을 위해 vm/file.c에서 정의된 do_mmap과 do_munmap을 구현하고 사용해야 한다.
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset);
fd로 파일을 열어offset바이트부터 시작해length바이트만큼을 프로세스의 가상 주소 공간의 주소addr에 매핑한다.- 전체 파일은 addr에서 시작하는 연속적인 가상 페이지들에 매핑된다.
- 파일의 길이가 PGSIZE의 배수가 아니면, 마지막으로 매칭된 페이지의 일부 바이트들이 EOF를 넘어 "stick out"된다.
- 페이지가 폴트되면 이 바이트들을 0으로 설정하고, disk로 write back되면 버린다.
- 성공할 경우, 이 함수는 파일이 매핑된 가상주소를 리턴한다.
- 실패할 경우, 파일이 매핑된 유효한 주소가 아닌 NULL을 리턴해야 한다.
mmap실패 조건fd로 연 파일이 0바이트 길이인 경우mmap호출은 실패할 수 있다.addr이 page-aligned되지 않았거나 기존에 매핑된 페이지 집합(스택이나 실행 가능 파일이 로딩되는 동안에 매핑된 페이지 포함)과 겹치는 경우 실패해야 한다.- 구현의 단순성을 위해 주어진
addr에서만mmap을 시도할 수 있다. addr이 0이라면, 일부 핀토스 코드가 가상 페이지 0이 매핑되지 않았다고 가정하기 때문에 실패해야 한다.length가 0일때도 실패해야 한다.- 콘솔 input 및 output을 나타내는 파일 디스크립터와는 매핑될 수 없다.
- mmap 페이지들은 anon 페이지들과 같은 방식으로 lazy하게 할당되어야 한다.
- 페이지 객체를 만들기 위해
vm_alloc_page_with_initializer를 사용할 수 있다.
- 페이지 객체를 만들기 위해
void munmap (void *addr);
addr범위의 지정된 주소의 매핑을 unmap한다.addr는 아직 매핑이 해제되지 않은 같은 프로세스의 mmap에 대한 이전 호출에서 반환한 가상주소여야 한다.
- 모든 매핑은 프로세스가 (
exit되거나 어떻게든) 종료될 때 묵시적으로 unmap되어야 한다.- (명시적으로든 묵시적으로든) 매핑이 해제되면, 프로세스에 의해 write된 모든 페이지들은 파일로 write back되어야 하고, write되지 않은 페이지들은 write back되면 안 된다.
- 이후 페이지들은 프로세스의 가상 페이지 리스트에서 제거된다.
- 파일을 닫거나 지우는 것이 매핑을 해제하지는 않는다.
- 일단 생성되면, (Unix 컨벤션에 의해 따라)
mummap이 호출되거나 프로세스가 종료되기 전까지는 유효하다. - 각 매핑에 대해 대한 분리되고 독립적인 참조를 얻기 위해
file_reopen함수를 사용해야 한다.
- 일단 생성되면, (Unix 컨벤션에 의해 따라)
- 둘 이상의 프로세스들이 같은 파일을 매핑한다해도 일관적인 데이터를 봐야 할 필요는 없다.
- Unix의 경우, 이러한 매핑들이 같은 물리 페이지를 공유하고
mmap시스템 콜이 클라이언트가 페이지가 공유된 것인지 사적인 것인지 특정하도록 허락하는 인자를 가진다. (예: copy-on-write)
- Unix의 경우, 이러한 매핑들이 같은 물리 페이지를 공유하고
- 필요에 따라
vm/vm.c에서vm_file_init과vm_file_initializer를 수정할 수도 있다.
void vm_file_init (void);
- File-bakced page 서브 시스템을 초기화한다: File-backed page와 관련된 어떤 것이든 설정할 수 있다.
bool file_backed_initializer (struct page *page, enum vm_type type, void *kva);
- File-backed page를 초기화한다.
- 먼저
page->operations에서 file-backed page를 위한 핸들러를 설정한다. struct page에서 메모리에 backing된 파일과 같은 정보들을 업데이트할 수 있다.
- 먼저
static void file_backed_destroy (struct page *page);
- 연관된 파일을 close함으로써 file-backed page를 파괴한다.
- 컨텐츠가 dirty하다면, 파일에 변경사항을 write back 해야 한다.
- 이 함수에서
struct page를 free할 필요는 없다: 이 함수의 호출자에서 free해야 한다.
💻 코드 구현
🔹 mmap() 시스템 콜
/* Load file data into memory. */
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset) {
if (length == 0 || pg_round_down(addr) != addr || pg_round_down(offset) != offset || addr == 0 || fd == 0 || fd == 1)
return NULL;
struct file *file = thread_current()->fdt[fd];
if (file == NULL) return NULL;
lock_acquire(&filesys_lock);
off_t len = file_length(file);
lock_release(&filesys_lock);
if (len == 0) return NULL;
return do_mmap(addr, length, writable, file, offset);
}
🔹 munmap() 시스템 콜
/* Unmap the mappings which has not been previously unmapped. */
void munmap (void *addr) {
if (pg_round_down(addr) != addr || addr == 0)
return;
do_munmap(addr);
}
🔹 File-backed page 구조체
struct file_page {
struct list_elem elem;
void *va;
struct file *file;
off_t ofs;
uint32_t read_bytes;
};
🔹 File-backed page 초기화 함수
/* Initialize the file backed page */
bool
file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
page->operations = &file_ops;
struct file_page *file_page = &page->file;
file_page->va = page->va;
}
🔹 File-backed page 제거 함수
/* Destory the file backed page. PAGE will be freed by the caller. */
static void
file_backed_destroy (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
if (pml4_is_dirty(thread_current()->pml4, page->va)) {
file_write_at(file_page->file, page->frame->kva, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, false);
}
pml4_clear_page(thread_current()->pml4, page->va);
}
🔹 do_mmap(): load_segment()를 응용함
/* Do the mmap */
void *
do_mmap (void *addr, size_t length, int writable, struct file *file, off_t offset) {
struct file *file_ = file_reopen(file);
size_t read_bytes = MIN(length, file_length(file_) - offset);
size_t zero_bytes = pg_round_up(length) - read_bytes;
ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT (pg_ofs (addr) == 0);
ASSERT (offset % PGSIZE == 0);
void *ret = addr;
while (read_bytes > 0 || zero_bytes > 0) {
/* Do calculate how to fill this page.
* We will read PAGE_READ_BYTES bytes from FILE
* and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = (read_bytes < PGSIZE) ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* TODO: Set up aux to pass information to the lazy_load_segment. */
struct lazy_load_args *aux = (struct lazy_load_args *)malloc(sizeof(struct lazy_load_args));
aux->file = file_;
aux->ofs = offset;
aux->read_bytes = page_read_bytes;
aux->zero_bytes = page_zero_bytes;
if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_segment, aux))
return NULL;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
addr += PGSIZE;
offset += page_read_bytes;
}
return ret;
}
🔹 do_munmap()
/* Do the munmap */
void
do_munmap (void *addr) {
/* TODO: Remove mmaped page from mmap list of current thread */
struct thread *curr = thread_current();
struct page *page = spt_find_page(&curr->spt, addr);
while (page != NULL) {
if (page_get_type(page) != VM_FILE)
return;
destroy(page);
list_remove(&page->file.elem);
spt_remove_page(&curr->spt, page);
addr += PGSIZE;
page = spt_find_page(&curr->spt, addr);
}
}
🔹 process_exit(): 프로세스 종료 시 mmap된 모든 페이지를 unmap함
#ifdef VM
struct list_elem *e = list_begin(&curr->mmap_pages);
while (e != list_end(&curr->mmap_pages)) {
struct file_page *fp = list_entry(e, struct file_page, elem);
e = list_next(e);
do_munmap(&fp->va);
}
#endif
🔹 page_fault(): 특정 테스트에서 커널 패닉을 일으키는 대신 exit(-1)을 호출함
#ifdef VM
/* For project 3 and later. */
if (vm_try_handle_fault (f, fault_addr, user, write, not_present))
return;
exit(-1);
#endif'Krafton Jungle > 5. PintOS' 카테고리의 다른 글
| [PintOS 5주차] Day 4-5 (0) | 2025.06.09 |
|---|---|
| [PintOS 5주차] Day 3 (0) | 2025.06.07 |
| [PintOS 5주차] Day 1 (0) | 2025.06.05 |
| [PintOS 4주차] Day 7 (0) | 2025.06.04 |
| [PintOS 4주차] Day 6 (0) | 2025.06.03 |