Krafton Jungle/5. PintOS

[PintOS 5주차] Day 2

munsik22 2025. 6. 6. 11:02

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되면 컨텐츠의 변경사항이 파일에 반영된다.

mmapmunmap 시스템 콜

메모리 매핑된 파일들을 위한 시스템 콜 mmapmummap을 구현해야 한다: VM 시스템은 mmap 영역에서 페이지를 지연 로딩해야 하고, mmap된 파일 자체를 매핑을 위한 backing용 저장소로 사용해야 한다. 시스템 콜 구현을 위해 vm/file.c에서 정의된 do_mmapdo_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의 경우, 이러한 매핑들이 같은 물리 페이지를 공유하고 mmap 시스템 콜이 클라이언트가 페이지가 공유된 것인지 사적인 것인지 특정하도록 허락하는 인자를 가진다. (예: copy-on-write)
  • 필요에 따라 vm/vm.c에서 vm_file_initvm_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