Sapphire9 개발 일지

일부 자원이 여러 프로세스에 의해 사용되고 있다면, 각 프로세스는 충돌이 일어나는 것을 막기 위해 자신만의 자원 복사본을 갖는다. 하지만 자원이 수정되지 않고 읽기 작업만 수행된다면, 물리 메모리에서 여러 개의 복사본을 가지고 있을 필요가 없다.

 

 

예를 들어, fork를 통해 새로운 프로세스가 생성되었다고 가정해보자. 자식 프로세스는 부모의 자원을 복사하여 상속받아야 한다. 이를 위해 프레임을 할당하고, 프레임에 데이터를 쓰고, 가상 주소와 물리 주소의 매핑 정보를 페이지 테이블에 추가하는 작업을 수행한다. 이러한 단계들은 시간이 오래 걸릴 수 있다. 더군다나 읽기 작업만을 위한 경우, 이는 비효율적이다.

 

 

copy on write 기술을 사용하여 이미 물리 메모리 상에 존재하는 프레임을 참조하도록 하여 더 빠른 복사를 수행할 수 있도록 한다. 따라서 자식 프로세스의 페이지 테이블에 가상 주소와 물리 주소의 매핑 정보만을 추가한다. 이후 자원의 내용을 수정하려고 시도하면, 자신을 위한 자원 복사본을 갖게된다.

 

 

"write-protect" 메커니즘을 이용해 이를 구현하는데, 이것은 write 접근일 때 페이지 폴트를 일으키는 것이다.

 

 

bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
						vm_initializer *init, void *aux)
{

		...

		new_page->writable = writable;
		new_page->cow = 0;
        
        	...
}

페이지 할당과정에서 페이지의 writable 정보를 저장하고 cow를 0으로 초기화한다.

 

 

bool supplemental_page_table_copy(struct supplemental_page_table *dst UNUSED,
				struct supplemental_page_table *src UNUSED)
{
	struct thread *curr = thread_current();
	struct hash_iterator i;
	struct hash *parent_hash = &src->spt_hash;

	hash_first(&i, parent_hash);
    
	while (hash_next(&i))
	{    
		struct page *parent_page = hash_entry(hash_cur(&i), struct page, h_elem);

		if (parent_page->operations->type == VM_UNINIT)
		{
			vm_initializer *init = parent_page->uninit.init;
			void *aux = parent_page->uninit.aux;

			vm_alloc_page_with_initializer(parent_page->uninit.type,
            			parent_page->va, parent_page->writable, init, aux);
		}
		else
		{
			struct page *child_page = 
            				(struct page *)malloc(sizeof(struct page));
                            
			memcpy(child_page, parent_page, sizeof(struct page));

			if (!spt_insert_page(dst, child_page))
				return false;

			if (!pml4_set_page(curr->pml4, child_page->va,
            					child_page->frame->kva, false))
				return false;

			if (!pml4_set_page(parent_page->frame->thread->pml4,
            			parent_page->va, parent_page->frame->kva, false))
				return false;

			child_page->cow = 1;
			parent_page->cow = 1;
		}
	}

	return true;
}

 

위 함수는 부모 프로세스의 자원을 복사하는 과정을 나타낸 것이다. memcpy를 이용해서 부모 페이지의 내용을 복사하고 가상 주소와 물리 주소 간의 매핑 정보를 페이지 테이블에 추가한다. 이때 pml4_set_page에서 부모와 자식의 writable을 0으로 하여 write protected page로 설정한다. 부모와 자식의 cow를 1로 설정하고 이후에 cow를 이용해서 write protected page에 대한 page fault를 처리한다.

 

 

bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED, bool user UNUSED, 
					bool write UNUSED, bool not_present UNUSED)
{
	...
    
	/* write protected page : Copy on Write */
	if (write && !not_present && (fault_p->cow == 1))
	{
		bool result = vm_handle_wp(fault_p);
		return result;
	}

	return vm_do_claim_page(fault_p);

}

write 접근일 때 페이지 폴트를 일으키면 vm_try_handle_fault의 인수 중 write값은 1이다. 또한 writing r/o page, cow를 통해 write protected page에 대한 page fault임을 구분하고 이를 처리한다.

 

 

/* Handle the fault on write_protected page */
static bool
vm_handle_wp(struct page *page UNUSED)
{
	struct frame *old_kva = page->frame->kva;
	struct frame *new_frame = vm_get_frame();
	new_frame->page = page;
	page->frame = new_frame;

	memcpy(new_frame->kva, old_kva, PGSIZE);

	page->cow = 0;

	return pml4_set_page(thread_current()->pml4, page->va, new_frame->kva, 1);
}

프레임을 할당하고 데이터를 복사한다. 페이지와 프레임을 연결하고 가상 주소와 물리 주소의 매핑 정보를 추가하며 writable을 1로 설정한다.

profile

Sapphire9 개발 일지

@Sapphire9

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그