[Pintos] project3 -WIL(1~2)

DoHee·2023년 5월 16일

SW정글_6기

목록 보기
6/7

Project3 - Virtual Memory 이번 주 목표
1) Memory Management
2) Anonymous Page

1. VM 실행 순서

  • 가상메모리 파트부터 Supplemental Page Table이 추가되면서, process_exec() 함수가 실행되기 전, spt 초기화를 먼저 진행. (supplement_page_table_init)
    - SPT를 디자인 하는 과정에서 비트맵, 리스트, 해시테이블 등 여러 선택지가 있음. 각 선택지에 맞춰 supplement_page_table_init() 함수 내에서 초기화를 진행함.
    - 해시테이블 선택시, hash_init() 함수로 초기화 진행

  • process_exec() 함수 실행 중, process_cleanup() 함수에서 현재 프로세스의 자원을 해제할 때, 해당 프로세스의 SPT 또한 더 이상 유지할 필요가 없으므로 함께 제거하는 과정이 추가됨 (supplemental_page_table_kill)

  • load() 함수에서 페이지 디렉토리 생성 및 활성화, 로드할 세그먼트의 유효성 검사, 스택 설정 등 프로세스의 실행환경을 구축

  • load_segment() 함수가 여기서 중요한데, 여기를 시작으로 SPT에서 페이지를 검색하고 초기화하거나, 페이지가 존재하지 않는 경우 새로운 페이지를 생성하여 SPT에 삽입하는 과정이 진행됨(빨간색 박스 부분)

2. Load_Segment()

기존수정 후
방식eager loadinglazy loading
역할파일의 세그먼트를 페이지 단위로 메모리에 로드가상 주소 공간에 페이지 단위로 메모리를 로드 & 매핑
Accessdata모든 데이터&리소스필수 데이터&리소스
페이지 매핑페이지를 프로세스의 주소 공간에 직접 매핑가상 주소를 물리 주소를 매핑하고, 페이지를 프로세스 주소 공간에 추가
주소 공간 확장주소 공간의 크기가 파일 크기보다 작을 경우, Fail 처리주소 공간의 크기가 파일 크기보다 작을 경우, 추가 페이지 할당하여 주소 공간 확장
  • VM 구현 전, load_segment() 함수는 모든 데이터와 리소스를 메모리에 로드했음. (페이지를 물리메모리에 직접 매핑)
    - 물리프레임으로부터 할당을 받고, 파일을 해당 프레임에 로드한 후에 페이지 테이블에서 가상주소와 물리주소를 매핑하는 방식이었음

  • Project 3에서는 lazy_loading 방식으로 바꿔야 하기 때문에, 가상 페이지를 먼저 할당한 뒤(vm_alloc_page_with_initializer), 가상주소와 물리주소를 매핑하여 페이지르르 프로세스의 주소공간에 추가하는 방식으로 변경해야 함

3. Lazy Loading vs Eager Loading

1) Lazy Loding

  • 필요한 데이터만 로드하기 때문에 초기 로딩시간이 짧고 메모리를 효율적으로 사용할 수 있음.
  • 대규모 데이터, 리소스 최적화 되어있는 경우, 또는 초기 시작시간 개선이 필요한 경우에 주로 사용함

2) Eager Loading

  • 즉시 로딩 방식으로, 파일의 모든 내용을 한 번에 로드하여 메모리에 올리기 때문에, 초기 로딩 시간이 길지만 데이터 접근 속도가 빠름.
  • 주로 작은 데이터나 예측가능한 패턴의 데이터를 다룰 때 사용

[참고]

  • 핀토스에서 기존 load_segment() 함수는 처음 파일을 읽어 올 때 파일 전체를 읽어오는데, 실제 파일 비트 개수와 load_segment 함수로 읽어온 파일의 비트 개수가 다름

  • 그 이유는, ELF 포맷에서는 ELF 헤더처럼 ELF 포맷 상에는 존재하지만 메모리에는 로드되지 않는 부분이 있기 때문임. 단순히 응용프로그램 크기와 load_segment에서 읽은 용량이 같지 않을 수 있음.


3) stack_growth
4) memory mapped files
5) swap out

4. PTE에서 사용하는 bit의 역할

1) PTE_P

  • present bit : 페이지가 물리메모리에 매핑이 되어 있는지 스왑아웃 되어 있는지 가리킴.(1이면 물리메모리에 존재, 0이면 디스크에 존재)
  • present bit가 0인 페이지에 접근하는 과정이 페이지 폴트!
  1. Paging_init : main 함수에서, 메모리 시스템을 초기화 할 때 사용되는 함수.

    • palloc_init 함수가 메모리 크기를 결정하면, paging_init이 결정된 크기만큼 페이지 테이블을 초기화하고 메모리 매핑을 수행.
    • 이 때, PTE_P를 사용하여 페이지 테이블 엔트리가 유효함을 나타냄.
  2. mmu.c 파일 → pml4 관련 모든 함수 : 마찬가지로 pml4 관련 모든 함수들에서 페이지 테이블 엔트리의 유효성을 표현할 때 valid bit를 사용.

  • valid bit는 페이지 테이블에서 가상주소와 물리주소를 매핑하는 모든 과정에서 사용됨.

2) PTE_A

[ PTE_A 사용 함수 : vm_get_frame → vm_evict_frame → vm_get_victim ]

  1. vm_get_frame : 물리메모리에서 페이지를 할당받는 함수. 이 때 모든 물리메모리의 프레임이 사용 중인 경우, 제거할 frame을 정해야 함. (vm_evict_frame 호출)
  2. vm_evict_frame : 삭제할 프레임을 결정한 후, 실제로 페이지를 하드디스크로 스왑 아웃. 여기서 삭제할 프레임을 결정하기 위해 다시 vm_get_victim을 호출
  3. vm_get_victim : 삭제할 프레임을 결정하는 함수!!
    • pml4_is_accessed : 주어진 pml4와 가상주소를 기반으로 해당 가상페이지가 접근(access)되었는지 확인하는 함수. PTE가 존재하고, 해당 엔트리의 accessed bit가 설정되어 있다면(1) true 반환
    • pml4_set_accessed : 해당 가상페이지의 accedssed bit를 설정하거나 해제하는 함수
    • vm_get_victim에서 Accessed Bit를 사용해 페이지 교체 알고리즘을 구현할 수 있음. 우리 팀은 LRU 방식으로 구현함. for문으로 frame_list를 차례로 돌면서 사용(접근)하지 않았던 페이지를 골라냄. pml4_is_accessed로 이미 접근한 기록이 있는 pte는 비트를 0으로 바꾸면서 지나가고, 접근 기록이 없는 pte가 나왔을 때 해당 페이지를 victim(희생양)으로 선택하여 리턴함.

3) PTE_D

[ PTE_D 사용 함수 : do_munmap & file_backed_swap_out ]

  1. do_munmap : 주어진 가상주소 범위의 메모리 매핑을 해제하는 함수.

    • while문을 사용해, PGSIZE씩 addr를 늘려가면서, 각 addr에 대응되는 페이지의 uninit 필드에 저장된 aux를 가져옴(container). (가져온 구조체에는 페이지와 관련된 추가 정보들이 저장되어 있음.)
    • pml4_is_dirty : 만약 페이지에 대응하는 pml4의 엔트리가 존재하고, Dirty bit가 설정된 경우(수정된 이력이 있는 경우),
    • file_write_at : 수정된 내용을 디스크에 업데이트하고,
    • pml4_set_dirty : 더티 비트를 0으로 초기화해 줌.
    • pml4_clear_page : 이 후, 현재 프로세스의 pml4에서 해당 가상주소에 대응하는 PTE를 제거!
  2. file_backed_swap_out : File_backed page를 스왑 아웃하는 함수.

    • 주어진 페이지의 uninit 필드에 저장된 aux(container) 를 가져온 후,
    • pml4_is_dirty 함수로 해당 페이지의 dirty bit를 확인하고,
    • file_write_at 함수로 수정된 페이지의 내용을 파일에 기록함.
    • pml4_set_dirty 함수로 pml4 엔트리의 더티 비트를 0으로 초기화
    • pml4_clear_page 함수로 현재 프로세스의 pml4에서 해당 가상주소에 대응하는 PTE를 제거!

[ 참고 ]

  • Dirty Bit : 페이지 변경 여부 저장하는 비트. 변경될 때마다 해당 비트는 1이 되고, 디스크에 변경 내용을 기록하면 0으로 초기화 됨.

  • do_munmap 과 file_backed_swap_out에서 더티 비트는 비슷한 플로우로 사용됨.

  • 어떤 페이지가 Swap Out 될 때, 해당 페이지가 수정되지 않았다면 스왑 아웃할 필요가 없음. 동일한 내용이 Disk 어딘가에 있기 때문임.
    따라서, 페이지의 수정 여부를 알 수 있다면, 페이지를 교체할 때 스왑 아웃 시간을 절약할 수 있음. (Swap In 만 해주면 되기 때문)
    이 때, 페이지의 수정여부를 알려주는 값이 바로 Dirty Bit임!

5. syscall 관련

1) vm_try_handle_fault : 페이지 폴트 발생 시 스택 포인터

  • 어떤 스택 포인터를 가져오는지 결정해야 함.
  • 유저 가상주소공간에서 페이지 폴트가 발생하는 경우 : 해당 프레임의 rsp멤버를 그대로 사용.

—> 페이지 폴트가 발생하고 모드 전환되면 유저 프로그램의 스택 포인터 값이 인터럽트 프레임에 저장되어 페이지 폴트 핸들러나 시스템콜 핸들러에 인자로 전달되기 때문!

  • 하지만 커널 가상주소공간에서 페이지 폴트가 발생하는 경우는 다름. 페이지 폴트나 시스템콜 핸들러가 호출될 때 struct thread에 현재 유저스택 포인터를 미리 저장해놓아야 함.

2) Syscall_handler 수정

  • 인터럽트 프레임의 rsp 멤보가 커널 스택을 가리키고 있을 때, 우리가 저장해 놓았던 스레드 구조체 안의 rsp를 사용!
  • 시스템 콜 핸들러가 받은 인터럽트 프레임의 rsp 값은 유저 프로세스의 유저 스택 포인터 값임.
/* syscall.c */

void syscall_handler (struct intr_frame *f) {

	#ifdef VM

	thread_current()->rsp_stack = f->rsp;

	#endif

...
}
profile
정글_6기_b반

0개의 댓글