안녕하세요, 저는 '코드로 알아보는 ARM 리눅스(제2판)'을 구입한 학부생 양현우라고 합니다.
책을 보다가 도저히 이해가 안가는 부분이 있었는데, 표지에 '독자 Q&A' 메일이 적혀있길래 이렇게 문의를 드리게 되었습니다.
책의 66쪽에 따르면 idmap_pg_dir과 swapper_pg_dir은 vmlinux.lds.S에 다음과 같이 정의되어 있다고 합니다. (커널 4.6 기준)
. = ALIGN(PAGE_SIZE);
idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
swapper_pg_dir = .;
. += SWAPPER_DIR_SIZE;
이와 같이 idmap_pg_dir과 swapper_pg_dir이 테이블에 필요한 공간에 따라 정렬되어있다고 설명이 되어있는데요,
저렇게 "."을 옮기면서 공간이 할당되는거라면, vmlinux.ld.S가 _text를 시작할 때
. = KIMAGE_VADDR + TEXT_OFFSET;
이렇게 시작하는건 물리적으로 KIMAGE_VADDR + TEXT_OFFSET만큼 공간이 할당되고 시작하는건가요?
물론 아닐거라 생각합니다. KIMAGE_VADDR은 0xFFFF000000000000 + 128M로 정의되어 있던데,
이게 물리주소는 아마도 아니고, 가상주소인데 제가 뭔가 기본지식이 없어서 착각하는 것 같습니다.
어떻게 바로잡아야하는지 궁금합니다.
그런데 그러면 또 한가지 드는 의문이, idmap_pg_dir과 swapper_pg_dir은 MMU enable 이전에 물리적으로 연속이어야하므로 물리주소상에서 어떻게 배치되어있냐가 중요할 것 같은데, 저기서 KIMAGE_VADDR을 보면 모두 가상주소로 위치가 정의되어 있는 것 같습니다. 이건 대체 왜 이런건지 이해가 잘 가지 않습니다.
마지막 질문으로, 책의 74쪽에서 __enable_mmu를 보면
__enable_mmu:
mrs x22, sctlr_el1 // preserve old SCTLR_EL1 value
mrs x1, ID_AA64MMFR0_EL1
ubfx x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4
cmp x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
b.ne __no_granule_supportupdate_early_cpu_boot_status 0, x1, x2
msr ttbr0_el1, x25 // load TTBR0
msr ttbr1_el1, x26 // load TTBR1
isbmsr sctlr_el1, x0
isb/*
* Invalidate the local I-cache so that any instructions fetched
* speculatively from the PoC are discarded, since they may have
* been dynamically patched at the PoU.
*/
ic iallu dsb nsh isb#ifdef CONFIG_RANDOMIZE_BASE
mov x19, x0 // preserve new SCTLR_EL1 value
blr x27 /*
* If we return here, we have a KASLR displacement in x23 which we need
* to take into account by discarding the current kernel mapping and
* creating a new one.
*/
msr sctlr_el1, x22 // disable the MMU
isbbl __create_page_tables // recreate kernel mapping
msr sctlr_el1, x19 // re-enable the MMU
isbic iallu // flush instructions fetched
dsb nsh // via old mapping
isbadd x27, x27, x23 // relocated __mmap_switched
#endif
br x27ENDPROC(__enable_mmu)
TTBR과 sctlr을 설정하고 isb를 해도 그 뒤로 코드가 이어지는걸 보면 MMU를 켰다고 갑자기 instruction fetch를 가상주소로 하느라 fetch하는 위치가 바뀐다거나 그러지는 않는 것 같습니다. 실제로 책 65쪽에도 다음과 같은 문장이 있습니다:
"MMU가 켜졌을 때 다음에 실행할 인스트럭션은 아직 물리 주소이므로 이 주소에 대한 idmap 페이지 테이블에 생성해두는 것이다."
그런데 br x27에서 x27이 가상주소인걸로 미루어보건대, MMU enable이 PC의 값을 변경시키는건 아니고, 상대주소 변경은 arithmetic하게 이루어지다가, 절대주소 분기를 하면 그때 값이 들어가는 것으로 보입니다. 그래서 x27이 대입되면 가상주소에서 fetch를 시작하는 것으로 지금은 받아들이고 있습니다.
그런데 그렇게 이해하자니 65쪽 저 구절의 뒷부분이 해석이 되지 않습니다. 왜 코드가 idmap 페이지에 따로 할당이 되어야 하나요? 게다가 idmap 페이지는 가상주소=물리주소인데 vmlinux.ld.S에서는 _text 위치 따로 idmap_pg_dir 위치 따로입니다.
이러한 몇 가지 이유로 챕터2부터 전체적으로 막혀 잘 진행이 되지 않습니다.
3장부터는 C 코드로 구성되어있던데, 이 부분을 누군가 답변해주시면 무사히 난관을 해쳐나갈 수 있을 것 같습니다.
부디 답변을 부탁드립니다.
양현우 드림
안녕하세요. 저자 구본규입니다.
먼저 vmlinux.lds.S와 링커 스크립트에 대해서 조금 설명 드리겠습니다.
- vmlinux.lds.S는 전처리기를 거쳐 vmlinux.lds로 파싱됩니다.
- vmlinux.lds는 같은 위치에 생성되며, 이는 링커에게 메모리 상의 섹션 배치 등을 지시하는 링커 스크립트(linker script)입니다.
- 링커 스크립트에서 .(dot)은 현재 위치를 나타내는 location counter입니다.
- 섹션이 배치될 메모리 위치를 지정하지 않는 경우에 섹션 주소는 자동으로 현재 location counter로 지정되며, location counter는 배치한 섹션의 크기만큼 증가됩니다.
. = ALIGN(PAGE_SIZE); // location counter를 다음 PAGE_SIZE 바운더리로 설정
idmap_pg_dir = .; // idmap_pg_dir 심볼의 위치를 현재 location counter 위치로 설정 (정렬 효과)
. += IDMAP_DIR_SIZE; // location counter를 IDMAP_DIR_SIZE만큼 증가
swapper_pg_dir = .;
. += SWAPPER_DIR_SIZE; // location counter를 SWAPPER_DIR_SIZE만큼 증가
이렇게 링커 스크립트에 따라 링커가 배치한 결과는 /System.map이나 /vmlinux (elf파일)을 통해 확인 가능합니다.
$ aarch64-linux-gnu-nm vmlinux
또는
$ cat System.map
...
ffffff8008e43930 B __bss_stop
ffffff8008e44000 B idmap_pg_dir // idmap_pg_dir 심볼이 정렬된 주소에서 시작됩니다.
ffffff8008e47000 B swapper_pg_dir // swapper_pg_dir은 +IDMAP_DIR_SIZE 주소에서 시작됩니다.
ffffff8008e49000 A __efistub__end
ffffff8008e49000 B _end
여기서 B는 uninitialized data section으로 흔히 BSS라 부르는 섹션임을 의미합니다.
즉, 메모리에 로드된 시점에서 idmap_pg_dir, swapper_pg_dir은 메모리 상의 특정 위치(주소)일 뿐 초기화 되지 않은 상태이며,
실제 초기화 되는 부분은 arch/arm64/kernel/head.S의 __create_page_tables 프로시저입니다.
질문 주신 ". = KIMAGE_VADDR + TEXT_OFFSET;" 도 location counter의 초기값을 설정하는 부분입니다.
(따라서 실제 메모리 할당하는 코드가 아닙니다)
그리고 idmap_pg_dir과 swapper_pg_dir의 역할은 다음 챕터를 보셔야 이해될텐데,
일단 MMU가 주소 변환에 사용할 정보를 담은 테이블이라고 생각하시기 바랍니다.
idmap_pg_dir과 swapper_pg_dir 테이블이 메모리 상에 구성되는 과정과 MMU가 그 위치를 찾아 테이블 내용을 해석해 변환하는 과정을 구분지어 생각하시기 바랍니다.
테이블도 메모리 위에 올라가야 하므로 그 심볼 주소를 지정하는 곳이 vmlinux.lds이고,
실제 테이블의 내용을 구성하는 코드가 __create_page_tables 프로시저이며,
준비를 마치고 MMU를 활성화시키는 코드가 __enable_mmu 프로시저입니다.
MMU가 켜지면 그 때부터 가상주소를 페이지 테이블에 기록된대로 해석해 물리주소로 변환하여 메모리에 접근하게 됩니다.
MMU를 활성화시키는 명령은 말 그대로 MMU를 켜기만 했을 뿐 코드가 동작 중인 주소까지 즉시 재계산해 업데이트 하지 않습니다.
따라서 __enable_mmu 프로시저의 끝에서 절대주소로 분기 할 때까지 접근하는 가상주소를 유효하게 해주는 idmap_pg_dir 테이블을 사용합니다.
다시 말씀드리면 idmap_pg_dir과 swapper_pg_dir에 생성되는 것은 MMU가 주소 변환에 사용하는 변환 테이블이며,
MMU가 이 테이블을 사용하기 위해 페이지 단위로 정렬되어 있어야 한다는 제약을 충족시키기 위해 vmlinux.lds에서 메모리 주소만 정렬시켜 준 것입니다.