https://developer.arm.com/documentation/101811/latest
1. 가상주소와 물리주소란?
우리가 사용하는 CPU는 가상주소(Virtual address, 이하 VA)를 이용하는 반면, 실제 메모리에 접근이 이뤄질 때는 물리주소 (Physical address, 이하 PA)를 이용합니다.
위 그림은 소프트웨어가 메모리에 올라왔을 때의 두 가지 모습,
- 좌측은 OS와 개발자 입장에서 본 메모리(VA)의 모습,
- 우측은 실제 메모리(PA)의 모습을 보여주고 있습니다.
- 두 메모리 사이에는 주소 변환을 위한 MMU의 translation table이 있습니다.
잘 알고 계시겠지만 정리하자면, VA를 사용해서 얻는 이득을 정리하면 다음과 같습니다.
- 여러 군데로 조각화된(Fragmented) 물리주소를 하나의 큰 연속된 덩어리로 간주할 수 있습니다.
- 각 프로세스는 오직 자신만의 독립된 메모리 덩어리를 가질 수 있습니다.
- 독립된 메모리 공간 덕분에 같은 PA를 서로 다른 VA로 가리킬 수 있습니다. 즉, 주소를 다루는 데 유연성이 크게 증가합니다.
2. 가상주소의 구조와 변환과정에 대해 (주소 관점에서)
따라서 반드시 가상주소와 물리주소를 변환해주는 과정이 필요 합니다. 시스템의 전체적인 성능을 보장하기 위해 이러한 변환은 다양한 요소와 관리기법을 이용해서 최대한 빠르고 효율적으로 해야합니다. 그렇다면 어떤 방법을 사용하면 좋을까요?
- TLB : 따로 기록해둔 VA-PA 변환 정보를 참조해서, 기록이 있다면 변환 필요없이 바로 기록 이용.
- 다단계 : 큰 범위에서 작은 범위로 좁혀가며 특정 물리 메모리 프레임을 특정.

다단계를 조금 더 자세히 예를 들어서 설명해보겠습니다.
10,000개의 노드가 연결된 연결리스트의 8,888번째 노드를 찾고싶다면,
head부터 포인터를 8,887번 순차적으로 조회해야만 원하는 노드를 찾을 수 있습니다.
만일, 1,000 단위, 100단위, 10단위로 주소가 기록된 배열 A, B, C가 있다면 어떨까요?
- 먼저 A의 8번째 요소에 접근한 뒤 다음 테이블 주소를 찾습니다.
다음 테이블은 [8,000, 9,000)까지 범위를 100단위로 주소가 기록된 8000_B입니다. - 8000_B의 8번째 요소에 접근한 뒤 다음 테이블 주소를 찾습니다.
다음 테이블은 [8,800, 8,900)까지 범위의 10단뒤로 주소가 기록된 8800_C입니다.
위 그림처럼 큰 덩어리부터 작은 덩어리를 찾아가는 방법은 32번만에 원하는 노드의 주소를 찾을 수 있습니다. (8,888 vs 32)
이를 적용하면, 먼저 큰부분 (e.g.1GB 블록단위)을 찾고, 중간부분 (e.g. 2MB 블록단위)를 찾고, 작은부분 (e.g. 4KB 블록단위)으로 특정 페이지(프레임)을 가리키는 엔트리를 찾습니다.


64-bit 아키텍처에서 VA는 64-bit로 표현하지만, 실질적으로 주소를 가리키는 데 사용하는 부분은 이 중 일부입니다. (64-bit를 모두 주소 지정하는 데 사용하면...최대 16에타바이트까지 표현할 수 있는데 너무 낭비죠? ㅋㅋㅋ)
위에서 언급한 '다단계 변환'을 구현하기 위해서는 VA 64-bit를 여러 부분으로 쪼개서 특정 변환 테이블에서 사용 합니다. 보통은 변환 테이블 3개 또는 4개를 채택하고, Level 3 방식은 39-bit가 주소를 (TnSZ = 25-bit), Level 4 방식은 48-bit를 주소를 가리키는 데 사용(TnSZ = 16-bit)합니다.

간략한 설명이지만, 'TCL_EL1' 레지스터는 주소변환에 관한 제어를 담당하는 레지스터 입니다. 이 레지스터 속 'TnSZ' 라는 bitfield가 VA에서 어느만큼 길이를 주소를 지정하는데 사용할지를 결정합니다. 일반적으로 이 값는 25를 사용하거나 16을 사용합니다. 그러므로 VA 속에서 단 39-bit 또는 48-bit만 주소를 지정하는데 사용되고, 나머지는 가리킨 주소의 특성, 접근권한 등의 정보를 담는데 사용합니다.

위에서 글로 설명했던 내용을 그림으로 나타내면 위 그림과 같습니다.
- 먼저, 'TTBRn_EL1' 이라는 레지스터를 읽어서 주소 변환이 시작되는 변환 테이블의 시작주소를 얻습니다. (TnSZ = 25라면, [38: ]부터 주소로 사용되므로 Level 1 변환 테이블의 시작주소가 들어있습니다.)
- VA를 여러 부분으로 쪼갠 것들 중 Level 1 변환 테이블에서 사용하는 부분인 [38:30] bitfield를 테이블 시작주소에 더해 원하는 엔트리를 찾습니다. (수식으로 나타내면 (*TTBRn_EL1 + ([38:30] * 8-byte)))
- 해당 엔트리에는 다음 레벨 변환 테이블(Level 2 변환 테이블)의 시작주소가 들어있습니다.
- 과정2에서 했던것과 똑같이, 이번에는 [29:21] bitfield를 이용해 Level 3 변환 테이블의 시작주소를 찾습니다.
- 마지막으로 [20:12] bitfield를 이용해 엔트리를 찾습니다. 여기에 페이지와 맵핑되는 물리주소 프레임 주소가 들어있습니다.
- 이 주소에 VA 속 offset 부분을 덧붙여주면, 우리가 정말 접근하고자 했던 물리주소를 얻게됩니다.
3. MMU와 TLB
지금까지 우리는 VA에 초점을 맞춰서 다른건 신경쓰지않고 오로지 VA가 PA로 변환되는 과정만을 자세히 배웠습니다. 다시 한 걸음 뒤로 물러서서 전체적인 그림을 보며 이번에는 MMU에 초점을 맞춰보겠습니다.

앞서 저희가 배운 일련의 VA-PA 변환은 MMU(Memory Management Unit)가 담당합니다. CPU core에서 애플리케이션/커널을 동작하며 사용/요청하는 VA는 MMU 속 TLB를 거쳐서 PA로 변환됩니다. TLB (Translation Look-up Buffer) 란, 주소 변환을 위한 '캐시' 역할을 하는 테이블 입니다. TLB에는 최근 VA-PA 변환정보가 기록돼있습니다. 이후 똑같은 요청이 들어왔을 때,
- 만일 TLB에 해당 기록이 있다면, 다시 변환할 필요없이 바로 PA를 알 수 있습니다.
- 해당 기록이 없다면, Table Walk Unit을 통해 메모리에 접근해 PA를 받아온 뒤 기록합니다.

위 그림은 TLB에 현재 VA에 대한 변환정보가 있는지 확인 ( 룩업(look-up) 이라고 표현)한 뒤, 있으니 곧바로 PA base 주소를 get 하는 모습입니다. 앞서 2절에서 살펴봤던 반가운 내용들 (VA의 일부는 주소부분, 나머지는 offset으로 사용되는 모습)도 보이네요 다시 말씀드리지만, 'Offset'은 찾은 물리주소 프레임 base 주소로부터 얼마나 떨어진 곳인지를 나타내는 중요한 정보 입니다. 이러한 룩업이 다단계로, 큰 블록부터 작은 블록으로 이뤄집니다.

TLB의 각 entry의 구조를 나타낸 그림입니다. 다단계 변환에서 다음 테이블의 시작주소를 가지고 있던 (Level 1, Level 2 Table)은 ①의 구조일테고, 특정 물리주소 block을 가리키던 마지막 단계 테이블은 ②, ③과 같습니다.
이쯤되니 한 가지 의문이 생기네요.
- '같은 물리주소를 서로 다른 가상주소가 가리켜도' 문제 없습니다. 각 프로세스가 독립된 가상주소 영역을 가지는 게 애초에 목적이었으며 TLB가 올바르게 변환해줄테니까요.
- 그렇다면 ' 같은 가상주소인데 서로 다른 물리주소를 가리킨다면 ' 어떻게 될까요?
- 프로세스 A는 물리주소 '사과'를, 프로세스 B는 물리주소 '수박'을 얻고자 VA 0x8000을 호출합니다.
- 서로 독립된 주소공간을 갖기 때문에 충분히 일어날 수 있는 상황입니다.


이와 같은 상황을 방지하기 'TTBRn_ELx' 레지스터에는 테이블 시작주소 뿐만 아니라 현재 실행중인 프로세스에 대한 ASID(Address Space ID) 도 기록됩니다.
원래는 각 프로세스마다 독립된 가상주소 공간을 사용하기 때문에, 앞서 말씀드린 주소 충돌이 발생할 수 있어서context switching을 할 때는 TLB를 비우는 flush 작업을 해야 맞습니다.
그러나, ASID 덕분에 TLB를 flush 하지 않고 일부 entry를 재활용 할 수 있게됐습니다.
Index ASID PA_Base Attributes
[0]: Process_A 0x8000 RW
[1]: Process_B 0x8000 RW
[2]: Process_B 0x8100 RW
[3]: Process_A 0x8100 RW
TLB에 위와같이 entry가 4개 있다고 가정합시다.
- [0]과 [1] 그리고 [2]와 [3]이 같은 물리주소 `0x8000`, `0x8100`을 가리키지만 ASID값이 다르기 때문에 서로 구분할 수 있습니다.
- Process A를 실행하다가 Process B로 context switching이 발생합니다.
- TLB를 flush를 하지 않더라도 우리는 [1], [2]번 인덱스의 entry를 재활용할 수 있습니다. [0], [3]은 ASID가 다르기 때문에 무시합니다.
4. 영역별로 다양한 곳에 존재하는 변환테이블

Non-secure, Secure 영역마다, 익셉션레벨마다 자신만의 translation table들을 가지고 있습니다.
핵심은 VA는 PA로 변환돼 메모리에 접근한다는 점 입니다. 세부적인 구조는 물론 다르겠지만, 전체적으로 트러스트존이 도입돼있든, ARMv7이든, ARMv8이든, Realm과 Root 영역이 도입된 ARMv9이든 모두 동일한 구조 를 갖고있습니다. 누가 어디에 변환테이블을 갖고있는지, 물리메모리에 대한 접근권한 여부 정도 차이지요. 😊
위 그림의 좌측상단 익셉션레벨 0, 1일 때 그림을 조금 더 자세히 들여다볼까요?

가상주소는 MSB(63번 bit)가 0인지 1인지에 따라 어느 영역을 가리키는지 구별되는데,
- `0x000...`으로 시작하는 주소는 User space에서 사용하는 VA,
- `0xFFF...`으로 시작하는 주소는 Kernel space에서 사용하는 VA 입니다.
5. 정리
지금까지 배운 내용을 짧게 요약/정리해봅시다.
가상주소(VA)는 64-bit로 나타내며, 이 중 일부(39-bit 또는 48-bit)를 주소를 가리키는 데 사용하고, 나머지는 속성정보와 offset으로 활용합니다. 'TCL_EL1' 레지스터의 TnSZ 필드를 통해 가상주소 영역의 크기를 조정할 수 있습니다. VA의 주소필드는 일정한 부분들로 쪼개서 Level -2부터 Level 3까지 총 6개의 변환 테이블에서 사용할 수 있습니다. 일반적으로 주소필드는 39-bit로 변환 테이블 Level 1, 2, 3을 사용합니다.
MMU는 다단계(Level 3 또는 Level 4)로 이루어진 TLB와 Table Walk Unit으로 이루어져있습니다. 트러스트존의 non-secure/ secure 영역, ARMv8의 익셉션 레벨, ARMv9의 Realm/ Root 등 각 영역별로 변환 테이블은 따로따로 존재합니다. 이때, 변환 테이블의 시작주소는 'TTBRn_ELx' 레지스터에 저장돼있습니다.
'Embedded' 카테고리의 다른 글
【공부/정리】 시스템 소프트웨어 개발을 위한 ARM 아키텍처 구조와 원리 ② 익셉션과 익셉션레벨 (0) | 2025.02.28 |
---|---|
【공부/정리】 시스템 소프트웨어 개발을 위한 ARM 아키텍처 구조와 원리 ① 레지스터와 명령어 (0) | 2025.02.26 |
[정리/요약] ARM Cortex-M 프로그래밍 (0) | 2025.02.11 |
임베디드 레시피 Chapter 8. Debug (0) | 2025.02.01 |
임베디드 레시피 Chapter 7. Device control (0) | 2025.02.01 |