[운체] 오늘의 삽질 - 0804

방법이있지·2025년 8월 4일
post-thumbnail

(1) 페이지 할당 요청

  • 커널은 페이지 할당을 요청받으면, 페이지에 대응하는 struct page는 만들어 둔다.
    • 하지만 뮬리 프레임은 아직 할당하지 않는다.
    • 물리 프레임이 없으니, 당연히 페이지의 내용도 로드되지 않는다.
  • 할당 및 로드는 페이지 폴트가 발생할 때 진행한다. -> lazy loading
  • 이때 lazy loading이 이루어지지 않은 페이지를, VM_UNINIT 페이지로 분류한다.

A. load_segment 수정하기

  • 가상 메모리의 세그먼트 로드를 위한 함수.
  • 당연히 페이지 할당을 해야 하므로, 함수 내 요청이 반복적으로 이루어짐.
  • 로드 성공하면 true, 실패하면 false.
// userprog/process.c

static bool load_segment(struct file *file, off_t ofs, uint8_t *upage, uint32_t read_bytes,
                         uint32_t zero_bytes, bool writable) {
    // 생략

    // 페이지 단위로 vm_alloc_page_with_initializer 호출.
    while (read_bytes > 0 || zero_bytes > 0) {
        // 현재 페이지에서, 파일에서 읽을 바이트 수 / 0으로 채울 바이트 수
        size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
        size_t page_zero_bytes = PGSIZE - page_read_bytes;

        // [구현 A] lazy_load_segment에서 파일 로딩할 때 필요한 정보를, 구조체로 묶어 aux로 보내기
        void *aux = NULL;
        if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux))
            return false;

        /* Advance. */
        read_bytes -= page_read_bytes;
        zero_bytes -= page_zero_bytes;
        upage += PGSIZE;
        // [구현 A]
    }
    return true;
}
  • 가상주소 upagefileofs부터 시작하는 세그먼트를 로드.

    • read_bytes: upage부터 read_bytes만큼 fileofs에서 로드한다. (실제 파일 읽기)
    • zero_bytes: upage + read_bytes부터 zero_bytes`만큼 0으로 채운다. (zero padding)
  • writabletrue면 읽기/쓰기 가능, false면 읽기 전용

  • load_segment는 페이지 단위로, vm_alloc_page_with_initializer를 반복 호출

    • VM_ANON: anonymous page로 설정
    • upage: 가상주소 upage에 해당하는 페이지를 초기화
    • writable: 초기화할 페이지의 쓰기 가능여부 설정
    • lazy_load_segment, aux: 이후 page fault가 발생하면 lazy_load_segment에서 파일 로드 시도. 이때 필요한 정보를 aux로 전달해야 함!!
  • [구현 A] lazy_load_segment에 파일 관련 정보 보내기

    • 일단 파일을 읽어야 함 -> struct file *file 필요
    • 파일 내 읽을 위치를 알아야 함 -> off_t ofs 필요
      • 반복문 실행할 때마다 ofs += PGSIZE로 갱신해야 할듯
    • 현재 페이지에서 얼마만큼 읽고, 얼마만큼 zero padding할지 알아야 함
      • -> page_read_bytes, page_zero_bytes 필요.
    • 이러한 데이터를 구조체로 묶어 전달하자. (이하 파일 정보 구조체)

B. vm_alloc_page_with_initializer 수정하기

  • 페이지 할당 요청을 받았을 때, 호출되는 함수.
  • 할당 성공하면 true, 실패하면 false.
// vm/vm.c
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
                                    vm_initializer *init, void *aux) {

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

    // 현재 SPT에 존재하지 않는 페이지일 경우
    if (spt_find_page(spt, upage) == NULL) {
        // [구현 B-1] struct page 할당 -> malloc OR calloc
        // [구현 B-2] uninit_new에 알맞은 매개변수 보내, struct page 필드 초기화
        // !!이때 vm_type에 따라 알맞은 initializer를 전달해야 함에 유의!!
        // [구현 B-3] SPT에 struct page 삽입 -> spt_insert_page
    }
err:
    return false;
}
  • (1) struct page 생성 -> malloc

  • (2) uninit_new 호출해, 생성한 struct page의 필드 초기화

    • void uninit_new(struct page *page, void *va, vm_initializer *init, enum vm_type type, void *aux, bool (*initializer)(struct page *, enum vm_type, void *))
      • page: (1)에서 생성한 struct page
      • va: 매개변수 upage, 즉 가상 주소를 전달
      • init, type, aux: 기존 매개변수 init, type, aux 그대로 전달
      • initializer: VM_TYPE(vm_type) == VM_ANON이면 anon_initializer, VM_TYPE(vm_type) == VM_FILE이면 file_backed_initializer. VM_TYPE 함수를 쓰는 이유는, type 매개변수를 VM_ANON | VM_MARKER_0 식으로 설정할 때도 있기 때문. 이때 VM_ANON만 끄집어내는 역할.
      • uninit_new는 수정하지 않아도 됨!!!! 다른 필드가 필요할 시, uninit_new 밖에서 초기화.
  • cf. 이후 uninit_new에선, page의 필드는 아래와 같이 초기화됨

    • (load_segment에서 호출했을 때 기준)
// vm/uninit.c 내 uninit_new 함수
// uninit_new는 struct page의 field를 초기화함.
 *page = (struct page){.operations = &uninit_ops,
    .va = va,           // upage
    .frame = NULL, // 당연히 지금은 아직 물리 프레임을 할당 안 했으니까?
    .uninit = (struct uninit_page){
        .init = init,   // lazy_load_segment
        .type = type,   // VM_ANON
        .aux = aux,     // 파일 정보 구조체
        .page_initializer = initializer,    // anon_initializer
    }
};
  • (3) uninit_new에서 초기화하지 못한 필드가 있는 경우, 초기화

    • struct pagewritable 필드를 만들어 두고 사용해야 하려나 싶음. 얘만 버려진 상황이라.
  • (4) SPT에 struct page 삽입 -> spt_insert_page.

  • 도중에 실패 시 goto err

(2) 페이지 폴트 이후 실제 할당

  • 이제서야 물리 메모리에 프레임이 할당된다.
  • 이후 페이지에 파일이 로딩될 수 있다.

C. page_fault

  • 페이지 폴트가 발생할 때마다 호출되는 폴트 핸들러. 반환값 없음.
// userprog/exception.c
static void page_fault(struct intr_frame *f) {


    /* fault가 발생한 가상주소 */

    fault_addr = (void *) rcr2();

    /* Turn interrupts back on (they were only off so that we could
       be assured of reading CR2 before it changed). */
    intr_enable();
    if ((f->error_code & PF_U) != 0) {
        // [구현 C] 강제 종료 시키면 절 대 안 됨
        exit(-1);
    }
    /* Determine cause. */
    not_present = (f->error_code & PF_P) == 0;
    write = (f->error_code & PF_W) != 0;
    user = (f->error_code & PF_U) != 0;

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

    /* Count page faults. */
    page_fault_cnt++;

    /* If the fault is true fault, show info and exit. */
    printf("Page fault at %p: %s error %s page in %s context.\n", fault_addr,
           not_present ? "not present" : "rights violation", write ? "writing" : "reading",
           user ? "user" : "kernel");
    kill(f);
}
  • 바로 exit하면 안 됨!!! Project 2에서 설정했던 exit(-1) 없애주기!!!
  • vm_try_handle_fault를 호출해, 해결 시도. 매개변수로 다음 값 전달.
    • fault_addr: page fault가 발생한 가상주소
    • not_present: 메모리에 존재하지 않는 페이지 -> 1. 메모리에 존재하는 페이지 -> 0. (spt 아니라 ㄹㅇ 물리메모리 적재 여부.)
    • write: 쓰기 -> 1. 읽기. -> 0.
    • user: 사용자 모드 -> 1, 커널 모드 -> 0.
  • vm_try_handle_fault 성공 시 true. 실패 시 false. true일시 바로 반환.
  • 이 값들을 어케 써야 하는지...는 구현 D에서 다룸.

D. vm_try_handle_fault 수정

  • page fault handling에 성공하면 true, 실패하면 false 반환.
  • 경우의 수는 2가지
  • 정상적인 page fault -> 이후 lazy loading해주면 됨.
  • 비정상적인 page fault -> false 반환!
    • (A) 사용자모드일 때 커널 영역의 주소에 접근시도
    • (B) 읽기 전용 영역에 쓰기시도
    • (C) 사용자 영역의 invalid 주소에 접근시도
// vm/vm.c
// 페이지 폴트 핸들러
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;
    // [구현 D] 비정상적인 page fault이면, 바로 false 반환하기

    return vm_do_claim_page(page);
}
  • user == true이며 is_kernel_vaddr(addr) == true 인 경우 (A) - 사용자모드일 때 커널영역의 주소에 접근시도

  • not_present == false, write == true인 경우 (B) - 읽기 전용 영역에 쓰기 시도

  • SPT에서 addr에 대응되는 struct page 검색 -> spt_find_page

    • 없는 경우 NULL 반환. -> (C) 사용자 영역의 invalid 주소에 접근 시도
    • 있는 경우 해당 페이지 반환. page에 저장.
  • 위 세 경우에 해당하지 않는 경우, 정상적인 page fault.

    • 이후 lazy loading은 vm_do_claim_page(page)에서 진행됨.
  • 위 세 경우에 해당되는 경우, process terminate

    • return false 해주면, page_fault에서 알아서 kill해줄 거임.

E. vm_do_claim_page 수정

  • 앞서 구현했던 함수. 성공하면 true, 실패하면 false 반환.
  • 기능을 되짚어보자면...
// vm/vm.c
struct frame *frame = vm_get_frame();
/* Set links */
frame->page = page;
page->frame = frame;
  • (1) vm_get_frame으로 프레임 할당, struct frame 생성
    • 바로 여기서 palloc 메모리 할당이 이루어짐. 꽤나 뒤늦게...!
pml4_set_page(thread_current()->pml4, page->va, frame->kva, 1)
  • (2) pml4_set_page로 페이지와 프레임 매핑
    • [구현 E] -> 여기서 수정을 하면 될 것 같음.
    • 현재는 writable 페이지 여부가 기본값 1로 고정되어 있음.
    • [구현 B-3]에서 struct frame에 저장해 둔 writable 사용해, 읽기/쓰기 여부 저장해 두면 될듯...
return swap_in(page, frame->kva);
  • (3) swap_in(page, frame->kva)???
    • swap_in은 사실 uninit_initialise를 호출.
    • 이걸 이해하려면... 함수 포인터에 대한 이해가 필요함.

함수 포인터

  • 앞서 [구현 B]에서, uninit_new에서 struct pageoperations 필드를 &uninit_ops로 설정함
// vm/uninit.c
static const struct page_operations uninit_ops = {
    .swap_in = uninit_initialize,
    .swap_out = NULL,
    .destroy = uninit_destroy,
    .type = VM_UNINIT,
};
  • 여기서 swap_inuninit_initialize.

uninit_initialize

  • 앞서 uninit_new에서 struct page에 설정한 필드 값들을 참고하여, 페이지 초기화를 진행하는 함수.
  • 성공하면 true, 실패하면 false 반환.
  • 일단 여기선 you may need to fix this function이라곤 하는데... 아직까지는 크게 고칠 건 없어보임.
// vm/uninit.c
// struct page의 필드값을 참고하여, 페이지 초기화 함수들을 호출.
static bool uninit_initialize(struct page *page, void *kva) {
    struct uninit_page *uninit = &page->uninit;

    // 주소값을 미리 왜 저장해 주냐면, uninit->page_initializer에서 값이 사라질 수 있기 때문임.
    vm_initializer *init = uninit->init;
    void *aux = uninit->aux;

    /* TODO: You may need to fix this function. */
    // 라고는 하는데, 잘은 모르겠음.
    return uninit->page_initializer(page, uninit->type, kva) && (init ? init(page, aux) : true);
}
  • struct uninit_page *uninit
    • 아래 필드들로 구성된 구조체.
// uninit_new에서 이렇게 바꿨었죠
.uninit = (struct uninit_page){
        .init = init,   // lazy_load_segment
        .type = type,   // VM_ANON
        .aux = aux,     // 파일 정보 구조체
        .page_initializer = initializer,    // anon_initializer
    }
  • 여기서 필드 initvm_initializer *init에 저장
    • load_segment에서 페이지 할당을 한 경우, lazy_load_segment
  • 그리고 필드 auxvoid *aux에 저장
    • load_segment에서 페이지 할당을 한 경우, 앞서 본 파일 구조체
  • 이후 두 함수 호출. load_segment에서 페이지 할당한 경우 기준으로
  • uninit->page_initializeranon_initializer가 담김.
    • 매개변수로는 page(가상페이지 주소), uninit->type(VM_ANON), kva(물리프레임 주소)를 보냄.
  • init에는 lazy_load_segment가 담김.
    • 매개변수로는 page(가상페이지 주소)와 aux(파일 구조체)를 보냄.

F. anon_initializer

  • uninitialized page -> anonymous page로 전직(...)을 시켜줌.
  • 성공하면 true, 실패하면 false 반환.
// uninitialized page -> anonymous page
bool anon_initializer(struct page *page, enum vm_type type, void *kva) {
    // operations 필드를, anonymous page 용도의 operations로 변경.
    page->operations = &anon_ops;

    struct anon_page *anon_page = &page->anon;

    // [구현 F] 성공/실패 여부에 따라 반환값 저장
}
  • 아직은 struct anon_page엔 아무 필드도 없음. 여기에 어떤 필드를 넣어야 하는지는 솔직히 모르겠음.
  • 근데 나중에 필드를 추가할 일이 있으면, 여기서 초기화를 해 줘야 할 듯함.
  • 일단 성공했다는 걸 전제로 return true만 있어도 되지 않을까? 나중에 고쳐보자

G. lazy_load_segment

  • 페이지에 파일을 로드하는 함수.
  • 드디어 load_segment에서 만든 파일 정보 구조체를 사용하게 됨! 우와!
  • 성공하면 true, 실패하면 false 반환.
static bool lazy_load_segment(struct page *page, void *aux) {
    // [구현 G] aux 내 파일 정보를 사용하여, page에 해당하는 주소에 로딩할 것.
}
  • 가상 페이지 pageaux의 파일 정보를 이용하여 로딩.
  • 그럴려면 가상 페이지에 대응하는 물리 프레임 주소가 필요할 것 같은데, struct pagestruct framekva 멤버로 주소를 구할 수 있을 듯함.
  • 일단 카이스트 공식 PPT를 보니까, file_read_at 사용을 추천하고 있음.
// filesys/file.c
off_t file_read_at(struct file *file, void *buffer, off_t size, off_t file_ofs)
  • filefile_ofs 위치부터 size만큼 buffer로 읽는다.

    • 파일 주소, 오프셋, 읽을 크기 (page_read_bytes) 모두 aux에 저장되어 있을 것. 활용하면 됨
    • buffer는 앞서 구한 프레임 주소로 하면 될듯.
    • file_read_at은 읽은 바이트 수를 반환. page_read_bytes와 동일한지 아닌지로 성공, 실패 여부 확인 가능
  • 이후 zero padding이 필요한 경우, 뒷부분은 0으로 채워야 함.

    • 프레임주소 + page_read_bytes부터 page_zero_bytes만큼 0으로 채우면 됨. memset 사용하도록 하자.

(3) 스택 할당방식 변경

  • 스택 영역의 메모리 할당 후, 성공 시 true 실패 시 false 반환 함수.
  • setup_stack은 원래는 palloc_get_page를 사용해서 바로 페이지 영역을 할당받음.
  • 하지만 이제 가상 메모리를 사용하니 할당 방식을 변경해야 함
  • 대신 (깃북 왈) lazy loading 안하고, 바로 SPT 테이블에 삽입한 뒤 곧바로 page claim해도 괜찮다고 함. (내가 해석한 게 맞다면)

H. setup_stack 변경하기

// userprog/process.c
// VM 미사용 시 setup_stack
static bool setup_stack(struct intr_frame *if_) {
    uint8_t *kpage;
    bool success = false;

    kpage = palloc_get_page(PAL_USER | PAL_ZERO);
    if (kpage != NULL) {
        success = install_page(((uint8_t *)USER_STACK) - PGSIZE, kpage, true);
        if (success)
            if_->rsp = USER_STACK;
        else
            palloc_free_page(kpage);
    }
    return success;
}

// VM 사용 시 setup_stack

static bool setup_stack(struct intr_frame *if_) {
    bool success = false;
    void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);

    // [구현 H-1] stack_bottom에 스택 할당 요청
    // [구현 H-2] 이후 바로 page claim
    // [구현 H-3] 스택포인터 위치 변경
    /* TODO: Map the stack on stack_bottom and claim the page immediately.
     * TODO: If success, set the rsp accordingly.
     * TODO: You should mark the page is stack. */
    /* TODO: Your code goes here */

    return success;
}
  • (1) vm_alloc_page_with_initializer로 할당 요청 보내기
    • 스택임을 사용하는데 VM_MARKER_0을 사용하라고 함.
    • (매개변수... 아마도?) type -> VM_ANON | VM_MARKER_0??? upage -> stack_bottom??? writable -> true, init -> NULL(파일 로드할거 아니니까), aux -> NULL???
  • (2) 바로 page를 claim
    • vm_claim_page 사용하면 될듯?
  • (3) if_->rspUSER_STACK으로 설정
  • 성공/실패여부 반환

여기까지 하면

  • Project 2에서 한 테스트 케이스 중 fork 계열 빼고 통과한다고 함.
  • 과연 그럴까? 이제 열심히 구현해보자고용용가리취킨
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글