[SW정글 75일차] pintos p3 part2. Lazy Loading for Executable

rg.log·2022년 12월 2일
0

SW 사관학교 JUNGLE

목록 보기
19/31

Lazy Loading을 사용한 page 초기화

Lazy Loading은 메모리 로딩이 필요한 순간까지 미룬다. 게으름의 끝. 이는 페이지가 할당되어 해당 페이지 구조체가 존재하지만, 그에 매핑된 물리 프레임이 없고 내부의 실제 컨텐츠도 아직 로드되지 않은 것이다. 컨텐츠는 딱 필요한 순간에 로드되고 page fault로 인해 알 수 있다.

커널이 새로운 페이지 요청을 받으면 vm_alloc_page_with_initializer 가 호출되어 요청받은 페이지의 타입에 따라 적절한 initializer를 세팅하고, 페이지 구조체를 부여하여 초기화를 진행한 후, 다시 유저 프로그램으로 제어권을 넘긴다.

유저 프로그램은 실행되다가 어느 순간 page fault를 일으키는데, 자신이 가지고있다 생각하던 실제 컨텐츠가 아직 로드(적재)되지 않은 상태의 page에 접근했기 때문이다.

page fault handling 과정을 거치면서 uninit_initialize가 호출되며, 그 안에서 이전에 세팅해둔 initializer를 부른다. 그 initializer는 익명 페이지라면 anon_initializer 가 될 것이고, file-backed 페이지라면 file_backed_initializer가 된다.

페이지의 삶은 아래와 같다.
초기화 -> page fault -> lazy load -> swap in -> swap out -> ... -> 제거

vm_alloc_page_with_initializer

매개변수로 받은 type별로 할당되지 않은 페이지 즉, uninit page를 만든다. swap_in 핸들러가 알아서 type별로 페이지를 초기화하고, 받은 aux와 함께 INIT을 호출한다는 것을 생각해야 한다.
페이지 구조체를 갖게 되면 프로세스의 spt에 해당 페이지를 추가한다.

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;

	/* Check wheter the upage is already occupied or not. */
	if (spt_find_page(spt, upage) == NULL)
	{
		/* 페이지를 생성하고 VM 유형에 따라 초기화 프로그램을 가져옴.
		 * 그 후 unit_new를 호출하여 "uninit" 페이지 구조 생성 */
		struct page *new_page = (struct 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);
		}
		else{
			goto err;
		}
        
		/* 만든 페이지를 spt에 추가 */
		if (spt_insert_page(spt, new_page) == false){
			goto err;
		}

	}
	return true;
err:
	return false;
}

load_segment

file load시 필요한 정보들(file, offset 등)을 aux_file_info 와 같은 하나의 구조체 안에 담아 initializer를 호출해 정보가 저장된 구조체를 넘겨준다.

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;

		/* lazy_load_segment에 정보를 전달하도록 aux를 설정 */
		struct file_info *aux_file_info;
		aux_file_info = (struct file_info *)malloc(sizeof(struct file_info));
		aux_file_info->file = file;
		aux_file_info->offset = ofs;
		aux_file_info->read_bytes = page_read_bytes;
		aux_file_info->zero_bytes = page_zero_bytes;
		aux_file_info->writable = writable;

		/* file에서 불러온 세그먼트지만, VM_ANON으로 설정해둠
            이유 : VM_FILE은 swap-out될 때 변경 내용이 디스크에 기록됨
            근데 .bss는 writable =1 로 들어오기 때문에, 수정이 가능하고
            수정 내용이 디스크에 기록될 경우 실행파일 영구 손상 가능성있음. 이를 막으려고 ANON으로 설정*/

		if (!vm_alloc_page_with_initializer(VM_ANON, upage,
											writable, lazy_load_segment, (void *)aux_file_info)){
			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한다.

bool
lazy_load_segment (struct page *page, void *aux) {

	struct file_info *aux_file_info = (struct file_info *)aux;

	file_seek(aux_file_info->file, aux_file_info->offset);  // file의 offset을 바꿔 offset부터 읽을 수 있게 함.
    
	int result = file_read(aux_file_info->file, page->frame->kva, aux_file_info->read_bytes);
	if ( result != (int)aux_file_info->read_bytes){
		palloc_free_page(page->frame->kva);
		return false;
	}

	memset(page->frame->kva + aux_file_info->read_bytes, 0, aux_file_info->zero_bytes);

	return true;
}

file_seek을 해주지 않아서 한동안 디버깅 했었다..

setup_stack

스택은 lazy loading 하지 않고 바로 물리메모리와 매핑해준다.
왜냐하면 setup_stack에서 lazy allocation을 할 경우,
setup_stack()이 끝나고 이어서 argument passing이 이루어질 때 스택 영역에 argument를 넣어주게 되는데 어차피 여기서 page fault가 일어나서 physical memory를 할당하기 때문이다.

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, stack_bottom, 1)){
		if (vm_claim_page(stack_bottom)){
			if_->rsp = USER_STACK;
			success = true;
		}
	}
	return success;
}

vm_try_handle_fault

페이지 폴트가 일어났을 때, 찐 페이지 폴트인지 lazy load로 인한 페이지 폴트인지를 구분하여
lazy load로 인한 가짜 페이지 폴트라면 페이지를 적재하여 제어권을 유저 프로그램에게 다시 넘긴다.

bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
	struct page *page = NULL;

if (not_present || write || user){ //  유효하지 않은 접근일 때
		page = spt_find_page(spt, addr);
		exit(-1);
	}
	/* lazy loading 으로 인한 page fault */
	page = spt_find_page(spt, addr);

	return vm_do_claim_page (page);
}

오늘의 나는

todo 끝내고 동기들이랑 포르투칼 한국전 응원했다. 조 1위와의 경기라 응원하는 동기들도 적어졌지만, 와 핀토스를 하다봐서 축구가 너무 재밌는 줄 알았는데 선수들이 너무 잘 뛰어줘서 경기가 재밌었다. 경우의 수를 뚫고 16강 진출!! 중꺾마 와아 ⚽️👹❤️‍🔥

0개의 댓글