대부분 시스템 소프트웨어 개발자는 C 언어로 프로그래밍합니다. C 언어로 작성된 코드는 컴파일러에 의해 기계어로 변환되는데, 과정에서 컴파일러는 성능을 최적화하기 위해 코드를 나름대로 재해석합니다. 컴파일러에 의해 최적화된 코드를 보면 " 컴파일러는 똑똑하구나, 어떻게 코드를 알아서 생성했을까!" 생각이 듭니다.

 

C 코드를 컴파일할 컴파일러 최적화 옵션을 적용하는데, 최적화 레벨이 높을수록 컴파일러는 높은 수준의 최적화를 수행합니다. 하지만 컴파일러는 프로그래머가 작성한 코드를 최적화하는 과정에서 종종 사고를 치기도 합니다. 코드를 최적화하다 보니 선을 넘어 프로그래머가 의도하지 않은 코드를 생성하기 때문입니다. 예를 들어, 함수를 인라인으로 처리하면서 심벌이 제거되기도 합니다.

 

컴파일러가 코드를 생성하는 과정에서 최적화를 수행해 문제를 일으키면 차라리 낫습니다. C 코드와 컴파일러가 생성한 어셈블리 명령어를 비교하면 컴파일러가 명령어를 어떻게 생성했는지 확인할 있기 때문입니다. 하지만 Arm 프로세서 내부에서 명령어의 순서를 바꿔서 실행하면 소프트웨어 개발자가 이를 눈으로 확인할 없습니다. Arm 프로세서는 명령어 의존성이 없다면 성능을 최적화하기 위해 '메모리 액세스' 순서를 변경할 있기 때문입니다.

 

이러한 노멀 메모리의 특징을 Weakly-Ordered 모델이라고 명시합니다. 'weakly Ordered 모델 가장 특징은 메모리 액세스 순서가 프로그램 순서와 같을 필요가 없다라는 점인데, 여기서 말하는 프로그래밍 순서의 기준은 로드와 스토어입니다.

지금까지 Arm 아키텍처에서 정의된 노멀 메모리 타입과 디바이스 메모리 타입을 알아봤습니다. 이어서 메모리 맵을 보면서 노멀 메모리 타입과 디바이스 메모리 타입의 특징을 자세히 알아봅시다.

[정보] 메모리 맵이란?

실전 프로젝트를 진행하면 메모리 맵이란 용어를 자주 듣습니다. 메모리 맵은 무엇일까요? 프로그램에 의해 실행되는 프로세스 입장에서 바라 본 메모리 레이아웃입니다. 지도를 보면 일정한 규칙(시/도)에 따라 구획이 나눠져 있듯이 메모리 영역을 속성별로 분류한 일종의 메모리 지도가 메모리 맵입니다. 


메모리 맵의 구조도 알아보기

메모리 맵은 데이터 영역, 코드와 같은 속성으로 마킹된 다양한 영역으로 구성돼 있습니다. 다음 그림을 보면서 메모리 맵에 대해 더 자세히 알아봅시다.

 
출처: Learn the architecture - ARMv8-A memory systems https://developer.arm.com/documentation/100941/latest/

그림 17.1 메모리 맵의 구조도 

그림 17.1을 보면 가장 왼쪽 열에는 메모리 구간별 주소를, 가운데에는 영역 이름, 그리고 가장 오른쪽에는 메모리 속성을 확인할 수 있습니다.

메모리 맵의 가장 윗부분에 있는 Peripheral부터 봅시다. Peripheral로 분류되는 메모리 영역은 디바이스 메모리 타입으로 분류됩니다. 이 영역에 있는 데이터는 오로지 읽고/쓰기만 가능하며 직접 실행할 수는 없습니다. Peripheral은 프로그래머들이 작성한 코드가 로딩될 수 없는 영역이기 때문입니다. 

그다음으로 가운데에 있는 Kernel Code를 봅시다. Kernel Code에 있는 데이터는 실행만 가능하며 읽고/쓰기는 불가능합니다. 운영체제 커널 코드가 로딩된 영역이므로 코드를 읽거나 쓰면 코드 내용이 바뀌므로 읽고/쓰기는 불가능하도록 설정합니다. 

이어서 바로 위에 있는 Kernel Data를 봅시다. Kernel Data에 있는 데이터는 오로지 읽고/쓰기만 가능하며 직접 실행할 수는 없습니다.  

여기서 주의깊게 봐야 할 점이 있습니다. Kernel Data와 Kernel Code의 공통적인 속성은 Cacheable, Privileged입니다. 둘 다 캐시에 의해 처리되며 Privileged 권한으로 접근됩니다. 또는 Privileged는 Armv8 기준으로 EL1 이상의 권한으로 실행되는 동작 권한을 뜻합니다.

마지막으로 가장 아랫부분에 있는 App Data와 App Code의 영역을 알아봅시다. Kernel Data와 Kernel Code 영역이 지닌 속성과 대부분 같지만 Unprivileged라는 속성이 다릅니다. Unprivileged(비특권)는 가장 낮은 실행 권한을 뜻하며, Unprivileged 권한으로 실행되는 코드는 Privileged 권한이 부여된 코드나 데이터에 직접 접근할 수 없습니다.

메모리 맵을 구성하는 주요 영역

앞에서 든 예시와 같이 메모리 맵은 메모리 주소별 영역의 이름과 각 속성 정보로 구성돼 있습니다. 그림에서 설명한 내용 이외에 메모리 맵은 다음과 같은 속성 정보로 구성돼 있습니다.

 메모리 페리페럴
 메모리 영역의 코드와 데이터
 운영체제와 유저 애플리케이션에 포함되는 리소스

그림 17.1을 보면 다양한 메모리 영역이 존재하나 Arm 아키텍처 관점에서 보면 메모리 타입은 크게 두 가지로 분류됩니다.

 디바이스 메모리: Peripheral  
 노멀 메모리: Kernel Code, Kernel Data, App Code, App Data 
 
Kernel Code, Kernel Data, App Code, App Data 주소 영역은 노멀 메모리로 관리하며 페리페럴은 디바이스 메모리(메모리 맵드 I/O)로 처리됩니다.

메모리 맵에 존재하는 노멀 메모리 타입과 디바이스 메모리 타입을 구분하는 가장 큰 차이점은 무엇일까요? 바로 명령어나 메모리를 리오더링하는 동작입니다. 노멀 메모리 타입으로 분류된 명령어나 데이터를 Arm 코어가 실행하면 내부에서 다양한 방식으로 최적화 작업을 수행합니다. 대표적으로 메모리 접근 순서(메모리 리오더링)나 명령어의 실행 순서를 바꿉니다. 소프트웨어 개발자가 작성한 대부분의 코드는 노멀 메모리 타입으로 분류되며, 명령어와 데이터는 이 방식으로 처리됩니다. 

디바이스 메모리를 처리하는 방식

Arm 프로세서가 디바이스 메모리를 처리하는 방식은 노멀 메모리를 처리하는 방식과 다릅니다. 디바이스 메모리로 분류된 영역의 데이터를 Arm 코어가 보면 메모리 리오더링와 같은 기법을 적용해 실행하지 않습니다. 디바이스 메모리 타입은 메모리 맵드 I/O 방식이며, 대부분 페리페럴은 디바이스 메모리 타입으로 관리됩니다. 메모리 맵드 I/O로 정의된 주소에 접근하는 한 가지 예를 들까요?

 0xD000_0000 주소 접근: 페리페럴에 인터럽트 활성화
 0xD000_0004 주소 접근: 페리페럴에 인터럽트를 잘 받았다는 ACK(Acknowledge)를 전달
 0xD000_0008 주소 접근: 페리페럴의 인터럽트를 비활성화

위 목록은 메모리 맵드 I/O 기반 물리 주소와 특징을 나타냅니다. 각각 드라이버에서 0xD000_0000, 0xD000_0004, 0xD000_0008 주소에 접근해 페리페럴을 제어할 수 있습니다.

0xD000_0000 주소와 0xD000_0008 주소에 순차적으로 접근해 페리페럴을 제어하려는 루틴이 있는데, 이를 Arm 코어가 노멀 메모리 타입으로 처리하면 주소에 접근하는 순서를 바꿔서 실행할 수 있습니다. 예를 들어 페리페럴에서 인터럽트를 받으면 인터럽트에 대한 ACK를 보내고 페리페럴의 인터럽트를 비활성화하는 명령어를 작성했는데, 명령어의 순서가 바뀌면 오동작할 수 있습니다. 즉, 디바이스 메모리 타입으로 분류되는 영역이 노멀 메모리 타입처럼 처리되면 이처럼 예상치 못한 사이트 이펙트가 유발될 수도 있습니다.

이어서 디바이스 메모리를 알아봅시다. 메모리 맵을 보면 다양한 페리페럴 디바이스의 세부 정보와 함께 레지스터를 있습니다. 이를 Memory-Mapped I/O 혹은 디바이스 메모리라고 합니다.

 

디바이스 메모리 타입은 노멀 메모리 타입과 어떤 차이점이 있을까요? Arm 프로세서 입장에서 노멀 메모리로 마킹된 데이터는 메모리에 접근하는 명령어의 순서를 바꾸는 메모리 리오더링 기법이 적용됩니다. 하지만 디바이스 메모리인 경우는 다릅니다. 디바이스 메모리로 마킹된 데이터에 액세스할 메모리 리오더링이 적용되면 프로그래머가 예상하지 못한 오동작이 발생할 있습니다. 그래서 디바이스 메모리로 마킹된 데이터는 명령어를 순차적으로 실행해야 합니다.

 

디바이스 메모리는 페리페럴을 제어하는 용도로 사용되므로 명령어는 디바이스 메모리 타입으로 마킹된 메모리 영역에 로딩되면 됩니다. 그래서 디바이스 메모리로 지정된 영역은 실행 불가(not executable) 지정돼야 합니다. 디바이스 메모리 영역으로 지정된 공간을 '실행 불가' 설정하지 않으면 프로세서는 명령어를 메모리에서 미리 예측해 로딩할 수도 있어 결과 시스템이 예상치 못한 오동작을 있습니다.

명령어를 실행하거나 명령어 실행 과정에서 처리되는 데이터나 코드는 노멀 메모리 타입으로 처리됩니다. 대부분의 소프트웨어 개발자가 입력한 코드는 노멀로 마킹된 메모리 영역에 로딩된다고 있습니다.

 

Arm 프로세서는 노멀 메모리로 마킹된 영역의 데이터를 처리하기 위해 캐싱 기법을 적용합니다.

CPU 노멀 메모리로 마킹된 메모리 영역에 대해 다음과 같은 기법을 적용해 최적화를 수행합니다.

 

l  머지 액세스

l  스페큘레이션(speculation)

l  리오더링 액세스

 

노멀 메모리를 처리하는 과정에서 적용되는 다양한 기법을 소개합니다.

 

머지 액세스

 

메모리 공간에 여러 접근하거나 연속된 메모리 공간에 접근하는 2 이상의 명령어를 번에 처리하는 동작을 머지 액세스(merge access)라고 합니다.

 

CPU 입장에서는 레지스터보다 메모리에 접근할 많은 사이클과 리소스를 사용합니다. 이유는 무엇일까요? 메모리에 접근하려면 메모리 버스에 먼저 접근해야 하며, DRAM 메모리 콘트롤러와 같은 메모리 하드웨어에서 부가적인 동작을 수행하기 때문입니다. 메모리에 액세스하는 횟수가 늘어날수록 이에 비례해 많은 사이클을 소모합니다. 따라서 메모리에 접근하는 횟수를 줄이면 시스템 성능을 높일 있습니다.

 

Arm 프로세서 내부에도 메모리에 액세스하는 횟수를 최소화해 성능을 높이는 메커니즘이 적용돼 있습니다.  예를 들어, 같은 주소 공간에 있는 메모리에 액세스하는 명령어가 있으면 이를 합쳐서 번만 메모리에 접근할 있습니다.

 

스페큘레이션 액세스

 

성능 최적화를 위한 다른 CPU 설계 기법은 스페큘레이션 액세스입니다. 소프트웨어에서 접근할 것으로 예상하는 데이터를 패턴 인식과 같은 알고리즘을 활용해 미리 예측해 미리 로딩하는 동작을 스페큘레이션 액세스라고 하며 이를 통해 성능을 높일 있습니다.  

 

밖에도 Arm 프로세서는 내부에서 분기 예측, 실행 예측을 통해 성능을 극대화하는  메커니즘이 적용되어 있습니다.

 

리오더링 액세스

 

Arm 프로세서는 성능을 향상하기 위해 프로세서 내부에서 명령어 실행 순서를 바꿔서 실행할 있습니다. 이를 위해 Arm 프로세서는 명령어 간에 의존성이 있는지 체크하며, 의존성이 없는 경우 명령어의 순서를 바꿔 처리합니다.

 

Arm 프로세서는 내부적으로 성능을 극대화하기 위해 다양한 기법을 적용해 왔습니다. 가운데 대표적인 기법은 "데이터 처리량을 높이기 위해 메모리 읽기 혹은 쓰기 작업을 재정렬(reorder)하는 동작"으로, 이를 메모리 리오더링이라고 합니다. 메모리 리오더링을 통해 프로세서와 외부 메모리 간의 데이터 처리량을 늘릴 있고, 결과 프로세서의 성능을 높일 있습니다.

 

앞에서 언급한 가지 동작 특성은 노멀 메모리 타입으로 마킹된 메모리 영역에 적용됩니다. 일반적으로 대부분 소프트웨어 개발자가 입력한 코드는 앞에서 언급한 가지 특징이 적용된 상태에서 실행됩니다.

 

 

[정보] 분기 예측과 실행 예측

 

최신 Arm 코어는 성능을 높이기 위해 분기 예측이나 실행 예측을 수행하기도 합니다. 이러한 CPU 코어의 특징을 악용해 해커들은 악의적인 코드나 데이터를 캐시에 남기려는 시도를 합니다. 이를 스펙터(spectre) 혹은 멜트다운(meltdown)이라고 합니다.

 

 

 

앞에서 설명한 특징을 가리켜 노멀 메모리 타입은 "Weakly ordered"라는 특징이 있다고 말합니다. 여기서 말하는 "Weakly ordered" 무엇일까요? 프로그램을 실행하면 메모리에 액세스하는데, 메모리 액세스 순서가 프로그램에 명시된 프로시저(실행 흐름) 같지 않다는 의미입니다.

트러스트존의 유래를 설명하기에 앞서 해커가 시스템에 침투해 해킹하는 것을 방지하기 위해 시스템을 설계하는 과정을 언급할 필요가 있습니다.

 

2000 초부터 다양한 소프트웨어 기술들이 소개되고 발전했는데, 이에 비례해 해킹 기술도 업그레이드됩니다. 해커들이 다양한 소프트웨어 기법을 활용해 시스템을 공격하면서 해킹의 공격 루트가 다양해집니다. 디바이스 드라이버를 시작으로 네트워트 패킷이나 서버를 통해 시스템을 침입하는 경로가 늘어났습니다.

 

많은 IT 업체들이 해킹으로 피해를 보면서 보안 환경을 구축할 있는 보안 시스템의 필요성을 절감하기에 이르렀습니다. 그래서 IT업체들은 중요한 데이터를 암호화하거나 해커의 공격으로부터 보호할 있는 시스템을 설계합니다. 고육지책으로 보안과 관련된 기능을 제공하는 하드웨어 부품을 시스템에 탑재합니다. 같은 노력으로 중요한 데이터는 보호할 있으나 회로 설계나 소프트웨어의 복잡도가 늘어나고, 결과로 제품의 가격이 높아집니다.

 

결국 CPU 아키텍처에서 보안과 관련된 기능(데이터 암호화, 복호화) 제공해 처음부터 보안을 고려해 시스템을 설계할 있는 환경이 구축되면 좋겠다는 요구사항이 생겼습니다. 특히 SoC 벤더나 보안 업체가 효율적으로 보안 환경을 구축할 있는 아키텍처가 필요했습니다. 또한  소프트웨어가 공격 당할 있는 취약점이 너무 다양한데, 이를 어떻게 방어할지에 대해 고민했습니다.

 

고민 끝에 아예 보안 수준을 높인 운영체제(Trusted OS) 실행될 있는 보안 실행 영역을 정의해 해커로부터 받는 공격 포인트를 하나로 줄이자는 방향으로 시스템 아키텍처를 설계했고, 과정에서 트러스트존이 만들어졌습니다.

 

그럼 트러스트존은 언제 처음 배포됐을까요? 트러스트존은 2004 Arm1176JZ-S™ 프로세서에 탑재됐으며, 현재 Cortex-A 제품군에 포함돼 있습니다.

 

 

[정보] TEE(Trusted Execution Environment)란?
TEE는 프로세서 내부에서 보안 수준이 높은 소프트웨어 플렛폼이 실행될 수 있는 환경을 뜻합니다. 이를 위한 다양한 표준을 세워야 하는데 이를 TEE Committee 단체에서 진행합니다.  

 

.

 

 

 

많은 분들이 생각하는 익셉션의 개념은 'Armv7 아키텍처의 익셉션'인 경우가 많아, Armv8 아키텍처를 배울 때 Armv7 아키텍처의 익셉션과 비슷할 것이라 예상합니다. 익셉션이 발생하면 지정한 주소로 프로그램 카운터를 브랜치하는 기본 개념은 Armv7/Armv8 아키텍처가 같으나, 익셉션을 처리하고 분류하는 체계와 세세한 처리 방식이 많이 다릅니다.

Armv8 아키텍처의 익셉션은 고성능 컴퓨터에서 적용되는 트러스트 존이나 가상화 시스템인 하이퍼바이저를 이해하기 위해 반드시 알아야 할 기반 지식이므로, 반드시 잘 알아야 둬야 합니다. 

익셉션(Exception)은 Armv8 아키텍처의 핵심 기능 중 하나입니다. Armv8 아키텍처에서 정의된 익셉션를 활용해 하이퍼바이저와 같은 가상 시스템을 설계할 수 있습니다. 또한 트러스트 존을 제대로 이해하려면 먼저 익셉션의 동작 방식을 알아야 합니다. 

Armv8 아키텍처에서 익셉션은 어떻게 정의내릴 수 있을까요? 기존에 소개된 Armv8 아키텍처의 익셉션과는 어떤 차이점이 있을까요? Armv8 아키텍처에서는 기존 Armv7 아키텍처와 비교해 익셉션의 종류를 분류하는 방식이 조금 다르지만, 익셉션의 동작 원리는 동일합니다. Armv8 아키텍처의 익셉션은 다음과 같이 설명할 수 있습니다.

 

“익셉션이란 Arm 코어가 명령어를 처리하다가 예외 사항이 발생할 때 이를 처리하는
방식이다. 익셉션이 발생하면 익셉션 벡터로 프로그램 카운터가 브랜치된다.”

 


위 문장을 읽으면 "어, Armv8 아키텍처의 익셉션은 Armv7 익셉션과 거의 같네"라는 생각이 들수 있습니다. 하지만 Armv8 아키텍처의 익셉션은 Armv7 익셉션과 비교했을 때 기본 개념은 같지만 익셉션을 분류하고 처리하는 방식이 다릅니다.

이제부터 Armv8 아키텍처의 익셉션에 대해서 배워 봅시다.

그 동안 Arm 아키텍처는 CPU 아키텍처 시장에서 '저전력', '소형 디바이스' 용으로 사용되는 CPU 아키텍처로 분류됐습니다. 최대한 적은 트렌지스터를 사용해 최소의 소모 전력으로 CPU를 설계했기 때문입니다. 물론 Arm 코어의 CPU 아키텍처도 최대한 심플하게 디자인했습니다.

익셉션의 구조 관점으로 보면, Armv7 아키텍처의 익셉션도 다음과 같이 심플하게 설계됐다고 볼 수 있습니다. 

   ❑ 익셉션 벡터가 4바이트 단위로 정렬
   ❑ 어떤 Arm 동작 모드에서 익셉션이 발생해도 익셉션 벡터로 브랜치
   ❑ 익셉션의 종류도 8개로 정의 내림

그런데, Armv8 아키텍처는 CPU 아키텍처 시장에 "우리는 고성능 컴퓨터나 클라우드 서버에 진출하겠다"라고 출사표를 던지고 설계한 것으로 보입니다. 기존의 소형 저전력 디바이스 용으로 설계된 CPU 아키텍처가 아니라 인텔의 x86과 경쟁할 수 있는 CPU 아키텍처를 설계한 것입니다.  

이런 면모는 Armv8 아키텍처의 익셉션에서 볼 수 있습니다. 이제 Armv8 아키텍처의 익셉션의 주요 특징을 Armv7 익셉션과 비교하면서 알아보겠습니다.

ARM 아키텍처에서는 메모리 어보트 타입과 더불어 외부 인터럽트도 익셉션의 한 종류로 처리합니다. 외부 인터럽트는 메모리 어보트와 달리 하나의 기능으로 동작하므로, 외부 인터럽트의 익셉션 핸들러는 인터럽트 서비스 루틴을 실행합니다. 인터럽트 서비스 루틴을 통해 외부 하드웨어의 변화에 대해 소프트웨어적인 처리를 수행합니다.

그런데 인터럽트 타입 익셉션은 메모리 어보트 타입 익셉션과 어떤 차이점이 있을까요? 메모리 어보트 타입 익셉션은 ARM 코어가 명령어를 제대로 실행할 수 없는 상황에서 유발됩니다. 하지만, 인터럽트 타입 익셉션은 메모리 어보트 타입 익셉션과 달리 외부 하드웨어가 인터럽트라는 전기 신호를 발생시키면 ARM 코어가 이를 익셉션의 한 종류로 받아 처리하는 것입니다. 즉, 외부 하드웨어에서 어떤 변화를 알리기 위해 인터럽트라는 전기 신호를 발생하면 이를 소프트웨어적으로 처리하기 위한 인터페이스이자 운영체제 커널의 기능 중 하나인 것입니다.

인터럽트 타입 익셉션에 대해 소개했으니 다음 그림을 보면서 인터럽트 타입 익셉션의 전체 실행 흐름을 알아봅시다. 

 


그림 8.3 인터럽트 타입 익셉션의 전체 실행 흐름

익셉션의 전체 실행 흐름도는 5단계로 분류할 수 있는데, 각 단계 별로 어떤 동작을 수행하는지 알아봅시다. 

 


1단계: 프로세스가 실행하는 도중에 외부 인터럽트 발생 

 

먼저 ①로 표시된 부분을 따라가 봅시다. 프로세스가 실행하는 도중에 외부 하드웨어에서 인터럽트가 발생하는 부분을 나타냅니다. 소프트웨어 관점으로 보면, 프로세스가 실행 중에 인터럽트가 발생하면 프로세스의 실행 흐름이 멈추게 되는 것입니다.

 

2단계: ARM 프로세서가 익셉션을 감지 

 

② 로 표시된 부분은 ARM 프로세서가 인터럽트 타입 익셉션을 감지하는 동작입니다. 1단계에서 외부 하드웨어에서 인터럽트가 발생하면 ARM 코어는 다음과 같은 익셉션을 유발합니다.  

    ❑ IRQ interrupt
    ❑ FIQ interrupt

②~③으로 표기된 부분의 외곽에 보이는 테두리는 ARM 프로세서가 하드웨어적으로 처리하는 동작을 나타냅니다. 

 


3단계: ARM 프로세서가 익셉션에 대한 세부 처리 

 

③으로 표기된 박스를 보겠습니다. 익셉션을 감지한 ARM 프로세서(ARMv7 기준)는 다음과 같은 동작을 처리합니다.

    ❑ 익셉션이 발생한 모드를 나타내는 spsr_irq 레지스터에 저장한다. 
    ❑ ARM의 동작 모드를 IRQ 모드로 변경한다.
    ❑ 익셉션 벡터 베이스 주소를 먼저 찾은 다음에 0x18 주소를 더한 값을 프로그램 카운터로 브랜치한다.

ARM 코어는 외부 인터럽트를 감지하면 "인터럽트 익셉션을 유발해야 겠다"라고 판단하는데, 위와 같은 순서로 레지스터를 설정합니다. 3단계까지의 동작은 "하드웨어적으로" ARM 프로세서가 처리합니다.  


4단계: 익셉션 핸들러에서 인터럽트 서비스 루틴 실행 

 

3단계에서 인터럽트 익셉션을 유발하면 익셉션 벡터 베이스 주소 기준으로 0x18 주소 오프셋을 적용해 프로그램 카운터를 브랜치합니다. 즉, 익셉션 벡터 주소로 프로그램 카운터를 넣어주게 되는데, 익셉션 벡터 베이스 주소 기준으로 0x18 주소 오프셋에 위치한 명령어가 실행됩니다. 이 부분이 소프트웨어적으로 익셉션 핸들러 코드가 실행되는 부분인데, ④으로 표기된 박스에 해당됩니다.

 IRQ 익셉션 핸들러는 다음과 같은 동작을 수행합니다.

    ❑ 인터럽트가 발생한 시점의 레지스터 세트를 프로세스의 스택 공간에 푸시
    ❑ 인터럽트 서비스 루틴을 실행해 인터럽트를 핸들링하는 코드를 실행
    ❑ 인터럽트 핸들러 함수가 호출됨

IRQ 익셉션은 메모리 어보트 타입 익셉션과는 달리 운영체제에서 인터럽트를 처리하는 기능으로 동작합니다. 이 점이 메모리 어보트 타입 익셉션과는 다르다라는 점을 기억합시다.


5단계: 인터럽트 익셉션이 발생한 주소로 복귀 

 

인터럽트 서비스 루틴에서 인터럽트에 대한 처리를 마무리한 다음에 인터럽트가 발생할 시점의 명령어가 실행된 주소로 복귀하는 동작을 수행합니다. 인터럽트 익셉션이 발생했을 시점에 프로세스의 스택 공간에 푸시된 레지스터 세트를 ARM 코어의 레지스터에 다시 로딩하는 것입니다. 

여기까지 ARM 아키텍처 관점으로 인터럽트 벡터 핸들러가 수행하는 동작을 설명했습니다. 인터럽트 익셉션의 전체적인 실행 흐름에 대해 정확히 이해하려면, 인터럽트 익셉션을 운영체제의 관점으로 분석할 필요가 있습니다. 그러면 운영체제 관점으로 인터럽트 익셉션은 어떻게 볼 수 있을까요? 인터럽트 익셉션은 운영 체제에서 하나의 기능으로 동작합니다. 프로세스 관점으로 보면 프로세스가 실행하는 도중에 인터럽트 익셉션이 발생한 것으로 볼 수 있습니다. 따라서 인터럽트 익셉션이 발생한 후 처리되는 인터럽트 서비스 루틴의 코드는 빠르고 간결하게 처리되어야 합니다.

트러스트존은 Arm 아키텍처가 제공하는 보안 확장 기술로서 IT 업계에서 많이 활용됩니다. 트러스트존과 관련된 명령어나 레지스터를 활용해 보안 업체나 SoC 벤더는 보안 환경을 구축할 있습니다. 많은 휴대폰과 디지털 TV 트러스트존이 활성화된 상태로 Arm 프로세서가 실행 중입니다.

 

그렇다면 트러스트존이란 어떤 기술일까요? 트러스트존은 다음과 같은 콘셉트로 설계됐다고 설명할 있습니다.

 

소프트웨어나 하드웨어적으로 CPU 내부에 신뢰할 있는 보안 영역을 확보하자!

 

CPU 내부에 신뢰할 있는 보안 영역을 시큐어 월드(secure world)라고 정의하고, 나머지 영역을 논시큐어 월드(non-secure world) 정의합니다. 트러스트존은 시큐어 월드에 보안성이 높은 시큐어 , 시큐어 OS 실행될 있는 환경을 제공하기 위해 설계됐습니다.   

 

Armv7 아키텍처의 익셉션과 비교했을 때, Armv8 아키텍처에서는 익셉션을 분류하는 체계와 익셉션 벡터 테이블이 약간 다릅니다. 그 특징에 대해서 더 자세히 알아봅시다.

첫째, Armv8 아키텍처에서는 익셉션의 종류를 계층 구조로 재정의했습니다. 먼저 익셉션을 Synchronous와 Asynchronous와 같이 큰 카테고리로 분류하고, 하부 카테고리로 익셉션 클래스를 정의했습니다. Armv7 의 익셉션의 종류는 익셉션 클래스에서 확인할 수 있습니다.


표 9.1 Armv8 아키텍처에서 익셉션의 분류 체계

먼저 익셉션을 Synchronous와 Asynchronous와 같이 큰 카테고리로 분류합니다. Arm 코어가 명령어를 실행하다가 유발하는 익셉션을 Synchronous, 외부 인터럽트나 외부 메모리 어보트와 같이 외부에서 비동기적으로 유발되는 익셉션을 Asynchronous로 분류합니다. 이처럼 기존 Armv7 아키텍처의 익셉션과 비교해 익셉션을 분류하는 방식이 다릅니다.

둘째, 익셉션 레벨(EL)이란 개념을 도입해 이 기준으로 익셉션을 처리합니다. 익셉션이 유발된 익셉션 레벨 별로 익셉션 벡터 주소가 존재하며, 익셉션 레벨 별로 익셉션 링크 레지스터를 정의합니다.

다음 그림은 Armv8 아키텍처에서 익셉션 레벨 별로 익셉션 처리되는 구조를 나타냅니다.



그림 9.1 Armv8 아키텍처에서 익셉션 레벨 별로 익셉션이 처리되는 흐름

그림의 윗 부분을 먼저 보겠습니다. 유저 애플리케이션이 구동되는 EL0에서 익셉션이 발생하면 다음 순서로 처리됩니다.
  
    1.유저 애플리케이션이 구동되는 EL0에서 익셉션이 발생
    2.EL1으로 진입
    3.EL0용 익셉션 벡터로 브랜치 

이어서 그림의 아랫 부분은 운영체제의 커널이 구동되는 EL1에서 익셉션이 발생하면 처리되는 흐름입니다. EL1에서 익셉션이 유발되면 다음 순서로 처리됩니다.

   1. 운영체제 커널이 구동되는 EL1에서 익셉션이 발생
   2. EL1용 익셉션 벡터로 브랜치

이처럼 Arm코어는 익셉션이 유발되면 익셉션이 발생한 익셉션 레벨 별로 지정된 익셉션 벡터로 프로그램 카운터를 브랜치합니다.

[정보]
EL0과 EL1은 각각 Armv7 아키텍처의 User 모드, Supervisor 모드에 대응됩니다.
Armv7 아키텍처의 User 모드에서는 유저 애플리케이션이 구동하고, Supervisor 모드에서는 운영체제의 커널이 동작합니다. 마찬가지로, Armv8 아키텍처의 EL0에서는 유저 애플리케이션이 실행되고, EL1에서는 운영체제의 커널이 동작합니다.  

Armv7 아키텍처에서는 어떤 Arm 동작 모드에서 익셉션이 유발돼도 익셉션 종류 별로 지정된 익셉션 벡터로 프로그램 카운터가 브랜치됩니다. 예를 들어 유저 애플리케이션이 실행되는 User 모드나 운영체제의 커널이 구동되는 슈퍼바이저 모드에서 데이터 어보트가 유발되면, 데이터 어보트 익셉션에 해당되는 익셉션 벡터로 프로그램 카운터가 브랜치됩니다. 

그래서 익셉션 핸들러에서 익셉션이 유발된 시점의 Arm 동작 모드를 읽어서 이를 세분화하는 명령어를 입력해야 합니다. 예를 들어, 리눅스 커널인 경우 다음 코드와 같이 Arm 동작 모드를 읽어서 세분화해 처리하는 동작을 확인할 수 있습니다. 

다음은 Armv7 아키텍처 기반 리눅스 커널에서 구현된 Undefined Instruction 익셉션 핸들러의 구현부입니다.

01 NSR:FFFF11A0|E88D4001        vector_und:   stm     r13,{r0,r14}
02 NSR:FFFF11A4|E14FE000                      mrs     r14,spsr
03 NSR:FFFF11A8|E58DE008                      str     r14,[r13,#0x8]
04 NSR:FFFF11AC|E10F0000                      mrs     r0,cpsr
05 NSR:FFFF11B0|E2200008                      eor     r0,r0,#0x8       ; r0,r0,#8
06 NSR:FFFF11B4|E16FF000                      msr     spsr_cxsf,r0
07 NSR:FFFF11B8|E20EE00F                      and     r14,r14,#0x0F    ; r14,r14,#15
08 NSR:FFFF11BC|E1A0000D                      cpy     r0,r13
09 NSR:FFFF11C0|E79FE10E                      ldr     r14,[pc,+r14,lsl #0x2]
10 NSR:FFFF11C4|E1B0F00E                      movs    pc,r14
11 NSP:FFFF11C8|C010F680                      dcd     0xC010F680       ; __und_usr
12 NSP:FFFF11CC|C010F130                      dcd     0xC010F130       ; __und_invalid
13 NSP:FFFF11D0|C010F130                      dcd     0xC010F130       ; __und_invalid
14 NSP:FFFF11D4|C010F2C0                      dcd     0xC010F2C0       ; __und_svc

02번째 줄은 spsr 레지스터를 읽어 r14 레지스터에 저장하는 명령어입니다.
r14 레지스터는 익셉션이 발생한 시점의 Arm 동작 모드가 저장돼 있는데, 이 값에 따라 다른 레이블로 브랜치하는 코드(01~10번째 줄)가 실행됩니다.

그런데 Armv8 아키텍처에서는 익셉션이 발생한 익셉션 레벨 기준으로 익셉션 벡터가 브랜치되므로, 구지 소프트웨어적으로 익셉션이 유발된 시점의 익셉션 레벨에 따라 처리할 필요가 없습니다. 

넷째, 기존 Armv7의 익셉션 벡터 주소는 워드(32비트 기준: 4바이트)로 정렬됐으나, Armv8의 경우 0x80바이트 단위로 정렬됐습니다. 익셉션 벡터 주소에서 익셉션를 바로 처리할 수 있는 명령어를 실행할 수 있습니다.

다음은 Arm 사에서 배포한 문서에 담긴 익셉션 벡터 테이블입니다.
 

그림 9.2 Armv8 아키텍처의 익셉션 벡터 테이블

위 그림에서 박스로 표기된 부분을 보면 0x000, 0x080, 0x100, 0x180이 보이는데 이는 익셉션의 종류 별 익셉션 벡터 오프셋을 나타냅니다. 이처럼 Armv8 아키텍처는 익셉션의 종류 별 오프셋 주소의 사이즈가 0x080 바이트입니다.

 


[정보]
익셉션 벡터 베이스 주소에 익셉션 벡터 오프셋 주소를 더한 주소로 프로그램 카운터가 브랜치됩니다.

+ Recent posts