효율적으로 인터럽트를 설정 및 처리하기 위해 GIC를 사용하는데, GIC의 가장 중요한 기능은 다음과 같이 요약할 수 있습니다.

 인터럽트의 우선순위를 각 I/O 장치에 적용해 설정
 입력으로 받은 인터럽트를 CPU에 전달

GIC는 인터럽트 컨트롤러이므로 GIC의 주된 기능은 다른 인터럽트 컨트롤러와 다르지 않습니다. 다른 인터럽트 컨트롤러와 다른 점은 GIC는 인터럽트를 4가지 종류로 분류해 관리한다는 점입니다. 이어서 GIC에서 정의된 4가지 인터럽트 소스를 소개하고 인터럽트 상태 머신을 설명합니다. 

GIC는 4가지 타입의 인터럽트 소스를 처리하며 다음 표에서 종류를 확인할 수 있습니다.


표 10.2 GIC를 구성하는 인터럽트 소스의 종류
 

이처럼 GIC는 페리페럴에서 전달된 4가지 인터럽트 입력을 처리하는데, 기능에 따라 다음과 같이 분류할 수 있습니다.

 PPI, SPI, LPI: 외부 I/O 디바이스에서 발생하는 하드웨어 인터럽트
 SGI: 외부 I/O 장치가 아닌 소프트웨어적으로 발생되는 인터럽트 

외부 I/O 장치에서 인터럽트가 발생하면 SPI나 PPI를 통해 CPU 코어로 전달됩니다. 먼저 SPI와 PPI에 대해 알아봅시다.

 

< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>

 

<강의 영상>

 


이어서 X30 레지스터를 스펙 문서 분석을 통해 알아봅시다.

출처: ARM® Cortex®-A Series Programmer's Guide for ARMv8-A https://developer.arm.com/documentation/den0024/latest/ 
6.4 Flow control
...
Calls to subroutines, where it is necessary for the return address to be stored in the link register (X30), use the BL instruction. This does not have a conditional version. BL behaves as a B instruction with the additional effect of storing the return address, which is the address of the instruction after the BL, in register X30.

X30 레지스터를 설명한 내용인데, 요약하면 다음과 같습니다.

 X30은 BL 명령어를 실행해 주소나 함수로 분기했을 때 서브루틴에서 복귀할 주소를 저장하는 레지스터다.  

 

---

[중요] Armv7의 R14_<mode> 레지스터와 Armv8의 X30 레지스터

Armv7 아키텍처에서는 R14가 각 동작 모드별로 존재했습니다. R14는 서브루틴(함수 호출)을 호출할 때 복귀할 주소뿐만 아니라 익셉션이 유발된 후 복귀할 주소를 저장했습니다.

그런데 Armv8 아키텍처에서는 익셉션이 발생한 후 복귀할 주소를 저장하는 ELR_ELx(Exception Link Register)를 익셉션 레벨별로 정의합니다. 그래서 Armv8 아키텍처에서 X30은 익셉션 레벨별로 존재하지 않습니다.
---

이처럼 SP와 X30 레지스터는 Armv8 아키텍처에서 정의된 다른 범용 레지스터와 달리 서브루틴으로 분기하기 위해 사용됩니다.

이번 절에서 소개한 SP_ELn과 X30 레지스터를 표로 정리해 봅시다.


표 13.1 SP_ELn과 X30 레지스터의 특징
  
어셈블리 명령어를 분석할 때 SP_ELn과 X30 레지스터를 보면 그 역할과 기능이 잘 떠올랐으면 좋겠습니다.

 <강의 영상>

 

 

[Arm프로세서] Armv8 - SP_ELn과 X30 레지스터란?
 
Armv8 아키텍처에서 정의된 레지스터 중 SP_ELn과 X30 레지스터는 AAPCS와 연관된 핵심 레지스터입니다. 전체 레지스터 목록 중에서 SP_ELn과 X30 레지스터를 먼저 소개하겠습니다.

전체 레지스터 목록 중 SP_ELn과 X30 레지스터

다음 그림을 보면서 Armv8 아키텍처에서 정의된 레지스터 중 AAPCS와 연관된 레지스터 목록을 알아봅시다.


 
그림 13.1 Armv8 아키텍처의 레지스터 목록 중 AAPCS와 연관된 레지스터

위 그림은 Armv8 아키텍처에서 정의된 레지스터 목록입니다. 그림에서 빗금으로 표기된 박스를 보겠습니다.

SP_EL1은 EL1(익셉션 레벨1)에서 실행되는 SP 레지스터, SP_EL0는 EL0(익셉션 레벨0)에서 실행되는 SP 레지스터를 의미합니다. 또한 하이퍼바이저가 실행되는 EL2에서 실행되는 SP 레지스터는 SP_EL2로 표기합니다.

---
[정보] SP 레지스터와 SP_ELn 레지스터의 상관관계

Armv8 아키텍처에서는 SP 레지스터를 SP_ELn으로도 표기합니다. 그렇다면 다음과 같은 명령어는 어떻게 해석해야 할까요?

SUB SP, SP, #4

Armv8 아키텍처의 어셈블리 명령어에서 SP가 보이면 어셈블리 명령어를 실행하는 익셉션 레벨을 기준으로 해석하면 됩니다. 만약 위 명령어를 실행하는 익셉션 레벨이 EL1이면 SP_EL1, EL2이면 SP_EL2를 의미합니다.

예를 들어, 운영체제 커널은 EL1에서 실행되므로 커널 공간에 있는 프로세스의 SP 레지스터는 SP_EL1에서 확인할 수 있습니다. 하이퍼바이저는 EL2에서 실행되므로 하이퍼바이저에서 실행되는 프로세스의 SP 레지스터는 SP_EL2에서 확인할 수 있습니다. 
---

X30 레지스터는 링크 레지스터를 뜻하며, 서브루틴을 호출한 다음에 복귀할 주소를 저장합니다. Armv7 아키텍처의 링크 레지스터인 R14와 같은 기능입니다.

SP_ELn 아랫 부분에 표기된 레지스터를 보겠습니다. 프로세서의 상태를 저장하는 SPSR(Saved Program Status Register) 레지스터와 ELR(Exception Link Register) 레지스터가 있습니다. 이 레지스터도 익셉션 레벨별로 정의돼 있어 SPSR_ELx, ELR_ELx로 표기합니다. 이 레지스터는 AAPCS와는 직접적인 연관은 없습니다. 익셉션이 유발돼 익셉션을 처리한 다음에 이전 익셉션 레벨로 복귀하기 위한 용도로 쓰입니다. 

Arm 스펙 문서에서 본 SP 레지스터

이번에는 Arm 스펙 문서를 보면서 SP와 X30 레지스터에 대해 더 자세히 알아봅시다. 먼저 SP 레지스터를 살펴봅시다.  

출처: ARM® Cortex®-A Series Programmer's Guide for ARMv8-A https://developer.arm.com/documentation/den0024/latest/
4.1.2 Stack pointer

In the ARMv8 architecture, the choice of stack pointer to use is separated to some extent from the Exception level. By default, taking an exception selects the stack pointer for the target Exception level, SP_ELn. 

For example, taking an exception to EL1 selects SP_EL1. Each Exception level has its own stack pointer, SP_EL0, SP_EL1, SP_EL2, and SP_EL3.

위 스펙 내용에서 중요한 부분은 다음과 같이 정리할 수 있습니다.

 익셉션 레벨별로 스택이 존재하는데, 이 스택 주소를 저장하는 SP_ELn 레지스터가 있다.
 SP_EL0, SP_EL1, SP_EL2, SP_EL3와 같은 스택 포인터 레지스터가 존재한다.

여기서 언급된 스택은 프로세스가 사용하는 스택을 의미합니다. Armv8에서 정의된 SP_ELn 레지스터도 프로세스의 스택 주소 구간 내 특정 주소로 업데이트됩니다. 
 

<강의 영상>

 

Armv8 아키텍처의 AAPCS를 다루기에 앞서 Armv7에서 정의된 AAPCS의 주요 내용을 요약하면 다음과 같습니다.

 서브루틴을 호출하면 프로세스의 스택 공간에 레지스터를 푸시한다.
 'BL [주소]' 명령어를 실행해 서브루틴으로 분기하면 Arm 코어는 링크 레지스터인 R14에 복귀할 주소를 업데이트한다.
 서브루틴을 호출할 때 전달되는 인자는 R0 ~ R3 레지스터에 저장된다.
 함수의 리턴값은 R0 레지스터에 저장된다.

위에서 설명한 내용은 Armv8 아키텍처 관점에서 다음과 같이 바꿔서 설명할 수 있습니다.

 서브루틴을 호출하면 프로세스의 스택 공간에 레지스터를 푸시한다.
 'BL [주소]' 명령어를 실행해 서브루틴으로 분기하면 Arm 코어는 링크 레지스터인 X30에 복귀할 주소를 업데이트한다.
 서브루틴을 호출할 때 전달되는 인자는 X0 ~ X7 레지스터에 저장된다.
 함수의 리턴값은 X0 레지스터에 저장된다.

위 내용을 보면 알 수 있듯이 Armv7 아키텍처에서 다룬 AAPCS의 주요 개념은 Armv8 아키텍처에 거의 그대로 적용됩니다. 서브루틴을 호출할 때 사용되는 레지스터와 어셈블리 명령어만 다릅니다. 

이번 포스트에서는 Armv8 아키텍처에서 정의된 레지스터 목록 중에서 AAPCS와 관련된 레지스터를 소개합니다. Armv7 아키텍처에서 소개한 AAPCS와 유사한 개념이 많으니 차이점 위주로 설명하겠습니다.

[정보] AAPCS64란?
이번 장에서 소개하는 Armv8 아키텍처의 AAPCS는 64비트를 기준으로 설명하는데, 이를 AAPCS64로 표기합니다.

 

<강의 영상>

 

공간 지역성과 시간 지역성을 활용해 캐시 알고리즘을 구성할 수 있다고 소개했습니다. 그런데 데이터 구조나 알고리즘을 사용할 때 메모리에 접근하는 패턴을 관찰하면 시간 지역성이나 공간 지역성을 확인하기 어려운 경우가 있습니다. 예를 들어, 링크드 리스트로 데이터를 관리할 때 메모리에 접근하는 패턴을 관찰하면 특정 위치에 있는 데이터에 다시 접근할 확률이 높지 않아 시간 지역성과 같은 특징을 지니지 않습니다. 

 

또한 링크드 리스트는 배열과 달리 인접한 메모리 주소 공간에 접근하지 않아 공간 지역성에도 맞지 않습니다. 그런데 코드를 유심히 분석하면 프로그램은 함수의 호출과 자료구조로 구성돼 있다는 사실을 알 수 있습니다. 자주 사용하는 자료 구조나 알고리즘은 어느 정도 정해져 있습니다. 예를 들어, 링크드 리스트나 스택과 같은 데이터 구조나 알고리즘을 사용해 데이터를 관리하는 패턴을 볼 수 있습니다. 

 

알고리즘의 특징을 활용해 프로그램이 메모리에 접근하는 패턴을 예측할 수 있는데, 이를 알고리즘 지역성이라고 합니다.
이번에는 간단한 예시 코드를 보면서 알고리즘 지역성에 대해 알아봅시다.

struct node {
02 struct node *next; // 다음 노드 주소를 저장
03 int data; // 데이터 저장 필드
04 };
05
06 struct node *curr = head->next; // 링크드 리스트 포인터에 첫 번째 노드의 주소를 저장
07 while (curr != NULL) // 노드의 포인터가 NULL이 아닐 때까지 실행
08 {
09 curr = curr->next; // 포인터에 다음 노드의 주소를 저장
10 process_data(curr); // process_data 함수를 호출해 노드를 처리
11 }

 

위 코드는 링크드 리스트를 구성하는 노드에 접근해 데이터를 처리하는 루틴입니다. 07 ~ 11번째 줄은 노드를 순회하면서 노드의 포인터가 NULL이 아닐 때까지 while 루프를 실행합니다. 그런데 링크드 리스트에 존재하는 자료 구조는 연속적인 메모리 공간에 위치하지 않습니다. 만약 링크드 리스트라는 자료 구조의 패턴을 미리 파악한다면 다음과 같이 처리할 수 있습니다.


06 struct node *curr = head->next; // 링크드 리스트 포인터에 첫 번째 노드의 주소를 저장
07 // 링크드 리스트를 순회하면서 노드 주소를 미리 로딩
08 while (curr != NULL) // 노드의 포인터가 NULL이 아닐 때까지 실행
09 {
10 curr = curr->next; // 포인터에 다음 노드의 주소를 저장
11 process_data(curr); // process_data 함수를 호출해 노드를 처리
12 }


이전 코드와 비교했을 때 달라진 점은 07번째 줄입니다. 07번째 줄은 미리 링크드 리스트를 순회해 노드포인터를 임시로 로딩하는 동작을 표현한 주석입니다. 07번째 줄과 같이 링크드 리스트를 미리 순회해 노드 포인터를 로딩하면 링크드 리스트를 구성하는 노드에 접근하는 08 ~ 12번째 줄의 실행 시간이 더 빨라질 것입니다.


링크드 리스트로 구성된 데이터를 어떤 패턴으로 접근할지는 예측이 가능한데, 이를 위해서는 링크드 리스트라는 자료구조와 알고리즘의 패턴을 이해해야 합니다. 알고리즘의 특징을 활용해 프로그램이 메모리에 접근하는 패턴을 예측하는 과정을 알고리즘 지역성이라고 합니다.


누군가 평소에 자주 들르는 카페에 다시 갈 가능성이 있을까요? 한 번도 안 간 사람보다 다시 갈 확률이 높을 것입니다. 이와 유사하게 프로그램이 특정 명령어나 특정 변수를 반복적으로 사용하는 패턴을 시간 지역성(temporal locality)이라고 합니다. 프로그램이 실행될 때 특정 명령어나 변수에 자주 접근하면 가까운 미래에 사용할 것을 대비해 사용된 명령어나 데이터를 캐시에 미리 로딩합니다.

 

앞에서 예시로 든 예제 코드를 보면서 시간 지역성에 대해 알아봅시다.

01 static int arr[10];
02 int sum = 0;
03 int weight = 10;
04
05 for (i = 0; i < 10; i++) {
06 sum += arr[i] + weight;
07 }


06번째 줄은 arr[] 배열의 &a[0] ~ &a[9] 주소에 있는 배열의 데이터를 읽어서 sum에 더하는 루틴입니다. 데이터에 접근하는 패턴을 자세히 관찰하면 다음과 같은 사실을 확인할 수 있습니다.


weight 지역 변수가 위치한 주소에 반복적으로 접근할 확률이 높다.

 


이런 특징을 활용해 &a[1] ~ &a[9] 주소에 있는 데이터를 로딩할 때 &weight 주소에 있는 데이터를 미리 로딩하면 속도 개선 효과를 얻을 수 있습니다. 이처럼 다시 접근할 것으로 예상되는 데이터를 미리 로딩해 캐시를 설계할 수 있는데, 이를 시간 지역성이라고 합니다.


시간 지역성을 활용한 다양한 알고리즘이 제안됐는데, 그중 프리페칭이 가장 널리 알려진 알고리즘입니다. 프리페칭은 최근에 CPU 코어에서 접근한 주소 접근 이력을 참고해 더 많은 양의 데이터를 미리 페치하는 기법입니다.

 

 

자주 가는 카페가 있다면 카페 옆에 있는 다른 가게에 갈 가능성도 있습니다. 이처럼 프로그램이 어떤 데
이터를 사용하면 그 데이터와 인접한 (주소에 있는) 데이터에 접근할 확률이 높습니다. 이 같은 패턴을 공
간 지역성(spatial locality)이라고 합니다.
【정보】 섹션 정보를 활용한 공간 지역성
프로그램을 컴파일하면 컴파일러는 연관된 객체(자료구조)를 특정 메모리 공간(섹션)에 모아두는 경향이 있습니다.
이번에는 다음 예제 코드를 보면서 공간 지역성에 대해 더 자세히 알아봅시다.
01 static int arr[10];
02 int sum = 0;


03
04 for (i = 0; i < 10; i++) {
05 sum += arr[i];
06 }
05번째 줄은 arr[] 배열의 &a[0] 주소에서 &a[9] 주소에 있는 배열의 데이터를 읽어서 sum에 더하는 루틴입
니다. 데이터의 위치와 데이터에 접근하는 패턴을 자세히 관찰하면 다음과 같은 사실을 확인할 수 있습
니다.
■ &a[0] ~ &a[9] 주소 구간에 위치한 데이터는 연속적인 메모리 공간에 위치한다.
■ &a[0] 주소에 접근하면 &a[1] ~ &a[9] 주소에 접근할 확률이 높다.
이런 특징을 활용해 &a[0] ~ &a[9]가 존재하는 주소에 미리 접근해 데이터를 캐시에 올려 놓으면 for 루프
가 더 빨리 동작할 것입니다. 이처럼 이미 사용된 데이터 주소 근처에 있는 데이터를 미리 로딩하는 방식
을 활용해 캐시를 설계합니다.
이처럼 공간 지역성은 최근 접근한 데이터의 주변 공간에 다시 접근하는 소프트웨어의 패턴을 뜻합니다.
이런 특징을 잘 활용해 연속적인 공간에 존재하는 메모리 데이터를 미리 로딩하면 메모리에 접근하는 횟
수를 줄일 수 있습니다. 이 같은 구조로 설계된 시스템에서는 같은 코드를 실행해도 더 빨리 실행될 수 있
습니다.


【정보】 디맨드 페치란?
공간 지역성을 활용하는 기법 중 하나는 디맨드 페치(demand fetch)입니다. 디맨드 페치란 프로그램이 메인 메모리에
접근해 명령어나 데이터를 캐시에 로딩한 다음에 일정 시간 동안 유지하는 방식을 의미합니다.



이전 절에서는 한 개의 캐시를 기준으로 캐시의 기본 개념을 설명했습니다. Arm 프로세서를 포함한 대부분의 프로세서는 멀티 레벨로 캐시가 구성돼 있습니다. 다음 그림을 보면서 멀티 레벨 캐시에 대해 알아봅시다. 
 


그림 18.2 메모리 계층 구조에서 캐시의 역할 

그림의 왼쪽과 오른쪽에 있는 Core는 말 그대로 CPU 코어를 뜻합니다. Core의 아랫부분을 보면 L1I$와 L1D$ 캐시가 보입니다. 여기서 L1은 레벨 1 캐시(레벨 원으로 발음)라고 하며 L1I$는 L1 명령어(Instruction) 캐시, L1D$는 L1 데이터(Data) 캐시를 뜻합니다.

위와 같이 Arm 프로세서를 비롯한 대부분 프로세서는 캐시가 여러 계층으로 구성돼 있습니다. L1(Level 1: 레벨 1 캐시), L2(Level 2: 레벨 2 캐시)는 대부분 Cortex 프로세서에서 볼 수 있고, L3 캐시까지 있는 프로세서도 확인할 수 있습니다. 그림에서 L3$ 로 명시된 부분은 L3 캐시를 나타냅니다. 

 


[중요] LLC(Last Level Cache) 란?
보통 마지막 레벨의 칩 내에 위치한 캐시를 특별히 LLC(Last Level Cache)라고 부릅니다. LLC 이후는 시간이 매우 오래 걸리는 칩 밖의 메모리 계층으로 이동하므로 특별히 구분합니다.

만약 Arm 코어에서 접근하는 데이터나 명령어가 L1, L2 캐시, 그리고 LLC에 없으면 프로세서 밖에 존재하는 메모리에 접근해 데이터나 명령어를 로딩해야 합니다. 이 과정에서 버스와 메모리 컨트롤러와 같은 하드웨어의 도움을 받아야 하므로 실행 시간이 더 오래 걸립니다. 따라서 LLC에서 가장 중요한 캐시 성능 지표는 캐시 미스로 선정하는 경우가 많습니다.

 



L1 캐시, L2 캐시를 처음 봤을 때 "캐시를 L1으로만 잘 설계하면 되지, 여러 계층의 캐시를 설계하는 이유는 무엇일까?"라는 의문이 생깁니다. 여러 계층의 캐시를 구성하면 프로세서의 성능을 최대한 끌어올릴 수 있기 때문입니다. 캐시의 접근 속도와 용량 사이에 트레이드오프가 있기 마련인데, 멀티 레벨로 캐시를 구성하면 트레이드오프를 최대한 극복할 수 있습니다.

프로세서가 데이터를 요청하면 다음과 같은 순서로 처리합니다. 

1. 먼저 L1 캐시에서 찾는다.
2. 만약 L1 캐시에 데이터가 없으면 L2 캐시에서 찾는다.
3. 그래도 데이터가 없으면 LLC에서 찾는다.
4. 만약 LLC에 도달해도 데이터가 없으면 메인 메모리에 접근해 데이터를 가져 온다.

CPU 코어에 가장 인접한 L1 캐시 내부는 명령어 캐시와 데이터 캐시가 따로 존재합니다. 그다음 레벨인 L2 캐시 이상의 캐시는(L2 캐시, L3 캐시) 하나의 캐시에서 명령어와 데이터를 함께 처리하는 방식으로 설계합니다. 

대부분의 Arm 프로세서는 캐시를 멀티 레벨로 구성하며, 성능을 극대화하는 방향으로 시스템을 설계합니다. 멀티 캐시에 대한 더 자세한 내용은 18.3절 ‘멀티 레벨 캐시’를 참고합시다.

이렇게 해서 캐시를 구성하는 주요 개념을 설명했습니다. 다음 절에서는 캐시와 관련된 알고리즘을 소개합니다.

 

 

 

MMU에서 가상 주소를 물리 주소로 변환하는 구조를 알아봤으니 변환 테이블의 내부 구조와 가상 주소가 물리 주소로 변환되는 실행 흐름을 알아봅시다.


페이지 테이블 엔트리


변환 테이블을 통해 가상 주소를 물리 주소로 변환하려면 먼저 가상 주소의 형식을 알아야 합니다. 가상 주소 영역을 일정한 크기의 블록으로 나눌 수 있는데, 여기서 한 개의 테이블 엔트리는 한 개의 블록에 해당됩니다.

 


그림 19.7 가상 주소가 변환 테이블을 통해 물리 주소로 변환되는 흐름

각각의 엔트리는 물리 메모리에서 해당되는 블록 주소와 물리 주소에 접근할 때 사용할 속성으로 구성돼 있습니다.

변환 테이블은 일종의 엔트리 블록으로 구성돼 있습니다. 변환 테이블에 있는 엔트리 0은 블록 0, 엔트리 1는 블록 1에 대한 매핑 정보를 제공합니다. 또한 각각 엔트리는 물리 주소의 블록에 해당되는 주소와 속성 정보를 담고 있습니다.


페이지 테이블 룩업

테이블 룩업은 변환 작업이 진행될 때 실행됩니다. 운영체제의 프로세스가 주소에 접근하면 주소 변환이 실행됩니다. 이때 가상 주소는 다음 다이어그램과 같이 두 가지 부분으로 나뉩니다.

 


그림 19.8 가상 주소의 구조와 페이지 룩업이 진행되는 흐름

위 다이어그램은 싱글 페이지 룩업 동작을 나타냅니다. ①에서 '엔트리'로 명시된 상위 비트는 찾을 블록 엔트리를 알려주는데, 테이블에 접근하는 인덱스로 활용됩니다. 변환 테이블을 구성하는 각각 엔트리 블록은 가상 주소에 대한 물리 주소의 베이스 주소를 담고 있습니다. 

그림을 통해 전체 구조를 확인했으니 주소가 변환되는 과정을 알아봅시다. 위 그림에서 ①로 표시된 부분은 CPU가 가상 주소를 실행하는 동작을 나타냅니다. CPU에서 실행되는 프로세스 입장에서 바라보는 주소는 가상 주소입니다. 이번에는 화살표와 함께 ②로 표시된 부분은 가상 주소의 베이스 주소를 바탕으로 주소 변환 정보를 담고 있는 페이지 테이블에 접근하는 동작입니다. 페이지 테이블의 시작 주소를 나타내는 TTBR1_EL1 레지스터를 통해 페이지 테이블의 베이스 주소를 참고해 변환 정보가 담긴 페이지 테이블 엔트리의 주소를 찾습니다. 그림의 ③은 페이지 테이블 엔트리 레코드를 읽어서 물리 주소로 변환하는 동작을 나타냅니다. 물리 주소에서 보이는 베이스는 페이지 테이블 엔트리에 있는 주소 정보입니다. ①에서 보이는 '오프셋'으로 명시된 하위 비트는 블록 내부의 오프셋을 나타내며, 변환 과정에서 변경되지 않습니다.


멀티 레벨 페이지 변환


이제까지 한 개의 주소 변환 테이블을 통해 주소가 변환되는 과정을 알아봤습니다. 한 개의 변환 테이블을 통해 한 번에 주소가 변환되므로 이를 싱글 룩업이라고 합니다. 그런데 Aarch64에서는 다수의 변환 페이지 테이블을 사용해 주소 변환 작업이 수행될 수 있는데, 이를 멀티 레벨 페이지 룩업을 통해 가상 주소가 물리 주소로 변환된다고 할 수 있습니다. 

이어서 다음 그림을 보면서 멀티 레벨 페이지 룩업의 실행 흐름을 알아봅시다.
 
출처: Learn the architecture - AArch64 memory management https://developer.arm.com/documentation/101811/latest/

그림 19.9 가상 주소가 멀티 레벨 변환 페이지를 통해 변환되는 과정

ARMv8-A 아키텍처에서 지원하는 최대 페이지 변환 레벨은 4개이며, 실제 레벨의 범위는 0 ~ 3 사이입니다. 멀티 레벨로 구성된 페이지 테이블을 구성하면 큰 사이즈의 블록과 작은 사이즈의 블록을 모두 활용할 수 있습니다. 멀티 레벨 변환 테이블을 구성하는 블록의 특징은 다음과 같습니다.

 큰 블록은 작은 블록보다 페이지 룩업에 걸리는 연산이 더 적습니다.(추가로 큰 블록들은 TLB에 더 효율적으로 캐싱됩니다.)
 작은 블록들은 TLB에서 캐싱할 때 비효율적입니다. 캐싱이 비효율적인 이유는 작은 블록들이 주소를 변환하는 과정에서 메모리에 더 자주 액세스해 데이터 읽기를 시도하기 때문입니다.


[정보] 큰 크기 블록과 작은 크기 블록을 사용할 때의 트레이드오프
큰 사이즈의 블록과 작은 사이즈의 블록으로 구성된 멀티 변환 테이블을 사용해 가상 주소를 변환할 때 트레이드오프가 있습니다. 이는 멀티 레벨 캐시와 비슷한 개념으로 볼 수 있습니다. L1 캐시의 가장 중요한 성능 지표는 캐시 라인을 읽어 오는 속도이고 L2 캐시의 가장 중요한 성능 지표는 캐시 히트 비율입니다. 따라서 큰 사이즈 블록과 작은 사이즈의 블록을 적절히 활용해 멀티 레벨 테이블을 구성할 필요가 있습니다.

 

 



 

Documentation – Arm Developer

 

developer.arm.com

 

 

MMU의 주된 역할은 가상 주소를 물리 주소로 변환하는 일입니다. 그렇다면 MMU는 어떻게 구성돼 있고 어떤 과정으로 가상 주소를 물리 주소로 변환할까요? 다음 그림을 보면서 MMU의 실행 흐름에 대해 알아봅시다.

 
출처: Learn the architecture - AArch64 memory management https://developer.arm.com/documentation/101811/latest/

그림 19.6 MMU를 통해 주소가 변환되는 흐름

프로세스가 가상 주소에 접근하면 먼저 MMU에게 주소가 전달됩니다. MMU는 먼저 TLB(Translation Lookaside Buffer)에 해당 주소에 대한 변환 정보가 있는지 확인합니다. 여기서 TLB는 최근에 사용된 주소 변환 정보를 담고 있는 일종의 캐시입니다. 

만약 가상 주소에 대한 변환 정보가 TLB에 있으면 바로 물리 주소로 변환합니다. 만약 MMU가 TLB 내에 최근에 변환된 테이블 정보를 읽지 못하면 ‘Table Walk Unit’을 통해 메모리로부터 적절한 테이블 엔트리를 읽습니다.

[중요] 캐시에 접근하기 전에 물리 주소로 변환하는 이유

Arm 코어 옆에 캐시가 있는데, 캐시에 접근하려면 먼저 MMU를 통해 가상 주소를 물리 주소로 변환하는 과정이 완료돼야 합니다. 메인 메모리 주소에 접근하기 전에 캐시에 접근해 메모리 주소가 있는 지 체크하기 때문입니다. 이 같은 과정을 캐시 룩업이라고 합니다.


+ Recent posts