ARM64 페이지 테이블 -3- (API)

<kernel v5.0>

ARM64 리눅스에서 사용하는 페이지 테이블 관련 명령(pgd, pud, pmd, pte)을 알아본다.

  • ARM64는 2~4 레벨 페이지 테이블을 사용한다.
    • 4 레벨 사용 시: pgd -> pud -> pmd -> pte
    • 3 레벨 사용 시: pgd(pud) -> pmd -> pte
    • 2 레벨 사용 시: pgd(pud, pmd) -> pte
  • 페이지 테이블은 4K 바이트를 사용하고, 각 엔트리는 8바이트이다.

 

엔트리 타입

다음 4 가지 테이블 엔트리들이 사용하는 타입 들(pgdval_t, pudval_t, pmdval_t, pteval_t)은 모두 8바이트를 사용하는 u64 타입이다.

  • pgd_t
    • typedef struct { pgdval_t pgd; } pgd_t;
  • pud_t
    • typedef struct { pudval_t pud; } pud_t;
  • pmd_t
    • typedef struct { pmdval_t pmd; } pmd_t;
  • pte_t
    • typedef struct { pteval_t pte; } pte_t;

 

주요 매크로 상수

다음은 ARM64 커널에서 사용되는 각종 페이지 테이블 관련 매크로 상수 값이다.

 


 

APIs

엔트리 값

p*d_val()

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

#define pgd_val(x)      ((x).pgd)
#define pud_val(x)      ((x).pud)
#define pmd_val(x)      ((x).pmd)
#define pte_val(x)      ((x).pte)

pgd | pud | pmd | pte 엔트리 값을 반환한다.

 

다음 그림은 p*d_val() 함수의 처리 과정을 보여준다.

 

엔트리 인덱스

p*d_index()

arch/arm64/include/asm/pgtable.h

/* to find an entry in a page-table-directory */
#define pgd_index(addr)         (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
/* Find an entry in the frst-level page table. */
#define pud_index(addr)         (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
/* Find an entry in the second-level page table. */
#define pmd_index(addr)         (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
/* Find an entry in the third-level page table. */
#define pte_index(addr)         (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

주어진 가상 주소에서 pgd | pud | pmd | pte 인덱스 값을 추출하여 리턴한다.

 

다음 그림은 4K 페이지, VA_BITS=39 커널 옵션을 가진 시스템에서 p*d_index() 함수의 처리 과정을 보여준다.


오프셋 (엔트리 포인터)

다음 그림은 엔트리 포인터를 알아오는 다양한 offset API들이 동작하는 과정을 보여준다.

 

pgd_offset()

arch/arm64/include/asm/pgtable.h

#define pgd_offset(mm, addr)    (pgd_offset_raw((mm)->pgd, (addr)))

@mm이 가리키는 pgd 테이블에서 가상 주소 @addr에 해당하는 pgd 엔트리 포인터를 알아온다.

 

pgd_offset_k()

arch/arm64/include/asm/pgtable.h

#define pgd_offset_k(addr)      pgd_offset(&init_mm, addr)

“_k” suffix(접미) 는 커널이라는 뜻이다. 즉 커널이 사용하는 pgd 테이블에서 가상 주소 @addr에 해당하는 pgd 엔트리 포인터를 알아온다.

 

pgd_offset_raw()

arch/arm64/include/asm/pgtable.h

#define pgd_offset_raw(pgd, addr)       ((pgd) + pgd_index(addr))

@pgd 테이블에서 가상 주소 @addr에 해당하는 pgd 엔트리 포인터를 알아온다.

 

pud_offset()

arch/arm64/include/asm/pgtable.h

#define pud_offset(dir, addr)           ((pud_t *)__va(pud_offset_phys((dir), (addr))))

pgd 엔트리 포인터 @dir이 가리키는 pud 테이블에서 @addr에 해당하는 pud 엔트리 포인터를 알아온다.

 

pud_offset_phys()

arch/arm64/include/asm/pgtable.h

#define pud_offset_phys(dir, addr)      (pgd_page_paddr(READ_ONCE(*(dir))) + pud_index(addr) * sizeoo
f(pud_t))

pgd 엔트리 포인터 @dir이 가리키는 pud 테이블에서 @addr에 해당하는 pud 엔트리의 물리 주소를 알아온다.

 

pmd_offset()

arch/arm64/include/asm/pgtable.h

#define pmd_offset(dir, addr)           ((pmd_t *)__va(pmd_offset_phys((dir), (addr))))

pud 엔트리 포인터 @dir이 가리키는 pmd 테이블에서 @addr에 해당하는 pmd 엔트리 포인터를 알아온다.

 

pmd_offset_phys()

arch/arm64/include/asm/pgtable.h

#define pmd_offset_phys(dir, addr)      (pud_page_paddr(READ_ONCE(*(dir))) + pmd_index(addr) * sizeoo
f(pmd_t))

pud 엔트리 포인터 @dir이 가리키는 pmd 테이블에서 @addr에 해당하는 pmd 엔트리의 물리 주소를 알아온다.

 

pte_offset_kernel()

arch/arm64/include/asm/pgtable.h

#define pte_offset_kernel(dir,addr)     ((pte_t *)__va(pte_offset_phys((dir), (addr))))

pmd 엔트리 포인터 @dir이 가리키는 pte 테이블에서 @addr에 해당하는 pte 엔트리 포인터를 알아온다.

 

pte_offset_phys()

arch/arm64/include/asm/pgtable.h

#define pte_offset_phys(dir,addr)       (pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeoo
f(pte_t))

pmd 엔트리 포인터 @dir이 가리키는 pte 테이블에서 @addr에 해당하는 pte 엔트리의 물리 주소를 알아온다.

 

pte_offset_kimg()

arch/arm64/include/asm/pgtable.h

#define pte_offset_kimg(dir,addr)       ((pte_t *)__phys_to_kimg(pte_offset_phys((dir), (addr))))

static하게 할당된 커널 이미지용 pmd 엔트리 포인터 @dir이 가리키는 pte 테이블에서 @addr에 해당하는 pte 엔트리 포인터를 알아온다.


페이지 변환

다음과 같이 pgd, pud, pmd 및 pte 엔트리 포인터가 가리키는 다음 레벨의 테이블, 블록 및 페이지에 해당하는 페이지 디스크립터를 알아온다.

 

pgd_page()

arch/arm64/include/asm/pgtable.h

#define pgd_page(pgd)           pfn_to_page(__phys_to_pfn(__pgd_to_phys(pgd)))

@pgd 엔트리 포인터에서 읽은 물리 주소에 해당하는 페이지 디스크립터를 알아온다.

  • pgd 엔트리 포인터 -> pgd 엔트리 물리 주소 -> pfn -> page 포인터

 

__pgd_to_phys()

arch/arm64/include/asm/pgtable.h

#define __pgd_to_phys(pgd)      __pte_to_phys(pgd_pte(pgd))

@pgd 엔트리 포인터에서 읽은 pgd 엔트리 값을 물리 주소로 변환해온다.

 

pgd_pte()

arch/arm64/include/asm/pgtable.h

static inline pte_t pgd_pte(pgd_t pgd)
{
        return __pte(pgd_val(pgd));
}

pgd_t 타입을 pte_t 타입으로 변환한다.

 

__pte_to_phys()

arch/arm64/include/asm/pgtable.h

#define __pte_to_phys(pte)      (pte_val(pte) & PTE_ADDR_MASK)

@pgd 엔트리 포인터에서 읽은 pgd 엔트리 값에서 물리 주소 값만을 읽어온다.

  • 위 소스는 52비트 미만의 물리 주소를 사용하는 경우에 한정한다.

 

pud_page()

arch/arm64/include/asm/pgtable.h

#define pud_page(pud)           pfn_to_page(__phys_to_pfn(__pud_to_phys(pud)))

@pud 엔트리 포인터에서 읽은 물리 주소에  해당하는 페이지 디스크립터를 알아온다.

 

__pud_to_phys()

arch/arm64/include/asm/pgtable.h

#define __pud_to_phys(pud)      __pte_to_phys(pud_pte(pud))

@pud 엔트리 포인터에서 읽은 pud 엔트리 값에서 물리 주소 값만을 읽어온다.

 

pud_pte()

arch/arm64/include/asm/pgtable.h

static inline pte_t pud_pte(pud_t pud)
{
        return __pte(pud_val(pud));
}

pud_t 타입을 pte_t 타입으로 변환한다.

 

pmd_page()

arch/arm64/include/asm/pgtable.h

#define pmd_page(pmd)           pfn_to_page(__phys_to_pfn(__pmd_to_phys(pmd)))

@pmd 엔트리 포인터에서 읽은 물리 주소에 해당하는 페이지 디스크립터를 알아온다.

 

__pmd_to_phys()

arch/arm64/include/asm/pgtable.h

#define __pmd_to_phys(pmd)      __pte_to_phys(pmd_pte(pmd))

@pmd 엔트리 포인터에서 읽은 pmd 엔트리 값에서 물리 주소 값만을 읽어온다.

 

pmd_pte()

arch/arm64/include/asm/pgtable.h

static inline pte_t pmd_pte(pmd_t pmd)
{
        return __pte(pmd_val(pmd));
}

pmd_t 타입을 pte_t 타입으로 변환한다.

 

pte_page()

arch/arm64/include/asm/pgtable.h

#define pte_page(pte)           (pfn_to_page(pte_pfn(pte)))

@pte 엔트리 포인터에서 읽은 물리 주소에 해당하는 페이지 디스크립터를 알아온다.

 

pte_pfn()

arch/arm64/include/asm/pgtable.h

#define pte_pfn(pte)            (__pte_to_phys(pte) >> PAGE_SHIFT)

@pte 엔트리 포인터에서 읽은 pte 물리 주소에 해당하는 pfn 값을 반환한다.

 


테이블 엔트리의 범위

pgd_addr_end()

include/asm-generic/pgtable.h

/*
 * When walking page tables, get the address of the next boundary,
 * or the end address of the range if that comes earlier.  Although no
 * vma end wraps to 0, rounded up __boundary may wrap to 0 throughout.
 */

#define pgd_addr_end(addr, end)                                         \
({      unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK;  \
        (__boundary - 1 < (end) - 1)? __boundary: (end);                \
})

가상 주소 @addr가 주어질 때 @end 범위내에서 pgd  엔트리가 담당하는 사이즈 단위로 정렬된 끝 주소를 반환한다. 반환 값이 @end를 넘어가면 @end 주소를 반환한다.

 

다음 그림은 4K 페이지, VA_BITS=48 커널 옵션을 사용한 경우 pgd_addr_end() 함수가 차례로 호출될 때 반환되는 값을 보여준다.

  • @addr=0x1234_5678_9000, @end=0x1280_0000_0000
    • 결과는 0x1280_0000_0000
  • @addr=0x1280_0000_0000, @end=0x1300_0000_0000
    • 결과는 0x1300_0000_0000
  • @addr=0x1300_0000_0000, @end=0x1334_5678_9000
    • 결과는 0x1334_5678_9000

 

pud_addr_end()

include/asm-generic/pgtable.h

#define pud_addr_end(addr, end)                                         \
({      unsigned long __boundary = ((addr) + PUD_SIZE) & PUD_MASK;      \
        (__boundary - 1 < (end) - 1)? __boundary: (end);                \
})

가상 주소 @addr가 주어질 때 @end 범위내에서 pud  엔트리가 담당하는 사이즈 단위로 정렬된 끝 주소를 반환한다. 반환 값이 @end를 넘어가면 @end 주소를 반환한다.

 

pmd_addr_end()

include/asm-generic/pgtable.h

#define pmd_addr_end(addr, end)                                         \
({      unsigned long __boundary = ((addr) + PMD_SIZE) & PMD_MASK;      \
        (__boundary - 1 < (end) - 1)? __boundary: (end);                \
})

가상 주소 @addr가 주어질 때 @end 범위내에서 pmd  엔트리가 담당하는 사이즈 단위로 정렬된 끝 주소를 반환한다. 반환 값이 @end를 넘어가면 @end 주소를 반환한다.

 

참고

 

댓글 남기기