start_kernel - setup_arch - early_fixmap_init 함수 중에
이해되지 않는 부분이 있어 질문드립니다.
질문1
pgd = READ_ONCE(*pgdp);
v5.1의 코드에서는 위의 보이는 코드와 같이 pgdp로부터 pgd를 구할 때 READ_ONCE를 사용하고 있습니다
하지만 모기향 2판에 사용된 코드(p.162)에서는 READ_ONCE 사용해서 변수에 저장하는 대신
포인터 변수를 참조해서 그 값을 사용하고 있습니다.
아래의 링크를 통해 페이지 테이블의 엔트리에 접근하는 코드에 READ_ONCE를 추가하는 커밋을 찾을 수 있었지만
아직 그 내용을 이해하지 못했는데 왜 페이지 테이블의 엔트리에 접근할 때 READ_ONCE를 사용해야 하는 것 일까요?
https://github.com/torvalds/linux/commit/20a004e7b017cce282a46ac5d02c2b9c6b9bb1fa
질문2
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 문의 조건에 따라
이미 만들어진 pgd 페이지 테이블로부터 또는
__pgd_populate_ 후 pudp를 구하고 있습니다
여기에서 if 문의 조건이 true이면
pgd 페이지 테이블이 이미 만들어진 것으로 판단하는 것으로 보입니다
그리고 if 문의 조건이 참이 되기 위해서는 아래의 2개가 모두 true이어야 하고
- CONFIG_PGTABLE_LEVELS > 3
- !(pgd_none(pgd) || pgd_page_paddr(pgd) == __pa_symbol(bm_pud))
이를 위해서는 아래의 2개가 모두 false이어야 하는데
- pgd_none(pgd)
- pgd_page_paddr(pgd) == __pa_symbol(bm_pud)
이해가 되지 않는 부분은
pgd_page_paddr(pgd) == __pa_symbol(bm_pud) 에 대한 의미입니다
pgd_page_paddr(pgd) == __pa_symbol(bm_pud) 의 논리 연산의 결과가
false가 아니라 true가 되어야
pgd 페이지 테이블이 이미 만들어진 것으로 판단할 수 있을 것 같은데
어째서 그 반대인 것 일까요?
댓글 3
.
안녕하세요? 문c블로그(http://jake.dothome.co.kr)의 문영일입니다.
답1:
해당 메모리 위치를 volatile(컴파일러에 의한 optimization을 off)을 동반한 읽기를
하게 하는 것이 READ_ONCE() 매크로를 사용하는 이유입니다.
최근 ARMv8.1 아키텍처부터 MMU가 페이지 테이블의 access/dirty 비트를 직접 수정할 수
있는 기능(ARMv8.1-TTHM feature)이 생기고, 다른 cpu에서 페이지 테이블을 변경할 수
있는 동시성 측면에서 안정적으로 동작하도록 이러한 코드 변경을 하였습니다.
또한 최근 아키텍처들이 out of order 메모리 접근을 허용하며,
순서가 섞이지 않고 반영된 내용을 정확하게 읽기 위해 READ_ONCE() 매크로를
사용하게 되었습니다.
답2:
pgd 테이블은 init_pg_dir 테이블에 매핑되어 있습니다.
early_fixmap_init() 함수에서는 앞으로 fixmap 가상 주소 공간을 사용할 수 있도록
pgd 테이블 --> pud 테이블 --> pmd 테이블을 연결(population)하려고 합니다.
아직 페이지 할당자가 동작하지 못하므로 동적으로 페이지 테이블용 페이지를
할당할 수 없습니다. 따라서 pud 테이블과 pmd 테이블 용도로 bm_pud, bm_pte 등의
컴파일 타임에 생성한 static 테이블을 사용하려 합니다.
아래 코드에 대해 알아보죠
<code>
if (CONFIG_PGTABLE_LEVELS > 3 &&
!(pgd_none(pgd) || pgd_page_paddr(pgd) == __pa_symbol(bm_pud))) {
</code>
보기 좋게 고쳐보면
<code>
if (CONFIG_PGTABLE_LEVELS > 3 &&
!pgd_none(pgd) &&
(pgd_page_paddr(pgd) != __pa_symbol(bm_pud)) {
</code>
위의 조건을 단순하게 해석하면
- 페이지 테이블 변환 레벨이 4단계이고,
- pgd 엔트리가 이미 매핑이되었고,
- 매핑된 물리 주소가 bm_pud 테이블이 아닌 경우입니다.
페이지 사이즈와 페이지 테이블 단계의 조합은 매우 다양한데 그 중 이를 만족하는
상황은 단 한가지 입니다.
16K 페이지 사이즈의 4 단계 페이지 테이블을 사용하는 커널은 fixmap용
pgd 엔트리가 bm_pud 테이블이 아닌 kimage와 관계된 init_pg_dir의
두 번째(pud용) 테이블에 연결(population)되어 있습니다.
head.S에서 커널 이미지(kimage)를 매핑할 때 사용한 pud 테이블입니다.
따라서 이러한 경우에는 커널 이미지와 fixmap을 같은 pud 테이블에서
사용하므로 별도로 pud 테이블을 population할 필요 없습니다.
참고 1)
16K 페이지를 사용하는 4단계 페이지 테이블에서 pgd 테이블은 2 개의 엔트리만
사용합니다. 각 엔트리는 커널 영역의 절반을 차지하며 그 용도는 다음과 같습니다.
1) 물리 메모리를 이 공간에 리니어 매핑
2) 다이내믹 매핑 공간(fixmap, vmalloc, kimage, module, ...)
- fixmap과 kimage는 같은 pgd 엔트리 pud 테이블을 공유
참고 2)
early_fixmap_init() 함수는 다음과 같이 두 곳에서 호출되어 사용됩니다.
1) 보안을 목적으로 커널의 위치를 숨기는 KASLR(option) 기능을 사용 시
head.S의 kaslr_early_init() 함수
2) setup_arch() 함수
복잡하지만 차근 차근 분석해나가시길 바랍니다.
감사합니다.