크래프톤 정글 일지

[PintOS] project 3 - 가상 메모리 개요 virtual memory

나한나한나한나 2024. 6. 1. 16:50

 

4.1 배경 정보

4.1.1 소스 파일

이 프로젝트에서는 vm 디렉토리에서 작업합니다. 이 디렉토리에는 Makefile 파일만 존재하며, 프로젝트 2의 userprog 디렉토리와의 차이점은 -DVM 플래그가 활성화되었다는 점입니다. 따라서 작성하는 코드는 새로운 파일이나 앞선 프로젝트에서 소개된 파일에 추가됩니다. 처음 접하는 몇몇 파일은 다음과 같습니다.

  • devices/block.h & devices/block.c: 블록 단위로 블록 장치에 대한 읽기/쓰기 접근을 제공합니다. 이 인터페이스를 사용하여 스왑 파티션에 접근합니다.

 

4.1.2 메모리 용어 정의

가상 메모리 시스템을 논의할 때 혼란을 방지하기 위해 정확한 용어 정의가 필요합니다. 다음은 메모리와 저장 장치 관련 용어 정의이며, 일부는 프로젝트 2 (3.1.4 절, 25 페이지 참조)에서 이미 다루었던 내용이지만, 대부분은 새로운 내용입니다.

 

4.1.2.1 페이지

페이지 (가상 페이지라고도 함)는 가상 메모리의 연속적인 영역이며 크기는 4,096 바이트 (페이지 크기)입니다. 페이지는 페이지 정렬되어 있어야 합니다. 즉, 가상 주소의 시작 위치가 페이지 크기로 나누어 떨어져야 합니다.

따라서 32비트 가상 주소는 다음과 같이 20비트 페이지 번호와 12비트 페이지 오프셋 (또는 단순히 오프셋)으로 나눌 수 있습니다.

각 프로세스는 독립적인 집합의 사용자(가상) 페이지를 가지고 있으며, 이 페이지들은 일반적으로 0xc0000000 (3GB)인 가상 주소 PHYS_BASE보다 낮은 페이지들입니다. 반면 커널(가상) 페이지 집합은 글로벌이며, 어떤 스레드 또는 프로세스가 활성화되어 있 regardless 관계없이 동일하게 유지됩니다. 커널은 사용자 페이지와 커널 페이지 모두에 액세스할 수 있지만 사용자 프로세스는 자신의 사용자 페이지만에 액세스할 수 있습니다.

 

4.1.2.2 프레임

프레임 (물리적 프레임 또는 페이지 프레임이라고도 함)은 물리 메모리의 연속적인 영역입니다. 페이지와 마찬가지로 프레임도 페이지 크기여야 하고 페이지 정렬되어 있어야 합니다. 따라서 32비트 물리 주소는 다음과 같이 20비트 프레임 번호와 12비트 프레임 오프셋 (또는 단순히 오프셋)으로 나눌 수 있습니다.

80x86은 물리 주소에 직접 메모리에 액세스하는 방법을 제공하지 않습니다. Pintos는 커널 가상 메모리를 물리 메모리에 직접 매핑하여 이를 해결합니다. 즉, 첫 번째 커널 가상 메모리 페이지는 첫 번째 물리 메모리 프레임에 매핑되고, 두 번째 페이지는 두 번째 프레임에 매핑됩니다. 따라서 프레임은 커널 가상 메모리를 통해 액세스할 수 있습니다. Pintos는 물리 주소와 커널 가상 주소 간의 변환 기능을 제공합니다. 

 

4.1.2.3 페이지 테이블

핀토스에서 페이지 테이블은 CPU가 가상 주소를 물리 주소(즉, 페이지에서 프레임으로) 변환하는 데 사용하는 데이터 구조입니다. 페이지 테이블의 형식은 80x86 아키텍처에 의해 결정됩니다. 핀토스는 pagedir.c 파일 (부록 A.7 [페이지 테이블], 페이지 79 참조)에서 페이지 테이블 관리 코드를 제공합니다.

아래 그림은 페이지와 프레임 간의 관계를 나타냅니다. 왼쪽의 가상 주소는 페이지 번호와 오프셋으로 구성됩니다. 페이지 테이블은 페이지 번호를 프레임 번호로 변환하며, 이 번호는 수정되지 않은 오프셋과 함께 결합되어 오른쪽의 물리 주소를 얻게 됩니다.

이 그림에서:

  • 가상 주소는 31비트이며 페이지 번호(20비트)와 오프셋(12비트)으로 나누어집니다.
  • 페이지 테이블은 페이지 번호를 프레임 번호로 변환합니다.
  • 프레임 번호는 물리 주소의 일부가 되며, 오프셋은 그대로 유지됩니다.
  • 변환된 물리 주소는 원래 가상 주소의 동일한 오프셋을 가집니다.

 

4.1.2.4 스와프 슬롯

스와프 슬롯은 스와프 파티션의 디스크 공간에서 연속적인 페이지 크기 영역입니다. 하드웨어 제약 사항으로 인해 슬롯 배치에 대한 제한은 페이지 및 프레임 배치에 비해 더 느슨하지만, 성능 저하 등의 단점이 없기 때문에 스와프 슬롯은 페이지 정렬되어 있어야 합니다.


4.1.3 자원 관리 개요

가상 메모리 시스템을 구현하기 위해 다음과 같은 데이터 구조를 설계해야 합니다.

  • Supplemental page table: 하드웨어 페이지 테이블을 보완하여 페이지 실패 처리를 지원합니다. (4.1.4 절, 42 페이지 참조)
  • Frame table: 효율적인 메모리 관리를 위해 사용 중인 프레임 정보를 추적합니다. (4.1.5 절, 42 페이지 참조)
  • Swap table: 스와프 슬롯의 사용 여부를 추적합니다. (4.1.6 절, 43 페이지 참조)
  • Table of file mappings: 프로세스가 파일을 가상 메모리에 매핑할 수 있도록 파일과 페이지 간의 매핑 정보를 저장합니다.

반드시 완전히 별개인 네 가지의 데이터 구조를 구현할 필요는 없습니다. 서로 관련된 리소스를 하나의 데이터 구조로 통합하는 것이 관리 측면에서 더 효율적일 수도 있습니다.

각 데이터 구조를 설계할 때는 다음과 같은 사항을 고려해야 합니다.

  • 데이터 구조의 요소가 포함해야 하는 정보: 각 데이터 구조에 필요한 정보를 정의해야 합니다.
  • 데이터 구조의 범위: 데이터 구조의 유효 범위를 프로세스별 (local) 또는 전체 시스템 (global)으로 설정해야 합니다.
  • 필요한 인스턴스 수: 설정된 범위 내에서 얼마나 많은 데이터 구조 인스턴스가 필요한지 결정해야 합니다.

가상 메모리 시스템 설계를 간소화하기 위해 이러한 데이터 구조는 비-페이징 메모리에 저장하는 것이 좋습니다. 왜냐하면 비-페이징 메모리에 저장된 데이터 구조 간의 포인터는 항상 유효한 메모리 주소를 가리키도록 보장할 수 있기 때문입니다.

데이터 구조를 선택할 때는 다음과 같은 몇 가지 옵션을 고려할 수 있습니다.

  • 배열: 가장 간단한 방법이지만, 사용하지 않는 공간이 많은 경우 메모리를 낭비합니다.
  • 리스트: 간단하지만 특정 위치를 찾기 위해 전체 리스트를 순회해야 하므로 시간 복잡도가 높아질 수 있습니다.
  • 비트맵: Pintos 커널에는 lib/kernel/bitmap.clib/kernel/bitmap.h 파일에 비트맵 데이터 구조가 구현되어 있습니다. 비트맵은 각 비트가 0 또는 1의 값을 가질 수 있는 배열입니다. 일반적으로 비트맵은 여러 동일한 리소스의 사용 여부를 추적하는 데 사용됩니다. 예를 들어, 비트맵의 n번째 비트가 1이면 n번째 리소스가 사용 중이라는 것을 의미합니다. Pintos의 기본 비트맵은 크기가 고정되어 있지만, 필요에 따라 크기를 조정할 수 있도록 수정할 수도 있습니다.
  • 해시 테이블: Pintos는 해시 테이블 데이터 구조도 지원합니다. (부록 A.8 [해시 테이블], 84 페이지 참조). 해시 테이블은 다양한 크기의 테이블에서 데이터 삽입과 삭제를 효율적으로 지원합니다.

더 복잡한 데이터 구조를 사용하면 성능 향상과 같은 이점이 있을 수도 있지만, 코드 구현의 복잡성을 높일 수도 있으므로 균형 잡힌 설계가 필요합니다. 따라서 이 프로젝트에서는 밸런스 트리와 같은 고급 데이터 구조를 구현하지 않는 것이 좋습니다.

 

4.1.4 보조 페이지 테이블 관리

보조 페이지 테이블은 각 페이지에 대한 추가 데이터를 페이지 테이블에 보충합니다. 이는 페이지 테이블의 형식에 의해 부과된 제한 때문에 필요합니다. 이러한 데이터 구조는 종종 "페이지 테이블"이라고도 불리며, 혼동을 줄이기 위해 "보조"라는 단어를 추가합니다. 보조 페이지 테이블은 적어도 두 가지 목적으로 사용됩니다. 가장 중요한 것은 페이지 폴트가 발생할 때, 커널이 보조 페이지 테이블에서 폴트가 발생한 가상 페이지를 찾아 어떤 데이터가 있어야 하는지 찾아냅니다. 두 번째로, 프로세스가 종료될 때 커널은 어떤 자원을 해제해야 할지 결정하기 위해 보조 페이지 테이블을 참조합니다. 보조 페이지 테이블은 원하는 대로 구성할 수 있습니다. 그 구성에는 기본적으로 두 가지 접근 방식이 있습니다: 세그먼트에 따른 것이나 페이지에 따른 것입니다. 선택적으로, 보조 페이지 테이블의 구성원을 추적하는 인덱스로 페이지 테이블 자체를 사용할 수 있습니다. 이를 위해 'pagedir.c'에서 Pintos 페이지 테이블 구현을 수정해야 합니다. 이 접근 방식은 고급 학생들만을 위해 권장됩니다. 자세한 내용은 섹션 A.7.4.2 [페이지 테이블 항목 형식], 페이지 82를 참조하십시오. 보조 페이지 테이블의 가장 중요한 사용자는 페이지 폴트 핸들러입니다. 프로젝트 2에서 페이지 폴트는 항상 커널 또는 사용자 프로그램의 버그를 나타냈습니다. 그러나 프로젝트 3에서는 이것이 더 이상 사실이 아닙니다. 이제 페이지 폴트는 페이지가 파일이나 스왑에서 가져와져야 한다는 것을 나타낼 수 있습니다. 이러한 경우를 처리하기 위해 더 정교한 페이지 폴트 핸들러를 구현해야 합니다. 'userprog/exception.c'의 page_fault()를 수정하여 구현해야 하는 페이지 폴트 핸들러는 대략 다음과 같은 작업을 수행해야 합니다:

  1. 보조 페이지 테이블에서 폴트가 발생한 페이지를 찾습니다. 메모리 참조가 유효하다면, 보조 페이지 테이블 항목을 사용하여 페이지에 들어가는 데이터를 찾습니다. 이 데이터는 파일 시스템에 있을 수도 있고, 스왑 슬롯에 있을 수도 있으며, 단순히 모든 비트가 0인 페이지일 수도 있습니다. 공유를 구현한다면, 페이지의 데이터는 이미 페이지 프레임에 있을 수도 있지만 페이지 테이블에는 없을 수 있습니다.
    • 보조 페이지 테이블이 사용자 프로세스가 접근하려는 주소에 데이터가 없어야 한다고 나타내거나, 페이지가 커널 가상 메모리 내에 있거나, 접근이 읽기 전용 페이지에 쓰기를 시도하는 경우, 그 접근은 유효하지 않습니다. 유효하지 않은 접근은 프로세스를 종료시키고 그 모든 자원을 해제합니다.
  2. 페이지를 저장할 프레임을 얻습니다. 자세한 내용은 섹션 4.1.5 [프레임 테이블 관리], 페이지 42를 참조하십시오.
    • 공유를 구현한다면, 필요한 데이터는 이미 프레임에 있을 수 있으며, 이 경우 그 프레임을 찾을 수 있어야 합니다.
  3. 데이터를 프레임에 가져옵니다. 이는 파일 시스템이나 스왑에서 읽어오거나, 0으로 만드는 등의 방법으로 이루어집니다.
    • 공유를 구현한다면, 필요한 페이지는 이미 프레임에 있을 수 있으며, 이 경우 이 단계에서는 아무런 조치도 필요하지 않습니다.
  4. 폴트가 발생한 가상 주소에 대한 페이지 테이블 항목을 물리 페이지를 가리키도록 합니다. 이를 위해 'userprog/pagedir.c'의 함수를 사용할 수 있습니다.

 

4.1.5 프레임 테이블 관리

프레임 테이블은 사용자 페이지를 포함하는 각 프레임에 대한 한 개의 항목을 포함합니다. 프레임 테이블의 각 항목은 현재 그것을 차지하고 있는 페이지(있는 경우)에 대한 포인터와 선택한 다른 데이터를 포함합니다. 프레임 테이블은 프레임이 무료일 때 추방할 페이지를 선택함으로써 Pintos가 추방 정책을 효율적으로 구현하게 합니다. 사용자 페이지에 사용되는 프레임은 palloc_get_page(PAL_USER)를 호출하여 "사용자 풀"에서 얻어야 합니다. "커널 풀"에서 할당하는 것을 피하기 위해 PAL_USER를 사용해야 합니다. 이는 예상치 못하게 일부 테스트 케이스가 실패하는 것을 방지할 수 있습니다. 프레임 테이블에서 가장 중요한 작업은 사용하지 않는 프레임을 얻는 것입니다. 프레임이 무료일 때 이것은 쉽습니다. 무료 프레임이 없을 때는 어떤 페이지를 그 프레임에서 추방함으로써 프레임을 무료로 만들어야 합니다. 스왑 슬롯을 할당하지 않고 프레임을 추방할 수 없지만 스왑이 가득 차 있으면 커널을 패닉시켜야 합니다. 추방 과정은 대략 다음 단계를 포함합니다:

  1. 페이지 교체 알고리즘을 사용하여 추방할 프레임을 선택합니다. 아래에 설명된 페이지 테이블의 "접근" 및 "더티" 비트가 유용하게 사용됩니다.
  2. 그것을 참조하는 모든 페이지 테이블에서 프레임에 대한 참조를 제거합니다. 공유를 구현하지 않았다면, 주어진 시간에 프레임을 참조하는 단일 페이지만 있어야 합니다.
  3. 필요한 경우, 페이지를 파일 시스템 또는 스왑에 쓰십시오. 그러면 추방된 프레임은 다른 페이지를 저장하는 데 사용될 수 있습니다.

4.1.5.1 접근 및 더티 비트

80x86 하드웨어는 페이지 교체 알고리즘을 구현하는 데 도움이 되는 페이지 테이블 항목(PTE)의 한 쌍의 비트를 통해 일부 지원을 제공합니다. 페이지에 읽기 또는 쓰기를 수행할 때마다 CPU는 페이지의 PTE에서 접근 비트를 1로 설정하고, 쓰기를 수행할 때마다 CPU는 더티 비트를 1로 설정합니다. CPU는 이러한 비트를 0으로 재설정하지 않지만, OS는 그렇게 할 수 있습니다.

4.1.6 스왑 테이블 관리

스왑 테이블은 사용 중인 스왑 슬롯과 무료 스왑 슬롯을 추적합니다. 이는 페이지를 그 프레임에서 스왑 파티션으로 추방할 때 사용하지 않는 스왑 슬롯을 선택하는 데 도움이 됩니다. 페이지가 프레임으로 다시 읽혀지거나 스왑된 페이지를 가진 프로세스가 종료될 때 스왑 슬롯을 해제할 수 있어야 합니다.

4.1.7 메모리 매핑 파일 관리

파일 시스템은 가장 일반적으로 읽기 및 쓰기 시스템 호출로 접근됩니다. 두 번째 인터페이스는 mmap 시스템 호출을 사용하여 파일을 가상 페이지에 "매핑"하는 것입니다. 그런 다음 프로그램은 파일 데이터에 직접 메모리 명령을 사용할 수 있습니다. 제출물은 메모리 매핑 파일에 의해 사용되는 메모리를 추적할 수 있어야 합니다. 이는 매핑된 영역에서 페이지 폴트를 적절하게 처리하고 매핑된 파일이 프로세스 내의 다른 세그먼트와 겹치지 않도록 보장하기 위해 필요합니다.

 

4.2 구현 순서 제안

다음과 같은 초기 구현 순서를 제안합니다:

  1. 프레임 테이블 (섹션 4.1.5 [프레임 테이블 관리], 42페이지 참고). 'process.c' 파일을 수정하여 프레임 테이블 할당기를 사용하세요. 아직 스와핑을 구현하지 마세요. 프레임이 부족할 경우, 할당기를 실패시키거나 커널을 패닉 상태로 만드세요. 이 단계 이후에도 커널은 프로젝트 2의 모든 테스트 케이스를 통과해야 합니다.
  2. 보조 페이지 테이블 및 페이지 폴트 핸들러 (섹션 4.1.4 [보조 페이지 테이블 관리], 42페이지 참고). 'process.c' 파일을 수정하여 실행 파일을 로드하고 스택을 설정할 때 필요한 정보를 보조 페이지 테이블에 기록하세요. 페이지 폴트 핸들러에서 코드와 데이터 세그먼트 로드를 구현하세요. 현재는 유효한 접근만을 고려하세요. 이 단계 이후, 커널은 프로젝트 2의 모든 기능 테스트 케이스를 통과해야 하지만, 일부 견고성 테스트만 통과할 것입니다. 여기서부터 스택 증가, 매핑된 파일, 프로세스 종료 시 페이지 회수 등을 병행하여 구현할 수 있습니다.

다음 단계는 페이지 추방을 구현하는 것입니다 (섹션 4.1.5 [프레임 테이블 관리], 42페이지 참고). 처음에는 추방할 페이지를 무작위로 선택할 수 있습니다. 이 시점에서는 접근 및 더티 비트 관리와 사용자 및 커널 페이지의 별칭 문제를 고려해야 합니다. 또한 동기화 문제도 고려해야 합니다: 만약 프로세스 A가 페이지 폴트를 발생시켰는데, 해당 프레임을 프로세스 B가 추방 중이라면 어떻게 처리할 것인지. 마지막으로, 클록 알고리즘과 같은 페이지 추방 전략을 구현하세요.

 

4.3 요구사항

이 과제는 개방형 설계 문제입니다. 어떻게 할 것인지에 대해 가능한 한 적게 언급할 것입니다. 대신, 운영 체제가 지원해야 하는 기능에 중점을 둘 것입니다. 페이지 폴트를 처리하는 방법, 스왑 파티션을 조직하는 방법, 페이징을 구현하는 방법 등을 스스로 설계해야 합니다.

4.3.1 설계 문서

프로젝트를 제출하기 전에, 프로젝트 3 설계 문서 템플릿을 소스 트리의 'pintos/src/vm/DESIGNDOC'에 복사하고 작성해야 합니다. 작업을 시작하기 전에 설계 문서 템플릿을 읽는 것을 권장합니다. 부록 D [프로젝트 문서화], 99페이지 참고, 가상의 프로젝트에 대한 샘플 설계 문서를 참조하세요.

4.3.2 페이징

실행 파일에서 로드된 세그먼트에 대해 페이징을 구현하세요. 이러한 페이지는 커널이 페이지 폴트를 가로챌 때만 지연 로드되어야 합니다. 추방 시, 로드 후 수정된 페이지는 스왑에 기록해야 합니다. 수정되지 않은 페이지, 특히 읽기 전용 페이지는 스왑에 기록되지 않아야 합니다. 이러한 페이지는 언제든지 실행 파일에서 다시 읽을 수 있습니다.

LRU를 근사하는 글로벌 페이지 교체 알고리즘을 구현하세요. 알고리즘은 최소한 "두 번째 기회" 또는 "클록" 알고리즘의 단순 변형만큼은 성능을 발휘해야 합니다.

병렬성을 허용하는 설계를 해야 합니다. 하나의 페이지 폴트가 I/O를 필요로 하는 경우, 그동안 폴트를 발생시키지 않는 프로세스는 계속 실행되어야 하고, I/O가 필요하지 않은 다른 페이지 폴트는 완료될 수 있어야 합니다. 이는 일부 동기화 노력을 요구합니다.

프로그램 로더의 핵심인 load_segment()의 루프를 수정해야 합니다. 각 루프마다 page_read_bytes는 실행 파일에서 읽을 바이트 수를 받으며, page_zero_bytes는 읽은 바이트 뒤에 0으로 초기화할 바이트 수를 받습니다. 두 값의 합은 항상 PGSIZE(4096)입니다. 페이지 처리는 이 변수들의 값에 따라 다릅니다:

  • page_read_bytes가 PGSIZE와 같으면, 페이지는 처음 접근 시 기본 파일에서 지연 로드되어야 합니다.
  • page_zero_bytes가 PGSIZE와 같으면, 페이지는 디스크에서 읽을 필요가 없고 모두 0입니다. 첫 번째 페이지 폴트 시 0으로 된 새 페이지를 생성하여 처리하세요.
  • 그렇지 않으면, 페이지의 일부분은 기본 파일에서 읽어야 하고 나머지는 0으로 초기화해야 합니다.

4.3.3 스택 성장

스택 성장을 구현하세요. 프로젝트 2에서는 스택이 사용자 가상 주소 공간의 상단에 있는 단일 페이지였고, 프로그램은 그만큼의 스택만 사용할 수 있었습니다. 이제, 스택이 현재 크기를 초과하면 필요한 만큼 추가 페이지를 할당하세요.

추가 페이지는 스택 접근으로 보이는 경우에만 할당하세요. 스택 접근과 다른 접근을 구분하기 위한 휴리스틱을 고안하세요.

사용자 프로그램이 스택 포인터 아래로 쓰기 작업을 하는 경우, 이는 버그가 있습니다. 일반적인 실 운영 체제는 언제든지 프로세스를 중단시켜 "신호"를 전달할 수 있으며, 이는 스택에 데이터를 푸시합니다. 그러나 80x86 PUSH 명령어는 스택 포인터를 조정하기 전에 접근 권한을 확인하므로 스택 포인터 아래 4바이트에서 페이지 폴트를 발생시킬 수 있습니다. 유사하게, PUSHA 명령어는 한 번에 32바이트를 푸시하므로 스택 포인터 아래 32바이트에서 폴트를 발생시킬 수 있습니다.

사용자 프로그램의 현재 스택 포인터 값을 얻을 수 있어야 합니다. 사용자 프로그램이 생성한 시스템 호출이나 페이지 폴트 내에서, 이를 syscall_handler()나 page_fault()에 전달된 struct intr_frame의 esp 멤버에서 가져올 수 있습니다. 사용자 포인터를 액세스하기 전에 검증하는 경우 (섹션 3.1.5 [사용자 메모리 액세스], 27페이지 참고), 이 경우만 처리하면 됩니다. 반면, 페이지 폴트를 통해 잘못된 메모리 액세스를 감지하는 경우, 커널에서 페이지 폴트가 발생한 경우도 처리해야 합니다. 프로세서는 사용자에서 커널 모드로 전환될 때만 스택 포인터를 저장하므로, page_fault()에 전달된 struct intr_frame에서 esp를 읽으면 정의되지 않은 값이 됩니다. 사용자 스택 포인터가 아닌 값을 얻습니다. 초기 사용자에서 커널 모드로의 전환 시 esp를 struct thread에 저장하는 등의 다른 방법을 마련해야 합니다.

스택 크기에 대한 절대적인 제한을 두어야 합니다. 대부분의 운영 체제는 이러한 제한을 두고 있습니다. 일부 운영 체제는 예를 들어, 많은 Unix 시스템의 ulimit 명령어처럼 제한을 사용자 조정 가능하게 합니다. 많은 GNU/Linux 시스템에서는 기본 제한이 8MB입니다.

첫 번째 스택 페이지는 지연 로드될 필요가 없습니다. 로드 시 명령 줄 인수로 초기화하여 할당하고, 폴트가 발생할 때까지 기다릴 필요가 없습니다.

모든 스택 페이지는 추방 후보가 되어야 합니다. 추방된 스택 페이지는 스왑에 기록되어야 합니다.

 

4.3.4 메모리 매핑 파일

메모리 매핑 파일을 구현하며, 다음 시스템 호출을 포함합니다:

시스템 호출

  • mapid_t mmap (int fd, void *addr)

파일 디스크립터 fd로 열린 파일을 프로세스의 가상 주소 공간에 addr부터 시작하여 연속적인 가상 페이지에 매핑합니다. VM 시스템은 mmap 영역에서 페이지를 지연 로드하고, 매핑의 백업 저장소로 mmap된 파일 자체를 사용해야 합니다. 즉, mmap으로 매핑된 페이지를 추방할 때는 해당 페이지를 매핑한 파일에 다시 씁니다.

파일의 길이가 PGSIZE의 배수가 아닐 경우, 마지막 매핑된 페이지의 일부 바이트가 파일 끝을 넘어갑니다. 이러한 바이트는 파일 시스템에서 페이지 폴트가 발생할 때 0으로 설정하고, 디스크에 다시 쓸 때는 폐기합니다.

이 함수가 성공하면 프로세스 내에서 매핑을 고유하게 식별하는 "매핑 ID"를 반환합니다. 실패하면 -1을 반환해야 하며, 이는 유효한 매핑 ID가 아니어야 하며, 프로세스의 매핑은 변경되지 않아야 합니다.

mmap 호출은 파일 디스크립터 fd로 열린 파일의 길이가 0 바이트일 경우 실패할 수 있습니다. 또한 addr이 페이지 정렬되지 않았거나, 매핑된 페이지 범위가 기존에 매핑된 페이지(스택 또는 실행 파일 로드 시 매핑된 페이지 포함)와 겹치는 경우 실패해야 합니다. addr이 0일 경우에도 실패해야 하며, 이는 일부 Pintos 코드가 가상 페이지 0이 매핑되지 않음을 가정하기 때문입니다. 마지막으로, 콘솔 입출력을 나타내는 파일 디스크립터 0과 1은 매핑할 수 없습니다.

  • void munmap (mapid_t mapping)

mapping으로 지정된 매핑을 해제합니다. 이는 이전에 동일한 프로세스에서 mmap 호출에 의해 반환된 매핑 ID여야 하며, 아직 해제되지 않은 매핑이어야 합니다.

모든 매핑은 프로세스가 종료될 때 암시적으로 해제됩니다. 이는 exit을 통해서든 다른 방법으로든 마찬가지입니다. 매핑이 암시적으로 또는 명시적으로 해제될 때, 프로세스가 작성한 모든 페이지는 파일에 다시 쓰여야 하고, 작성되지 않은 페이지는 그렇지 않아야 합니다. 그런 다음 페이지는 프로세스의 가상 페이지 목록에서 제거됩니다.

파일을 닫거나 제거해도 그 파일의 매핑은 해제되지 않습니다. 생성된 매핑은 munmap이 호출되거나 프로세스가 종료될 때까지 유효합니다. Unix 규칙을 따릅니다. 자세한 내용은 [오픈 파일 제거], 35페이지를 참조하세요. 각 매핑에 대해 파일에 대한 별도이고 독립적인 참조를 얻기 위해 file_reopen 함수를 사용해야 합니다.

두 개 이상의 프로세스가 동일한 파일을 매핑하는 경우, 일관된 데이터를 볼 필요는 없습니다. Unix는 두 매핑이 동일한 물리적 페이지를 공유하도록 처리하지만, mmap 시스템 호출에는 페이지가 공유되는지 또는 사적으로(즉, 쓰기 시 복사) 지정할 수 있는 인자가 있습니다.

 

4.3.5 사용자 메모리 접근

시스템 호출을 처리하는 동안 사용자 메모리에 접근하기 위해 코드를 수정해야 합니다 (섹션 3.1.5 [사용자 메모리 접근], 27페이지 참고). 사용자 프로세스가 현재 파일이나 스왑 공간에 있는 페이지에 접근할 수 있는 것처럼, 이러한 비거주 페이지를 참조하는 주소를 시스템 호출에 전달할 수도 있습니다. 더 나아가, 커널이 이를 방지하지 않는 한, 커널 코드가 페이지를 접근하는 동안에도 페이지가 프레임에서 추방될 수 있습니다. 커널 코드가 이러한 비거주 사용자 페이지에 접근하면 페이지 폴트가 발생합니다.

사용자 메모리에 접근하는 동안, 커널은 이러한 페이지 폴트를 처리할 준비를 하거나, 페이지 폴트가 발생하지 않도록 방지해야 합니다. 커널은 페이지 폴트를 처리하는 데 필요한 리소스를 보유하는 동안에는 이러한 페이지 폴트가 발생하지 않도록 방지해야 합니다. Pintos에서 이러한 리소스에는 파일 시스템과 스왑 공간을 포함하는 장치를 제어하는 장치 드라이버가 획득한 잠금이 포함됩니다. 구체적인 예로, 장치 드라이버가 file_read에 전달된 사용자 버퍼에 접근하는 동안 페이지 폴트가 발생하도록 허용해서는 안 됩니다. 그렇지 않으면, 이러한 폴트를 처리하는 동안 드라이버를 호출할 수 없게 됩니다.

이러한 페이지 폴트를 방지하려면 접근이 발생하는 코드와 페이지 추방 코드 간의 협력이 필요합니다. 예를 들어, 프레임 테이블을 확장하여 프레임에 포함된 페이지가 추방되지 않아야 할 때 이를 기록할 수 있습니다. (이는 페이지를 프레임에 "고정" 또는 "잠금"한다고도 합니다.) 고정은 페이지 교체 알고리즘이 추방할 페이지를 찾을 때 선택을 제한하므로, 필요한 기간 이상으로 페이지를 고정하지 말고, 필요하지 않을 때 페이지를 고정하지 않도록 하세요.