- 프레임(물리 프레임)은 실제 메모리 상에서 연속된 4096 바이트 영역입니다.
- 페이지와 마찬가지로 프레임 정렬되어야 합니다.
- 64비트 물리 주소도 하위 12비트는 오프셋, 상위 비트는 프레임 번호로 나뉩니다.
- x86-64 아키텍처는 물리 주소에 직접 접근하는 방법을 제공하지 않기 때문에, Pintos에서는 커널 가상 메모리를 물리 메모리에 직접 매핑합니다(즉, 1:1 매핑).
63-48 | 47-39 | 38-30 | 29-21 | 20-12 | 11-0
SignEx | PML4Off | PDPTOff | PDOff | PTOFF | Offset
여기서 내가 생각한 것은 64bit 물리 주소를 하위 12비트는 오프셋, 상위 48bit는 프레임 번호로 쪼개서 쓰는 것을 어떻게 구현해야 되는 거지? 라는 생각이였다.
#define PAGE_SHIFT 12 // 2^12 = 4096
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE - 1)) // 상위 비트만 남김
// 주어진 물리 주소에서 프레임 번호만 추출
static inline uint64_t get_frame_number(uint64_t phys_addr) {
return phys_addr >> PAGE_SHIFT;
}
// 주어진 물리 주소에서 페이지 내부 오프셋만 추출
static inline uint64_t get_page_offset(uint64_t phys_addr) {
return phys_addr & (PAGE_SIZE - 1); // 하위 12비트만 추출
}
uint64_t phys_addr = 0x12345ABC;
// 분리
uint64_t frame_num = get_frame_number(phys_addr); // → 0x12345
uint64_t offset = get_page_offset(phys_addr); // → 0xABC
// 다시 합치기
uint64_t new_phys_addr = make_phys_addr(frame_num, offset); // → 0x12345ABC
shift 연산을 사용해서 프레임 번호와 오프셋을 찾을 수 있다.
// 커널 가상 주소 영역의 시작 (Pintos에서 보통 이렇게 정의됨)
#define KERN_BASE 0x8004000000
// 물리 주소를 커널 가상 주소로 변환 (phys → virt)
static inline void *ptov(uintptr_t phys_addr) {
return (void *)(phys_addr + KERN_BASE);
}
// 커널 가상 주소를 물리 주소로 변환 (virt → phys)
static inline uintptr_t vtop(const void *virt_addr) {
return (uintptr_t)virt_addr - KERN_BASE;
}
void *kpage = palloc_get_page(PAL_USER); // 커널 가상 주소로 반환됨
uintptr_t phys_addr = vtop(kpage); // 실제 물리 주소 계산
// 물리 주소에 직접 접근하는 대신, 커널 가상 주소로 접근
memset(kpage, 0, PGSIZE); // 실질적으로 물리 메모리를 초기화
커널 메모리와 물리 메모리의 1대1 매핑 부분은 이런 식으로 구현이 될 수 있다. palloc_get_page()함수를 사용해서 커널 가상 주소를 찾고, 반환된 가상 주소를 이용해서 물리 주소를 계산할 수 있다(?)
우리는 process_exec에서 시작한다.
/* Switch the current execution context to the f_name.
* Returns -1 on fail. */
int process_exec (void *f_name) {
char *file_name = f_name;
bool success;
/* 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 ();
/*-- Project 2. User Programs 과제 --*/
// for argument parsing
char *parse[64]; // 파싱된 문자열(토큰)들을 저장
char *token, *save_ptr; // token: 현재 파싱된 문자열, save_ptr: strtok_r의 내부 상태 유지를 위한 포인터
int count = 0; // 파싱된 문자열의 개수
for (token = strtok_r(file_name, " ", &save_ptr); // file_name(例: "ls -a -l")에서 첫 번째 공백(" ")을 기준으로 문자열을 자르고, 첫 번째 토큰("ls")을 token에 할당.
token != NULL; // token이 NULL이 아닐 때까지 (더 이상 자를 문자열이 없을 때까지).
token = strtok_r(NULL, " ", &save_ptr) // 다음 공백(" ")을 기준으로 문자열을 잘라, 다음 토큰을 token에 할당 (NULL을 넣어 이전 호출의 다음 지점부터 계속 파싱).
){
parse[count++] = token;
}
/*-- Project 2. User Programs 과제 --*/
/* And then load the binary */
success = load (file_name, &_if);
/* If load failed, quit. */
if (!success){
palloc_free_page(file_name);
return -1;
}
// Project 2. User Programs의 Argument Passing ~
argument_stack(parse, count, &_if.rsp); // 함수 내부에서 parse와 rsp의 값을 직접 변경하기 위해 주소 전달.
_if.R.rdi = count; // 첫 번째 인자: argc, 즉 프로그램에 전달된 인자의 개수를 레지스터 rdi에 저장 (시스템 V AMD64 ABI 규약???).
_if.R.rsi = (char *)_if.rsp + 8; // 두 번째 인자: argv. 스택의 첫 8바이트는 NULL이고, 그 다음 주소가 argv[0]이므로, rsp + 8이 argv 배열의 시작 주소.
// hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true); // 디버그용. 유저 스택을 헥스 덤프로 출력.
// ~ Project 2. User Programs의 Argument Passing
/* Start switched process. */
do_iret(&_if);
NOT_REACHED();
}
process_exec코드를 보면 크게 두 가지의 경우로 빠진다. 하나는 do_iret(&_if)이고, 나머지 하나는 load(file_name, &_if)이다.
/* 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_) {
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 ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
/* Open executable file. */
file = filesys_open (file_name);
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
/* 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;
}
/* Read program headers. */
file_ofs = ehdr.e_phoff;
for (i = 0; i < ehdr.e_phnum; i++) {
struct Phdr phdr;
if (file_ofs < 0 || file_ofs > file_length (file))
goto done;
file_seek (file, file_ofs);
if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
goto done;
file_ofs += sizeof phdr;
switch (phdr.p_type) {
case PT_NULL:
case PT_NOTE:
case PT_PHDR:
case PT_STACK:
default:
/* Ignore this segment. */
break;
case PT_DYNAMIC:
case PT_INTERP:
case PT_SHLIB:
goto done;
case PT_LOAD:
if (validate_segment (&phdr, file)) {
bool writable = (phdr.p_flags & PF_W) != 0;
uint64_t file_page = phdr.p_offset & ~PGMASK;
uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
uint64_t page_offset = phdr.p_vaddr & PGMASK;
uint32_t read_bytes, zero_bytes;
if (phdr.p_filesz > 0) {
/* Normal segment.
* Read initial part from disk and zero the rest. */
read_bytes = page_offset + phdr.p_filesz;
zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
- read_bytes);
} else {
/* Entirely zero.
* Don't read anything from disk. */
read_bytes = 0;
zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
}
if (!load_segment (file, file_page, (void *) mem_page,
read_bytes, zero_bytes, writable))
goto done;
}
else
goto done;
break;
}
}
/* TODO: Your code goes here.
* TODO: Implement argument passing (see project2/argument_passing.html). */
// project 2. user programs - rox ~
// 현재 스레드의 실행 중인 파일에 이 파일을 추가.
t->running = file;
// 지금 읽고 있는 실행 파일에 뭐 쓰면 안되니까.
file_deny_write(file); // 해당 파일을 쓰기 금지로 등록
// ~ project 2. user programs - rox
/* Set up stack. */
if (!setup_stack (if_))
goto done;
/* Start address. */
if_->rip = ehdr.e_entry;
success = true;
done:
/* We arrive here whether the load is successful or not. */
// file_close (file); // TODO: 여기 말고 process_exit에서 닫도록 해야.
return success;
}
```c
static bool load (const char *file_name, struct intr_frame *if_)
```
파싱된 file_name을 인자로 받습니다.
```c
/* Allocate and activate page directory. */
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
```
```c
/* Open executable file. */
file = filesys_open (file_name);
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
```
```c
/* 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;
}
```
```c
/* Read program headers. */
file_ofs = ehdr.e_phoff;
for (i = 0; i < ehdr.e_phnum; i++) {
struct Phdr phdr;
if (file_ofs < 0 || file_ofs > file_length (file))
goto done;
file_seek (file, file_ofs);
if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
goto done;
file_ofs += sizeof phdr;
switch (phdr.p_type) {
case PT_NULL:
case PT_NOTE:
case PT_PHDR:
case PT_STACK:
default:
/* Ignore this segment. */
break;
case PT_DYNAMIC:
case PT_INTERP:
case PT_SHLIB:
goto done;
case PT_LOAD:
if (validate_segment (&phdr, file)) {
bool writable = (phdr.p_flags & PF_W) != 0;
uint64_t file_page = phdr.p_offset & ~PGMASK;
uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
uint64_t page_offset = phdr.p_vaddr & PGMASK;
uint32_t read_bytes, zero_bytes;
if (phdr.p_filesz > 0) {
/* Normal segment.
* Read initial part from disk and zero the rest. */
read_bytes = page_offset + phdr.p_filesz;
zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
- read_bytes);
} else {
/* Entirely zero.
* Don't read anything from disk. */
read_bytes = 0;
zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
}
if (!load_segment (file, file_page, (void *) mem_page,
read_bytes, zero_bytes, writable))
goto done;
}
else
goto done;
break;
}
}
```
```c
/* Set up stack. */
if (!setup_stack (if_))
goto done;
```
```c
/* Start address. */
if_->rip = ehdr.e_entry;
```
```c
return success;
```
file_read (file, &ehdr, sizeof ehdr)
file을 ehdr size만큼 읽겠다. 이 말은 즉, file의 헤더 정보를 읽어서 ehdr에 저장하겠다는 말임.
file_read (file, &phdr, sizeof phdr)
위와 동일하지만 인자가 phdr로 변경됨.
load_segment (file, file_page, (void *) mem_page,
read_bytes, zero_bytes, writable))
file의 특정 부분을 읽어 들인다. file과 file_page 두 개의 인자가 있는 이유는 읽어들일 file이 무엇인지와 어떤 부분을 읽어야 하는 지에 대한 정보를 나타낸다. 파일을 읽었다면 가상 메모리의 어떤 부분에 올려야할 지 정해야 한다. 가상 메모리의 주소와 읽은 byte 값, zero_bytes는 남은 영역을 0으로 채운다는 말 아닐까?, writable로 쓰기 가능 영역인 지 구분한다.
결론은 실행파일을 사용자가 접근할 수 있는 메모리 공간으로 로드하는 역할을 수행한다. user pool의 Upage로 데이터를 로드한다.
/* 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. */
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) {
/* 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;
/* 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 false;
}
memset (kpage + page_read_bytes, 0, page_zero_bytes);
/* Add the page to the process's address space. */
if (!install_page (upage, kpage, writable)) {
printf("fail\n");
palloc_free_page (kpage);
return false;
}
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
}
return true;
}
uint8_t *kpage = palloc_get_page (PAL_USER);
file_read (file, kpage, page_read_bytes)
/* A kernel thread or user process.
*
* Each thread structure is stored in its own 4 kB page. The
* thread structure itself sits at the very bottom of the page
* (at offset 0). The rest of the page is reserved for the
* thread's kernel stack, which grows downward from the top of
* the page (at offset 4 kB). Here's an illustration:
*
* 4 kB +---------------------------------+
* | kernel stack |
* | | |
* | | |
* | V |
* | grows downward |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* +---------------------------------+
* | magic |
* | intr_frame |
* | : |
* | : |
* | name |
* | status |
* 0 kB +---------------------------------+
*
* The upshot of this is twofold:
*
* 1. First, `struct thread' must not be allowed to grow too
* big. If it does, then there will not be enough room for
* the kernel stack. Our base `struct thread' is only a
* few bytes in size. It probably should stay well under 1
* kB.
*
* 2. Second, kernel stacks must not be allowed to grow too
* large. If a stack overflows, it will corrupt the thread
* state. Thus, kernel functions should not allocate large
* structures or arrays as non-static local variables. Use
* dynamic allocation with malloc() or palloc_get_page()
* instead.
*
* The first symptom of either of these problems will probably be
* an assertion failure in thread_current(), which checks that
* the `magic' member of the running thread's `struct thread' is
* set to THREAD_MAGIC. Stack overflow will normally change this
* value, triggering the assertion. */
/* The `elem' member has a dual purpose. It can be an element in
* the run queue (thread.c), or it can be an element in a
* semaphore wait list (synch.c). It can be used these two ways
* only because they are mutually exclusive: only a thread in the
* ready state is on the run queue, whereas only a thread in the
* blocked state is on a semaphore wait list. */
struct thread {
/* Owned by thread.c. */
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
int priority; /* Priority. */
/* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint64_t *pml4; /* Page map level 4 */
#endif
#ifdef VM
/* Table for whole virtual memory owned by thread. */
struct supplemental_page_table spt;
#endif
/* Owned by thread.c. */
struct intr_frame tf; /* Information for switching */
unsigned magic; /* Detects stack overflow. */
/*-- Alarm clock 과제 --*/
int64_t wakeup_tick; // Alarm clock 과제 - 어느 틱에 깨울지.
/*-- Alarm clock 과제 --*/
/*-- Priority donation 과제 --*/
int original_priority;
struct lock *wait_lock;
struct list donations;
struct list_elem donation_elem;
/*-- Priority donation 과제 --*/
/*-- Project 2. User Programs 과제 --*/
int exit_status;
struct file **fd_table;
int next_fd;// fd테이블에 open spot의 인덱스
struct intr_frame parent_if;
struct list child_list; // 자신의 자식 목록
struct list_elem child_elem; // 부모의 child_list에 들어갈 때 사용하는 노드
struct semaphore load_sema; // 동기화 대기용 세마포어. 자식 프로세스가 load() 완료 후 부모에게 알리기 위함. fork() 직후 자식이 실행을 성공적으로 시작했는지 부모가 알기 위해 사용됨.
struct semaphore exit_sema; // 자식 프로세스가 종료되었음을 부모가 확인할 수 있도록 하기 위한 세마포어
struct semaphore wait_sema; // 부모가 자식의 종료를 기다릴 수 있도록 하기 위한 세마포어
struct file *running; // 현재 실행 중인 파일
/*-- Project 2. User Programs 과제 --*/
};
kernel pool에서 struct thread 정보를 가지고 있음.
palloc_get_page (PAL_USER);을 통해서 물리 페이지를 할당받았음. 그러면 유저 모드에서 할당된 물리 메모리에 접근할 수 있게 되는데 물리 메모리 프레임에서 커널 page와 매핑된 가상 주소를 가지고 있기 때문이다. 사용자가 접근한 가상주소에 kpage가 매핑되어 있지 않다면 페이지 폴트가 발생하고 물리 페이지를 매핑하는 과정을 거친다??
install_page (upage, kpage, writable)
/* 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. */
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));
}
유저 가상주소인 upage에서부터 커널 가상 주소인 kpage까지를 매핑하는 함수.
이 매핑과정을 통해서 추후에 upage만으로 pml4함수를 활용해서 kpage를 참조할 수 있게 된다.
pml4_set_page (t->pml4, upage, kpage, writable)
pml4를 통해서 upage를 통해서 kpage가 가리키고 있는 물리 메모리로 접근할 수 있게 함.
결론 : 유저 모드에서 page에 있는 가상 주소를 통해 물리 메모리에 데이터를 할당할 수 있다.
그림과 같이 Upage는 pml4_walk()함수를 통해서 물리 메모리를 가리키는 가상주소 kpage와 연결된다.
/* 유효성 검사를 통해 PAGE를 spt에 삽입합니다. */
bool spt_insert_page(struct supplemental_page_table *spt,
struct page *page)
{
int succ = false;
if (spt == NULL || page == NULL) // 좋았어.
{
return false;
}
if (hash_insert(&spt->hash, &page->hash_elem) == NULL) // null이면 삽입 성공
{
succ = true;
}
else // 실패
{
succ = false;
}
return succ;
}
/* Gets a new physical page from the user pool by calling palloc_get_page.
* When successfully got a page from the user pool, also allocates a frame, initialize its members, and returns it.
* After you implement vm_get_frame, you have to allocate all user space pages (PALLOC_USER) through this function.
* You don't need to handle swap out for now in case of page allocation failure.
* Just mark those case with PANIC ("todo") for now. */
/* palloc()으로 프레임을 할당받습니다.
* 사용 가능한 페이지가 없다면 페이지를 교체하여 빈 공간을 만듭니다.
* 항상 유효한 주소를 반환해야 합니다. 즉, 유저 풀 메모리가 가득 차더라도
* 이 함수는 페이지를 교체해서라도 공간을 확보해야 합니다. */
static struct frame *
vm_get_frame(void)
{
struct frame *frame = malloc(sizeof(struct frame));
if (frame == NULL)
{
PANIC("TODO");
}
frame->kva = palloc_get_page(PAL_USER);
frame->page = NULL;
ASSERT(frame != NULL);
ASSERT(frame->page == NULL);
if (frame->kva == NULL)
{
free(frame);
frame = vm_evict_frame(); // HACK: evict frame 함수가 아직 구현되지 않음.
}
ASSERT(frame->kva != NULL);
return frame;
}
/* VA에 할당된 페이지를 할당(claim)합니다. */
// - 주소 `va`에 대해 `struct page`를 찾아오고,
// - `vm_do_claim_page()`를 호출합니다.
bool vm_claim_page(void *va)
{
struct page *page = NULL;
page = spt_find_page(&thread_current()->spt, va); // va를 가지고 현재 쓰레드의 spt 에서 페이지를 찾아냄.
if (page == NULL) // spt에 없으면 false 리턴.
return false;
return vm_do_claim_page(page); // 있으면 바로 프레임 할당해서 돌려줌.
}
/* 주어진 PAGE를 할당하고 MMU 설정을 합니다. */
// - 주어진 페이지를 물리 프레임에 연결합니다.
// - 내부적으로 `vm_get_frame()`을 호출해 프레임을 할당한 후,
// 페이지 테이블에 가상 주소 → 물리 주소 매핑을 추가해야 합니다.
// - 성공 여부를 반환해야 합니다.
static bool
vm_do_claim_page(struct page *page)
{
struct frame *frame = vm_get_frame();
/* 링크 설정 */
frame->page = page;
page->frame = frame;
/* 페이지의 VA와 프레임의 PA를 매핑하기 위해 페이지 테이블 엔트리를 삽입하세요. */
if (pml4_get_page(thread_current()->pml4, page->va) == NULL) // HACK: 확신 없음.
{
if (!pml4_set_page(thread_current()->pml4, page->va, frame->kva, page->writable)) // HACK: writable 이렇게 추가하는 거 맞나요?
{
return false;
}
}
return swap_in(page, frame->kva);
}
// 주어진 타입으로 초기화되지 않은 페이지를 생성합니다.
// 초기화되지 않은 페이지의 swap_in 핸들러는 페이지를 자동으로 주어진 타입에 맞게 초기화하고, 주어진 AUX와 함께 INIT을 호출합니다.
// 페이지 구조체를 얻은 후, 해당 페이지를 프로세스의 supplemental page table에 삽입합니다.
// vm.h에서 정의된 VM_TYPE 매크로를 사용하는 것이 유용할 수 있습니다.
/* Implement vm_alloc_page_with_initializer().
* You should fetch an appropriate initializer according
* to the passed vm_type and call uninit_new with it. */
/* 초기화 함수와 함께 대기 중인 페이지 객체를 생성합니다.
* 페이지를 생성하고 싶다면 직접 만들지 말고 반드시 `vm_alloc_page_with_initializer`나 `vm_alloc_page`를 사용해야 합니다. */
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux)
{
ASSERT(VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current()->spt;
/* 해당 upage가 이미 존재하는지 확인합니다. */
if (spt_find_page(spt, upage) == NULL) // 페이지가 존재하지 않는다면
{
// HACK: 플래그는? 말록이야? 아니야? 몰라?
struct page *page = palloc_get_page(PAL_USER); // 물리가 바로 나오는 거잖아. 그럼 lazy loading이 아니잖아.
/* TODO: 페이지를 생성하고(이러고 나면 page->va 참조 가능), -> va가 여전히 알쏭달쏭.
* VM 타입에 맞는 초기화 함수를 가져와서, -> 이건 처리된 거 같음.
* TODO: uninit_new를 호출하여 "uninit" 페이지 구조체를 생성하세요. -> 이건?
* TODO: 생성 이후 필요한 필드를 수정하세요. -> 뭐가 필요한데요??
*/
page->va = upage;
page->writable = writable;
switch (VM_TYPE(type))
{
case (VM_ANON):
// HACK: va가 문제. 어떤 값을 va로 넘겨줘야 할지 불분명. VM_uninit에 대해 처리?.
uninit_new(page, page->va, init, type, aux, anon_initializer);
break;
case (VM_FILE):
uninit_new(page, page->va, init, type, aux, file_backed_initializer);
break;
};
/* 생성한 페이지를 spt에 삽입하세요. */
return spt_insert_page(&thread_current()->spt, page); // install page 역할을 해주는 거 아닌가?
}
err:
return false;
}
/* FILE의 OFS(오프셋)부터 시작하는 세그먼트를
* UPAGE 주소에 로드합니다. 총 READ_BYTES + ZERO_BYTES 바이트의
* 가상 메모리를 다음과 같이 초기화합니다:
*
* - READ_BYTES 바이트를 FILE에서 OFS 오프셋부터 읽어
* UPAGE에 저장합니다.
*
* - UPAGE + READ_BYTES 위치부터 ZERO_BYTES 바이트는 0으로 채웁니다.
*
* 이 함수가 초기화한 페이지는 WRITABLE이 true이면
* 사용자 프로세스가 쓸 수 있어야 하고, false이면 읽기 전용이어야 합니다.
*
* 메모리 할당 오류나 디스크 읽기 오류가 없으면 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);
while (read_bytes > 0 || zero_bytes > 0)
{
/* 이 페이지를 어떻게 채울지 계산합니다.
* 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: lazy_load_segment에 정보를 전달하기 위한 aux를 설정하세요. */
struct lazy_aux *aux = NULL; // 파일에서 세그먼트 읽어올 때 필요한 정보가 여기?
// 여기서 file, page_read_bytes, page_zero_bytes 설정?
// struct lazy_aux: file, page_read_bytes, page_zero_bytes
aux = malloc(sizeof(struct lazy_aux));
aux->file = file;
aux->read_bytes = page_read_bytes;
aux->zero_bytes = page_zero_bytes;
if (!vm_alloc_page_with_initializer(VM_ANON, upage,
writable, lazy_load_segment, aux)) // uninit 페이지를 만들고 익명 페이지로 초기화
return false;
/* 다음 페이지로 이동합니다. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
}
return true;
}
/* USER_STACK 위치에 스택용 PAGE를 만듭니다.
* 성공 시 true를 반환합니다. */
static bool
setup_stack(struct intr_frame *if_)
{
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE); // 유저 프로세스의 가상 주소상의 stack bottom
/* stack_bottom에 스택을 매핑한 뒤 즉시 페이지를 claim 하십시오.
* 성공하면 rsp 값을 적절히 설정합니다.
* 해당 페이지를 스택으로 표시해야 합니다. */
/* 여기에 코드 작성 */
// You might need to provide the way to identify the stack.
// You can use the auxillary markers in vm_type of vm/vm.h
// (e.g. VM_MARKER_0) to mark the page.
if (vm_alloc_page_with_initializer(VM_MARKER_STACK, stack_bottom, true, anon_initializer, NULL)) // HACK: 마커 이렇게 쓰면 되는지 잘 모르겠음. 전반적으로 잘 모르겠음.
{
success = vm_claim_page(stack_bottom); // 바로 이 주소로 프레임을 할당.
if (success)
{
if_->rsp = USER_STACK;
}
}
return success;
}
/* src에서 dst로 supplemental page table을 복사합니다. */
// - `src`의 supplemental page table을 `dst`에 복사
// - fork 시 부모의 실행 컨텍스트를 자식에게 복사할 때 사용
// - 각 페이지를 순회하며 `uninit_page`로 생성하고 **즉시 claim 처리**해야 함
// TODO: 구현 하다 말았음.
bool supplemental_page_table_copy(struct supplemental_page_table *dst,
struct supplemental_page_table *src)
{
ASSERT(src != NULL);
ASSERT(dst != NULL);
struct hash_iterator hi;
struct hash *src_hash = &src->hash;
// hash iterator initialized
hash_first(&hi, src_hash);
for (; hi.elem != NULL; hash_next(&hi))
{
struct page *src_page = hash_entry(hi.elem, struct page, hash_elem);
struct page *dest_page;
vm_alloc_page(page_get_type(src_page), src_page->va, src_page->writable); // 결국 uninit_page를 만들긴 함.
vm_claim_page(src_page->va); // src_page의 가상 주소를 가지고 현재 쓰레드의 spt에서 페이지를 찾아서 프레임을 할당해준다. spt 복사하는데 이게 왜 있어야 하는 거?
spt_insert_page(src, src_page);
}
}
/* supplemental page table이 가지고 있는 리소스를 해제합니다. */
void supplemental_page_table_kill(struct supplemental_page_table *spt)
{
/* TODO: 해당 스레드가 가지고 있는 supplemental page table의 모든 항목을 제거하고,
* TODO: 수정된 내용을 저장소에 기록하세요(write-back). */
// NOTE: 민혁이가 clear 쓰라고 함
hash_clear(&spt->hash, spt_destructor); // HACK: destructor 뭘로 줘야 하는지 모르겠음
}
void spt_destructor(struct hash_elem *he)
{
struct page *page = hash_entry(he, struct page, hash_elem);
vm_dealloc_page(page);
}