<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 주소를 반환한다.
참고
- ARM64 페이지 테이블 -1- (Basic) | 문c
- ARM64 페이지 테이블 -2- (매핑) | 문c
- ARM64 페이지 테이블 -3- (API) | 문c – 현재 글