PintOS Project 3 - Virtual Memory (5) mmap() 및 page fault 관련 추가 정리(정글사관학교 82일차 TIL)
저번 시간에는 mmap/munmap을 통해 가상 메모리에 페이지를 할당해주는 방법에 대해 배웠다. mmap에 대해 궁금한 점을 마저 정리하고 내일 swap in/out으로 넘어가련다.
정리 1: mmap으로 페이지를 할당받으면 힙/스택 중 어느 공간에 페이지가 할당되는가?
결론부터 말하면 mmap으로 페이지를 할당받으면 힙도 스택도 아닌 공간에 할당된다. 아래 그림에서도 나와있듯 mmap은 file의 offset 위치부터 length 길이만큼 읽어들여 가상 메모리에 매핑한다. 이때 주의할 점은 첫번째로 매핑할 때는 물리 프레임에는 올리지 않는다는 점.
이 역시 malloc()과 차이라고 할 수 있는데, malloc()은 힙 공간에 인자로 넣어주는 사이즈만큼의 공간을 할당한다.
공부하면서 느끼는 건데, malloc()과 mmap()은 아예 다르다고 봐야 할듯. malloc()은 응용 프로그램이 런타임에 메모리를 동적으로 할당해달라고 요청하는 함수이고(이는 heap에서 생성됨 - anonymous page가 할당), mmap()은 정해진 일련의 가상 페이지를 정적으로 요청하는 함수(힙도 스택도 아닌 미할당 공간 중 페이지를 생성 - file-backed page)이다.
정리 2: page fault가 발생해 가상 페이지와 물리 프레임이 매핑되는 과정 흐름
물리 프레임을 할당해주는 함수는 palloc_get_page()이며(이때 반환되는 주소는 커널 가상 주소: 물리 주소는 커널 가상주소(KERN_BASE 위)에 해당하기 때문) 이를 유저 가상 주소(KERN_BASE 아래) 연결해 실제 물리 메모리에 정보를 올리는 함수는 install_page()이다.
이를 project 2까지는 load_segment() 함수 안에서 모두 처리해줬다. 즉, 물리 프레임을 곧바로 할당(palloc_get_page(PAL_USER))한 뒤, 유저 가상 페이지와 매핑(install_page)해줬다.
project2까지의 load_segment()
#ifndef VM
(...)
static bool
load_segment (struct file *file, off_t ofs, uint8_t *upage,
(...)
while (read_bytes > 0 || zero_bytes > 0) {
(...)
/* Get a page of memory. */
uint8_t *kpage = palloc_get_page (PAL_USER);
if (kpage == NULL)
return false;
(...)
/* Add the page to the process's address space. */
if (!install_page (upage, kpage, writable)) {
printf("fail\n");
palloc_free_page (kpage);
return false;
}
(...)
}
하지만 Project 3부터는 곧바로 물리 프레임과 가상 페이지를 연결해주는 게 아니라 파일 관련 메타데이터만 가상 페이지에 올려놓고 실제 정보는 올리지 않는다. 이후에 프로세스가 해당 페이지에 접근하면 그때 page_fault exception을 일으켜 물리 프레임과 연결해주는 식.
이때 의문 하나: container 구조체는 왜 malloc()으로 선언해줄까?
위에서 정리했던 것처럼, malloc()은 런타임에서 메모리를 동적으로 할당받기 위해 제공하는 라이브러리 함수이다. 일단 container 구조체는 디스크에 저장되어 있던 특정 파일을 불러오는 게 아니다. 런타임에서 정보를 저장해놓기 위해 가상 메모리에 페이지까지 요청할 필요가 없으며 struct container 안에 담는 멤버 크기에 따라 사이즈가 동적으로 변할 수 있으니 page가 너무 클 수도 있다. 그렇다고 load_segment()를 실행하고 곧바로 사라지는 지역 변수도 아니라(vm_alloc_page_with_initializer()에 인자로 넘겨줘야 함) 스택에 할당할 수도 없다.
무튼 그래서 흐름을 보면
1. 맨 처음 파일을 가상 페이지에 할당할 때 (load_segment가 이를 시행)
Pintos 부팅
-> init.c 실행
-> ... ->run_actions()
-> run_task()
-> process_create_initd()
-> initd() -> spt_init()으로 spt 테이블 초기화되고
-> process_exec()
-> load(): 입력으로 넣어준 file_name에 해당하는 실행 파일을 현재 스레드에 load
-> load_segment(): container 구조체 생성해 해당 구조체에 파일 메타데이터(파일 구조체, page_read_byte, offset)를 저장한 뒤 vm_alloc_page_with_initializer()를 호출
tatic 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) {
/* 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(container로 대체) to pass information to the lazy_load_segment. */
// struct container *container;
// container = palloc_get_page(PAL_ZERO | PAL_USER);
struct container *container = (struct container *)malloc(sizeof(struct container));
container->file = file;
container->page_read_bytes = page_read_bytes;
container->offset = ofs;
if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, container)) {
return false;
}
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
->vm_alloc_page_with_initializer(): 해당 파일 형태(uninit/anonymous/file-backed)에 맞게 초기화해준다.
여기까지가 처음 해당 파일을 호출했을 때 파일이 가상 페이지에 할당되는 과정이며 해당 파일 자체 정보는 물리 프레임에는 1도 할당되지 않는다. (관련 메타데이터는 malloc 호출해서 heap에 저장 - 함수가 호출되는 동안만 잠깐 저장할 거면 stack에 하겠지만 지금 상황은 나중에 해당 페이지에 프로세스가 접근할 때까지 해당 정보를 들고 있어야 하니 heap에 저장할 필요가 있다! - malloc)
2. 프로세스가 페이지만 할당되어 있는 가상 주소에 접근한 상황 발생
exception_init(): exception 발생: handler가 작동한다.
->page_fault(): 해당 페이지에 정보가 있다고 해서 가봤더니 정보가 없다. 그러면 page fault가 발동한다.
->vm_try_handle_fault(): 먼저 유저 스택 포인터를 가지고 온다.
-> vm_claim_page(): 그 다음, 해당 인자로 받은 가상 주소에 해당하는 페이지를 spt 테이블로부터 들고온다.
->vm_do_claim_page()
-> vm_get_frame(): 물리 프레임을 할당받아 온다.
-> install_page(): 위에서 받아온 물리 프레임과 가상 페이지를 연결한다.
이렇게 가상 페이지가 물리 프레임과 연결되는 것을 확인할 수 있다.