이번 포스팅에서는 Stack Growth, Memory Mapped Files에 대한 구현을 해보겠습니다.
Stack Growth 구현
1️⃣ bool vm_try_handle_fault (struct intr_frame *f, void *addr, bool user, bool write, bool not_present)
- 이 함수는 페이지 오류 예외를 처리하는 동안 userprog/exception.c의 page_fault에서 호출됩니다.
- 이 함수에서는 페이지 오류가 스택 증가에 유효한 경우인지 여부를 확인해야 합니다.
- 스택 증가로 오류를 처리할 수 있음을 확인했다면 오류가 발생한 주소로 vm_stack_growth를 호출합니다.
bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
....
else if(USER_STACK >= addr && addr >= USER_STACK - (1<<20) && addr == thread_current()->rsp-8){
if(!user)
return false;
vm_stack_growth(addr);
return true;
}
....
}
위에서 addr == thread_current()->rsp-8에서 stack_growth를 호출하는 이유는 x86-64 PUSH 명령어는 스택 포인터를 조정하기 전에 액세스 권한을 확인하므로 스택 포인터 8바이트 아래에서 페이지 오류가 발생할 수도 있기 때문입니다. 그래서 위 조건이 일치하게 될 때 vm_stack_growth를 호출합니다.
2️⃣ void vm_stack_growth (void *addr)
- 추가 주소가 더 이상 결함이 있는 주소가 되지 않도록 하나 이상의 익명 페이지를 할당하여 스택 크기를 늘립니다.
- 할당을 처리할 때 addr을 PGSIZE로 반올림해야 합니다.
- 이 프로젝트의 경우 스택 크기를 최대 1MB로 제한해야 합니다.
static void
vm_stack_growth (void *addr UNUSED) {
// 주어진 주소를 페이지 경계로 정렬
void* page_addr = pg_round_down(addr);
// 주어진 페이지 주소에 해당하는 페이지를 스레드의 페이지 테이블에서 찾음
struct page* page = spt_find_page(&thread_current()->spt, page_addr);
// 페이지가 없는 경우, 새로운 페이지 할당과 클레임을 수행하여 스택 성장
while (page == NULL) {
// 가상 페이지를 익명 페이지로 할당
vm_alloc_page(VM_ANON, page_addr, true);
// 페이지 클레임
vm_claim_page(page_addr);
// 다음 페이지 경계로 이동
page_addr += PGSIZE;
// 이동한 페이지 주소에 해당하는 페이지를 다시 찾음
page = spt_find_page(&thread_current()->spt, page_addr);
}
}
Memory Mapped Files 구현
1️⃣ void *mmap (void *addr, size_t length, int writable, int fd, off_t offset)
- 오프셋 바이트에서 시작하여 fd로 열린 파일을 프로세스의 가상 주소 공간인 addr에 길이 바이트로 매핑합니다.
- 페이지에 오류가 발생하면 튀어나온 바이트를 0으로 설정하고 페이지가 디스크에 다시 기록될 때 이 바이트를 삭제합니다.
- 성공하면 이 함수는 파일이 매핑된 가상 주소를 반환합니다. 실패하면 파일을 매핑할 수 있는 유효한 주소가 아닌 NULL을 반환해야 합니다.
- fd로 열린 파일의 길이가 0바이트인 경우 mmap 호출이 실패할 수 있습니다.
- addr이 페이지 정렬되지 않았거나 매핑된 페이지 범위가 스택 또는 실행 파일 로드 시 매핑된 페이지를 포함하여 매핑된 페이지의 기존 세트와 겹치는 경우 실패해야 합니다.
- 핀토스 코드는 가상 페이지 0이 매핑되지 않은 것으로 가정하므로 addr이 0이면 실패해야 합니다.
- 길이가 0이면 mmap도 실패해야 합니다.
위와 같은 여러 가지 예외 처리를 해주고 예외 처리일 때 MAP_FAILED(NULL)을 반환값으로 넘겨주어 테스트를 확인합니다.
1-1) mmap()
void *
mmap (void *addr, size_t length, int writable, int fd, off_t offset)
{
struct file* file = thread_current()->fdt[fd];
void *addr_ = addr;
size_t length_ = length;
while(true){
if(addr_ + length_ == NULL || is_kernel_vaddr(addr_+length_)){
return MAP_FAILED;
}
if(spt_find_page(&thread_current()->spt, addr_) != NULL){
return MAP_FAILED;
}
if(length_<PGSIZE) break;
length_ -= PGSIZE;
}
if(offset % PGSIZE != 0){
return MAP_FAILED;
}
if(file == NULL || length == 0 || fd == 0 || fd == 1){
return MAP_FAILED;
}
if(addr != pg_round_down(addr) ){
return MAP_FAILED;
}
if (!do_mmap(addr, length, writable, file, offset)){
return MAP_FAILED;
}
return addr;
}
1-2) do_mmap()
void *
do_mmap (void *addr, size_t length, int writable,
struct file *file, off_t offset) {
file = file_reopen(file);
if(file == NULL) return false;
void* start_addr = addr;
struct supplemental_page_table *spt = &thread_current()->spt;
enum vm_type check = VM_MARKER_MMAP;
struct list* mmap_list = malloc(sizeof(struct list));
list_init(mmap_list);
while(length >0){
size_t page_read_bytes = length < PGSIZE ? length : PGSIZE;
size_t page_zero_bytes = PGSIZE-page_read_bytes;
struct file_loader *file_loader = malloc(sizeof(struct file_loader));
file_loader->page_read_bytes = page_read_bytes;
file_loader->page_zero_bytes = page_zero_bytes;
file_loader->ofs = offset;
file_loader->file = file;
if (!vm_alloc_page_with_initializer (VM_FILE | check, addr,
writable, lazy_load_file, file_loader)){
free(file_loader);
return false;
}
struct page* page = spt_find_page(spt,addr);
list_push_back(mmap_list,&page->mmap_elem);
/* Advance. */
length -= page_read_bytes;
addr += PGSIZE;
offset += page_read_bytes;
check = 0;
}
struct page* page = spt_find_page(spt,start_addr);
page->mmap_list = mmap_list;
return true;
}
1-3) lazy_load_file()
static bool
lazy_load_file (struct page *page, void *aux) {
/* TODO: Load the segment from the file */
struct file_loader *file_loader = (struct file_loader*)aux;
struct file *file = file_loader->file;
off_t ofs = file_loader->ofs;
uint8_t *upage = page->va;
uint32_t page_read_bytes = file_loader->page_read_bytes;
uint32_t page_zero_bytes = file_loader->page_zero_bytes;
/* TODO: This called when the first page fault occurs on address VA. */
/* TODO: VA is available when calling this function. */
file_seek(file,ofs);
page_read_bytes = (int)file_read(file,page->frame->kva, page_read_bytes);
page_zero_bytes = PGSIZE - page_read_bytes;
memset (page->frame->kva+page_read_bytes, 0, page_zero_bytes);
// memcpy (page->va, page->frame->kva,PGSIZE);
struct file_page* file_page = &page->file;
file_page ->ofs = ofs;
file_page ->read_bytes = page_read_bytes;
file_page ->zero_bytes = page_zero_bytes;
file_page -> file = file;
// free(file_loader);
return true;
}
위의 코드들을 살펴보면 mmap 함수는 파일 매핑 요청을 받고, 오류 검사 및 매핑 관련 정보를 설정한 후, do_mmap 함수를 호출하여 실제 파일 매핑을 수행합니다. 이때 vm_alloc_page_with_initializer함수에 lazy_load_file을 실행시킵니다. do_mmap 함수는 파일 데이터를 페이지 단위로 로딩하고, 가상 주소와 supplemental_page_table 엔트리를 생성하여 매핑을 완료합니다.
2️⃣ void munmap(void* va)
- 지정된 주소 범위 addr에 대한 매핑을 언매핑하며, 이 주소는 아직 매핑이 언매핑되지 않은 동일한 프로세스에서 이전에 mmap을 호출하여 반환한 가상 주소여야 합니다.
- 프로세스가 종료되거나 다른 방법으로 종료될 때 모든 매핑은 암시적으로 매핑 해제됩니다.
- 매핑이 암시적이든 명시적이든 매핑이 해제되면 프로세스가 쓴 모든 페이지가 파일에 다시 쓰이고, 쓰지 않은 페이지는 다시 쓰여서는 안 됩니다. 그러면 해당 페이지는 프로세스의 가상 페이지 목록에서 제거됩니다.
- 각 매핑에 대해 파일에 대한 별도의 독립적인 참조를 얻으려면 file_reopen 함수를 사용해야 합니다.
- 두 개 이상의 프로세스가 동일한 파일을 매핑하는 경우 일관된 데이터를 볼 필요는 없습니다.
- 유닉스에서는 두 매핑이 동일한 물리적 페이지를 공유하도록 하여 이 문제를 처리하며, mmap 시스템 호출에는 클라이언트가 페이지가 공유인지 비공개인지(즉, 쓰기 시 복사)를 지정할 수 있는 인수도 있습니다.
2-1) munmap()
void munmap(void* va){
check_address(va);
struct page* page = spt_find_page(&thread_current()->spt, va);
if(page == NULL || page_get_type(page) != VM_FILE) return;
if(!page->file.mmap_start) return;
lock_acquire(&filesys_lock);
do_munmap(va);
lock_release(&filesys_lock);
}
2-2) do_munmap()
void do_munmap(void *addr) {
struct supplemental_page_table *spt = &thread_current()->spt;
struct page *page = spt_find_page(spt, addr);
struct list *mmap_list = page->mmap_list;
struct list_elem *e;
struct file *file = NULL; // 파일 포인터 초기화
for (e = list_begin(mmap_list); e != list_end(mmap_list); ) {
page = list_entry(e, struct page, mmap_elem);
if (VM_TYPE(page->operations->type) == VM_UNINIT) {
e = list_remove(e);
spt_remove_page(spt, page);
continue;
}
if(!pml4_is_dirty(thread_current()->pml4, page->va)){
e = list_remove(e);
continue;
}
struct file_page *file_page = &page->file;
if (file == NULL) {
file = file_page->file; // 파일 포인터 갱신
}
file_write_at(file, page->frame->kva, file_page->read_bytes, file_page->ofs);
e = list_remove(e);
if(page->frame !=NULL){
free_frame(page->frame);
}
spt_remove_page(spt, page);
}
free(mmap_list);
if (file != NULL) {
file_close(file);
}
}
위 코드들을 살펴보면 munmap 함수는 사용자가 호출하여 파일 매핑을 해제하는 함수이며, 내부적으로 do_munmap 함수를 호출하여 실제 매핑 해제를 수행합니다. do_munmap 함수는 매핑된 페이지들을 처리하고 관련된 자원을 해제하는 역할을 수행합니다. 추가적으로 요구사항에 따른 각각의 예외처리를 해줍니다.
3️⃣ bool file_backed_initializer (struct page *page, enum vm_type type, void *kva)
- 파일 지원 페이지를 초기화합니다.
- 이 함수는 먼저 page-> operations에서 파일 백업 페이지에 대한 핸들러를 설정합니다.
- 메모리를 백업하는 파일과 같은 페이지 구조체의 일부 정보를 업데이트할 수 있습니다.
- mmap의 시작페이지임을 나타내는 mmap_start를 MARKER타입으로 지정합니다.
bool
file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
page->operations = &file_ops;
struct file_page *file_page = &page->file;
file_page ->mmap_start = VM_IS_MMAP(type);
}
💡위와 관련된 자세한 코드내용은 아래 Github를 참고하세요.
https://github.com/Blue-club/pintos2_Team3/tree/dbscks97
'운영체제' 카테고리의 다른 글
DeadLock과 Redis 대기열 사용하기 - (1) (0) | 2023.08.30 |
---|---|
[Pintos-Kaist] Project3 - Swap In/Out (0) | 2023.06.26 |
[Pintos-Kaist] Project3 - Anonymous Page (0) | 2023.06.19 |
[Pintos-Kaist] Project3 - Memory Management (0) | 2023.06.16 |
[Pintos-Kaist] Project2 - System Call(4) - (exec, fork, wait) (0) | 2023.06.11 |