이전 시간에는 두 개의 매핑 테이블을 만드는 과정을 살펴봤어요. 이제 남은 것은 CPU가 가상 메모리를 활성화 시키고, 해당 매핑 테이블을 사용하도록 시스템 레지스터들을 세팅해주는 일이에요. 이 부분은 하드웨어어 매우 밀접한 부분이므로, 중요한 레지스터들만 살펴보도록 할게요. 그럼 시작해볼까요?
해당 글의 타겟 아키텍처는 aarch64이고, kernel code는 5.1버전을 다룹니다. 유의하세요!
__cpu_setup
아래의 코드는 __create_pagetable 다음에 호출되는 __cpu_setup 함수에요.
위 코드 블록에서 하는 일들은 사실 그렇게 중요한 내용이 아니에요.
Line 2~3에서는 주석대로 Local TLB를 invalidate해요.
Line 5~6에서는 CPU의 부동소수 연산 기능을 활성화시켜요.
Line 7~11은 디버깅과 관련된 세팅이에요.
보신 대로, MMU와는 상관없는 부분이에요. 이 아래부터 MMU 활성화에 관련된 부분이에요.
위 코드를 살펴보면 실제로 두 개의 시스템 레지스터에 값을 쓰는데요, 하나는 mair_el1이고, 나머지는 tcr_el1이에요. 그럼 이 두개의 레지스터에 대해 알아볼게요.
mair_el1은 memory attribute indirect register의 약자로, 이 레지스터는 아래 그림 처럼 8개의 메모리 attribute를 저장해요.
attribute는 매핑 테이블에서 매핑된 물리 메모리의 attribute를 표현하는데 사용되어요. Block/Page 엔트리 디스크립터를 살펴보면 아래와 같이 다양한 속성을 저장하는 Lower attribute 필드가 있는데요, 여기서 AttrIndx[2:0]이 mair에 저장된 attribute의 인덱스를 가르키는 거에요.
그럼 이제 가볍게 메모리의 타입에 대해 살펴볼게요. ARM v8에서는 두 가지의 메모리 타입이 존재하는데요, 바로 Normal과 Device 타입이에요.
Normal 메모리 타입은 일반적인 메모리의 유형으로 RAM, ROM, FLASH가 여기에 해당해요. 그리고 Normal 메모리의 중요한 특성으로, Speculation, Reorder, Merge 등이 가능해요. 그렇기 때문에 빠른 속도로 동작할 수 있는 메모리인 것이지요.
Device 메모리는 메모리에 대한 접근이 sided effect가 있을 수 있는 영역을 뜻해요. 일반적으로, 디바이스가 메모리 맵된 영역에 해당해요. 예를 들어, UART의 FIFO register에 접근하는데, 이런 접근이 Caching 되거나 Speculation 되서는 안되겠지요? Device 메모리는 여러 속성들이 있는데,
Gather 속성은 여러 access를 하나의 bus transaction으로 합쳐도 되는지를 알려주는 속성이에요. 예를 들어 연속된 주소에 2byte 씩 write를 할 때, 이것을 4byte write로 합치는 것이지요.
Reorder 속성은 access의 순서가 뒤바뀌어도 되는지를 알려주는 속성이에요. 프로그램의 load/store 순서대로 가 Bus transaction이 발생해야 하는지를 알려주는 것이지요.
Early Write Ack 속성은 Write가 실제로 end point에 도달해야 하는지를 알려주는 속성이에요. 즉, Write가 중간 버퍼에 저장되어 Ack을 보낸는 것을 허용하는 지를 알려주는 것이지요.
이런 속성들과 함께 표현하여 Device-nGnRnE, Device-nGnRE , Device-GRE와 같이 표현할 수 있어요. 커널에서도 이 표현법을 사용하여 매크로를 정의하였어요.
아마, 이 내용을 읽으면서 잘 이해가 안 가실것 같은데요. 사실은 그럴 수 밖에 없어요. 해당 내용은 Out Of Order Processor와 Memory model에 대해 알아야 완전히 이해할 수 있거든요. 그래서 일단은 "이런 느낌이구나" 정도로 정리하시고 나중에 다시 자세히 보도록 할게요.
이제 tcr_el1에 대해 알아보도록 할게요. 아래 그림에서 이 레지스터의 필드를 보여주고 있어요. 머리가 아플 정도로 정말 많은 정보들을 저장하지요? 여기서 우리가 다룰 필드들은 노란색으로 칠해두었어요.
이제 각각의 필드에 대해서 알아보도록 할게요.
Top Byte ignored의 줄임말로, 상위 1바이트를 Translation에 사용할지 안 할지를 결정해요. 해당 기능이 설정되면 상위 1바이트는 다른 용도로 사용이 가능하겠지요? KASAN에서 해당 영역을 tag로 사용하고 있어요.
TB0: TTBR0의 영역의 주소를 Translation을 할 때, 해당 필드가 1로 설정되어 있고 bit[55]가 0이라면 bit[63:56]은 0으로 간주해요.
TB1: TTBR1의 영역의 주소를 Translation을 할 때, 해당 필드가 1로 설정되어 있고 bit[55]가 1이라면 bit[63:56]은 1으로 설정된다.
TG1/TG0: Granule size이에요. 사이즈는 4KB, 16KB, 64KB가 있고 일반적으로 4K를 사용해요.
TG0: TTBR0의 Granule size를 정해요.
TG1: TTBR1의 Granule size를 정해요.
0b00: 4KB
0b01: 64KB
0b10: 16KB
T1SZ/T0SZ: 이전에 봤던 TTBRx의 Translatoin 범위를 정의하는 레지스터이에요. 영역의 크기는 아래와 같이 정의되어요.
SH1/SH0: Table walking 할 때 사용할 때 사용하는 Shareability attribute이에요.
IRGN0/IRGN1: Table walking할 때 사용하는 Inner cacheability attribute이에요.
ORGN0/ORGN1: Table walking할 때 사용하는 Outter cacheability attribute이에요.
SHx, IRGNx, ORGNx 레지스터들은 Table walking을 다룰 때 자세히 다루도록 할게요.
AS: address space id의 사이즈를 나타내는 필드이에요. asid는 TLB의 캐시를 매칭할 때 사용해요. 즉, 동일한 asid와 address일 때만 TLB hit하기 때문에 태스크 변경되어도 TLB flush를 할 필요가 없지요. asid는 TTBR에 PGD의 주소와 함께 아래 그림처럼 저장되어요.
0b0: asid의 크기가 8비트로 설정되어요.
0b1: asid의 크기가 16비트로 설정되어요.
만약 8bit만 사용하기로 했다면, 앞의 8bit는 0으로 간주되어요.
A1: TTBR0에 asid를 저장하는지 TTBR1에 asid를 저장하는지를 결정해요. 저장된 위치와는 상관없이 asid는 TTBR0, TTBR1을 통해 주소를 변환할 때 모두 적용되어요.
결과적으로, TCR 레지스터에 원하는 기능들을 설정하고 해당 함수를 마무리하게 되어요.
정리
이번 시간에는 하드웨어 specific한 내용이라, 해당 아키텍처에 익숙하지 않은 분이라면 어려웠을거라 생각해요. 또한 메모리 타입과, asid, shareability와 같이 아직은 생소한 개념이 나와서 더욱 힘드셨을 거라 생각해요. 하지만 이 부분에 대해서는 앞으로 천천히 더 자세히 알아볼테니 이번 시간에는 이 정도로 정리를 하도록 할게요. 다음 시간에는 MMU를 정말로 활성화시키고 Start_kernel로 진입하는 과정을 살펴보도록 할게요. 모두 수고하셨어요~
Series
Comments