ARM64 페이지 테이블 -1- (Basic)

<kernel v5.0>

가상 주소 공간

ARM64는 32비트 주소 공간을 갖고 있던 ARM과 달리 대폭 커진 64비트의 주소 공간을 갖고 있다.

 

다음 그림과 같이 ARM64 커널에서는 64비트 가상 주소의 시작 부분과 끝부분의 가상 주소 공간을 사용한다.

 

MMU 디바이스 내부에 있는 2개의 TTBR 레지스터(Translation Table Base Register)가 가리키는 페이지 테이블을 통해 커널 주소 공간과 유저 주소 공간에 매핑된 물리 자원에 접근할 수 있다. 커널 주소 공간과 유저 주소 공간은 각자 페이지 테이블을 갖고 있다.
커널 주소 공간을 위한 페이지 테이블은 TTBR1 레지스터가 가리킨다. 유저 주소 공간을 위한 페이지 테이블은 TTBR0 레지스터가 가리킨다. 각 레지스터를 사용하면 CPU는 두 주소 공간에 접근할 수 있다.

 

 

페이지 테이블은 여러 개의 엔트리로 구성되어 있다. 페이지 테이블 엔트리 하나는 페이지 1개에 대한 물리 주소로 변환하는 데 사용한다. ARM64 커널에서는 페이지 크기로 4KB, 16KB, 64KB 중 하나를 사용할 수 있다(기본 크기는 4KB). 페이지 테이블의 유지 관리 편의를 위해 커널을 설정할 때 페이지 크기를 혼용하여 사용하지 않고 1개의 페이지 크기를 선택하여 사용한다.

 

 

 

ARM64 커널이 64비트 가상 주소 전부를 사용하는 것이 아니라 36, 39, 42, 47 및 최대 48 비트와 같이 아키텍처가 지원하는 비트만을 선택해 사용할 수 있고 이 범위를 가상 주소로 사용한다.

  • 최근 커널 v5.0-rc1의 경우 에는 커널은 48비트이지만 유저용 가상 공간의 크기를 52비트까지 확장시켜 제공한다.
  • ARMv8.2 아키텍처 부터 가상 주소 및 물리 주소 크기를 지원하는 주소 비트가 각각 52비트까지 사용할 수 있다.

 

예를 들어, 48비트를 선택해 ARM64 커널을 빌드하여 사용할 경우 주소 공간의 크기를 계산하기 위해 2^48을 연산하면 256TB임을 알 수 있다. 그리고 이 공간에 필요한 페이지 테이블의 엔트리 수를 계산하기 위해서는 페이지 크기로 나누어야 한다. 예를 들어, 커널이 4K 페이지를 사용하는 경우 256TB/4KB를 연산하면 640억 개(64G)의 엔트리 수가 산출됨을 알 수 있다.

다음 그림을 보면 VA_BITS = 48 커널 옵션을 사용해 48비트를 선택하여 주소 공간으로 0x00000000_00000000~0x0000ffff_ffffffff 범위를 사용할 수 있다.

  • ARM64 시스템이 지원하는 세 가지 페이지 크기(4KB, 16KB, 64KB)에 따라 페이지 테이블 엔트리 수가 결정됨을 알 수 있다.

 

각 태스크에 주어지는 가상 주소 공간마다 64G개의 엔트리를 제공하기에는 너무 많은 메모리가 낭비되므로 ARM64 커널은 테이블을 최소 2 레벨부터 최대 4 레벨로 나누어 사용한다.

다음 그림은 36비트(VA_BITS)를 사용하는 주소 공간을 나타내는 2 레벨 페이지 테이블을 구성한 것이다. 필요한 페이지 테이블 엔트리가 2048개로 줄어듦을 알 수 있다

  • 페이지 테이블 인덱스에 해당하는 비트가 페이지 오프셋에 사용하는 비트 – 3인 것에 주목한다.

 

페이지 테이블 개요

ARM64 커널의 페이지 테이블 명칭은 다음과 같다.

  • PGD(Page Global Directory)
    • 모든 단계의 레벨 구성에서 사용된다.
      • 2 단계 레벨 구성에서는 2 레벨 테이블이다.
      • 3 단계 레벨 구성에서는 1 레벨 테이블이다.
      • 4 단계 레벨 구성에서는 0 레벨 테이블이다.
  • PUD(Page Upper Directory)
    • 총 단계 3 레벨 구성에서는 제외되며, 구성되어 사용될 때에는 1 레벨 테이블이다.
  • PMD(Page Mid-level Directory)
    • 총 단계 2 레벨 구성에서는 제외되며, 구성되어 사용될 때에는 2 레벨 테이블이다.
  • PTE(Page Table Entry)
    • 모든 단계의 레벨 구성에서 3 레벨 테이블로 사용된다.

 

ARM64 커널에서 사용할 수 있는 페이지 크기(4K, 16K, 64K)와 가상 주소 비트 수(36, 39, 42, 47, 48, 52)에 따른 조합을 알아본다. defconfig 기준으로 ARM64 커널은 4KB의 페이지와 가상 주소 비트 수(이하 VA_BITS)가 48로 구성된 4레벨 테이블을 사용한다.

  • 커널 v4.7-rc1 이전에는 VA_BITS=39, 3 레벨로 구성된 테이블을 사용하였다.

 

페이지 크기와 VA_BITS 조합에 의한 각 페이지 테이블 구성

다음 그림은 2 레벨로 구성된 페이지 테이블(pgd와 pte)을 사용해서 페이지 프레임에 연결되는 두 가지 방법을 보여준다.

  • ARM 아키텍처에서 pgd는 2 레벨 테이블, pte는 3 레벨 테이블이다.
  • pmd 사이즈 블럭을 직접 연결하여 사용할 수 있다.

 

VA_BITS = 36인 경우를 알아보자. 이 경우에는 pgd와 pte 테이블이 각각 11비트를 사용하므로 엔트리는 2048개가 된다. 최종 엔트리가 16K 페이지를 가리키면 64G(2048 * 2048 * 16K) 주소에 해당하는 페이지 프레임을 변환할 수 있다.

 

다음 그림은 3 레벨로 구성된 페이지 테이블(pgd, pmd, pte)을 사용해서 페이지 프레임에 연결되는 세 가지 방법을 보여준다.

  • 커널 v5.0-rc1 부터 가장 아래 3 레벨 페이지 테이블 + 64K 페이지 프레임을 사용하는 경우 USER_VA_BITS=52도 사용할 수 있다.
  • ARM 아키텍처에서 pgd는 1 레벨 테이블, pmd는 2레벨 테이블, pte는 3 레벨 테이블이다.
  • pud 사이즈 및 pmd 사이즈 블럭을 직접 연결하여 사용할 수 있다. (16K 페이지의 경우는 pmd 사이즈 블럭만 가능하다)
  • 커널 메인라인에서 4T 블럭에 대한 블럭(pud 섹션 사이즈) 매핑은 아직 구현하지 않은 상태이다.

 

다음 그림은 4 레벨로 구성된 페이지 테이블(pgd, pud, pmd, pte)을 사용해서 페이지 프레임에 연결되는 두 가지 방법을 보여준다.

  • ARM 아키텍처에서 pgd는 0 레벨 테이블, pud는 1 레벨 테이블, pmd는 2레벨 테이블, pte는 3 레벨 테이블이다.
  • pud 사이즈 및 pmd 사이즈 블럭을 직접 연결하여 사용할 수 있다. (16K 페이지의 경우는 pmd 사이즈 블럭만 가능하다)

 

Contiguous Bits

pmd 및 pte 매핑 속성에만 사용할 수 있는 contiguous bit 속성이 있다. 연속된 PMD 또는 PTE 사이즈(CONT_PMD_SIZE, CONT_PTE_SIZE) 만큼의 공간을 매핑하는 경우 TLB 엔트리를 절약하여 성능을 높일 수 있는 매핑 방법이다.

  • 커널 v4.12-rc1에 채택되어 사용된다.
  • 연속되는 공간 사이즈
    •  CONT_PMD_SIZE
      • 4K 페이지: 16 * 4K = 64KB
      • 16K 페이지: 128 * 16K = 2MB
      • 64K 페이지: 32 * 64K = 2MB
    •  CONT_PTE_SIZE
      • 4K 페이지: 16 * 2MB = 32MB
      • 16K 페이지: 32 * 32MB = 1GB
      • 64K 페이지: 32 * 512MB = 16G

 

페이지 테이블 구성 예)

다음 그림은 대표적으로 구성하여 사용하는 세 가지 가상 주소 공간의 크기를 보여준다.

 

4K 페이지 + VA_BITS=39 예)

4K 페이지와 VA_BITS = 39로 구성된 3 레벨 페이지 테이블을 사용하는 경우에 대해 알아보자. 64비트 가상 주소에서 상위 25비트는 커널 또는 유저 주소 공간의 구분에 사용하고, 그다음 9비트 세트 3개는 각 레벨의 페이지 테이블 엔트리에 접근할 때 사용한다. 마지막 12비트는 페이지 내의 오프셋으로 사용한다.

 

각 테이블에 대해 9비트 인덱스를 사용하여 총 512개씩의 엔트리에 접근할 수 있음을 알 수 있다. 커널은 2개의 공간, 즉 커널과 유저 주소 공간을 나누어 사용하므로 64비트 가상 주소 중 변환에 사용하지 않는 최상위 비트 [63:39]를 체크하여 비트 값이 0인 경우에는 유저 페이지 테이블을 가리키는 TTBR0 레지스터를 사용한다. 그리고 그 비트들 값이 모두 1인 경우에는 커널 페이지 테이블을 가리키는 TTBR1 레지스터를 사용한다.

 

그러나 ARM64 커널에서는 그 비트들을 모두 비교할 필요 없이 최상위 비트 하나만을 비교하여 커널 및 유저의 가상 주소 영역을 구분한다.

 

다음 그림은 4K 페이지와 VA_BITS=39를 사용하는 페이지 테이블의 연결 구성 예를 보여준다.

 

4K 페이지 + VA_BITS=48 구성 예)

커널 v4.7-rc1부터 가상 주소 크기가 VA_BITS=39에서 VA_BITS=48로 변경되었다.

 

다음 그림은 4K 페이지와 VA_BITS=48을 사용하는 페이지 테이블의 연결 구성 예를 보여준다.

 


 

페이지 테이블 관련 주요 레지스터

TCR_EL1

다음 그림은 페이지 테이블 변환 관련된 TCR_EL1을 보여준다.

  • TxSZ (TTBRx Size offset)
    • TTBRx가 관리할 영역 크기는 2^(64-T0SZ)이다.
    • 예) VA_BITS=48의 경우 TxSZ=16이 필요하다.
  • IRGNx (Inner cacheability Normal Memory attribute for TTBRx)
    • Normal Memory Inner 캐시 속성
      • 0b00: NC (Non Cache)
      • 0b01: WB-WA(Write Back, Write Allocation)
      • 0b10: WT (Write Through)
      • 0b11: WB (Write Back)
  • ORGNx (Outer cacheability Normal Memory attribute for TTBRx)
    • Normal Memory Outer 캐시 속성 (상동)
  • SHx (Shareability attributer for TTBRx)
    • 0b00: Non-shareable
    • 0b10: Outer Shareable
    • 0b11: Inner Shareable
  • TGx (Granule size for TTBRx)
    • 0b00: 4KB
    • 0b01: 64KB
    • 0b10: 16KB
  • A1 (select TTBR0 & TTBR1 for ASID)
    • 0: TTBR0를 위해 ASID 사용
    • 1: TTBR1을 위해 ASID 사용
  • IPS (Intermediate Physical Address Size)
    • 중간 물리 주소(IPA) 사이즈
      • 0b000: 32 bits (4GB)
      • 0b001: 36 bits (64GB)
      • 0b010: 40 bits (1TB)
      • 0b011: 42 bits (4TB)
      • 0b100: 44 bits (16TB)
      • 0b101: 48 bits (256TB)
      • 0b110: 52 bits (4PB)
  • AS (ASID Size)
    • 0: TTBR0 및 TTBR1 양쪽에서 8 bit ASID 사용
    • 1: TTBR0 및 TTBR1 양쪽에서 16 bit ASID 사용
  • TBIx (Top Byte ignored for TTBRx)
    • 이 값을 1로 하는 경우 가상 주소의 최상위 8 비트를 주소 변환에 사용하지 않게 한다.

 

TTBRx_EL1

다음 그림은 두 개의 페이지 테이블의 base 주소를 가리키는 TTBR0_EL1 과 TTBR1_EL1을 보여준다.

  • ASID
    • mm->context.id의 하위 8 bit 또는 16 bit ASID를 저장한다.
    • TLB 캐시의 변환 엔트리 hit에 ASID + 가상 주소(VA) 를 사용하게하여 context 스위치 시 TLB 플러시를 최소화한다.
  • BADDR
    • 페이지 테이블의 물리 주소가 담긴다.
    • ARMv8.2 아키텍처 이상의 경우 52 비트 가상 주소 및 물리 주소를 지원한다. 이 때 52비트 물리 주소를 지원해야 하는 경우 52 비트 물리 주소의 msb를 사용한다.
  • CnP
    • Inner shable 영역의 코어에 공유할지 여부를 지정한다.

 


 

페이지 테이블 엔트리 포맷

ARMv8 엔트리 포맷

다음은 ARMv8 아키텍처 페이지 테이블의 4가지 유형의  엔트리 디스크립터 포맷이다.

  • Invalid
    • 모든 레벨에서 사용되며 bit[1:0] 값이 00 이다.
  • Block 디스크립터
    • 레벨 0~2에서 사용되며 bit[1:0] 값이 01 이다.
  • Table 디스크립터
    • 레벨 0~2에서 사용되며 bit[1:0] 값이 11 이다.
  • Page 디스크립터
    • 마지막 레벨 3에서 사용되며 bit[1:0] 값이 11 이다.

 

다음 그림은 각 레벨에서 사용하는 디스크립터들의 포맷을 보여준다.

  • ARMv8.2-LPA 옵션을 사용하는 시스템의 경우 52비트까지 확장된다.

Level 0, 1, 2의 블럭 및 테이블 관련 비트를 알아본다.

  • nT
    • Level 1 및 Level 2에서 이 비트가 설정된 엔트리가 MMU에 의해 사용될 떄 변환 fault를 유발한다. 또한 TLB 캐시에 캐시되지 않는다.
    • ARMv8.4-TTRem 기능이 구현된 시스템에서 사용한다.
  •  NSTable
    • 다음 레벨의 테이블이 Non-Secure State 인지 여부를 나타낸다.
  • APTable
    • 2 비트의 접근 권한을 지정한다.
      • 0b00: 다음 레벨의 테이블에 어떠한 영향도 없다.
      • 0b01: EL0에서 다음 레벨의 테이블에 접근을 금지한다.
      • 0b10: 다음 레벨의 테이블에서 수정을 금지한다.
      • 0b11: 위의 두 기능(0b01 & 0b10)이 동시에 동작한다.
  • UXN or XN
    • 이 비트를 1로 하면 유저 레벨이 다음 레벨의 테이블에서 실행 코드에 대한 주소 변환을 하지 못하게 한다.
      • 하이라키로 실행 금지를 제어한다.
  • PXNTable
    • 이 비트를 1로 하면 커널 레벨이 다음 레벨의 테이블에서 실행 코드에 대한 주소 변환을 하지 못하게 한다.
      • 하이라키로 실행 금지를 제어한다.

 

모든 레벨의 블럭 및 페이지 디스크립터에서 사용되는 Upper 속성 비트를 알아본다.

  • PBHA
    • Page 기반의 하드웨어 속성 비트로 지정한 디스크립터 비트의 사용 유무를 제어한다.
    • ARMv8.2에서만 사용된다.
  • SW
    • 하드웨어 아키텍처는 사용하지 않는 4 개의 비트이지만 소프트웨어에서 활용할 수 있는 비트들이다.
  • UXN 또는 XN
    • 이 비트를 1로 하면 유저 레벨이 다음 레벨의 테이블 또는 페이지에서 실행 코드에 대한 주소 변환을 하지 못하게 한다.
      • 하이라키로 실행 금지를 제어한다.
  • PXN
    • 이 비트를 1로 하면 커널 레벨이 다음 레벨의 테이블 또는 페이지에서 실행 코드에 대한 주소 변환을 하지 못하게 한다.
      • 하이라키로 실행 금지를 제어한다.
  • Contiguous
    • 이 비트를 1로 하면 하나의 엔트리로 이어지는 블럭또는 페이지가 동일한 매핑을 사용하게 된다.
  • DBM
    • ARMv8.1 아키텍처가 지원하는 기능으로 이 비트를 1로 하면  하드웨어가 Access 플래그와 Dirty 비트를 직접 관리하도록 한다.
    • 기존 ARM 아키텍처는 이 기능을 지원하지 않아 fault exception 후에 소프트웨어가 Access 플래그와 Dirty 비트를 기록하였다.

 

모든 레벨의 블럭 및 페이지 디스크립터에서 사용되는 Lower 속성 비트를 알아본다.

  • AttrIndx
    • MAIR_ELx를 위해 사용되는 메모리 속성이다.
  • NS
    • Non-Secure 비트이다.
  • AP
    • 데이터 접근 권한이다.
      • 0b00: 상위 Exception 레벨에서 RW, 유저 레벨에서 None 이다.
      • 0b01: 상위 Exception 레벨에서 RW, 유저 레벨에서 RW 이다.
      • 0b00: 상위 Exception 레벨에서 R, 유저 레벨에서 None 이다.
      • 0b00: 상위 Exception 레벨에서 R, 유저 레벨에서 R 이다.
  • SH
    • 공유 비트
      • 0b00: Non-Shareable
      • 0b01: REserved
      • 0b10: Outer Shareable
      • 0b11: Inner Shareable
  • AF
    • 페이지나 블럭에 접근한 경우 설정되는 Access 플래그이다.
    • ARMv8.1의 DBM 설정에 따라 하드웨어가 갱신할지 아니면 소프트웨어가 갱신할 지 선택할 수 있다.
  • nG
    • not Global 비트로 이 비트가 설정되는 경우 지정된 ASID의 TLB 엔트리에서만 변환을 시도한다.
    • 0으로 설정하는 경우 ASID와 상관 없이 이 엔트리를 사용하여 변환을 한다.

 

PTE 속성

커널 레벨(stage 1)에서 사용하는 리눅스 PTE  속성이다. 하이퍼 바이저 레벨(stage 2)는 생략한다.

arch/arm64/include/asm/pgtable-hwdef.h

#define PTE_TYPE_MASK           (_AT(pteval_t, 3) << 0)
#define PTE_TYPE_FAULT          (_AT(pteval_t, 0) << 0)
#define PTE_TYPE_PAGE           (_AT(pteval_t, 3) << 0)
#define PTE_TABLE_BIT           (_AT(pteval_t, 1) << 1)
#define PTE_USER                (_AT(pteval_t, 1) << 6)         /* AP[1] */
#define PTE_RDONLY              (_AT(pteval_t, 1) << 7)         /* AP[2] */
#define PTE_SHARED              (_AT(pteval_t, 3) << 8)         /* SH[1:0], inner shareable */
#define PTE_AF                  (_AT(pteval_t, 1) << 10)        /* Access Flag */
#define PTE_NG                  (_AT(pteval_t, 1) << 11)        /* nG */
#define PTE_DBM                 (_AT(pteval_t, 1) << 51)        /* Dirty Bit Management */
#define PTE_CONT                (_AT(pteval_t, 1) << 52)        /* Contiguous range */
#define PTE_PXN                 (_AT(pteval_t, 1) << 53)        /* Privileged XN */
#define PTE_UXN                 (_AT(pteval_t, 1) << 54)        /* User XN */
#define PTE_HYP_XN              (_AT(pteval_t, 1) << 54)        /* HYP XN */

 

참고

8 thoughts to “ARM64 페이지 테이블 -1- (Basic)”

  1. 안녕하세요.

    페이지 테이블을 공부하는데 많은 도움이 되었습니다.

    페이지 크기와 VA_BITS 조합에 의한 각 페이지 테이블 구성에서 의문이 생겨 질문드립니다.pgd에 pud 사이즈의 블럭을 직접 연결하듯 pud, pmd가 자신보다 작은 사이즈의 블럭을 연결하는 경우가 있던데, 이 블럭들은 어디에 사용되나요?

    항상 좋은 글 감사드립니다.

    1. 안녕하세요?

      매핑을 할 리니어 메모리 사이즈가 작은 경우에는 페이지 단위로 매핑을 하고,
      다음 조건에 부합하면 블럭 매핑을 시도합니다.
      – pud 사이즈 블럭 매핑: 가상 주소와 물리 주소가 1G 단위 정렬되어 있고, 4K 페이지
      – pmd 사이즈 블럭 매핑: 가상 주소와 물리 주소가 pmd 사이즈 단위로 정렬되어 있는 경우

      실제 커널 부팅 업 시 4G DRAM 메모리를 리니어 매핑할 때 처음에 대부분을 블럭 매핑합니다.

      감사합니다.

      1. 리니어 매핑용이였군요. 조건까지 달아주셔서 이해하기 편했습니다.

        명쾌한 답변 감사합니다.

  2. 안녕하세요.
    문씨블로그 잘보고있습니다.
    문서 초반부에 엔트리레벨을 두는 이유를 설명하는 과정에서 “각 태스크에 주어지는 가상 주소 공간마다 64G개의 엔트리를 제공하기에는 너무 많은 메모리가 낭비되므로 ARM64 커널은 테이블을 최소 2 레벨부터 최대 4 레벨로 나누어 사용한다” 라는 문구가 있는데. 이해가 잘 안되서 질문드립니다.

    우선 VA_BITS 48기준으로 1레벨 페이지테이블에 4K 페이지를 사용한다고 가정하면 2^36-1개의 엔트리가 생겨 64G개의 엔트리가 계산된다는 것은 이해했습니다.

    그런데 여기서 페이지 레벨을 3단계, 4단계를 구성할때 PTE는 줄어들지몰라도 PUD또는 PMD가 존재하기때문에 결국은
    2^36-1개의 테이블이 생긴다고 생각됩니다. 예를들어 VA_BITS 48, 4K page, 4레벨 구성이라고 한다면,
    pgd 9bits, pud 9bits, pmd 9bits, pte 9bits 따라서 512개 * 512개 * 512개 * 512개 로 64GiB 개의 테이블이 생깁니다.
    물론 PTE는 512개이지만요.

    질문을 정리하자면,
    1. 결국 64G의 테이블이 생기는것은 마찬가지인데 어떻게 메모리가 낭비되지 않을 수 있는지?
    2. 실제로 메모리를 사용하는것은 결국 PTE라서 그런것인지?
    이렇게 정리하고싶습니다.

    실제로 페이지테이블을 어떻게 시스템이 사용하는지 이해하지 못해서 드리는 질문같습니다. 그래도 조금 힌트를 주시면 좀더 수월하게 이해할 수 있어서 질문드립니다. 감사합니다.

  3. 안녕하세요?

    1 단계만 사용하면 테이블을 한꺼번에 만들기 때문에 메모리가 많이 낭비되지만,
    2 단계 이상으로 구성하는 경우 2 단계 부터는 필요한 테이블만 추가로 생성하여 만듭니다.

    하나의 유저 프로세스가 생성되면 해당 유저 페이지 테이블을 생성하는데 1단계 테이블만 생성하고,
    나머지는 cpu가 해당 주소를 읽어낼 때마다 fault가 발생하면 메모리를 로딩하여 2단계 이상 테이블을 사용하면서
    점점 테이블이 커지는 방식입니다. 즉 사용하지 않는 공간에 대해서는 2단계 이상의 테이블이 만들어지지 않습니다.

    감사합니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다