[주의] - 코드 분석 시 잘못된 부분도 있을 수 있으므로, 참고만 부탁드립니다.
[분석 기준]
kernel version : linux kernel 4.16
architecture : aarch64 (arm64)
[사전 지식]
fixmap영역은 컴파일 타임에 가상 주소공간이 이미 결정된 매핑 메모리 영역입니다. 따라서 매핑된 메모리 영역을 사용하기 위해서 fixmap영역을 초기화 해줄 필요가 있습니다.
리눅스 커널은 pgd, pud, pmd, pte를 이용하여 페이징 기법을 제공하고 있습니다. pgd와 pud, pmd, pte 중 무엇을 사용하느냐에 따라 단계별 페이징으로 나뉠 수 있습니다. pgd -> pte을 사용하는 2단계 페이징, pgd -> pmd -> pte를 이용한 3단계 페이징, 4가지 모두 사용하는 4단계 페이징 기법입니다.
[코드 분석]
early_fixmap_init() 은 커널 부팅 시 fixmap 영역 사용을 위해 pgd, pud, pmd, pte를 초기화하는 함수입니다.
함수 원형은 아래와 같습니다. 코드 전체 흐름은 아래와 같습니다.
1. addr(FIXADDR_START) 주소로부터 pgdp를 구함
void __init early_fixmap_init(void)
{
pgd_t *pgdp, pgd;
pud_t *pudp;
pmd_t *pmdp;
unsigned long addr = FIXADDR_START;
pgdp = pgd_offset_k(addr);
pgd = READ_ONCE(*pgdp);
if (CONFIG_PGTABLE_LEVELS > 3 &&
!(pgd_none(pgd) || pgd_page_paddr(pgd) == __pa_symbol(bm_pud))) {
/*
* We only end up here if the kernel mapping and the fixmap
* share the top level pgd entry, which should only happen on
* 16k/4 levels configurations.
*/
BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
pudp = pud_offset_kimg(pgdp, addr);
} else {
if (pgd_none(pgd))
__pgd_populate(pgdp, __pa_symbol(bm_pud), PUD_TYPE_TABLE);
pudp = fixmap_pud(addr);
}
if (pud_none(READ_ONCE(*pudp)))
__pud_populate(pudp, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);
pmdp = fixmap_pmd(addr);
__pmd_populate(pmdp, __pa_symbol(bm_pte), PMD_TYPE_TABLE);
/*
* The boot-ioremap range spans multiple pmds, for which
* we are not prepared:
*/
BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT)
!= (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT));
if ((pmdp != fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)))
|| pmdp != fixmap_pmd(fix_to_virt(FIX_BTMAP_END))) {
WARN_ON(1);
pr_warn("pmdp %p != %p, %p\n",
pmdp, fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)),
fixmap_pmd(fix_to_virt(FIX_BTMAP_END)));
pr_warn("fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n",
fix_to_virt(FIX_BTMAP_BEGIN));
pr_warn("fix_to_virt(FIX_BTMAP_END): %08lx\n",
fix_to_virt(FIX_BTMAP_END));
pr_warn("FIX_BTMAP_END: %d\n", FIX_BTMAP_END);
pr_warn("FIX_BTMAP_BEGIN: %d\n", FIX_BTMAP_BEGIN);
}
}
지역변수는 pgd_t *, pud_t *, pmd_t *의 포인터형과 pgd_t 자료형, unsigned long형으로 구성되어 있습니다. 사전 지식에서 간단히 설명한 pgd, pud, pmd를 위한 변수들입니다. addr 변수는 FIXADDR_START란 fixmap 영역이 시작되는 주소로 초기화 됩니다. 저 주소부터 fixmap영역 초기화를 진행합니다.
pgd_t *pgdp, pgd;
pud_t *pudp;
pmd_t *pmdp;
unsigned long addr = FIXADDR_START;
먼저 pgd 작업부터 시작합니다. addr로 부터 pgdp를 얻어옵니다. 그 후 READ_ONCE() 함수를 호출하여 pgd 를 구합니다. READ_ONCE는 메모리 배리어와 pgd를 얻어오게 됩니다.
pgdp = pgd_offset_k(addr);
pgd = READ_ONCE(*pgdp);
CONFIG_PGTABLE_LEVELS는 Kconfig에 선언되어 있습니다. 어떤 아키텍처와 페이지 사이즈 등에 따라 Level이 정의됩니다. 여기선 처음 fixmap영역에 생성하는 것이므로 else문으로 진행됩니다. pgd_none()함수로 pgd가 none인 상태를 확인 후 __pgd_populate() 함수로 pgd를 생성하게 됩니다. __p*d_populate() 함수들은 별도로 다루겠습니다.
if (CONFIG_PGTABLE_LEVELS > 3 &&
!(pgd_none(pgd) || pgd_page_paddr(pgd) == __pa_symbol(bm_pud))) {
/*
* We only end up here if the kernel mapping and the fixmap
* share the top level pgd entry, which should only happen on
* 16k/4 levels configurations.
*/
BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
pudp = pud_offset_kimg(pgdp, addr);
} else {
if (pgd_none(pgd))
__pgd_populate(pgdp, __pa_symbol(bm_pud), PUD_TYPE_TABLE);
pudp = fixmap_pud(addr);
}