sylee6529 / PintOS-VM

PintOS Virtual memory 구현 (교육용 OS 직접 구현)-C language
Other
2 stars 2 forks source link

김주영 #2

Open Vacayy opened 10 months ago

Vacayy commented 10 months ago
Vacayy commented 10 months ago

Anonymous Page 트러블 슈팅

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

image

spt_insert_page 에서 문제를 발견

image image

다른 분 코드 무지성 참고했는데, 그래서 발생한 문제였음. 반성합니다 ㅎ

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

image

exit(-33) 을 어디다 박아뒀냐면 ..

image

spt에서 있어야 할 페이지를 못찾는 게 문제였음!

vm_alloc_page_with_initializer()

before

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);
}

뭐가 문제??

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을 비트 플래그로 저장합니다.

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

따라서 vm_alloc_page_with_initializer()에서는 다음 식을 활용하거나, 직접 and 연산을 수행합니다.

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

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

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 등 예외적인 주소를 주고 이를 제대로 처리하는지 보는 케이스들이었습니다.

지원님과 서연님께서 슬쩍 팁을 주셔서 두 가지 문제 부분을 찾을 수 있었습니다.

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);
        }
    }
}
Vacayy commented 10 months ago

Stack growth 트러블 모음

pt-write-code2 테스트 케이스

pt-write-code2 테스트는 잘못된 영역(코드 세그먼트)에 read() 시도를 하는 테스트입니다.

이를 확인해보겠습니다.

pt-write-code2 테스트 코드는 다음과 같습니다.

void
test_main (void)
{
  int handle;

  CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
  read (handle, (void *) test_main, 1);
  fail ("survived reading data into code segment");
}

output 파일을 보면 다음과 같이 출력되고 있습니다.

image

이를 해결하기 위해 read() 시스템 콜 내부에 다음과 같은 코드를 추가하였습니다.

int read (int fd, void *buffer, unsigned length) {  
    struct page *page = spt_find_page(&thread_current()->spt, buffer);
    if (page) {
        if (!page->writable) {
            exit(-1);
        }
    ...
}

pt-grow-stk-sc 테스트 케이스

pt-grow-stk-sc 테스트 케이스는 첫 스택접근이 syscall에 의한 것일 때도 스택이 올바르게 확장되는가를 체크합니다.

결론부터 말하자면, read() 시스템 콜에서는 check_address(buffer)를 직접 호출하지 않고, 꼭 필요한 주소 유효성 검사 로직만 일부 추가하는 방식으로 수정하였습니다. 그 결과 테스트케이스를 통과할 수 있었습니다.

image

기존 read() 함수는 바로 check_address로 주소의 유효성을 검사하게끔 설계되어 있습니다.

int read (int fd, void *buffer, unsigned length) {
    check_address(buffer);
    ...
}

check_address() 함수는 Anonymous Page에서 수정한 함수인데, 어떻게 짰었는지 다시 한번 확인해보겠습니다.

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(-2);
    }

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

(exit(-2), exit(-3)은 디버깅을 위해 임시로 바꾼 것이며, 원래 -1을 반환해야 합니다.)

이렇게 둔 상태로 pt-grow-stk-sc.output 파일의 결과를 확인해보면,

image

기존 제 코드에서 stack 영역에 해당하는 페이지는 스택 확장시에 spt에 추가됩니다. 그러나 syscall로 처음 접근했을 때는 스택 확장이 이루어지기 전이므로, spt에 페이지가 존재하지 않아 에러 검출로 빠져나가는 상황이라고 판단했습니다.

제가 생각한 방법은 두 가지였습니다.

  1. check_address() 함수 수정: spt를 검사하는 코드를 지우기
  2. read() 함수 수정: 함수 내부에서 check_address(buffer)를 수행하지 않고, 최소한의 검사 코드 부분을 따로 추가하기

테스트해본 결과 둘 다 pass는 가능했지만, 1번의 경우 다른 시스템 콜에도 영향을 주기 때문에, read() 함수에만 영향을 주는 2번 방법이 잠재적 문제가 적은 방법이라고 판단했습니다. 따라서 2번 방향으로 수정을 진행했습니다.

int read (int fd, void *buffer, unsigned length) {
    // check_address(buffer); 삭제

    // 주소가 user 영역인지, null 값은 아닌지 정도만 유효성 검사를 해준다.
    if (!is_user_vaddr(buffer) || buffer == NULL) {
        exit(-1);
    }

    ...
}

결과적으로, 위의 두 가지 테스트 케이스(pt-write-code2, pt-grow-stk-sc)를 반영한 후의 read() 함수는 다음과 같이 바뀌었습니다.

int read (int fd, void *buffer, unsigned length) {  
    /*  
    [pt-write-code2 테스트]
    - 잘못된 영역(코드 세그먼트)에 read() 시도를 한다. 
    - 메모리 관점에서 보면 read() 시스템콜은 파일의 데이터를 읽어서 메모리의 버퍼에 쓰는 작업을 수행
    - 즉, not writable한 페이지에 write를 시도하고 있다. 
    => 해당 addr 페이지의 writable 여부를 체크해주고, exit(-1)을 반환해야 한다.
    */ 
    struct page *page = spt_find_page(&thread_current()->spt, buffer);
    if (page) {
        if (!page->writable) {
            exit(-1);
        }
    }

    /* 
    [pt-grow-stk-sc 테스트]
    - 첫 스택접근이 syscall에 의한 것일 때도 스택이 올바르게 확장되는가를 체크한다.
    - check_address(buffer) 내부의 spt 검사에서 문제가 발생함. 
    => check_address()를 삭제해주고, 주소가 user 영역인지, null 값은 아닌지 정도만 유효성 검사를 해준다.
    */
    if (!is_user_vaddr(buffer) || buffer == NULL) {
        exit(-1);
    }

    ... 기존 코드 ...
}