jun-wiki

View My GitHub Profile

Posts (Latest 10 updated) :
Read all
Contents:
  1. 프로젝트 3 소개
    1. 배경
      1. 소스 파일
    2. 메모리 용어
      1. 페이지
      2. 프레임
      3. 페이지 테이블
      4. 스왑 슬롯
    3. 자원 관리 개요
    4. 구현 선택지 (성능 관점)
    5. 보조 페이지 테이블 관리(Managing the Supplemental Page Table)
      1. SPT의 구성
    6. 페이지 폴트 처리(Handling page fault)
    7. 프레임 테이블 관리(Managing the Frame Table)
      1. Accessed & Dirty 비트
      2. 에일리어스 (alias)
    8. 스왑 테이블 관리
    9. 메모리 매핑 파일 관리(Managing Memory Mapped Files)
    10. 요약

프로젝트 3 소개

통합했다

배경

소스 파일

건드려야 할 곳이다

이상한 곳 건드리지 않게 유의하자

  • include/vm/vm.h, vm/vm.c
    • 가상 메모리의 일반 인터페이스를 제공한다
    • 보조 페이지 테이블을 여기서 구현한다
    • vm_type의 정의와 설명을 볼 수 있다
      • VM_UNINIT
      • VM_ANON
      • VM_FILE
      • VM_PAGE_CACHE (프로젝트 4용이라 지금은 무시)


  • include/vm/uninit.h, vm/uninit.c
    • 미초기화 페이지 처리
    • 현재는 모든 페이지가 처음에 미초기화였다가 익명 페이지(VM_ANON) 혹은 파일 기반 페이지(VM_FILE)로 변환


  • include/vm/anon.h, vm/anon.c
    • 익명 페이지(VM_ANON) 처리를 제공한다


  • include/vm/file/h, vm/file.c
    • 파일 기반 페이지(VM_FILE) 처리를 제공한다


  • include/vm/inspect.h, vm/inspect.c
    • 채점용 메모리 검사 연산 수정 X


메모리 용어

페이지

페이지는 알다시피 4kB, 정확히는 4,096바이트다 palloc 많이 써서 안다

아무튼, 가상 페이지도 4,096바이트 짜리 가상 메모리 구간인데

페이지 정렬로 이루어져야 한다

63          48 47            39 38            30 29            21 20         12 11         0
+-------------+----------------+----------------+----------------+-------------+------------+
| Sign Extend |    Page-Map    | Page-Directory | Page-directory |  Page-Table |    Page    |
|             | Level-4 Offset |    Pointer     |     Offset     |   Offset    |   Offset   |
+-------------+----------------+----------------+----------------+-------------+------------+
              |                |                |                |             |            |
              +------- 9 ------+------- 9 ------+------- 9 ------+----- 9 -----+---- 12 ----+
                                          Virtual Address

하위 12비트는 오프셋, 상위 비트들은 페이지 테이블의 인덱스로 사용된다

오프셋(12비트)은 페이지 내부 바이트 위치(0~4095)를 알 수 있고

각 인덱스(각 9비트)는 해당 레벨 테이블에서 512(=2^9)개 엔트리 중 하나를 고르는 거다

PML4(최상위) → PDPT → PD → PT(최하위) → PT 엔트리(PTE)

Sign Extend는 말그대로 연장한거라 47비트 값으로 꽉 채우면된다 47비트가 1이었으면 1로, 0이었으면 0으로

각 프로세스는 KERN_BASE(0x8004000000) 이하의

사용자(가상) 페이지 집합을 독립적으로 가지나

커널(가상) 페이지 집합은 전역이다

커널은 어떤 스레드든 프로세스든 위치가 같다는 거다

커널은 사용자/커널 페이지 모두 접근 가능하지만
사용자 프로세스는 자신의 사용자 페이지만 접근 가능하다

Pintos에서 가상 주소 다루기 위한 함수들도 존재한다
Virtual address참조

추가로 KERN_BASE가 뭐고 왜 0x8004000000인지도 알아봤다

KERN_BASE는 경계값이다



프레임

프레임은 물리 메모리 상의 페이지 크기이지 정렬된 연속 구간이다

64비트 물리 주소는 프레임 번호와 오프셋으로 나뉜다

                          12 11         0
    +-----------------------+-----------+
    |      Frame Number     |   Offset  |
    +-----------------------+-----------+
              Physical Address

x86-64는 물리 주소로 직접 메모리에 접근하는 방법을 제공하지 않는다

Pintos는 이를 우회하기 위해

커널 가상 메모리를 물리 메모리에 일대일로 매핑한다

난 여기가 잘 이해가 안 갔다

가상 메모리 거쳐서 물리 메모리 만지는 거나

물리 메모리 바로 만지는 거나 동일한 거 아닌가?

명령어 누가 주느냐 차이 정도기에 하나 안되면 둘 다 안되야 한다고 생각되었고

만약 둘 다 동일하게 접근 하는 거라면 사실상 눈가리고 아웅하는 거나 다를 바 없어보여 말이다

다행히도 좀 더 알아보니 이해가 되었다

기본적으로 물리 메모리 접근 방법이

  • 명령어 가상 주소 받음

  • TLB에서 VA→PA(물리 주소) 번역을 찾음 (miss시 PTE로)

  • 권한/보호 검사

  • 캐시/메모리 접근

이 순서로 진행되기에

애시당초 직접 접근하는 방식이 없는 거였다

그렇지만 커널OS 직접 접근하듯 다룰 필요가 있기에

커널 가상 메모리와 물리 메모리를 1대1로 매핑 시킨거였다

KERN_BASE보다 큰 가상 주소는 물리 주소 0을, 가상 주소 KERN_BASE + 0x1234물리 주소 0x1234 를 대응해주는 식으로 말이다



페이지 테이블

페이지 테이블은 CPU가 가상 주소 → 물리 주소(페이지 → 프레임)로 변환할 때 사용하는 자료구조다

pintos에서는 threads/mmus.c에 페이지 테이블 관리 코드가 있다

페이지와 프레임 관계

                          +----------+
         .--------------->|Page Table|-----------.
        /                 +----------+            |
        |   12 11 0                               V  12 11 0
    +---------+----+                         +---------+----+
    | Page Nr | Ofs|                         |Frame Nr | Ofs|
    +---------+----+                         +---------+----+
     Virt Addr   |                            Phys Addr    ^
                  \_______________________________________/



스왑 슬롯

스왑 슬롯은 스왑 파티션 내의 페이지 크기 디스크 공간 구간이다

스왑 파티션이 무엇이냐?

디스크에서 스왑 용도로만 쓰라고 따로 떼어 둔 전용 구역이다

보통 페이지 정렬로 둔다

굳이 페이지로 해야한다는 제약은 없지만 보통 스왑 자체를 페이지 단위로 하는데다가

페이지 테이블이나 보조 페이지 테이블로 하여금 가상 페이지, 슬롯 번호만 기억하면 되도록 설계할 수 있어 깔끔하다

디스크 I/O도 페이지 한번만 읽고/쓰기면 끝이라 효율적이기도 하다



자원 관리 개요

자료 구조 설계/구현해야 하는 것들이다

  • 보조 페이지 테이블 (Supplemental Page Table, SPT)

    • 페이지 폴트 처리를 위해 필요
  • 프레임 테이블 (Frame Table)

    • 물리 프레임의 eviction(축출) 정책을 효율적으로 구현하기 위해 필요
  • 스왑 테이블 (Swap Table)

    • 스왑 슬롯 사용 현황을 추적


원한다면 부분적으로 통합하거나 해도 괜찮다고 한다

각 자료구조마다 원소에 담길 정보, 스코프(프로세스 로컬 vs 시스템 전역), 인스턴스 개수를 결정해야 한다

이 자료구조들을 비페이지 가능 메모리(malloc, calloc으로 할당) 에 저장해도 좋다고 한다


구현 선택지 (성능 관점)

배열(array), 리스트(list), 비트맵(bitmap), 해시 테이블(hash table) 등을 사용 ㄱㄴ

비트맵과 해시 테이블이 성능 좋으니 애용하라는 내용이다


보조 페이지 테이블 관리(Managing the Supplemental Page Table)

페이지 테이블의 한계 보완위해 각 페이지에 추가 데이터 제공한다

이를 보조 페이지 테이블(SPT)이라 부른다

이것도 페이지 테이블로 부르지만 헷갈리니 보조 붙여주자

SPT는 두 가지 목적에 쓰인다

  1. 페이지 폴트 발생 시

    • 폴트가 난 가상 페이지 데이터 파악
  2. 프로세스 종료 시

    • 해제할 자원 판단


SPT의 구성

마음대로 만들면 되고 최소 두 가지 접근이 있다

  • 세그먼트 기반
    연속 페이지 묶음 단위로 관리

  • 페이지 기반
    개별 페이지 단위로 관리

추가로, 원한다면 페이지 테이블 자체를 SPT 추적에 활용할 수도 있다고 한다

이를 위해서는 threads/mmu.c의 Pintos 페이지 테이블 구현 수정해야해서

고급 학습자에게만 권장한단다

할 건 아니지만 무슨 말인지 궁금하니 가볍게 뭔말인지만 알아보면

따로 보조 자료구조를 두는 대신 기존의 페이지 테이블 엔트리(PTE) 안에 SPT의 메타데이터를 넣는 방식이라고 한다

즉, 페이지테이블에 SPT도 같이 쓰는 방식이다

어떻게 하는지까지는…
나중에 다뤄보겠다


페이지 폴트 처리(Handling page fault)

페이지 폴트 버그처리는 프로젝트 2까지만이다

프로젝트 3부터는 파일/스왑에서 페이지를 가져와야 함이라는 뜻일 수도 있다

그렇기에 userprog/exception.cpage_fault()가 호출하는 vm_try_handle_fault()(in vm/vm.c) 를 구현해야 한다

수행해야하는 목록 ↓

  1. SPT에서 폴트가 난 가상 페이지를 조회

    • 유효한 접근 일 시에 해당 페이지 데이터가 파일 시스템, 스왑 슬롯, 제로 페이지인지 판단

    • 공유(CoW 등)를 구현했다면, 프레임에 있어도 PTE가 안 잡혀 있을 수 있음

    • SPT가 이상한 곳(커널 가상 메모리 범위, 읽기 전용 페이지에 쓰기)에 오면 무효 처리하고 프로세스 종료 후 자원해제

  2. 프레임 확보

    • 공유 구현했다면 프레임에 이미 데이터 있으니 그 프레임 찾아와야 한다
  3. 데이터를 프레임으로

    • 파일 시스템/스왑에서 읽거나, 제로로 채우는 등

    • 공유 구현해서 프레임에서 가져온거라면 추가 작업 불필요할 수 있다

  4. 해당 가상 주소의 PTE를 프레임으로 매핑

    • threads/mmu.c의 함수 사용


프레임 테이블 관리(Managing the Frame Table)

프레임 테이블은 각 물리 프레임에 대해 하나의 엔트리를 가진다

각 엔트리에는 현재 그 프레임을 차지하는 페이지의 포인터(있다면) 및 선택한 부가 데이터가 포함되어 있다

프레임 테이블은 eviction 정책 구현에 사용된다 (사용 가능한 프레임이 없을 때 축출 대상 페이지를 고르는 등)

사용자 페이지에 쓰일 프레임은 반드시 "user pool"에서 palloc_get_page(PAL_USER)로 얻어야 한다

이렇게 안얻으면 "kernel pool"에서 할당되어 테스트 케이스 실패 할 수 있다

가장 중요한 연산은 미사용 프레임 확보다

여유 프레임이 있으면 쉽지만, 없다면 축출(evict) 로 프레임을 비워야 한다

축출 시 스왑 슬롯 할당 없이는 축출할 수 있는 프레임이 하나도 없고, 스왑도 가득 찼다면 커널 패닉 일으키면 된다

축출 과정 ↓

  1. 페이지 교체 알고리즘으로 축출할 프레임 선택

    • accessed/dirty 비트가 유용
  2. 해당 프레임을 참조하는 모든 페이지 테이블의 참조 제거

    • 공유를 구현 X시 항상 단일 페이지만 프레임을 참조
  3. 필요 시, 페이지를 파일 시스템이나 스왑에 기록

    • 프레임을 다른 페이지 저장에 사용할 수 있게 된다


Accessed & Dirty 비트

페이지 교체 알고리즘 구현 위한 각 PTE의 두 비트다

  • accessed 비트: 페이지에 읽기/쓰기가 발생하면 CPU가 1로 설정

  • dirty 비트: 페이지에 쓰기가 발생하면 CPU가 1로 설정

이는 OS가 필요에 따라 0으로 재설정한다

무슨 alias 문제를 조심하란다

에일리어스 (alias)

서로 다른 가상주소가 같은 물리 프레임 가리키는 상황을 의미한다

A/D 비트(Accessed/Dirty)는 접근에 사용된 그 PTE에만 반영되는데 그로 인해

동일 프레임이라도 어느 PTE를 통해 접근했느냐에 따라 A/D 비트가 불일치 할 수 있다

Pintos에서는 모든 사용자 가상 페이지가 해당 커널 가상 페이지와 에일리어스라서 이를 잘 처리해줘야 한다

방법은 여럿 있을거다

예를 들어, 두 주소의 accessed/dirty 비트를 모두 확인/업데이트하거나, 커널이 사용자 데이터를 접근할 때는 사용자 가상 주소만 사용하도록 하거나 하여 말이다

그 외 에일리어스는 공유 구현 또는 버그가 있는 경우에만 발생해야 한다

자세한 함수는…

여기서 참고 해라


스왑 테이블 관리

스왑 테이블스왑 슬롯의 사용/여유를 추적한다

스왑으로 내보낼 떄 사용할 미사용 스왑 슬롯 선택
페이지가 프레임으로 돌아오거나 해당 프로세스 종료될 때 스왑 슬롯 해제
를 지원해야 한다

vm/build 디렉터리에서 스왑 파티션이 포함된 디스크를 이 명령어로 만들 수 있다

스왑 파티션?

pintos-mkdisk swap.dsk --swap-size=n

이후 swap.dsk는 pintos 실행 시 추가 디스크로 자동 부착

아니면 임시 nMB 스왑 디스크 사용할 수 있는데 --swap-size=n을 pintos 실행 인자에 주면 된다고 한다

그래서 이게 뭐하는 거야 하면 페이지 스왑 할때 사용할 임시 메모리 저장소 만드는 거라고 보면 된다

스왑 슬롯은 지연 할당(lazy allocation)해야 한다

미리 하지 말고 진짜 축출이 필요할 때만 할당 하라는 뜻이다

이후, 페이지 내용이 프레임으로 다시 읽혀오면 스왑 슬롯 해제하면 된다


메모리 매핑 파일 관리(Managing Memory Mapped Files)

파일 시스템은 일반적으로 read/write 시스템 콜로 접근한다

그치만 보조 인터페이스로는 파일을 mmap 시스템 콜을 통해 가상 페이지에 매핑 가능하다

read/write로 접근할 경우 “파일 → 커널 버퍼 → 사용자 버퍼”로 복사 2번을 해야 하지만

mmap로 접근할 경우 파일을 가상 페이지간주하고 페이지 폴트가 I/O를 트리거
그 이후는 포인터로 바로 접근한다

접근해서 쓰고 난 뒤에서는 munmap으로 해제 해줘야 한다

구현관점에서 중요한건

메모리 매핑된 파일이 사용하는 메모리를 추적할 수 있어야 한다는 것이다


요약

  • SPT/프레임 테이블/스왑 테이블을 설계·구현해야 한다

  • 페이지 폴트 핸들러(vm_try_handle_fault)가 SPT 기반으로 lazy 하게 페이지를 적재하고, 필요 시 eviction + swap을 수행하도록 한다

  • 사용자 페이지 프레임은 반드시 user pool에서 PAL_USER로 할당한다

  • accessed/dirty 비트에일리어스 문제를 올바르게 처리해야 한다

  • mmap/munmap으로 파일 매핑을 지원하며, 중첩/겹침 방지페이지 폴트 처리가 가능해야 한다