in Pint OS
기존의 Pint OS는 Eager loading(Immediate loading) 방식이다. Eager loading은 데이터나 자원을 가능한 빠른 시점에서 로드하는 방식을 의미한다. 즉, 필요한 자원을 처음으로 요청하기 전에 미리 load하여 사용 가능한 상태로 준비하는 것의 의미한다.
Pint OS의 Project 3 - Anonymous Page 파트에서는 이 Eager loading 방식을 Lazy loading 방식으로 바꿔주어야 한다. 따라서 Lazy loading 방식에 대해 공부하고 정리해보았다. Lazy loading의 구현은 userprog/process.c
에서 이루어진다.
Lazy loading
지연 로딩.
프로그램이 필요한 데이터나 자원을 처음으로 요청할 때까지 load를 지연시키는 방식이다.
장점
- 자원의 효율적인 사용: Lazy loading은 필요한 시점에만 데이터나 자원을 로드하기 때문에 불필요한 자원의 로딩을 피할 수 있다. 이로 인해 초기 부팅 시간이나 프로그램 실행 시간이 단축되며, 자원의 사용량이 줄어들어 시스템 자원의 효율성을 향상시킬 수 있다.
- 성능 개선: 필요한 데이터를 실제로 사용하기 전까지 로드를 지연시키는 것은 메모리나 디스크 등의 I/O 작업을 최소화할 수 있습니다. 이는 응용 프로그램의 성능을 향상시키고 응답 시간을 단축시킬 수 있다.
- 메모리 관리 향상: Lazy loading은 큰 데이터나 자원을 프로그램 실행 초기에 미리 로드하지 않고 필요한 시점에 로드합니다. 이를 통해 메모리 사용량을 최적화하고 프로그램의 메모리 소비를 줄일 수 있다. 특히, 대용량 파일이나 데이터베이스와 같은 경우에 효과적이다.
- 자원의 유연한 관리: Lazy loading을 통해 필요한 자원만을 로드하는 것은 동적으로 자원을 관리하는 데 도움이 된다. 응용 프로그램이 필요한 시점에 자원을 로드하고 해제할 수 있으므로, 자원의 생명주기를 유연하게 제어할 수 있다.
- 초기 부담 완화: Lazy loading은 프로그램 실행 초기에 필요한 자원을 모두 로드하지 않고 필요한 시점에 로드하기 때문에 초기 로딩 시 부담을 완화시킬 수 있다. 특히, 대규모 애플리케이션이나 대용량 데이터를 다루는 경우에 유용하다.
기존 Pint OS
load_segment()
// userprog/process.c
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) {
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;
}
- Eager loading 방식의 load_segment() 함수이다.
- load_segment()는 프로그램 로드 시에 호출된다.
- process_exec => load => load_segment
- 파일을 반복문을 사용해서 페이지 단위로 읽는다.
- 읽고나서 install_page()를 호출해서 Page Table에 매핑한다.
- 따라서 파일 로드 시, 가상 페이지와 물리 프레임을 매핑시켜서 페이지 테이블에 적재하는 것을 알 수 있다.
install_page()
// userprog/process.c
static bool
install_page (void *upage, void *kpage, bool writable) {
struct thread *t = thread_current ();
return (pml4_get_page (t->pml4, upage) == NULL
&& pml4_set_page (t->pml4, upage, kpage, writable));
}
- va인 upage와 kva인 kpage를 Page Table에 매핑한다.
setup_stack()
static bool
setup_stack (struct intr_frame *if_) {
uint8_t *kpage;
bool success = false;
kpage = palloc_get_page (PAL_USER | PAL_ZERO);
if (kpage != NULL) {
success = install_page (((uint8_t *) USER_STACK) - PGSIZE, kpage, true);
if (success)
if_->rsp = USER_STACK;
else
palloc_free_page (kpage);
}
return success;
}
- stack 공간을 페이지 크기만큼 할당한다.
- USER_STACK - PGSIZE인 가상 주소에 물리 프레임 주소(kpage)를 매핑한다.
- 성공하면 레지스터 rsp를 USER_STACK을 지정한다.
- 현재는 stack 공간을 1페이지만큼만 사용하지만, 이후에 Stack Growth에 대해 구현할 것이다.
Lazy loading을 구현한 Pint OS
load_segment()
// userprog/process.c
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) {
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* Project 3. */
void *aux = NULL;
struct file_segment *file_segment = malloc (sizeof (struct file_segment));
file_segment->file = file;
file_segment->ofs = ofs;
file_segment->page_read_bytes = page_read_bytes;
file_segment->page_zero_bytes = page_zero_bytes;
aux = (void *)file_segment;
if (!vm_alloc_page_with_initializer (VM_ANON, upage,
writable, lazy_load_segment, aux))
return false;
/* Project 3. */
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
/* Project 3. */
ofs += page_read_bytes;
}
return true;
}
- 파일을 페이지 단위로 나누어서 순차적으로 처리하는 것은 동일하다.
- Lazy loading 방식은 파일 로드 시에 파일을 읽는 것이 아니라, Page Fault가 발생했을 때 폴트가 발생한 주소값에 대해서만 파일을 읽는다.
- 따라서 가상 주소 va에 대해 Page Fault가 발생했을 때 파일의 어떤 페이지를 읽어야 하는지를 넘겨주어야 한다.
- 이를 위해 새로운 구조체 file_segment에 파일 정보를 담아 넘겨준다.
- 파일 페이지를 순차적으로 돌기 위해
ofs += page_read_bytes
를 수행해주어야 한다.- file_read를 사용했을 때는 알아서 파일의 읽기 위치가 변경되었지만, Lazy loading 방식에서는 file_raed를 사용하지 않기 때문에 직접 ofs을 옮겨주어야 한다.
struct file_segment
// userprog/process.c
struct file_segment {
struct file *file;
off_t ofs;
size_t page_read_bytes;
size_t page_zero_bytes;
};
- 읽어야 하는 파일 구조체를 저장한다.
- 어떤 파일 페이지를 읽을지 ofs, page_read_bytes, page_zero_bytes를 저장한다.
lazy_load_segment()
// userprog/process.c
static bool
lazy_load_segment (struct page *page, void *aux) {
/* Project 3. */
struct file_segment *file_segment = (struct file_segment *)aux;
struct file *file = file_segment->file;
off_t ofs = file_segment->ofs;
size_t page_read_bytes = file_segment->page_read_bytes;
size_t page_zero_bytes = file_segment->page_zero_bytes;
if (pml4_get_page (thread_current ()->pml4, page->va) == NULL) {
return false;
}
/* Get a page of memory. */
void *kpage = page->frame->kva;
if (kpage == NULL) {
return false;
}
/* Load this page. */
file_seek (file, ofs);
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);
free (file_segment);
return true;
}
- aux에 load_segment()에서 넘겨준 file_segment가 담겨있다.
- file_seek()를 호출해서 파일의 읽을 위치를 변경시킨다.
- file_read()를 호출해서 물리페이지 kpage에 데이터를 적재한다.
- kpage의 남는 페이지 공간을 0으로 채운다.
- 사용한 file_segment를 free한다.
setup_stack
static bool
setup_stack (struct intr_frame *if_) {
bool success = false;
void *stack_bottom = (void *) (((uint8_t *) USER_STACK) - PGSIZE);
if (vm_alloc_page (VM_ANON | VM_MARKER_0, stack_bottom, true)) {
success = vm_claim_page (stack_bottom);
if (success)
if_->rsp = USER_STACK;
}
return success;
}
- stack_bottom부터 USER_STACK 사이의 공간이 스택 영역이다.
- 이 영역은 현재 한 페이지 크기이다.
- 이후 Stack Growth에 대해 구현할 것이다.
- 가상 주소 stack_bottom에 대해 page 구조체를 할당한다.
- vm_claim_page()를 호출하여 가상 주소 stack_bottom에 대해 물리 프레임을 할당하고 페이지 테이블에 매핑한다.
- 성공하면, 레지스터 rsp를 USER_STACK을 지정한다.
'프로젝트 > Pint OS' 카테고리의 다른 글
[Pint OS] Stack Growth (0) | 2023.06.25 |
---|---|
[Pint OS] Lazy loading(3) (0) | 2023.06.22 |
[Pint OS] Lazy loading(1) (0) | 2023.06.18 |
[Pint OS] System Calls (6) (0) | 2023.06.17 |
[Pint OS] 에러: multi-oom (0) | 2023.06.17 |