Adventure Time - Finn 3

새소식

운영체제

[Pintos-Kaist] Project3 - Stack Growth, Memory Mapped Files

  • -

이번 포스팅에서는 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

 

GitHub - Blue-club/pintos2_Team3: Pint OS 3주차 3팀 - 홍윤표, 박희은, 박윤찬

Pint OS 3주차 3팀 - 홍윤표, 박희은, 박윤찬. Contribute to Blue-club/pintos2_Team3 development by creating an account on GitHub.

github.com

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.