상세 컨텐츠

본문 제목

PintOS | Project3. Virtual Memory | 트러블 모음 1

Computer Science/운영체제 (Operating System)

by hyuga_ 2023. 12. 24. 13:28

본문

Anonymous Page

1. Lazy Loading - thread_current()에서 터지는 문제

 

spt_insert_page 에서 문제를 발견할 수 있었다. 

 

 

 

 

 

  • hash_insert()는 첫번째 인자로 hash 구조체를 받아야 하는데, spt를 줬다.
  • &spt 를 &spt->spt_pages 로 바꿔주니 문제 해결

 

2. Page fault 핸들링 때, spt에서 페이지를 찾지 못하는 문제

 

exit(-33)을 어디에 넣어놨냐면 ..

 

 

뭐가 문제였을까?? 

애초에 page를 spt에 제대로 넣어주지 못했거나, 제대로 넣어줬으나 탐색 과정상의 문제로 찾지 못하는 상황인 듯 하다. 

여기서 출발하여 원인을 탐색하다보니 다음과 같은 문제점을 발견할 수 있었다. 

 

vm_stack_growth(), setup_stack()에서 처음에 vm_alloc_page 함수로 페이지를 할당할 당시에 .. 

vm_alloc_page(VM_ANON | VM_MARKER_0, pg_round_down(addr), 1);

 

VM_MARKER_0과의 or 연산을 통해 type을 비트 플래그로 저장한다. 

  • VM_ANON이 1 (이진수 0001)이고,
  • VM_MARKER_0이 (1 << 3) 즉, 8 (이진수 1000)
  • VM_ANON | VM_MARKER_0은 9 (이진수 1001)가 된다.

그러므로 나중에 type을 비교해서 찾을 때도 비트 마스킹으로 찾아야 한다. 

 

따라서 다음 식을 활용하여 type을 구해준다. 직접 and 연산을 수행해도 되지만, 예기치 않은 동작이 가능하므로 가능하면 기정의된 매크로를 활용하자. 

#define VM_TYPE(type) ((type) & 7)

 

근데, 내가 기존에 짰던 vm_alloc_page_with_initializer() 함수를 보면 다음과 같다. 

 

(before)

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)

  bool success = false;
  struct supplemental_page_table *spt = &thread_current()->spt;

  /* Check wheter the upage is already occupied or not. */
  if (spt_find_page(spt, upage) == NULL){
    /* TODO: Create the page, fetch the initializer according to the VM type,
     * TODO: and then create "uninit" page struct by calling uninit_new. You
     * TODO: should modify the field after calling the uninit_new. */
    struct page *p = (struct page *)malloc(sizeof(struct page));
    
    if (type == VM_ANON){
      uninit_new(p, pg_round_down(upage), init, type, aux, anon_initializer);
    } else if (type == VM_FILE){
      uninit_new(p, pg_round_down(upage), init, type, aux, file_backed_initializer);
    }

    p->writable = writable;
    /* TODO: Insert the page into the spt. */
    success = spt_insert_page(spt, p);
  }
  return success;
err:
  return success;
}

 

 

가운데 type을 비교하는 조건문을 보자.

VM_TYPE 매크로를 이용한 type 변환 과정을 거치지 않고 바로 VM_ANON, 또는 VM_FILE 과 비교했다. 전혀 쌩뚱맞은 값들을 서로 비교하고 있었던 것. 

 

이렇게 되면 조건문 내부로 들어가지 않아 uninit_new가 호출되지 않는다. 결과적으로 spt에 revisit을 할 때마다 va와 연결되지 않은 빈 page 구조체가 spt에 삽입되고 있었던 것이다. 

 

다음과 같이 바꿔주면 Type 비교를 올바르게 할 수 있어, 더 이상 해당 문제는 발생하지 않는다. 

 

 

(after 1: 매크로 활용)

if (VM_TYPE(type) == VM_ANON){
  uninit_new(p, pg_round_down(upage), init, type, aux, anon_initializer);
} else if (VM_TYPE(type) == VM_FILE){
  uninit_new(p, pg_round_down(upage), init, type, aux, file_backed_initializer);
}

 

(after 2: 직접 and 연산)

if (type & VM_ANON){
  uninit_new(p, pg_round_down(upage), init, type, aux, anon_initializer);
} else if (type & VM_FILE){
  uninit_new(p, pg_round_down(upage), init, type, aux, file_backed_initializer);
}

 

3. read-boundary 등 예외적인 주소 처리 못하는 문제

supplement_page_table_copy, kill 까지 구현하고 나서도 Project 2의 일부 Test case가 통과되지 않았다.

 

해당 문제들은 read-boundary, bad-read, bad-write 등 예외적인 주소를 주고 이를 제대로 처리하는지 보는 케이스들이었다.

고민하고 있을 때 지원님과 서연님께서 팁을 주셔서 두 가지 문제 부분을 찾을 수 있었고, 몇가지 로직을 수정해주니 Test case들을 통과할 수 있었다. 

 

page_fault 함수에 예외처리 로직 추가 (exception.c)

가상 메모리 환경에서 Page fault가 발생했을 때, 해당 주소가 uva 범위를 벗어난 경우(kva에 있거나 0 아래의 없는 주소를 참조하고 있을 경우) 예외처리를 해주는 로직을 추가했다. 

static void page_fault(struct intr_frame *f) {
		...

    /* Determine cause. */
    not_present = (f->error_code & PF_P) == 0;
    write = (f->error_code & PF_W) != 0;
    user = (f->error_code & PF_U) != 0;
     
		///// 추가한 부분 //////
    int PHYS_BASE = 0x80040000;
    int VIRTUAL_BASE = 0x00000000;
   
    if (user) {      
        if (is_kernel_vaddr(fault_addr)) {            
            exit(-1);
        } else {            
            if (fault_addr <= VIRTUAL_BASE) {               
                exit(-1);
            }
        }
    }
		///// 추가한 부분 //////

#ifdef VM
    /* For project 3 and later. */
    if (vm_try_handle_fault(f, fault_addr, user, write, not_present)) return;
#endif

 // ...
}

 

check_address 함수 로직 수정 (syscall.c)

그 다음으로 system call 함수들에서 호출되는 check_address 함수(address의 validity를 검사)를 수정해주었다.

 

가상 메모리 환경이라면 pml4에서 해당 주소를 찾지 못했다고 반드시 에러인 것은 아니다. lazy loading 중일 때는 처음 페이지 할당시에는 실제 메모리에 로드되지 않도록 되어있기 때문에, pml4에서 해당 주소를 탐색할 수 없는 것은 당연하다.

 

따라서 spt까지 탐색한 후에, spt에서도 해당 주소를 찾지 못했을 때에 비로소 에러 처리를 해주는 식으로 바꿔주었다.

void check_address(void *addr) {
	struct thread *t = thread_current();
	/* [기존 코드: 정상적인 lazy loading 에서도 에러 검출되는 구조]

	if (!is_user_vaddr(addr) || addr == NULL || pml4_get_page(t->pml4 , addr) == NULL) {
		exit(-1);
	}
	*/

	/* [수정 코드: lazy loading이라 pml4에 없는 상황 고려] */
	if (!is_user_vaddr(addr) || addr == NULL) {
		exit(-1);
	}

	if (pml4_get_page(t->pml4 , addr) == NULL){
		// spt에 있으면 정상적인 lazy loading 상황 -> 에러 아님!
		if(!spt_find_page(&t->spt, addr)){
			exit(-1);
		}
	}
}

 

 

 

 

관련글 더보기