이전 포스트에서 Armv8 아키텍처에서 정의된 레지스터에 대해 알아봤습니다. 레지스터마다 각자 주어진 기능이 있지만 결국 Arm 코어가 사용하는 변수라는 점은 같습니다. 레지스터의 용법을 파악하면 다음과 같은 의문이 생길 수 있습니다. 
 
    "레지스터의 값을 어떻게 읽거나 쓸 수 있을까?"
 
레지스터의 값을 읽거나 쓰려면 명령어를 실행해야 합니다. 그런데 레지스터의 종류에 따라 사용할 수 있는 명령어가 다음과 같이 제한돼 있습니다.

 

 
   * 범용 레지스터: 모든 명령어
   * 시스템 레지스터: MSR, MRS 명령어
 
Armv8에서 정의된 범용 레지스터는 Armv8에서 정의된 모든 명령어에서 볼 수 있습니다. 데이터를 산술 연산하거나 메모리에 위치한 데이터를 읽을 때 사용됩니다. 시스템 레지스터의 값을 읽거나 쓰는 명령어에서도 범용 레지스터를 볼 수 있습니다. 
 
그런데 시스템 레지스터는 MRS, MSR 명령어를 통해서만 접근할 수 있습니다. 한 가지 예를 들어 볼까요?
 
01 MRS x0, SCTLR_EL1 
02 MSR SCTLR_EL1, x0 
 
01번째 줄은 SCTLR_EL1(EL1에 적용되는 시스템 콘트롤 레지스터) 값을 x0 레지스터에 저장하는 명령어입니다. MRS 명령어를 읽을 때는 다음과 같이 옵코드(opcode)의 가장 오른쪽에서 왼쪽 방향으로 해석할 필요가 있습니다.
 
'MRS'은 'MRegisterSystem'로 해석하면 좋은데요. 가장 오른쪽에 있는 S는 System Register, 바로 왼쪽에 있는 R은 Register(범용 레지스터)로 떠올리면 MRS 명령어를 바로 이해할 수 있습니다.
 
이번에는 시스템 레지스터에 값을 쓰는 02번째 줄을 봅시다. 
 
02 MSR SCTLR_EL1, x0 
 
02번째 줄은 EL1에 적용되는 시스템 콘트롤 레지스터(SCTLR_EL1)에 x0 레지스터의 값을 저장하는 명령어입니다. MSR 명령어를 읽을 때도 다음과 같이 옵코드(opcode)의 가장 오른쪽에서 왼쪽 방향으로 해석할 필요가 있습니다.
 
'MSR'은 'MSystemRegister'로 해석하면 좋은데요. 가장 오른쪽에 있는 R은 Register(범용 레지스터), 바로 왼쪽에 있는 S는 System Register 로 떠올리면 MSR 명령어를 쉽게 해석할 수 있습니다. 
 
[중요]
시스템 레지스터는 MSR, MRS 명령어로만 접근할 수 있습니다. 만약에 MRS이나 MSR 명령어가 아닌 'mov x0, SCTLR_EL1'와 같은 명령어를 실행해 시스템 레지스터에 접근하면 Arm 코어는 메모리 어보트 타입 익셉션을 유발합니다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 유튜브 강의 영상
 
 
 
 
 
.
Armv8 아키텍처는 범용 레지스터 뿐만 아니라 시스템의 세부 속성을 설정할 수 있는 시스템 레지스터를 정의합니다. 이 중에 자주 사용되는 시스템 레지스터는 잘 익혀둘 필요가 있는데요. 이어서 Arm 스팩 문서를 보면서 시스템 레지스터에 대해 알아봅시다.
 
4.3 System registers

 

In AArch64, system configuration is controlled through system registers, and accessed using MSR and MRS instructions. This contrasts with ARMv7-A, where such registers were typically accessed through coprocessor 15 (CP15) operations. The name of a register tells you the lowest Exception level that it can be accessed from.
 
Arm 스팩 문서의 내용을 요약하면 다음과 같습니다.
 
   * Armv8 아키텍처에서 시스템의 세부 속성을 설정하려면 시스템 레지스터를 사용해야 한다.
   * 시스템 레지스터에 MSR와 MRS 명령어를 사용해 접근(읽기/쓰기)할 수 있다.
   * 시스템 레지스터의 이름으로 시스템 레지스터에 접근할 수 있는 가장 낮은 익셉션 레벨을 알 수 있다.
 
시스템 레지스터는 Armv8 아키텍처를 이루는 다양한 기능을 설정하기 위해 사용됩니다. 그렇다면 시스템 레지스터를 읽거나 쓰려면 어느 명령어를 실행해야 할까요? 다음 예시 명령어를 보면서 더 자세히 알아봅시다.
 
01 MRS x0, TTBR0_EL1 // TTBR0_EL1을 x0 레지스터에 읽기 
02 MSR TTBR0_EL1, x0 // x0 레지스터의 값을 TTBR0_EL1에 이동
 
01번째 줄은 TTBR0_EL1 시스템 레지스터를 x0 레지스터로 읽는 명령어입니다. 'MRS' 명령어는 시스템 레지스터의 값을 범용 레지스터에 로딩하는 기능입니다.
 
02번째 줄은 반대 기능입니다. x0 레지스터의 값을 TTBR0_EL1 시스템 레지스터에 쓰는 코드입니다. 'MSR' 명령어는 x0와 같이 범용 레지스터의 값을 시스템 레지스터에 쓰는 기능입니다.
---
[정보]
MSR 명령어는 'Move to system coprocessor register from ARM register'의 약자로 범용 레지터의 값을 시스템 코프로세서로 이동시키는 명령어입니다. 이어서 MRS 명령어는 'Move to ARM register from system coprocessor register'의 약자로 시스템 코프로세서의 값을 범용 레지스터로 이동하는 명령어입니다.
---
 
시스템 레지스터의 이름을 보면 익셉션 레벨을 나타내는 접미사가 보입니다. TTBR0_EL1는 TTBR0 다음에 EL1란 접미사가 붙습니다. 시스템 레지스터의 이름으로 알 수 있는 규칙은 다음과 같습니다.
 
    "시스템 레지스터에 접근할 수 있는 최소한의 익셉션 레벨"
 
예를 들어 TTBR0_EL1 레지스터의 경우 접미사로 EL1이 보입니다. 눈치빠른 독자는 TTBR0_EL1에 접근할 수 있는 최소한의 익셉션 레벨은 EL1이라고 예측하겠죠. 그렇다면 TTBR0_EL1에는 EL1만 접근할 수 있을까요? 그렇지 않습니다. 최소한의 익셉션 레벨이 EL1이므로 EL2, EL3에서도 TTBR0_EL1에 접근할 수 있습니다.
 
그렇다면 EL0에서 TTBR0_EL1에 접근할 수 있을까요? TTBR0_EL1을 실행할 수 있는 최소 익셉션 레벨이 EL1이므로, EL0은 이 조건을 만족하지 않습니다. EL0에서 TTBR0_EL1에 접근하면 익셉션을 유발해 프로세스를 종료시킵니다. 
 
[중요]
레지스터에 접근이 가능하다는 말은 레지스터의 값을 읽거나 쓸 수 있다는 의미입니다.
 
 
이번에는 TTBR0_EL2를 예로 들어 볼까요? 접미사로 EL2가 있으니 TTBR0_EL2에 접근할 수 있는 최소한의 익셉션 레벨은 EL2입니다. 역시 TTBR0_EL2에는 EL2와 EL3에서 접근할 수 있습니다.
 
EL1에서는 TTBR0_EL2에 접근할 수 있을까요? TTBR0_EL2을 실행할 수 있는 최소 익셉션 레벨이 EL2이라서, EL1에서 TTBR0_EL2에 접근할 수 없습니다. EL1에서 TTBR0_EL2에 접근하면 익셉션이 유발됩니다. 
 
시스템 레지스터에 접근할 수 있는 익셉션 레벨을 알아봤으니, 자주 사용되는 시스템 레지스터를 소개합니다.
 
다음 표에서 Armv8 아키텍처에서 정의된 주요 시스템 레지스터의 목록을 확인할 수 있습니다.
 
 
표 2.7 시스템 레지스터 목록
 
표 2.7은 실전 개발에서 많이 활용되는 시스템 레지스터 목록입니다. 더 자세한 레지스터의 목록은 부록 3을 참고하세요.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 강의 영상
 

 

 

.

SPSR 레지스터는 Saved Program Status Register의 약자로 PSTATE 정보를 백업하는 용도로 사용됩니다. 따라서 SPSR 레지스터의 비트 맵은 PSTATE와 같습니다. 그렇다면 PSTATE는 SPSR 레지스터로 언제 백업될까요?  
 
    "바로 익셉션이 유발될 때입니다."
 

 

SPSR은 익셉션이 발생했을 때의 PSTATE의 정보를 저장하는 용도로 사용되며, 익셉션을 처리한 후 익셉션이 발생한 시점의 익셉션 레벨로 복귀하기 위해 사용됩니다. 즉, 익셉션과 관련된 처리를 하기 위해 사용됩니다.
 
Arm 아키텍처 문서나 어셈블리 명령어를 분석하면 SPSR 보다는 SPSR_EL1 혹은 SPSR_EL0과 같은 규칙으로 SPSR 레지스터를 명시합니다. SPSR은 익셉션 레벨을 스위칭하기 전의 정보를 저장하므로 익셉션 레벨마다 존재합니다. SP 레지스터와 같은 방식(SP_EL0, SP_EL1)으로 표기되는데요. 익셉션 레벨 별로 사용되는 SPSR 레지스터는 다음과 같습니다.
 
   * EL0: SPSR_EL0
   * EL1: SPSR_EL1
   * EL2: SPSR_EL2
   * EL3: SPSR_EL3
 
SPSR_EL0은 EL0에서 익셉션이 유발될 때의 PSTATE를 저장하고 있어 EL0로 복귀할 때 사용됩니다. 마찬가지로 SPSR_EL1은 EL1에서 익셉션이 유발될 때의 PSTATE를 저장하고 있어 EL1로 복귀할 때 사용됩니다. SPSR_EL2와 SPSR_EL3도 같은 목적으로 활용할 수 있습니다.
 
이번 절의 도입부에서 SPSR_ELx 레지스터는 PSTATE를 백업하는 용도로 사용된다고 설명했습니다. 익셉션이 유발될 때 PSTATE와 SPSR_ELx가 Arm 코어 내부에서 어떻게 업데이트되는지는 어떻게 알 수 있을까요?  다음 슈도 코드에서 확인할 수 있습니다.
 
[익셉션을 유발하는 과정]
   01 SPSR_ELx = PSTATE;
   02 branchToAddress(Exception_vector_address);  // 익셉션 벡터 주소로 점프
 
[익셉션 이전 상태로 복귀]
   03 PSTATE = SPSR_ELx;
   04 PC = ELR_ELx;
 
[정보]
위 코드는 Arm 코어가 하드웨어적으로 처리하는 동작입니다. 이 점을 염두에 둡시다.
 
 
슈도 코드에서 01번째 줄은 PSTATE를 SPSR_ELx 레지스터에 저장하는 동작입니다. 02번째는 익셉션의 종류에 따라 오프셋을 적용해 익셉션 벡터 주소로 프로그램 카운터를 브랜치하는 동작을 나타냅니다. 
 
02번 슈도 코드가 실행되면 익셉션과 관련된 처리를 하는데요. 익셉션에 대한 처리를 마무리 한 후 익셉션이 유발된 시점의 주소로 복귀해야 하는 상황에서는 03~04번째 줄와 같은 동작을 실행합니다. 
 
[중요]
03~04번째 슈도 코드가 실행되려면 eret(Exception Return) 명령어가 실행돼야 합니다.
 
 
03번째 줄은 SPSR_ELx 에 저장된 값을 PSTATE에 저장하는 동작입니다. 이를 통해 익셉션이 유발된 익셉션 레벨로 복귀합니다. 04번째 줄은 ELR_ELx에 저장된 값을 PC에 넣어주는 동작입니다.
<강의 영상>

 

.

그런데 Armv8에서는 익셉션 레벨 별로 사용되는 레지스터가 있는데 이를 스페셜 레지스터라고 합니다. 그 목록은 다음 그림과 같습니다. 
 
 
그림 2.4 Armv8(Aarch64) 아키텍처의 스페셜 레지스터 목록
 
Armv7 에서는 각 동작 모드 별로 r13_<mode>/r14_<mode>와 같이 뱅크드된 레지스터가 있듯이, Armv8은 익셉션 레벨 별로 사용되는 레지스터가 있습니다. 이를 스페셜 레지스터라고 합니다. 
 
SP_ELx 레지스터
 
이 중에서 먼저 SP_EL0을 알아봅시다. SP_EL0은 EL0(익셉션 레벨 0)에서 실행되는 프로세스의 스택 Top 주소를 가리킵니다. 그렇다면 SP_EL1은 어느 익셉션 레벨에서 사용되는 레지스터일까요? SP_EL1은 EL1(익셉션 레벨 1)에서 실행 중인 프로세스의 스택 Top 주소를 가리킵니다.
 
SP_EL1와 같이 이름에 익셉션 레벨이 보이는 스페셜 레지스터는 Armv7에서 소개한 뱅크드 레지스터와 비슷한 방식으로 동작합니다. 이번에도 이해를 돕기 위해 다음과 같은 명령어를 소개합니다.
 
sub sp, sp, #8
 
위 명령어를 실행하면 실제 어느 레지스터가 변경될까요? Armv7 아키텍처에서는 이 명령어를 실행할 때 Arm 동작 모드에 따라 변경되는 레지스터가 결정됩니다. 같은 맥락으로 Armv8 에서는 이 명령어를 실행할 때의 익셉션 레벨에 따라 레지스터가 바뀝니다. 
 
[정보]
sub, sp, sp #8 명령어를 실행할 때 SP 레지스터의 값을 0xc000d000이라고 가정하겠습니다.
 
 
만약 EL1에서 위 명령어를 실행하면 SP_EL1과 SP 레지스터의 값이 같이 바뀝니다. SP 레지스터의 값이 0xc000d000이니 이 값에서 8를 빼면 0xc000cff8이 됩니다. 
 
이번에는 'sub, sp, sp #8' 명령어를 EL2에서 실행한다고 가정합시다. 이 때는 어느 레지스터가 업데이트될까요? SP_EL2와 SP 레지스터가 바뀝니다. EL3인 경우도 마찬가지입니다. SP_EL3와 SP 레지스터가 바뀝니다.
 
이처럼 sp 레지스터를 어셈블리 명령어에서 보면 해당 명령어가 어느 익셉션 레벨에서 실행되는지 머릿 속으로 그릴 필요가 있습니다.
 
ELR_ELx 레지스터
 
그 다음으로 ELR_EL0 레지스터를 알아봅시다. ELR은 Exception Link Register의 약자로, 익셉션이 발생한 시점의 주소를 저장하는 레지스터입니다. 그렇다면 ELR_EL1은 어느 익셉션 레벨에서 사용되는 레지스터일까요? 바로 EL1(익셉션 레벨 1)에서 익셉션이 유발될 시점의 주소 정보를 저장합니다. 
 
PC 프로그램 카운터
 
마지막으로 현재 Arm 코어가 읽어 들이는 명령어의 주소를 담고 있는 프로그램 카운터(PC)를 알아보겠습니다. Armv7 에서는 PC를 범용 레지스터로 봤는데, Armv8 에서는 스페셜 레지스터로 정의합니다. PC를 임의로 바꾸면 보안상 취약점이 될 수 있어 Armv8에서는 PC를 명령어를 통해 조작할 수 없게 설계했습니다.
 

 

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

 

* 강의 영상
 

 

 

.

 

Armv7에서 Armv8로 넘어 오면서 Armv7의 구조가 많이 변경됐습니다. CPU 아키텍처의 최신 기법을 Armv8 아키텍처에 적용하면서 아키텍처의 구조를 많이 개선했으므로 Armv7 대비 20% 이상의 성능 개선이 있었다고 합니다. 그렇다면 Armv8 아키텍처에서 가장 많이 바뀐 기능이 무엇일까요? 레지스터와 기존의 Arm 동작 모드를 개선한 익셉션 레벨이라고 할 수 있습니다.
 
앞으로 Armv8 아키텍처에서 정의된 다음과 같은 레지스터를 설명합니다.
 
   * 범용 레지스터
   * 스페셜 레지스터
   * 시스템 레지스터
 
먼저 범용 레지스터를 알아봅시다.
 
Armv8 아키텍처의 범용 레지스터
 
Armv8 아키텍처에서 다양한 레지스터를 정의하는데 먼저 범용 레지스터가 무엇인지 알아야 합니다. 이번 절에서는 X0~X30로 불리는 범용 레지스터를 소개합니다.
시스템 프로그래머 입장에서 가장 먼저 익혀야 하는 레지스터는 범용 레지스터입니다. Armv8에서 정의된 범용 레지스터의 목록을 소개하고 각 레지스터의 역할을 알아봅시다.
 
다음 표는 Armv8 아키텍처에서 정의된 레지스터의 목록입니다.
 
 
표 2.4 Armv8 아키텍처의 범용 레지스터 목록
 
Armv7에서 정의된 범용 레지스터인 R0--R15 대신에 X0-X30가 보입니다. 표 2.4에서 명시된 레지스터를 Armv8 아키텍처의 범용 레지스터로 정의하며 X와 번호로 구성돼 있습니다. X0, X1에서 X29, X30와 같이 표기할 수 있습니다. X0--X30 레지스터의 역할은 R0--R15와 거의 같습니다. 이번 장 도입부에서 설명했듯이, X0-X30 레지스터로 Arm 코어가 사용하는 변수입니다.
 
이번에는 실전 프로젝트에서 자주 활용되는 커널 로그에서 Armv8 아키텍처의 범용 레지스터를 확인합시다.
 
[0.939869] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.4.61 #1
[0.960583] pstate: a0c00005 (NzCv daif +PAN +UAO)
[0.980153] pc : common_general_probe+0x84/0x3d0
[0.981280] lr : common_general_probe+0x74/0x3d0
[0.981398] sp : 0xffffffc010053b80
[1.958000] x29: 0xffffffc010053b80 x28: 0x0000000000000000
[1.209500] x27: 0xffffffc010fa0478 x26: 0xffffffc0110220b0
[1.222600] x25: 0xffffffc010f868d0 x24: 0xffffffc010ba2470
[1.218260] x23: 0xffffffc010ba2420 x22: 0xffffff89684e0000
[1.229610] x21: 0x00000000ffffffea x20: 0xffffff8967f6e400
[1.424910] x19: 0x0000000000000000 x18: 0x0000000000000010
[1.436270] x17: 0x0000000000000001 x16: 0x0000000000000010
[1.437550] x15: 0xffffff89684e0510 x14: 0x202c292865626f72
[1.633240] x13: 0x705f6c6172656e65 x12: 0x675f6e6f6d6d6f63
[1.644600] x11: 0x205d4f4950475b20 x10: 0x5d31202020203a30
[1.645890] x9 : 0x0000000000000000 x8 : 0x0000000000000003
[1.841580] x7 : 0x00000000000001fc x6 : 0xffffffc011327000
[1.852940] x5 : 0x0000000000000001 x4 : 0xffffffc01132a808
[1.104821] x3 : 0x00000000ffffffff x2 : 0x00000000ffffffc0
[1.105957] x1 : 0x000000000000000a x0 : 0x0000000000000000
 
처음에 소개했던 X0부터 X30 레지스터까지 모두 보입니다. 
 
이번에는 임베디드 개발에서 많이 활용되는 TRACE32 프로그램에서 확인한 범용 레지스터를 소개합니다. 
 

 
 
그림 2.3 TRACE32 프로그램으로 본 Armv8 아키텍처의 레지스터 목록
 
X0에서부터 X30까지 레지스터가 보입니다. X0-X30는 Armv8 아키텍처에서 정의된 범용 레지스터로 프로세스가 실행 흐름을 저장합니다. 그래서 커널 크래시와 같이 시스템에 오류가 있을 때 범용 레지스터의 값을 로그로 뿌려줍니다.
 
이번에는 Armv8 아키텍처에서 정의된 범용 레지스터 세트를 알아봅시다. 다음은 Arm 스팩 문서에서 발췌한 내용입니다.
 
출처: DDI0487A_g_Armv8_Arm.pdf
B1.2.1 Registers in AArch64 state
 
In the AArch64 application level view, an Arm processing element has:
R0-R30 31 general-purpose registers, R0 to R30. Each register can be accessed as:
  • A 64-bit general-purpose register named X0 to X30.
  • A 32-bit general-purpose register named W0 to W30.
 
See the register name mapping in Figure B1-1.
 
X0~X30 레지스터는 범용 레지스터로 사용된다는 내용인데, 레지스터에 저장하는 데이터의 크기에 따라 다음과 같은 방식으로 표기합니다.
 
   * X0~X30: 64비트 크기 데이터를 저장  
   * W0~W30: 32비트의 데이터를 저장 
 
X0~X29와 같은 범용 레지스터는 주로 데이터를 연산하거나 메모리에 접근하는 명령어를 실행할 때 주소나 상수값을 저장합니다. X30은 Armv8에서 링크 레지스터(Link Register)로 사용되며, 서브 루틴으로 분기한 다음에 복귀할 주소를 저장합니다. Armv7의 R14와 유사한 기능입니다.
 
 
[정보]
함수 호출 규약(AAPCS)과 연관된 레지스터는 PC와 SP 그리고 X30(링크드 레지스터)입니다.

 

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

Updated 02.06.2024

SPSR는 Saved Program Status Registers의 약자로 CPSR을 백업하는 용도로 사용되는 레지스터입니다. 이어서 Arm 스팩 문서를 보면서 SPSR을 배워봅시다. 

 

 
The Saved Program Status Registers (SPSRs)
The purpose of an SPSR is to record the pre-exception value of the CPSR. On taking an exception, the CPSR is copied to the SPSR of the mode to which the exception is taken. Saving this value means the exception handler can:
 
위 내용은 다음과 같이 요약할 수 있습니다.
 
   * SPSR는 익셉션이 발생하기 전 시점의 CPSR를 저장하려는 목적으로 사용된다.
   * 익셉션을 유발하는 과정에서, 익셉션이 일어나는 시점의 Arm 모드 정보가 담긴 CPSR이 SPSR로 복사된다.
   * 이 값을 활용해 익셉션 핸들러는 다양한 방식으로 처리를 할 수 있다.
 
정리하면 SPSR은 레지스터의 이름과 같이 CPSR를 백업하는 용도로 사용되는 스페셜 레지스터입니다. 그렇다면 SPSR은 CPSR을 언제 백업할까요? 대부분 다음과 같은 상황에서 활용됩니다.
 
    "익셉션이 발생하기 전 시점의 Arm 동작 모드로 복귀하고 싶을 때"
 
익셉션이 발생한 다음에 SPSR 레지스터는 익셉션이 일어난 시점의 Arm 동작 모드를 저장하고 있습니다. 이 정보를 백업해놓고 SPSR 레지스터의 값을 CPSR 레지스터에 다시 로딩하면 이전 Arm 동작 모드로 복귀할 수 있습니다.
 
SPSR 레지스터는 각 Arm 동작 모드 별로 존재하며 이를 'spsr_<mode>'로 표기할 수 있습니다.
r13와 r14와 같이 SPSR는 각 동작 모드 별로 뱅크드됐다고 볼 수 있습니다.
 
한 가지 예를 들어 볼까요? 슈퍼바이저 모드에서 'IRQ 인터럽트'가 발생한 상황입니다. 이 조건에서 SPSR 레지스터가 어떻게 바뀔까요? 다음은 이 동작을 표기한 슈도 코드입니다.
 
[슈퍼바이저 모드] 
1. CPSR.M = 0x13
2. IRQ 인터럽트 익셉션 유발(하드웨어적으로 처리)
   2.1 SPSR_IRQ.M = 0x13
   2.2 CPSR.M = 0x12
 
1번은 현재 Arm의 동작 모드가 슈퍼바이저 모드라는 정보를 나타냅니다. 그런데 슈퍼바이저 모드에서 인터럽트가 유발되면 IRQ 인터럽트 익셉션을 유발하면서 2.1와 2.2에 해당되는 동작을 Arm 코어가 하드웨어적으로 처리합니다. IRQ 인터럽트 익셉션을 유발하면 IRQ 모드로 진입하니 SPSR_IRQ.M 레지스터에 현재 Arm 동작 모드(슈퍼바이저 모드)를 나타내는 0x13을 저장합니다. 
 
[정보]
SPSR_IRQ.M은 SPSR_IRQ 레지스터의 [4:0] 비트를 나타냅니다.
 
 
이번에는 IRQ 모드로 진입한 다음의 세부 동작을 슈도 코드로 알아봅시다. 
 
[IRQ 모드] 
3. IRQ 익셉션 벡터 주소로 분기
   3.1 SPSR_IRQ.M이 담고 있는 값인 0x13(슈퍼바이저 모드)을 백업
   3.2 IRQ 모드에 해당 후속 처리
4. CPSR.M에 백업한 0x13(슈퍼바이저 모드)을 저장
 
[슈퍼바이저 모드] 
5. 슈퍼바이저 모드로 복귀
 
설명한 내용 중에서 3번 동작을 조금 더 자세히 알아봅시다.
 
SPSR_IRQ.M은 IRQ 모드로 진입하기 전의 Arm 동작 모드를 나타내는 비트 정보인 0x13을 저장합니다. 이 값을 다른 레지스터에 백업할 수 있습니다. IRQ 모드에 진입한 후 후속 처리를 3.2에서 한 다음에 4번 동작을 수행하면, IRQ 모드 이전의 Arm 동작 모드로 복귀할 수 있습니다.
 
이번에는 IRQ 모드에서 Undefined Instruction 모드로 진입할 때 SPSR 레지스터가 어떻게 바뀌는지 알아봅시다.
 
[IRQ 모드] 
1. CPSR.M = 0x12
2. Undefined Instruction 익셉션 유발(하드웨어적으로 처리)
   2.1 SPSR_UND.M = 0x12
   2.2 CPSR.M = 0x1b
 
[Undefined Instruction 모드] 
3. Undefined Instruction 익셉션 벡터 주소로 분기
   3.1 SPSR_IRQ.M이 담고 있는 값인 0x12(IRQ 모드)을 백업
   3.2 Undefined Instruction 모드에 해당 후속 처리
4. CPSR.M에 백업한 0x12(IRQ 모드)을 저장해 IRQ 모드로 복귀
 
1번은 현재 Arm의 동작 모드가 IRQ 모드라는 사실을 나타냅니다. 그런데 IRQ 모드에서 Undefined Instruction 익셉션이 유발되면 Arm 코어는 2.1와 2.2에 해당되는 동작을 하드웨어적으로 수행합니다. Undefined Instruction 익셉션을 유발하면 Undefined Instruction 모드로 진입하니 SPSR_IRQ.M 레지스터 현재 Arm 동작 모드인 IRQ 모드를 나타내는 0x12를 저장합니다. 
 
이어서 Undefined Instruction 모드로 진입한 다음의 동작을 나타내는 3번째 슈도 코드를 분석합시다.
 
SPSR_IRQ.M은 Undefined Instruction 모드로 진입하기 전의 Arm 동작 모드를 나타내는 비트 정보를 저장합니다. Undefined Instruction 모드로 진입하기 전에 IRQ 모드였으니 0x12을 저장합니다. 이 값은 다른 레지스터에 백업할 수 있습니다. Undefined Instruction 모드에 진입한 후 3.2와 같이 후속 처리를 하고 나서 4번 동작을 수행하면, Undefined Instruction 모드 이전의 Arm 동작 모드인 IRQ 모드로 복귀할 수 있습니다.
 
이처럼 Arm 동작 모드 별로 SPSR 레지스터가 존재하며, SPSR_<mode>의 특징을 잘 활용해 Arm 동작 모드를 스위칭합니다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 유튜브 강의 영상
 

 

 

Updated 02.06.2024

 

 

Armv7에서는 프로세서의 상태 정보를 저장하는 CPSR, SPSR 레지스터를 제공합니다. 먼저 CPSR 레지스터를 설명하고 CPSR 레지스터를 백업하는 용도로 설계된 SPSR 레지스터에 대해서 알아봅시다.
 
CPSR 레지스터
CPSR은 Current Program Status Register의 약자로 프로세서의 상태 정보를 저장하는 레지스터입니다. 먼저 CPSR 레지스터의 비트 맵 정보를 알아봅시다.
 
 

 
그림 2.2 CPSR 레지스터의 비트 맵
 
CPSR 레지스터를 구성하는 비트 맵에서 주로 Conditional Flags, Mask Bit 그리고 M[4:0]이 사용됩니다. 먼저 Conditional Flags를 알아봅시다.
 
Condition Flags: bits[31:28]
 
조건 플래그는 명령어를 연산한 후 설정되는 플래그입니다. 다음 표를 보면 각 플래그가 어느 조건에서 업데이트되는지 알 수 있습니다.
 

 
표 2.2 CPSR 레지스터의 Condition flags의 역할
 
Arm 코어는 옵코드(Opcode)를 페치하고 난 다음에 이를 무조건 실행하지 않습니다. 위에서 명시된 컨디셔널 플래그를 참고해 다음 주소에 있는 명령어이 실행됩니다. 한 가지 예를 들까요?
 
01 cmp     r0,#0x0          ; r0,#0
02 beq     0x80101064       ; no_work_pending
03 movlt   r7,#0x0          ; r7,#0
  
01번재 줄에서는 r0 레지스터의 값과 0x0을 비교합니다. 01번째 줄의 명령어를 실행하면 CPSR 레지스터의 Z 비트가 업데이트되는데, 이 값을 보고 02번째 줄이 다른 방식으로 실행됩니다.
 
[정보]
CPSR 레지스터를 구성하는 비트 맵의 정보만 파악하면 배운 내용을 활용하기 어렵습니다. CPSR의 비트 정보를 활용해 명령어의 실행 흐름을 어떤 방식으로 제어하는지를 파악하는게 더 중요합니다.
 
Mask bits, bits[8:6]
 
마스크 비트는 익셉션과 인터럽트를 마스크(비활성화)할 때 사용됩니다. 각 마스크 비트가 어떤 조건에서 활성화되는지는 다음 항목을 보면 알 수 있습니다.
 
   * A, bit[8]: Asynchronous 어보트를 비활성화 
   * I, bit[7]: IRQ를 비활성화 
   * F, bit[6]: FIQ를 비활성화 
 
위에서 정의된 마스크 비트를 1로 설정하면 해당 기능이 비활성화됩니다.
 
M[4:0], bits[4:0]
 
CPSR 레지스터는 Arm의 동작 모드를 나타내는 비트를 저장하며 이를 CPSR.M으로 표기합니다.
Arm 동작 모드별로 M[4:0]은 다음과 같은 비트 정보를 저장합니다.
 
 
표 2.3 Arm 모드 별 비트 인코딩 값
 
동작 모드를 나타내는 비트 값은 많은 코드에서 확인할 수 있으니 잘 익혀둘 필요가 있습니다.
 

 

 

.

그림 2.1의 아랫 부분을 보면 r13_svc와 r14_svc 라는 레지스터가 보입니다. 
 
 
오른쪽에는 r13_irq와 r14_irq 레지스터가 보입니다. 이런 종류의 레지스터의 정체는 무엇일까요? Arm 동작 모드에 뱅크드된 레지스터라고 합니다.
 
뱅크드 레지스터를 주로 기계적인 관점으로 설명해서 소프트웨어 개발자가 이해하기 어렵습니다. 이해를 돕기 위해 다음과 같은 명령어를 봅시다.
 
sub, sp, sp, #4
 
sp 레지스터인 r13 레지스터의 값을 4만큼 빼는 명령어입니다. 여기서 sub은 뺄셈 연산 명령어입니다. 위와 같은 명령어를 실행하면 그림 2.1기준으로 어느 레지스터의 값이 업데이트될까요? r13의 종류는 r13_svc, r13_irq, r13_abt 이니, 이 중 하나입니다.
 
‘sub, sp, sp, #4’ 명령어를 실행할 때의 Arm의 동작 모드가 슈퍼바이저 모드라고 가정합시다. 이 때 r13 레지스터와 r13_svc 레지스터는 같은 값으로 업데이트됩니다. 대신 r13_irq와 r13_abt 레지스터의 값은 바뀌지 않습니다.
 
만약 위에서 소개한 명령어를 실행할 시점의 Arm의 동작 모드가 IRQ 모드이면 어느 레지스터가 업데이트될까요? 눈치가 빠른 독자는 r13, r13_irq 레지스터가 변경된다는 예측할 겁니다.
 
r14 레지스터도 r13와 마찬가지로 동작합니다. 이번에는 다음 명령어를 보겠습니다.
 
01 0xc000a000 bl <sub_routine>
02 0xc000a004 cmp r0, #0
 
01번째 줄의 명령어를 실행하면 r14 레지스터는 0xc000a004로 업데이트됩니다.
 
[정보]
'bl' 명령어의 용법은 Arm AAPCS(함수 호출 규약)의 내용을 참고하세요.
 
 
위와 같은 명령어를 실행하면 그림2.1 기준으로 어느 레지스터의 값이 업데이트될까요? r14의 종류는 r14_svc, r14_irq, r14_abt 이니 역시 이 중 하나입니다. r13와 마찬가지로 r14 레지스터도 Arm 동작 모드에 따라 뱅크드된 레지스터의 값이 변경됩니다.
 
01번째 줄의 명령어를 실행할 때의 Arm의 동작 모드가 슈퍼바이저 모드라고 가정합시다. 이 때 r14와 r14_svc 레지스터가 같은 값으로 업데이트됩니다. 이 상황에서 r14_irq와 r14_abt 레지스터의 값은 바뀌지 않습니다.
 
만약 위에서 소개한 명령어를 실행할 시점의 Arm의 동작 모드가 IRQ 모드이면 어느 레지스터가 업데이트될까요? r14, r14_irq 레지스터라고 예상할 것입니다. 
 
그렇다면 이렇게 Arm 동작 모드 별로 업데이트되는 뱅크드 레지스터를 설계한 이유는 무엇일까요? 바로 Arm 모드 별로 실행 흐름을 저장하는 레지스터가 Arm 모드 별로 있으면 소프트웨어 로직이 간단해지기 때문입니다.
 
IRQ 모드에서 어떤 코드를 실행하다가 슈퍼바이저 모드로 변경하는 상황을 예로 듭시다.
이 때 IRQ 모드에서 사용된 r13와 r14 레지스터의 값을 어딘가에 저장해야 합니다. 그래야 다시 IRQ 모드로 진입할 때 r13와 r14를 로딩해 이전의 (소프트웨어적인) 실행 흐름을 이어갈 수 있습니다.
 
그런데 각 IRQ 모드를 위한 전용 레지스터가 있으면 이렇게 r13이나 r14를 어딘가에 백업할 필요가 없습니다. 다른 Arm 동작 모드에서 IRQ 모드로 바뀌면 r13_irq, r14_irq 레지스터의 값은 r13 그리고 r14 레지스터 값으로 매핑되기 때문입니다.
 
Arm 동작 모드 별로 뱅크드된 레지스터가 존재하는 다른 이유는 무엇일까요? ftrace나 TRACE32와 같은 장비로 성능을 측정하면 실제 디바이스에서 Arm 동작 모드가 굉장히 자주 바뀌는 현상이 확인됩니다. 그런데 Arm 동작 모드가 바뀌면 이전 동작 모드에서 처리된 스택 주소나 링크 레지스터의 값을 어딘가에 백업하고 로딩하는 연산을 수행해야 합니다. 1초에 300번 이상 Arm 동작 모드가 바뀌면 스택 주소나 링크 레지스터를 저장하고 로딩하는 동작을 수행하므로, 성능에 악 영향을 끼칠 수 있습니다. Arm 동작 모드마다 접근할 수 있는 뱅크드 레지스터가 있으니 스택 주소를 어딘가에 백업하고 로딩하는 동작을 수행할 필요가 없습니다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 강의 영상

 

Updated 02.06.2024

CPU 아키텍처를 배울 때 가장 먼저 어셈블리 명령어와 레지스터의 용법을 파악합니다. 어셈블리 명령어는 레지스터로 구성돼 있어 어셈블리 명령어를 익히려면 레지스터의 용법을 알아야 합니다. 그렇다면 Arm 아키텍처는 같은 레지스터를 사용할까요? 그렇지 않습니다. Armv7와 Armv8 아키텍처 별로 각각 레지스터를 정의합니다.
 
이번 포스트에서 Armv7 아키텍처에서 정의된 레지스터를 다룹니다. 먼저 범용 레지스터를 설명하겠습니다.
 
범용 레지스터 
 
레지스터란 무엇일까요? 레지스터는 메모리 계층 구조 관점으로 설명할 수 있습니다. Arm 코어가 사용하는 데이터를 저장하는 임시 저장 공간을 레지스터라고 합니다. 그런데 소프트웨어 관점으로는 Armv7 아키텍처에서 레지스터는 범용 레지스터와 CP15 레지스터로 분류될 수 있습니다. 이 중에서 먼저 범용 레지스터를 알아봅시다. 
 
Arm 스팩 문서에서 범용 레지스터 확인하기
 
누군가 레지스터라고 말하면 ‘범용 레지스터’를 떠올리는 분이 많습니다. 시스템 소프트웨어 개발자들이 가장 먼저 접하는 레지스터가 범용 레지스터이기도 합니다.
 
다음은 Arm 스팩 문서에서 범용 레지스터를 설명한 내용입니다.
 
<출처: DEN0013D_cortex_a_series_PG.pdf>
3.1 Registers
The ARM architecture provides sixteen 32-bit general purpose registers (R0-R15) for software use. Fifteen of them (R0-R14) can be used for general purpose data storage, while R15 is the program counter whose value is altered as the core executes instructions. An explicit write to R15 by software will alter program flow. Software can also access the CPSR, and a saved copy of the CPSR from the previously executed mode, called the Saved Program Status Register
(SPSR). 
 
범용 레지스터를 포함한 전반적인 레지스터를 명확하게 설명하는 내용입니다. 스팩의 내용을 요약해 볼까요?
 
   * Arm 아키텍처는 32-비트로 구성된 범용 레지스터(R0-R15)를 제공한다. 
     (이 레지스터는 소프트웨어에서 사용된다.)
   * R0-R14 레지스터는 데이터를 처리하는 범용 용도로 사용되고, R15는 Arm 코어가 실행하는 명령어(Instruction)의 주소를 저장하는 프로그램 카운터이다.
   * R15를 바꾸면 프로그램의 실행 흐름이 바뀔 수 있다.
   * CPSR 레지스터는 프로세서의 실행 상태를 저장하는데 CPSR 레지스터의 복사본이 SPSR 레지스터이다. SPSR 레지스터는 이전에 Arm 동작 모드의 프로세서 상태 정보를 저장한다.
 
Arm 스팩 문서는 포괄적인 내용을 담고 있어 용어나 문구를 바로 이해하기 어렵습니다. 이어지는 절에서 스팩 문서에서 조금 보충해야 할 내용을 설명합니다.
 
R0~R15 레지스터의 역할
 
Armv7 아키텍처에서 정의된 범용 레지스터를 알려면 R0~R15 레지스터의 역할을 파악할 필요가 있습니다. 다음은 Armv7 코어에서 사용되는 레지스터 세트를 나타낸 그림입니다.
 
 
그림 2.1 Arm 모드 별로 사용되는 레지스터의 목록
<출처: DDI0406C_C_Arm_architecture_reference_manual.pdf>
 
위 그림에서 가장 윗 부분에 보이는 User~FIQ는 Armv7에서 지원하는 Arm 동작 모드를 나타냅니다. 이 내용으로 Arm 동작 모드 별로 사용되는 레지스터가 있다는 사실을 추정할 수 있습니다.
 
가장 왼쪽 행에 있는 R0부터 PC까지 흰색 배경으로 표시된 부분을 보면, 이는 범용으로 사용되는 레지스터 세트입니다. 어떤 Arm의 동작 모드(Operation Mode)에서도 사용할 수 있는 레지스터를 뜻합니다.
 
그런데 회색 음영으로 표시된 레지스터들은 특정 동작 모드에서만 접근할 수 있는 레지스터입니다. 다음 표에서 Supervisor 모드, IRQ 모드 그리고 FIQ모드에서 엑세스할 수 있는 레지스터를 확인할 수 있습니다.
 
 
표 2.1 각 모드별 접근 가능한 레지스터의 목록
 
위 표에서 명시된 SP_<mode>, LR_<mode>, SPSR_<mode> 레지스터들은 지정된 모드에서만 접근되는 레지스터인데, 이런 레지스터는 뱅크드됐다라고도 합니다.
 
이어서 R0-R14 레지스터를 알아봅시다. R0-R14 레지스터는 데이터를 처리할 때 입력이나 출력값을 저장할 때 사용됩니다. 데이터를 처리하는 연산은 다음과 같습니다.
 
     “산술 연산, 메모리 접근”
 
R13은 스택 포인터(Stack Pointer) 레지스터로 사용되며 현재 프로세스의 스택을 어디까지 사용했는지를 가리킵니다. R13 레지스터는 프로세스가 가장 마지막에 사용된 스택의 위치를 저장하므로 프로세스의 실행 흐름을 알 수 있는 매우 중요한 정보입니다.
 
R14는 링크(Link) 레지스터로 사용되며 Arm 코어가 서브 루틴으로 분기할 때 복귀할 주소를 저장합니다. 쉽게 설명하면 Arm 코어는 어딘가(주소, 함수)로 브랜치를 할 때 어디서 브랜치를 했는지를 R14 레지스터에 업데이트합니다. 그래서 디버깅을 할 때 가장 많이 참고하는 레지스터가 R14입니다.
 
R15는 프로그램 카운터(Program Counter)로 사용되며 Arm 코어가 실행 중인 명령어(Instruction)의 주소를 저장하는 레지스터입니다. Arm 코어가 명령어를 읽을 때 사용되는 가장 중요한 레지스터인 PC는 Arm 코어가 읽어 올 명령어가 있는 주소를 담고 있습니다. 프로그램 카운터를 변경하면 프로그램의 실행 흐름이 바뀝니다.
 
[정보]
x86 아키텍처에서 Armv7 아키텍처의 PC와 같은 용도로 사용되는 레지스터를 IP(Instruction Pointer) 레지스터라고 합니다. x86 아키텍처는 레지스터의 이름을 잘 지은 듯 합니다. Instruction Pointer라는 명칭만 봐도 명령어가 위치한 주소를 가리킨다고 떠오르지 않나요?  
 
 
그런데 여러분이 TRACE32나 GDB와 같은 디버깅 툴을 사용하면 바로 다음과 같은 사실을 확인할 수 있습니다.
 
    "Arm 코어는 PC가 가리키는 주소에 있는 명령어를 읽은 후, PC를 4바이트로 증가시킨다."
 
PC가 명령어를 읽은 후에 4바이트 씩 증가하는 이유는 무엇일까요? Arm 아키텍처에서 사용되는 명령어의 사이즈는 4바이트이기 때문입니다.

 

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

 

Updated 02.06.2024

Arm 아키텍처를 구성하는 기능을 이해하려면 먼저 무엇을 알아야 할까요? Arm 코어에 내장된 레지스터입니다. 레지스터를 잘 알려면 무엇을 알아야 할까요? 레지스터들이 어떻게 구성돼 있고 어떤 방식으로 사용되는지 파악하면 레지스터를 잘 안다고 말할 수 있습니다.
 
Arm 아키텍처에서 정의된 기능들은 "레지스터를 어떻게 변경하고 설정할까?"가 그 실체이고 정체입니다.
 
메모리 아키텍처 관점으로 레지스터는 무엇일까요? Arm 코어가 사용하는 저장 매체 중에 가장 속도가 빠른 게 레지스터입니다. 레지스터 다음으로 속도가 빠른 저장 매체로 캐시와 RAM을 주로 언급합니다. 캐시나 RAM을 사용하는 것보다 되도록 레지스터를 사용해 데이터를 연산하면 성능을 최적화할 수 있습니다.  
 
그럼 레지스터는 어떻게 표기할까요? 전통적으로 Arm 아키텍처에서 레지스터는 R 혹은 r과 숫자를 이용해 표현합니다. r1은 레지스터 1 혹은 r1 레지스터라고 합니다. 이 규칙은 Armv7 아키텍처에 정의된 레지스터에 적용됩니다. 
 
그런데 Armv8 아키텍처에서는 레지스터는 X 혹은 x와 숫자와 함께 표현됩니다. x19는 레지스터 19 혹은 x19 레지스터라고 부릅니다.
 

+ Recent posts