Anonymous page
- 파일으로부터 매핑되지 않은, 커널로부터 할당된 페이지를 뜻한다.
- 익명 페이지는 힙을 거치지 않고 할당받은 메모리 공간
- 스택, 힙과 같은 실행 파일에서 사용됨
Lazy Loading(Demanding Paging)
lazy loading은 메모리 로딩이 필요한 시점까지 지연되는 디자인
가상 page만 할당해두고 필요한 page를 요청하면 page fault가 발생하고 해당 page를 type에 맞게 초기화하고 frame과 연결하고 userprogram으로 제어권을 넘긴다.
지연로딩 순서
- 커널이 새 page를 요청하면 vm_alloc_page_with_initializer 호출
- initializer는 페이지 구조를 할당하고 페이지 type에 따라 적절한 initalizer를 할당하고 SPT 페이지에 추가 후 userprogram으로 반환함
- 해당 페이지에 access 호출이 오면 내용이 없는 페이지 임으로 page fault가 발생
- uninit_initialize 를 호출하고 이전에 설정한 initializer를 호출한다.
- page를 frame가 연결하고 lazy_load_segment 를 호출하여 필요한 데이터를 물리 메모리에 올린다.
생명주기
initialize->(page_fault->lazy-load->swap-in>swap-out->...)->destroy
1. Unintalized page 구현
vm_alloc_page_with_initializer
- 인자로 주어진 type으로 초기화되지 않은 page를 만든다.
- SPT에 새로 만든 page를 추가하여 준다.
- 제일 처음 만들어지는 페이지는 uninit 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;
if (spt_find_page(spt, upage) == NULL)
{
struct page *new_page = malloc(sizeof(struct page));
if (type == VM_ANON)
{
uninit_new(new_page, upage, init, type, aux, anon_initializer);
}
else if (type == VM_FILE)
{
uninit_new(new_page, upage, init, type, aux, file_backed_initializer);
}
new_page->writable = writable;
spt_insert_page(spt, new_page);
return true;
}
err:
return false;
}
2. Anonymous Page구현
ANON page 타입에 대한 함수들 수정
Anonymous Page에서 중요한 점은 물리 메모리와 맵핑될 때 초기화할 때 모든 데이터를 Zeroing 해주어야 한다.
anon_initializer
- 기존 page안에 있는 union 안의 uninit_page에 대한 데이터를 모두 0으로 만들어준다.
- operations을 anon_ops로 변경하고 anon_page에 대한 정보를 바꿔준다.
bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
/* page struct 안의 Union 영역은 현재 uninit page이다.
ANON page를 초기화해주기 위해 해당 데이터를 모두 0으로 초기화해준다.
Q. 이렇게 하면 Union 영역은 모두 다 0으로 초기화되나? -> 맞다. */
struct uninit_page *uninit = &page->uninit;
memset(uninit, 0, sizeof(struct uninit_page));
/* Set up the handler */
/* 이제 해당 페이지는 ANON이므로 operations도 anon으로 지정한다. */
page->operations = &anon_ops;
struct anon_page *anon_page = &page->anon;
anon_page->swap_index = -1;
return true;
}
이 부분은 구현에서 전혀 작성하지 못한 부분이다. union에 대한 값 초기화와 anon_page에 대한 값을 생각하지 못하였다.
3. load_segment 구현 (수정)
project1, 2까지의 pintos는 디스크에서 실행시킬 파일 전체를 물리 메모리에 올리고 페이지 테이블에 맵핑해주었다.
이제는 load_segment를 수정하여 page를 만들고 file에 대한 정보만을 initializer로 넘겨준다. 디스크에 있는 데이터를 물리 메모리에 바로 올리지 않는다.
load_segment
- file_information 구조체를 추가하여 file을 load 할 때 필요한 file, offset, read_bytes를 저장하고 initializer를 호출하고 aux 인자로 넘겨준다.
- 파일을 page 단위로 끊어서 uninit 페이지로 만들고 file 정보를 page에 저장하고 SPT에 추가한다.
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);
/* upage 주소부터 1페이지 단위씩 UNINIT 페이지를 만들어 프로세스의 spt에 넣는다(vm_alloc_page_with_initializer).
이 때 각 페이지의 타입에 맞게 initializer도 맞춰준다. */
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;
struct file_information *file_inf = (struct file_information *)malloc(sizeof(struct file_information));
file_inf->file = file;
file_inf->ofs = ofs;
file_inf->read_bytes = page_read_bytes;
if (!vm_alloc_page_with_initializer(VM_ANON, upage,
writable, lazy_load_segment, file_inf))
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
lazy_load_segment() 구현
- 프로세스가 uninit_page로 처음 접근하여 page_fault가 발생하면 해당 함수가 호출된다.
- 호출된 page를 frame과 맵핑하고 해당 page에 연결된 물리 메모리에 file 정보를 load 해준다.
static bool
lazy_load_segment(struct page *page, void *aux)
{
/* TODO: Load the segment from the file */
struct file *file = ((struct file_information *)aux)->file;
off_t offset = ((struct file_information *)aux)->ofs;
size_t page_read_bytes = ((struct file_information *)aux)->read_bytes;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
file_seek(file, offset); // file의 오프셋을 offset으로 바꾼다. 이제 offset부터 읽기 시작한다.
/* 페이지에 매핑된 물리 메모리(frame, 커널 가상 주소)에 파일의 데이터를 읽어온다. */
/* 제대로 못 읽어오면 페이지를 FREE시키고 FALSE 리턴 */
if (file_read(file, page->frame->kva, page_read_bytes) != (int)page_read_bytes)
{
palloc_free_page(page->frame->kva);
return false;
}
/* 만약 1페이지 못 되게 받아왔다면 남는 데이터를 0으로 초기화한다. */
memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);
return true;
}
setup_stack() 재구현
기존의 setup_stack()를 SPT가 추가된 상황에 맞게 수정한다.
setup_stack은 page를 할당하고 바로 물리 메모리와 맵핑하여 준다. → page_fault가 발생 전에 물리 메모리에 바로 할당
- stack_bottom을 page의 va(가상주소 시작위치)로 할당하기 위해 인자로 넘겨준다.
- stack_bottom을 추가 할당을 위하여 thread_current에 stack_bottom저장 변수를 만들고 넣어준다.
static bool
setup_stack(struct intr_frame *if_)
{
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
/* ANON 페이지로 만들 UNINIT 페이지를 stack_bottom에서 위로 PGSIZE만큼(1 PAGE) 만든다.
이 때 TYPE에 VM_MARKER_0 flag를 추가함으로써 이 페이지가 STACK에 있다는 것을 표시한다. */
if (vm_alloc_page_with_initializer(VM_ANON, stack_bottom, true, NULL, NULL))
{
success = vm_claim_page(stack_bottom);
};
if (success)
{
if_->rsp = USER_STACK;
thread_current()->stack_bottom = stack_bottom;
}
return success;
}
/* 구현 후 스택의 모습
------------------------- <---- USER_STACK == if_->rsp
| |
| NEW PAGE |
| |
| |
------------------------- <---- stack_bottom
*/
Q. 왜 스택은 lazy loading을 하지 않고 바로 물리메모리와 맵핑해 주는 것인가??
- setup stack 이후 파일 실행에 필요한 argument를 바로 stack에 넣어 주어야한다.
- stack은 바로 사용하기 때문에 uninit page로 두지 않고 생성 후 바로 frame과 맵핑시켜준다.
/* Set up stack. */
if (!setup_stack(if_))
goto done;
if_->rip = ehdr.e_entry;
argument_stack(if_, argv_cnt, argv_list);
Supplemental Page Table - Revisit(fork, exec)
SPT 테이블에 대한 copy와 clean up을 위한 수정이 필요함
child를 만들때 copy, process를 종료할 때 destroy가 필요
supplemental_page_table_copy
- 부모의 STPd의 page는 같게 frame은 다르게 복사한다.
- 부모의 물리메모리에 있는 데이터를 자식에게 복사하여 준다.
구현 요구사항
- src에서 dst로 SPT를 복사한다.
- child가 parent의 실행 컨텍스트를 상속해야 할때 사용된다.
- 각 page를 반복하고 dst의 SPT에 있는 항목의 복사본을 만든다.
- unitit page를 할당하고 claim을 바로한다.
- 구현 코드
/* 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)
{
struct hash_iterator parnet_hast_iter;
hash_first(&parnet_hast_iter, &src->hash);
while (hash_next(&parnet_hast_iter))
{
struct page *page = hash_entry(hash_cur(&parnet_hast_iter), struct page, hash_elem);
if (!vm_alloc_page_with_initializer(VM_ANON, page->va, page->writable, NULL, NULL))
{
return false;
}
struct page *child_page = spt_find_page(dst, page->va);
if (!vm_claim_page(page->va))
{
return false;
}
if (page->frame != NULL)
{
memcpy(child_page->frame->kva, page->frame->kva, PGSIZE);
}
}
return true;
}
supplemental_page_kill
- 프로세스가 종료 될 때 함수를 호출하고 SPT에 모든 리소스를 해제한다.
- 모든 페이지에 대해서 destroy(page) 로 리소스 해제
uninit_destroy
- 페이지의 type에 따라 페이지 구조가 보유한 리소스 해제
- 현재까지는 anon_page만 처리하고 나중에 file_backed_page를 정리할 수 있다.
anon_destroy
- 익명 페이지가 보유한 리소스 해제
- page struct를 명시적으로 해제할 필요가 없고 호출자가 수행한다.
구현 중 문제점
- page, frame 구조체를 malloc이냐 palloc_get_page로 할당해주어야 할지 헷갈림
- 결론: page와 frame의 구조체는 malloc, frame→kva에는 palloc_get_page 로 할당해줌
- </aside>
- page fault 발생 시 SPT 에서 해당 page를 찾지 못하는 문제
- 만들어진 page va는 0x400000, 0x401000, 0x402000 값이고 Hash Table의 key값이 된다.
- page fault가 발생한 주소는 0x400bfa 값이 들어오고 key로 찾으려하니 찾을 수 없었다.
- key값으로 저장되는 page의 va는 **4KB(12bit)**단위로 정렬되어 있어 12bit 앞 부분이
- page table number로 사용된다.
- vm_alloc_page_with_initializer 함수에서 uninit_new 함수의 호출이 끝나고 new_page에 값을 넣어주어야 정상 동작하고 uninit_new 전에 구조체 안의 값을 할당해주면 제대로 동작하지 않았다. ???이유를 잘 모르겠음
'지난 글 모음' 카테고리의 다른 글
[sw 정글] pintos 3주차 - part 4: Memory Mapped Files (0) | 2022.06.21 |
---|---|
[sw 정글] pintos 3주차 - part 3: Stack Growth (0) | 2022.06.21 |
[sw 정글] pintos 3주차 - part 1: Memory Management (0) | 2022.06.21 |
[Malloc-Lab] 기본개념과 Implicit 구현하기 (3) | 2022.05.10 |
[week06] sw정글 사관학교 6주차 회고 (2) | 2022.05.09 |