PintOS_3 | Virtual Memory | Anonymous Page

wony·2022년 6월 26일

핀토스가 진행될수록 코드 안에 달려있던 주석이 생각보다 친절했음을 깨닫는 것 같다. 늘 그랬듯이 타고 들어가면서 이번에는 나오는 주석들을 먼저 정리했다. 흐름을 이해하는 데도 도움이 되고, 코드만 읽고 만들기엔 고민되는 부분들도 어느정도 해소해주는 것 같다.

COMMENT 정리

file.c/file_read

/* Reads SIZE bytes from FILE into BUFFER,
 * starting at the file's current position.
 * Returns the number of bytes actually read,
 * which may be less than SIZE if end of file is reached.
 * Advances FILE's position by the number of bytes read. */
/* file의 현재위치에서 시작해서 size바이트를 버퍼로 읽음
 * 실제로 읽은 바이트수를 반환
 * 파이 끝에 도달하면 size보다 작을 수 있음
 * 읽은 바이트 수만큼 file의 위치를 이동함 */
off_t
file_read (struct file *file, void *buffer, off_t size) {
	off_t bytes_read = inode_read_at (file->inode, buffer, size, file->pos);
	file->pos += bytes_read;
	return bytes_read;
}

vm.h

#ifndef VM_VM_H
#define VM_VM_H
#include <stdbool.h>
#include "threads/palloc.h"

enum vm_type {
	/* page not initialized */
	/* 페이지가 초기화되지 않음 */
	VM_UNINIT = 0,
	/* page not related to the file, aka anonymous page */
	/* 파일과 관련 없는 페이지 */
	VM_ANON = 1,
	/* page that realated to the file */
	/* 파일과 관련된 페이지 */
	VM_FILE = 2,
	/* page that hold the page cache, for project 4 */
	/* 페이지 캐시가 들어있는 페이지 - project 4 */
	VM_PAGE_CACHE = 3,

	/* Bit flags to store state */
	/* 저장 상태에 대한 비트 플래그 */

	/* Auxillary bit flag marker for store information. You can add more
	 * markers, until the value is fit in the int. */
	/* 저장소 정보에 대한 보조 비트 플래그 마커.
	 * int 사이즈 내에서(?) 마커를 추가할 수 있음?? */
	VM_MARKER_0 = (1 << 3),
	VM_MARKER_1 = (1 << 4),

	/* DO NOT EXCEED THIS VALUE. */
	/* 이 값을 초과할 수 없음 */
	VM_MARKER_END = (1 << 31),
};

#include "kernel/hash.h"
#include "vm/uninit.h"
#include "vm/anon.h"
#include "vm/file.h"
#ifdef EFILESYS
#include "filesys/page_cache.h"
#endif

struct page_operations;
struct thread;
struct frame_table frame_table;

...

threads/mmu.c

/* Returns the address of the page table entry for virtual
 * address VADDR in page map level 4, pml4.
 * If PML4E does not have a page table for VADDR, behavior depends
 * on CREATE.  If CREATE is true, then a new page table is
 * created and a pointer into it is returned.  Otherwise, a null
 * pointer is returned. */
uint64_t *
**pml4e_walk** (uint64_t *pml4e, const uint64_t va, int create) {
	uint64_t *pte = NULL;
	int idx = PML4 (va);
	// 받은 가상주소에서 pml4 offset만큼만 떼와서 idx에 저장
	int allocated = 0;
	if (pml4e) {
		// pml4e 가 있으면 : init에서 넘어올때는 당연히 있음 palloc 할당받을때 assert 줬어서 안됐으면 패닉 났을거
		// 다른 경우에는 할당 안됐으면 null일수도.
		uint64_t *pdpe = (uint64_t *) pml4e[idx];
		// pml4e[idx] = pml4 첫 테이블에서 idx에 해당하는 page directory pointer entry?
		if (!((uint64_t) pdpe & PTE_P)) {
			// PTE_P(1) : pdpe의 사용유무 확인 
			if (create) {
				// create가 1이면(init에선 1로 들어옴) pallocgetpage
				uint64_t *new_page = palloc_get_page (PAL_ZERO);
				if (new_page) {
					// new_page 주소에 user, writable(쓰기가능유무), present(사용유무) 세팅해서
					// pml4e[idx]에 방금 만든 페이지를 물리주소로 바꿔서 넣고
					// allocated = 1
					pml4e[idx] = vtop (new_page) | PTE_U | PTE_W | PTE_P;
					allocated = 1;
				} else
					// new_page 안만들어졌으면 null
					return NULL;
			} else
				// create가 1이 아니면 null
				return NULL;
		}
		// pdpe가 사용중이었을 때, 혹은 아니었지만 create가 1이라 새로 만들고 나서 여기로 옴
		pte = pdpe_walk (ptov (PTE_ADDR (pml4e[idx])), va, create);
		// 다음 단계로 보내는데 12 밑으로 잘라서 주는거같음, va랑 create도 그대로
	}
	if (pte == NULL && allocated) {
		// 만들지도 않았고 찾지도
		palloc_free_page ((void *) ptov (PTE_ADDR (pml4e[idx])));
		pml4e[idx] = 0;
	}
	return pte;
}

/************************************************************************/

/* Adds a mapping in page map level 4 PML4 from user virtual page
 * UPAGE to the physical frame identified by kernel virtual address KPAGE.
 * UPAGE must not already be mapped. KPAGE should probably be a page obtained
 * from the user pool with palloc_get_page().
 * If WRITABLE is true, the new page is read/write;
 * otherwise it is read-only.
 * Returns true if successful, false if memory allocation
 * failed. */
/* 사용자 가상 페이지 upage로 부터 
 * 커널 가상 주소 kpage로 식별된 물리적 프레임으로의 매핑을 pml4에 추가
 * upage는 아직 매핑되지 않아야함
 * kpage는 palloc_get_page를 통해 user pool에서 가져온 페이지여야 함
 * writable이 true이면 유저프로세스가 페이지를 수정할 수 있고, 아니면 읽기 전용임
 * 성공하면 true를 반환하고, 
 * 메모리 할당이 실패하면 false를 반환
 * (install_page랑 굉장히 비슷) */

bool
**pml4_set_page** (uint64_t *pml4, void *upage, void *kpage, bool rw) {
	ASSERT (pg_ofs (upage) == 0);
	ASSERT (pg_ofs (kpage) == 0);
	ASSERT (is_user_vaddr (upage));
	ASSERT (pml4 != base_pml4);

	uint64_t *pte = pml4e_walk (pml4, (uint64_t) upage, 1);

	if (pte)
		*pte = vtop (kpage) | PTE_P | (rw ? PTE_W : 0) | PTE_U;
	return pte != NULL;
}

userprog/exception.c/page_fault

/* Page fault handler.  This is a skeleton that must be filled in
   to implement virtual memory.  Some solutions to project 2 may
   also require modifying this code.
   가상 메모리를 구현하기 위해 반드시 채워져야함
   // 프로젝트2에 대한 일부 솔루션도 이 코드를 수정해야할수도.

   At entry, the address that faulted is in CR2 (Control Register
   2) and information about the fault, formatted as described in
   the PF_* macros in exception.h, is in F's error_code member.  The
   example code here shows how to parse that information.  You
   can find more information about both of these in the
   description of "Interrupt 14--Page Fault Exception (#PF)" in
   [IA32-v3a] section 5.15 "Exception and Interrupt Reference".
   입력시 폴트가 발생하는 주소는 cr2에 있고?
   exception.h에 있는 PF_* 매크로에 설명된 대로 포맷된?, 
   폴트에 대한 정보는 f의 error_code 멤버에 있음?

   이 예제코드는 어떻게 정보를 파싱하는지 보여준다. 
   
   이 두가지에 대한 자세한 내용은 
   [IA32-v3a] section 5.15 "Exception and Interrupt Reference의 
   Interrupt 14--Page Fault Exception (#PF)에서 찾을 수 있다.   
    */
static void
page_fault (struct intr_frame *f) {
	bool not_present;  /* True: not-present page, false: writing r/o page. */
	bool write;        /* True: access was write, false: access was read. */
	bool user;         /* True: access by user, false: access by kernel. */
	void *fault_addr;  /* Fault address. */

	/* Obtain faulting address, the virtual address that was
	   accessed to cause the fault.  It may point to code or to
	   data.  It is not necessarily the address of the instruction
	   that caused the fault (that's f->rip).
	   폴팅 주소를 가져옴 : 폴트를 발생시키도록 접근한 가상 주소
	   코드 또는 데이터를 가리킬 수 있음
	   오류를 일으킨 명령어의 주소(f->rip)일 필요는 없음
	   // 명령의 주소 말고 요청된 페이지의 주소나 접근하려는 주소겠지..?	   
	    */

	fault_addr = (void *) rcr2();

	/* Turn interrupts back on (they were only off so that we could
	   be assured of reading CR2 before it changed).
	   인터럽트를 다시 켬(변경되기 전에 cr2를 읽을 수 있도록 하기 위해(보장받기위해?) 꺼졌을 뿐이래)
	    */
	intr_enable ();

	/* Determine cause. */
	/* 원인 파악 */
	not_present = (f->error_code & PF_P) == 0;
	write = (f->error_code & PF_W) != 0;
	user = (f->error_code & PF_U) != 0;

#ifdef VM
	/* For project 3 and later. */
	if (vm_try_handle_fault (f, fault_addr, user, write, not_present))
		return;
#endif

	/* Count page faults. */
	page_fault_cnt++;

	/* If the fault is true fault, show info and exit. */
	printf ("Page fault at %p: %s error %s page in %s context.\n",
			fault_addr,
			not_present ? "not present" : "rights violation",
			write ? "writing" : "reading",
			user ? "user" : "kernel");
			
	/* Project 2 : File Descriptor*/
	// kill (f);
	exit(-1);
}

process.c/load

/* Loads an ELF(실행파일) executable from FILE_NAME into the current thread.
 * Stores the executable's entry point into *RIP(다음 실행할 명령어의 주소 보관함)
 * and its initial stack pointer into *RSP(스택의 꼭대기).
 * Returns true if successful, false otherwise. */
static bool
load (const char *file_name, struct intr_frame *if_) {
	// printf("\n ##### start load ##### \n");

	struct thread *t = thread_current ();
	struct ELF ehdr;
	struct file *file = NULL;
	off_t file_ofs;
	bool success = false;
	int i;

	/* Allocate and activate page directory. */
	t->pml4 = pml4_create ();
	// pml4 = palloc_get_page(0)해서 
	// base_pml4에 있는거 PGSIZE만큼 copy하고 pml4반환
	if (t->pml4 == NULL)
		goto done;
	process_activate (thread_current ());
	// printf("\n ##### after process_activate ##### \n");

	// pml4_activate(thread->pml4)
	// ㄴ pml4의 주소를 vtop해서 cr3레지스터에 저장
	// tss_update(thread)
	// ㄴ tss->rsp0에 rsp저장?(계산이 thread+PGSIZE 임 왜이렇게 계산함)

	/* project 2 : Denying Write to Executable */
	/* lock 획득 */
	// lock_acquire(&deny_write_lock);

	/* Open executable file. */
	file = filesys_open (file_name);
	if (file == NULL) {

		// lock_release(&deny_write_lock);
		printf ("load: %s: open failed\n", file_name);
		goto done;
	}
	/* project 2 : Denying Write to Executable */
	/* 실행 중인 스레드 t의 running을 실행할 파일로 초기화*/
	t->running = file;

	/* 현재 오픈한 파일에 다른내용 쓰지 못하게 함 */
	file_deny_write(file);

	// lock_release(&deny_write_lock);

	/* Read and verify executable header. */
	if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
			|| memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7)
			|| ehdr.e_type != 2
			|| ehdr.e_machine != 0x3E // amd64
			|| ehdr.e_version != 1
			|| ehdr.e_phentsize != sizeof (struct Phdr)
			|| ehdr.e_phnum > 1024) {
		printf ("load: %s: error loading executable\n", file_name);
		goto done;
	}

	
    ...
    

process.c/load_segment (for #ifndef VM)

/* Loads a segment starting at offset OFS in FILE at address
 * UPAGE.  In total, READ_BYTES + ZERO_BYTES bytes of virtual
 * memory are initialized, as follows:
 *
 * - READ_BYTES bytes at UPAGE must be read from FILE
 * starting at offset OFS.
 *
 * - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
 *
 * The pages initialized by this function must be writable by the
 * user process if WRITABLE is true, read-only otherwise.
 *
 * Return true if successful, false if a memory allocation error
 * or disk read error occurs. */

/* upage의 file에서 ofs에서 시작하는 세그먼트를 로드함
 * 총 가상 메모리의 read_bytes + zero_bytes는 다음과 같이 초기화됨
 * 
 * upage에서 read_bytes는 file에서 ofs부터 시작해서 읽어야함? ofs부터 시작하는 file?
 * 
 * upage에서 zero_bytes + read_bytes 는 0으로 설정
 * 
 * writable이 true이면 사용자 프로세스에 의해 쓰기 가능해야 하고,
 * 아니라면 읽기전용이어야 함
 * 
 * 성공하면 true를 반환하고, 
 * 메모리 할당 오류 또는 디스크 읽기 오류가 발생하면 false를 반환 */

static bool
load_segment (struct file *file, off_t ofs, uint8_t *upage,
		uint32_t read_bytes, uint32_t zero_bytes, bool writable) {
	ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
	ASSERT (pg_ofs (upage) == 0);
	ASSERT (ofs % PGSIZE == 0);

	file_seek (file, ofs);
	while (read_bytes > 0 || zero_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. */
		/* 이 페이지를 어떻게 채울지 계산할 것
		 * file에서 page_read_bytes 바이트를 읽고
		 * page_zero_bytes를 0으로 설정? */
		size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
		// 읽을 바이트수가 pgsize보다 작으면 그대로, 아니면 PGSIZE로 세팅
		size_t page_zero_bytes = PGSIZE - page_read_bytes;
		// PGSIZE - 읽을 바이트 수 : 페이지 단위로 남는 바이트 수?

		/* Get a page of memory. */
		uint8_t *kpage = palloc_get_page (PAL_USER);
		if (kpage == NULL)
			return false;

		/* Load this page. */
		if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes) {
			// 읽어온 바이트 수를 반환 != 읽을 바이트 수 => 읽으려던 만큼 못읽은건가..?
			palloc_free_page (kpage);
			// 그런 거라면 실패니까 반환 ㅇㅇ return도 ㅇㅇ
			return false;
		}
		memset (kpage + page_read_bytes, 0, page_zero_bytes);
		// 실패 아니면? (kpage+읽을(은) 바이트 수)에서 zero만큼 0으로 세팅
		// => 한 페이지 단위로 읽어오고 남은 부분은 0으로 맞춰주는 듯
		// 읽을 바이트가 PGSIZE면 zero는 0임 
		// 근데 이러면 둘이 더하면 0이 아니라 PGSIZE 나오는데..?

		/* Add the page to the process's address space. */
		/* 유저 프로세스의 주소공간에 페이지를 추가 */
		if (!install_page (upage, kpage, writable)) {
			// pml get, set 호출
			// 여기도 실패하면 반환
			printf("fail\n");
			palloc_free_page (kpage);
			return false;
		}

		/* Advance. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		upage += PGSIZE;
	}
	return true;
}

process.c/install_page (for #ifndef VM)

/* Adds a mapping from user virtual address UPAGE to kernel
 * virtual address KPAGE to the page table.
 * If WRITABLE is true, the user process may modify the page;
 * otherwise, it is read-only.
 * UPAGE must not already be mapped.
 * KPAGE should probably be a page obtained from the user pool
 * with palloc_get_page().
 * Returns true on success, false if UPAGE is already mapped or
 * if memory allocation fails. */
/* 사용자 가상 주소 upage에서 커널 가상 주소 kpage로의 매핑을 페이지 테이블에 추가
 * writable이 true이면 유저프로세스가 페이지를 수정할 수 있고, 아니면 읽기 전용임
 * upage는 아직 매핑되지 않아야함
 * kpage는 palloc_get_page를 통해 user pool에서 가져온 페이지여야 함
 * 성공하면 true를 반환하고, 
 * 이미 매핑되어 있거나 메모리 할당이 실패하면 false를 반환 */
static bool
install_page (void *upage, void *kpage, bool writable) {
	struct thread *t = thread_current ();

	/* Verify that there's not already a page at that virtual
	 * address, then map our page there. */
	/* 해당 가상 주소에 페이지가 아직 없는지 확인하고 해당 페이지에 매핑 */
	return (pml4_get_page (t->pml4, upage) == NULL
			&& pml4_set_page (t->pml4, upage, kpage, writable));
}

IMPLEMENT

process.h

#ifndef USERPROG_PROCESS_H
#define USERPROG_PROCESS_H

#include "threads/thread.h"

tid_t process_create_initd (const char *file_name);
tid_t process_fork (const char *name, struct intr_frame *if_);
int process_exec (void *f_name);
int process_wait (tid_t);
void process_exit (void);
void process_activate (struct thread *next);

/* project 2 : Argument Passing */
// static void argument_stack(int argc_cnt, char **argv_list, void **stp)
static void argument_stack(struct intr_frame *if_, int argv_cnt, char **argv_list);
bool setup_stack (struct intr_frame *if_);

/* project 2 : Process Structure */
struct thread * get_child(int pid);

struct aux_lazy {
	struct file *file;
	off_t ofs;
	uint32_t read_bytes;
	uint32_t zero_bytes;
};

/* project 2 : Denying Write to Executable */
// struct lock deny_write_lock;

#endif /* userprog/process.h */

anon.h

#ifndef VM_ANON_H
#define VM_ANON_H
#include "vm/vm.h"
struct page;
enum vm_type;

struct anon_page {
	enum vm_type type;
	bool is_stack;
	// 스왑 슬롯
};

void vm_anon_init (void);
bool anon_initializer (struct page *page, enum vm_type type, void *kva);

#endif

vm.h

/* 헤더 추가 */
#include "kernel/hash.h"

/* 구조체 선언 추가 */
struct frame_table frame_table;

/* page 구조체 수정 */
struct page {
	const struct page_operations *operations;
	void *va;              /* Address in terms of user space */
	struct frame *frame;   /* Back reference for frame */

	/* Your implementation */
	struct hash_elem hash_elem;
	bool writable;
	/* Per-type data are binded into the union.
	 * Each function automatically detects the current union */
	union {
		struct uninit_page uninit;
		struct anon_page anon;
		struct file_page file;
#ifdef EFILESYS
		struct page_cache page_cache;
#endif
	};
};

/* frame 구조체 수정 */
struct frame {
	void *kva;	// physical
	struct page *page;
	struct hash_elem hash_elem;
};

/* frame table 구조체 추가*/
struct frame_table{
	struct hash *hash_table;
};

/* spt 구조체 수정*/
struct supplemental_page_table {
	struct hash *hash_table;	
};

/* 함수 선언 */
unsigned page_hash(const struct hash_elem *h, void *aux UNUSED);
bool page_less(const struct hash_elem *a, const struct hash_elem *b, void *aux UNUSED);
unsigned frame_hash(const struct hash_elem *h, void *aux UNUSED);
bool frame_less(const struct hash_elem *a, const struct hash_elem *b, void *aux UNUSED);

process.c

initd()

/* A thread function that launches first user process. */
static void
initd (void *f_name) {
// #ifdef VM
// 	supplemental_page_table_init (&thread_current ()->spt);
// #endif

	process_init ();

	if (process_exec (f_name) < 0)
		PANIC("Fail to launch initd\n");
	NOT_REACHED ();
}a
  • spt 초기화 위치 이동(→process_exec)

process_exec()

/* Switch the current execution context to the f_name.
 * Returns -1 on fail. */

/* process 실행 */
// 파싱되지 않은 f_name을 인자로 받아서 전체적으로 파싱해준다
int
process_exec (void *f_name) { 
	// printf("\n ##### debug ##### start process_exec | f_name : %s \n", f_name);
	char *file_name = f_name;
	bool success;
	char *token, *save_ptr;
	int argc=0;
	char *argv[30];

	/* -------------- Project 2 ---------------*/
	/* Command Line 전체 Parsing */
	for (token = strtok_r (file_name, " ", &save_ptr); token != NULL; 
	token = strtok_r (NULL, " ", &save_ptr)){
		
		argv[argc] = token;
		argc++;

	}
   
	/* We cannot use the intr_frame in the thread structure.
	 * This is because when current thread rescheduled,
	 * it stores the execution information to the member. */
	struct intr_frame _if;
	_if.ds = _if.es = _if.ss = SEL_UDSEG;
	_if.cs = SEL_UCSEG;
	_if.eflags = FLAG_IF | FLAG_MBS;

	/* We first kill the current context */
	process_cleanup ();

#ifdef VM
	supplemental_page_table_init (&thread_current ()->spt);
#endif

	/* And then load the binary */
	success = load (file_name, &_if);

	/* If load failed, quit. */
	if (!success)
	{
		palloc_free_page(file_name);
		return -1;
	}

	argument_stack(&_if, argc, argv);

	// argument_stack(argc, argv, _if.rsp);

	// hex_dump(_if.rsp, _if.rsp, USER_STACK - _if.rsp, true);

	/* Start switched process. */
	do_iret (&_if);
	NOT_REACHED ();
}
  • spt 초기화 위치 이동(←initd)

lazy_load_segment()

static bool
lazy_load_segment (struct page *page, void *aux) {
	// printf("\n ##### debug ##### start lazy_load_segment \n");
	/* TODO: Load the segment from the file */
	/* TODO: This called when the first page fault occurs on address VA. */
	/* TODO: VA is available when calling this function. */
	/* todo: 파일에서 세그먼트를 로드 */
	/* todo: va에서 첫번째 페이지폴트가 발생할 때 호출됨 */
	/* todo: 이 함수를 호출할 때(호출해야?) va를 사용할 수 있음 */
	
	struct aux_lazy *aux_lazy = aux;
	
	file_seek (aux_lazy->file, aux_lazy->ofs);
	// printf("\n ##### debug ##### in lazy_load_segment \n");
	// printf("\n ##### debug ##### aux->file : %p\n", &aux_lazy->file);
	// printf("\n ##### debug ##### aux->read : %d\n", &aux_lazy->read_bytes);
	// printf("\n ##### debug ##### aux->zero : %d\n", &aux_lazy->zero_bytes);
	
	/* Load this page. */
	if (file_read (aux_lazy->file, page->frame->kva, aux_lazy->read_bytes) != (int) aux_lazy->read_bytes) {
		// 읽어온 바이트 수를 반환 != 읽을 바이트 수 => 읽으려던 만큼 못읽은건가..?
		// palloc_free_page (page->frame->kva);
		// 그런 거라면 실패니까 반환 ㅇㅇ return도 ㅇㅇ
		return false;
	}
	memset (page->frame->kva + aux_lazy->read_bytes, 0, aux_lazy->zero_bytes);
	free(aux_lazy);

	return true;
	
}

load_segment()

/* Loads a segment starting at offset OFS in FILE at address
 * UPAGE.  In total, READ_BYTES + ZERO_BYTES bytes of virtual
 * memory are initialized, as follows:
 * upage에서 file의 오프셋 ofs에서 시작하는 세그먼트를 로드함
 * 총 가상메모리의 (read_bytes + zero_bytes)바이트가 초기화됨, 다음과 같이
 * 
 * - READ_BYTES bytes at UPAGE must be read from FILE
 * starting at offset OFS.
 * - upage의 read_bytes는 오프셋 ofs에서 시작하는 file에서 읽어야함
 * file의 ofs부터 읽어야한다고..?
 *
 * - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
 * - upage에서 read+zero = 0이어야 함
 *
 * The pages initialized by this function must be writable by the
 * user process if WRITABLE is true, read-only otherwise.
 * 이 함수로 초기화된 페이지는 
 * writable이 true일 때 유저프로세스에 의해 쓰기가능하고,
 * 아니라면 읽기전용임
 *
 * Return true if successful, false if a memory allocation error
 * or disk read error occurs. 
 * 성공하면 true를 반환하고,
 * 메모리 할당 오류 또는 디스크 읽기 오류가 발생하면 false를 반환
 * */

static bool
load_segment (struct file *file, off_t ofs, uint8_t *upage,
		uint32_t read_bytes, uint32_t zero_bytes, bool writable) {
	// printf("\n ##### start load_segment ##### \n");
	ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
	ASSERT (pg_ofs (upage) == 0);
	ASSERT (ofs % PGSIZE == 0);

	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. */
		/* 이 페이지를 어떻게 채울지 계산할 것
		 * file에서 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;

		/* TODO: Set up aux to pass information to the lazy_load_segment. */
		/* todo: lazy_load_segment에 정보를 전달하도록 aux를 세팅 */
		
		
		struct aux_lazy *aux = calloc(1, sizeof(struct aux_lazy));
		aux->file = file;
		aux->ofs = ofs;
		aux->read_bytes = page_read_bytes;
		aux->zero_bytes = page_zero_bytes;
		
		// printf("\n ##### debug ##### in load_segment \n");
		// printf("\n ##### debug ##### aux->file : %p\n", &aux->file);
		// printf("\n ##### debug ##### aux->read : %d\n", &aux->read_bytes);
		// printf("\n ##### debug ##### aux->zero : %d\n", &aux->zero_bytes);
		
		ofs += PGSIZE;
		if (!vm_alloc_page_with_initializer (VM_ANON, upage,
					writable, lazy_load_segment, aux))
			// 여기서 부르는 건 vm_alloc_page_with_initializer에 인자로 전달돼서
			// uninit_new할때 init으로 들어가서 세팅만 돼있는거고
			// 나중에 진짜 폴트나서 uninit_initialize가 호출되면
			// 그때 init이 실행돼서 lazy_load_segment로 넘어가네
			// 여기선 부르는게 아님 
			// vm_alloc~_initializer만 제대로 되면 됨
			return false;

		// 그럼 정작 파일 읽어오는 것도 나중인거?네?아닌가? 카운트를 여기서하누;
		// 음 저쪽(lazy_load_segment)에서 읽는거 맞는거 같음
		/* Advance. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		upage += PGSIZE;
	}
	return true;
}

setup_stack()

/* Create a PAGE of stack at the USER_STACK. Return true on success. */
bool
setup_stack (struct intr_frame *if_) {
	// printf("\n ##### start setup_stack ##### \n");
	bool success = false;
	void *stack_bottom = (void *) (((uint8_t *) USER_STACK) - PGSIZE);

	/* TODO: Map the stack on stack_bottom and claim the page immediately.
	 * todo: stack bottom에 스택을 매핑하고, 즉시 페이지를 요청 
	 * TODO: If success, set the rsp accordingly.
	 * todo: 성공하면 그에 따라 rsp를 설정
	 * TODO: You should mark the page is stack.
	 * todo: 페이지가 스택임을 표시 */
	/* TODO: Your code goes here */

	if(vm_alloc_page(VM_ANON|VM_MARKER_0, stack_bottom, 1)){
		success = vm_claim_page(stack_bottom);
		if(success){
			if_->rsp = USER_STACK;
		}
	}

	return success;
}

syscall.h

/* 헤더 추가 */
#include "vm/vm.h"

/* 매핑 검사 테이블 변경 */
// pml4_get_page(page map, addr(유저 가상주소)) : 유저 가상주소와 대응하는 물리주소를 확인하여 해당 물리주소와 연결된 커널 가상 주소를 반환하거나 만약 해당 물리주소가 가상 주소와 매핑되지 않은 영역이면 NULL 반환
void
check_address(void *addr)
{	
	struct thread *t = thread_current(); // 현재 스레드의 thread 구조체를 사용하기 위해서 t를 선언
	if (!is_user_vaddr(addr)||addr==NULL||spt_find_page(&t->spt, addr)==NULL){
		// 해당 주소값이 유저 가장 주소에 해당하지 않고 or addr = Null or 유저 가상주소가 물리주소와 매핑되지 않은 영역

		exit(-1);
	}

}

anon.c

/* anon.c: Implementation of page for non-disk image (a.k.a. anonymous page). */

#include "vm/vm.h"
#include "devices/disk.h"

/* DO NOT MODIFY BELOW LINE */
static struct disk *swap_disk;
static bool anon_swap_in (struct page *page, void *kva);
static bool anon_swap_out (struct page *page);
static void anon_destroy (struct page *page);

/* DO NOT MODIFY this struct */
static const struct page_operations anon_ops = {
	.swap_in = anon_swap_in,
	.swap_out = anon_swap_out,
	.destroy = anon_destroy,
	.type = VM_ANON,
};

/* Initialize the data for anonymous pages */
void
vm_anon_init (void) {
	/* TODO: Set up the swap_disk. */
	
	
	swap_disk = NULL;
}

/* Initialize the file mapping */
bool
**anon_initializer** (struct page *page, enum vm_type type, void *kva) {
	/* Set up the handler */
	page->operations = &anon_ops;
	// printf("\n ##### debug ##### anon_initializer | type : %d \n", type);

	struct anon_page *anon_page = &page->anon;
	if(type & VM_MARKER_0){
		// printf("\n ##### debug ##### anon_initializer | if \n");
		anon_page->is_stack = 1;
	}
	else{
		anon_page->is_stack = 0;
	}
	return true;
}

/* Swap in the page by read contents from the swap disk. */
static bool
anon_swap_in (struct page *page, void *kva) {
	struct anon_page *anon_page = &page->anon;
}

/* Swap out the page by writing contents to the swap disk. */
static bool
anon_swap_out (struct page *page) {
	struct anon_page *anon_page = &page->anon;
}

/* Destroy the anonymous page. PAGE will be freed by the caller. */
static void
**anon_destroy** (struct page *page) {
	struct anon_page *anon_page = &page->anon;
	palloc_free_page(page->frame->kva);
	// hash_delete(&frame_table, &page->frame->hash_elem);
	free(page->frame);
	return;
}

uninit.c

/* uninit.c: Implementation of uninitialized page.
 *
 * All of the pages are born as uninit page. When the first page fault occurs,
 * the handler chain calls uninit_initialize (page->operations.swap_in).
 * The uninit_initialize function transmutes the page into the specific page
 * object (anon, file, page_cache), by initializing the page object,and calls
 * initialization callback that passed from vm_alloc_page_with_initializer
 * function.
 * 
 * 모든 페이지는 uninit page로 생성됨
 * 첫번째 page fault에서 핸들러가 uninit_initialize(page->operations.swap_in)을 호출
 * uninit_initialize는 페이지 개체를 초기화함으로써 페이지를 특정 개체(anon, file, page_cache)로 변환하고
 * vm_alloc_page_with_initializer 함수에서 전달된 initialization callback을 호출
 * 
 * */

#include "vm/vm.h"
#include "vm/uninit.h"

static bool uninit_initialize (struct page *page, void *kva);
static void uninit_destroy (struct page *page);

/* DO NOT MODIFY this struct */
static const struct page_operations uninit_ops = {
	.swap_in = uninit_initialize,
	.swap_out = NULL,
	.destroy = uninit_destroy,
	.type = VM_UNINIT,
};

/* DO NOT MODIFY this function */
void
uninit_new (struct page *page, void *va, vm_initializer *init,
		enum vm_type type, void *aux,
		bool (*initializer)(struct page *, enum vm_type, void *)) {
	// printf("\n ##### start uninit_new ##### \n");
	ASSERT (page != NULL);

	*page = (struct page) {
		.operations = &uninit_ops,
		.va = va,
		.frame = NULL, /* no frame for now */
		.uninit = (struct uninit_page) {
			.init = init,
			.type = type,
			.aux = aux,
			.page_initializer = initializer,
		}
	};
}

/* Initalize the page on first fault */
static bool
uninit_initialize (struct page *page, void *kva) {
	struct uninit_page *uninit = &page->uninit;
/* 	uninit_new에서 page->uninit에 세팅했던걸 꺼냄
	Initiate the contets of the page 
	vm_initializer *init;
	enum vm_type type;
	void *aux;
	* Initiate the struct page and maps the pa to the va *
	bool (*page_initializer) (struct page *, enum vm_type, void *kva); 	
	init, aux 꺼내놓고,
	page_initializer 호출할때 인자로 page, type, kva 넘겨주고
	init이 있으면 init(page, aux) 로 lazy_load_segment 호출
*/

	/* Fetch first, page_initialize may overwrite the values */
	vm_initializer *init = uninit->init;
	void *aux = uninit->aux;

	/* TODO: You may need to fix this function. */
	/* todo: 수정하라는 게 여기야? 호출될 함수들이야? */
	/* anon -> anon_initializer 호출 */
	/* file_backed -> file_backed_initializer 호출 */
	return uninit->page_initializer (page, uninit->type, kva) &&
		(init ? init (page, aux) : true);
		// lazy_load_segment(page, aux)
}

/* Free the resources hold by uninit_page. Although most of pages are transmuted
 * to other page objects, it is possible to have uninit pages when the process
 * exit, which are never referenced during the execution.
 * PAGE will be freed by the caller. */
static void
uninit_destroy (struct page *page) {
	struct uninit_page *uninit UNUSED = &page->uninit;
	/* TODO: Fill this function.
	 * TODO: If you don't have anything to do, just return. */
	return;
}

별로 고친게 없음

vm.c

헤더 추가

vm_init()

/* Initializes the virtual memory subsystem by invoking each subsystem's
 * intialize codes. */
void
vm_init (void) {
	vm_anon_init ();
	vm_file_init ();
#ifdef EFILESYS  /* For project 4 */
	pagecache_init ();
#endif
	register_inspect_intr ();
	/* DO NOT MODIFY UPPER LINES. */
	/* TODO: Your code goes here. */
	frame_table.hash_table = calloc(1, sizeof(struct hash));
	if(frame_table.hash_table){
		hash_init(frame_table.hash_table, frame_hash, frame_less, NULL);
	}
}

page_get_type()

/* Get the type of the page. This function is useful if you want to know the
 * type of the page after it will be initialized.
 * This function is fully implemented now. */
enum vm_type
page_get_type (struct page *page) {
	int ty = VM_TYPE (page->operations->type);
	switch (ty) {
		case VM_UNINIT:
			return VM_TYPE (page->uninit.type);
		default:
			return ty;
	}
}

vm_alloc_page_with_initializer()

/* Create the pending page object with initializer. If you want to create a
 * page, do not create it directly and make it through this function or
 * `vm_alloc_page`. */
bool
vm_alloc_page_with_initializer (enum vm_type type, void *upage, bool writable,
		vm_initializer *init, void *aux) {

	// printf("\n ##### start VM_initializer ##### \n");
	ASSERT (VM_TYPE(type) != VM_UNINIT)

	struct supplemental_page_table *spt = &thread_current ()->spt;

	/* Check wheter the upage is already occupied or not. */
	if (spt_find_page (spt, upage) == NULL) {
		// printf("\n ##### after spt_find_page ##### \n");

		/* TODO: Create the page, fetch the initialier according to the VM type,
		 * 페이지 유형에 따라 페이지 구조체를 할당, 적절한 이니셜라이저 설정, 새 페이지 초기화 
		 * TODO: and then create "uninit" page struct by calling uninit_new. You
		 * TODO: should modify the field after calling the uninit_new. */
		struct page *new_page = calloc(1, sizeof(struct page));
		if(!new_page)
			goto err;
	
		bool (*initializer)(struct page *, enum vm_type, void *);
		// struct aux_lazy *aux_lazy;

		switch(VM_TYPE(type)){
			case VM_ANON:
				// printf("\n ##### in ANON ##### \n");
				// aux_lazy = calloc(1, sizeof(struct aux_lazy));
				// memcpy(aux_lazy, aux, sizeof(struct aux_lazy));
				// aux = aux_lazy;
				initializer = &anon_initializer;
				break;
			case VM_FILE:
				initializer = &file_backed_initializer;
				break;
			// case VM_PAGE_CACHE:
			// 	initializer = &page_cache_initializer;
			// 	break;
			default:
				goto err;
		}
		uninit_new(new_page, upage, init, type, aux, initializer);
		new_page->writable = writable;
		
		/* TODO: Insert the page into the spt. */
		if(spt_insert_page(spt, new_page)){
			// printf("\n ##### in if ##### \n");

			return true;
		}
		free(new_page);
		return false;
		// vm_dealloc_page(new_page);

		// 어디서 true를 리턴해줌;;
	}
	// printf("\n ##### out of spt_find_page ##### \n");

err:
	// printf("\n ##### debug ##### vm_initializer | err \n");

	return false;
}

spt_find_page()

/* Find VA from spt and return page. On error, return NULL. */
struct page *
spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
	// printf("\n ##### start spt_FIND_page ##### \n");
	struct page *page = NULL;
	/* TODO: Fill this function. */
	
	struct page p;
	struct hash_elem *e;

	// p.va = pg_round_down (va);	// 찾고자하는 va
	p.va = pg_round_down(va);
	e = hash_find(spt->hash_table, &p.hash_elem);

	if(e!=NULL){
		// printf("\n ##### e is not NULL ##### \n");

		return hash_entry(e, struct page, hash_elem);		
	}
	else{
		// printf("\n ##### e is NULL ##### \n");
		return NULL;
	}

	// struct hash *h = spt->hash_table;
	// for(int i=0; i<h->bucket_cnt; i++){
	// 	struct list *bucket = &h->buckets[i];
	// 	struct list_elem *e = list_begin(&bucket);

	// 	for(e; e != list_end(&bucket); e = list_next(e)){
	// 		void *p = list_entry(e, struct page, va)
			
	// 	}

	// }

	// return page;
}

spt_intsert_page()

/* Insert PAGE into spt with validation. */
bool
spt_insert_page (struct supplemental_page_table *spt UNUSED,
		struct page *page UNUSED) {
	int succ = false;
	/* TODO: Fill this function. */
	// 확인해서 어쩌라고..?
	// 있었든 없었든 여기선 insert 호출하잖아..?
	// null 체크
	if(page){
		if(!hash_insert(spt->hash_table, &page->hash_elem))
			succ = true;
	}

	return succ;
}

vm_get_frame()

/* palloc() and get frame. If there is no available page, evict the page
 * and return it. This always return valid address. That is, if the user pool
 * memory is full, this function evicts the frame to get the available memory
 * space.*/
static struct frame *
vm_get_frame (void) {
	struct frame *frame = NULL;
	/* TODO: Fill this function. */
	// frame에 메모리 할당
	void *kva = palloc_get_page(PAL_USER|PAL_ZERO);
	if(kva){
		frame = calloc(1, sizeof(struct frame));
		frame->kva = kva;
		frame->page = NULL;

		if(!hash_insert(frame_table.hash_table, &frame->hash_elem)){
			return frame;
		}

		// 중복있으면 원래값 없으면 null 반환?
		// if(!e){
		// 	free(frame);
		// }
	}
	else{
		PANIC("TODO");
	}

	ASSERT (frame != NULL);
	ASSERT (frame->page == NULL);
	return NULL;
}

vm_try_handle_fault()

/* Return true on success */
bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
	// vm_try_handle_fault (f, fault_addr, user, write, not_present)
	// printf("\n ##### start vm_try_handle_fault ##### \n");

	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
	struct page *page = NULL;
	/* TODO: Validate the fault */
	/* TODO: Your code goes here */
	/* todo: 폴트를 확인하고 */
	/* todo: 여기서부터 써라 */

	page = spt_find_page(spt, addr);

	// if(!page){
	// 	return false; 	// ?
	// }
	
	// user, write, not_present는 어떻게 넘겨줌

	return vm_do_claim_page (page);
}

vm_dealloc_page()

/* Free the page.
 * DO NOT MODIFY THIS FUNCTION. */
void
vm_dealloc_page (struct page *page) {
	destroy (page);	// 페이지 type별 ops의 destroy 호출
	free (page);	// page 구조체 메모리 반환
}

vm_claim_page()

/* Claim the page that allocate on VA. */
/* va에 할당하는 페이지를 요청 */
bool
vm_claim_page (void *va UNUSED) {
	struct page *page = NULL;
	/* TODO: Fill this function */
	struct supplemental_page_table *spt = &thread_current()->spt;

	// if(pml4_get_page(curr->pml4, va)){	// null이 아닐때 false 반환 -> 할당할 거니까 null이어야함 
	// 	return false;
	// }
	// if(va){
	// 	page = calloc(1, sizeof(struct page));
	// 	page->va = va;
	// 	return vm_do_claim_page (page);	
	// }		
	page = spt_find_page(spt, va);
	if(page){
		return vm_do_claim_page(page);
	}

	return false;
}

vm_do_claim_page()

/* Claim the PAGE and set up the mmu. */
static bool
vm_do_claim_page (struct page *page) {
	// printf("\n ##### start vm_do_claim_page ##### \n");

	struct frame *frame = vm_get_frame ();		// frame 만들고

	/* Set links */
	if(!frame){
		return false;
	}
	frame->page = page;							// 연결하고
	page->frame = frame;
	
	/* TODO: Insert page table entry to map page's VA to frame's PA. */
	/* todo: pte를 삽입하여 page의 va를 frame의 pa에 매핑 */
	struct thread *curr = thread_current();
	if(pml4_get_page(curr->pml4, page->va) == NULL && pml4_set_page(curr->pml4, page->va, frame->kva, 1)){
		return swap_in(page, frame->kva);
	}
	palloc_free_page(frame->kva);
	free(frame);
	free(page);
	// spt에 page 추가
	return false;
	
	// return swap_in (page, frame->kva);
	// => page->operations->swap_in (page, frame->kva)
	// => uninit_initialize(page, frame->kva)
}

supplemental_page_table_init()

/* Initialize new supplemental page table */
void
supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
	// printf("\n ##### debug ##### start supplemental_page_table_init \n");
	if(spt){
		spt->hash_table = calloc(1, sizeof(struct hash));
		hash_init (spt->hash_table, page_hash, page_less, NULL);
	}
}

supplemental_page_table_copy()

/* Copy supplemental page table from src to dst */
bool
supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED,
		struct supplemental_page_table *src UNUSED) {
	printf("\n ##### debug ##### start supplemental_page_table_copy \n");
	struct hash_iterator i;
	hash_first (&i, src->hash_table);
	int cnt = 0;
	while (hash_next (&i)) {
		printf("\n ##### debug ##### copy : in while  cnt : %d \n", cnt++);

		struct page *p = hash_entry (hash_cur (&i), struct page, hash_elem);
		struct page *new_p;
		struct thread *curr = thread_current();
		enum vm_type type = page_get_type(p);
		
		if(p->operations->type == VM_UNINIT){	// 초기화된 UNINIT
			struct aux_lazy *aux = calloc(1, sizeof(struct aux_lazy));
			memcpy(aux, p->uninit.aux, sizeof(struct aux_lazy));
			if(!vm_alloc_page_with_initializer(type, p->va, p->writable, p->uninit.init, aux)){
				return false;
			}
		}
		else{					// 메모리 할당, 매핑된 ANON(지금은 이거만)
			// bool flag = ;
			if(p->anon.is_stack){	// 스택
			// if(p->uninit.type & VM_MARKER_0){	// 스택
				// printf("\n ##### debug ##### copy | stk | type : %d \n", p->uninit.type);
				// printf("\n ##### debug ##### copy | stk | type : %d \n", p->anon.is_stack);

				if(!setup_stack(&curr->tf)){
					return false;
				}
			}	
			else{					// 스택 X
				// printf("\n ##### debug ##### copy | no_stk | type : %d \n", p->uninit.type);
				// printf("\n ##### debug ##### copy | no_stk | type : %d \n", p->anon.is_stack);
				if(!vm_alloc_page(type, p->va, p->writable)){
					return false;
				}
				if(!vm_claim_page(p->va)){
					return false;
				}
			}
			// new_p = spt_find_page(&curr->spt, p->va);
			new_p = spt_find_page(&dst->hash_table, p->va);
			memcpy(new_p->frame->kva, p->frame->kva, PGSIZE);
		}		
	}
	return true;
}

destructor()

void destructor(struct hash_elem *h, void *aux){
	struct page *p = hash_entry(h, struct page, hash_elem);
	vm_dealloc_page(p);
}

supplemental_page_table_kill()

/* Free the resource hold by the supplemental page table */
void
supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
	/* TODO: Destroy all the supplemental_page_table hold by thread and
	 * TODO: writeback all the modified contents to the storage. */
	struct hash *pages = &spt->hash_table;
	hash_destroy(pages, destructor);

}

hash for page table

unsigned page_hash(const struct hash_elem *h, void *aux UNUSED){
	const struct page *p = hash_entry(h, struct page, hash_elem);
	return hash_bytes(&p->va, sizeof p->va);
}

bool page_less(const struct hash_elem *a, const struct hash_elem *b, void *aux UNUSED){
	const struct page *pa = hash_entry(a, struct page, hash_elem);
	const struct page *pb = hash_entry(b, struct page, hash_elem);

	return pa->va < pb->va;

}

hash for frame table

unsigned frame_hash(const struct hash_elem *h, void *aux UNUSED){
	const struct frame *f = hash_entry(h, struct frame, hash_elem);
	return hash_bytes(&f->kva, sizeof f->kva);
}

bool frame_less(const struct hash_elem *a, const struct hash_elem *b, void *aux UNUSED){
	const struct frame *fa = hash_entry(a, struct frame, hash_elem);
	const struct frame *fb = hash_entry(b, struct frame, hash_elem);

	return fa->kva < fb->kva;

}

0개의 댓글