이어서 Arm 아키텍처에서 정의된 동작 모드가 운영체제나 RTOS에서는 어떻게 활용되는지 알아봅시다.

유저 모드는 유저 애플리케이션이 실행되는 모드이고, 슈퍼바이저 모드는 운영제체의 커널이나 커널 드라이버가 실행되는 모드입니다. 

그런데 동작 모드에 대한 내용을 처음 접하면 다음과 같은 다양한 의문이 생깁니다.

동작 모드는 왜 존재하며, 각각 어떻게 활용될까?
진행 중인 프로젝트에서 혹시 운영체제를 사용하지 않는 경우에는 유저 모드만 사용하면 될까?

이 질문에 대해 "프로젝트의 스펙에 맞게 동작 모드를 선택하면 된다"라고 말씀드리고 싶습니다. 

동작 모드를 선택하는 예시

한 가지 예를 들까요? 디스플레이 디바이스에 'Hello, world'만을 출력하는 동작만 수행하는 제품을 만든다고 가정해 봅시다. 키보드나 마우스와 같은 외부 입출력 디바이스에서 인터럽트가 유발되지도 않고 메모리를 설정할 필요도 없습니다. 이런 기능은 슈퍼바이저 모드에서 모두 프로그래밍해도 됩니다. 혹은 유저 모드에서 코드가 실행돼도 상관없습니다.

이번에는 음악만 재생하는 소형 MP3 플레이어를 예로 듭시다. 그런데 소형 MP3 플레이어에는 애플리케이션을 설치할 수 없는 조건입니다. 이처럼 매우 심플한 RTOS가 구동되는 상황이면 이번에도 모든 프로그램을 슈퍼바이저 모드에서 실행되도록 시스템을 설계할 수 있을까요?

그런데 한 가지 중요한 스펙이 제안됐습니다. '소형 MP3 플레이어에 블루투스 기능이 있어야 하는데, 블루투스를 연결할 때 인터럽트가 발생한다'라는 요구사항입니다. 이럴 때는 IRQ 모드에서도 프로그램이 반응해 동작하도록 시스템을 구성해야 합니다.

마지막으로 다양한 유저 애플리케이션을 설치해 실행되는 범용 운영체제를 예로 듭시다. 이 시스템에서는 어떤 모드에서 어떤 소프트웨어 스택이 실행되도록 설계할까요? 

유저 모드에서는 유저 애플리케이션이 구동되고 슈퍼바이저 모드에서는 리눅스 커널과 같은 운영체제의 커널이 실행되도록 시스템을 구성하면 좋을 것입니다. 물론 인터럽트가 발생하면 IRQ 모드로 진입하니 IRQ 모드에서도 인터럽트를 처리하도록 시스템을 설계할 수 있습니다.

그런데 실전 프로젝트에서 개발자가 실수로 NULL 포인터 익셉션을 유발하는 코드를 작성할 수 있습니다. 이러한 코드(명령어)가 실행되면 Arm 코어는 'Data Abort'나 'Prefetch Abort' 익셉션을 유발하면서 Abort 모드에 진입합니다. 이런 조건에서는 시스템의 오류 정보를 출력하고 시스템을 리셋하거나 프로세스를 종료시키는 코드가 구현되면 좋을 것입니다.

이처럼 시스템을 설계하는 개발자는 동작 모드의 특징을 잘 살려서 적절한 모드에 소프트웨어를 구현하면 됩니다. 

[중요] 동작 모드와 특권 레벨과의 관계

시스템을 설계할 때 동작 모드의 특성을 잘 파악하는 것이 중요합니다. 그래서 각 동작 모드가 어떤 PL에서 실행되는지 염두에 둬야 합니다. 유저 모드는 PL0이고 나머지 모드는 PL1으로 실행됩니다.


동작 모드를 선택할 때의 기본 원칙 

이어서 동작 모드를 선택할 때 고려해야 할 세 가지 기본 원칙을 설명하겠습니다. 

첫 번째, PL0에서 PL1으로 자유롭게 진입할 수 없습니다. PL0에서 실행되는 유저 모드에서는 SVC 명령어를 실행해 익셉션(트랩)이 유발돼야 PL1으로 진입할 수 있습니다.

두 번째, PL0에서는 MMU나 IRQ나 FIQ 인터럽트를 직접 설정할 수 없습니다. 하드웨어적으로 시스템을 설정하는 동작은 PL1에서 수행돼야 합니다. 

세 번째, PL1으로 정의된 동작 모드에서는 SPSR 레지스터의 모드 필드에 변경하려는 모드 비트를 설정한 후, 'SUBS PC LR' 혹은 'MOVS PC LR'과 같은 명령어를 실행하면 PL1으로 정의된 동작 모드로 바로 스위칭됩니다. PL1에서 실행되는 동작 모드끼리는 익셉션 없이 스위칭될 수 있습니다.






















이번 절에서는 동작 모드를 소개하고 각 동작 모드에 진입하는 방법을 알아봤습니다. 이어지는 절에서 동작 모드와 관련된 레지스터에 대해 알아봅시다. 

Arm 동작 모드를 이해하려면 Privilege level(PL)의 개념을 먼저 알면 좋습니다. Arm 동작 모드는 PL의 기반 위에서 정의했기 때문입니다. 
 
PL과 Arm 동작 모드에 대한 Arm 스팩 문서 분석
 
먼저 Arm 스팩 문서에서 PL0를 어떻게 정의했는지 알아봅시다.
 
(출처: DEN0013D_cortex_a_series_PG.pdf)
PL0 
The privilege level of application software, that executes in User mode. Software executed in User mode is described as unprivileged software. This software cannot access some features of the architecture. In particular, it cannot change many of the configuration settings. Software executing at PL0 can make only unprivileged memory accesses.
 
스팩 문서의 내용은 다음과 같이 요약할 수 있습니다.
 
   ❑ User 모드에서 실행되는 애플리케이션 소프트웨어가 실행되는 PL이다.
   ❑ User 모드에서 실행되는 소프트웨어는 unprivileged 소프트웨어라고 명시한다.
   ❑ PL0에서는 몇 가지 아키텍처 기능을 설정할 수 없다.
   ❑ PL0에서는 메모리 접근 권한에 제약이 있다. 
 
PL0에서 설정할 수 없는 몇 가지 아키텍처 기능이 있습니다. 그 중 하나는 MMU 설정이나 인터럽트 설정입니다. Arm 아키텍처에서는 메모리에 접근할 때 권한을 체크하는데, PL0에서는 접근할 수 있는 메모리 공간에 제약이 있습니다.  
이번에는 PL1에 대해 설명한 Arm 스팩 문서를 보면서 PL1에 대해 알아봅시다.
 
(출처: DEN0013D_cortex_a_series_PG.pdf)
PL1 
Software execution in all modes other than User mode and Hyp mode is at PL1.
Normally, operating system software executes at PL1. The PL1 modes refers to all the modes other than User mode and Hyp mode.
 
스팩 문서의 내용은 다음과 같이 요약할 수 있습니다.
 
   ❑ User 모드와 Hyp 모드를 제외한 모든 모드가 PL1에서 실행된다. 
   ❑ 보통 운영체제의 시스템(커널)이 PL1에서 실행된다.
 
User와 Hyp를 모드를 빼고는 PL1에서 실행되는데요. 더 구체적으로 PL1에서 실행되는 동작 모드는, Supervisor (SVC), IRQ, FIQ, Abort (ABT), Undef (UND), System (SYS) 모드입니다.
 
PL1에서는 메모리에 아무런 제약 없이 접근할 수 있으며, CoProcessor와 같은 Configuration 레지스터를 엑세스할 수 있습니다. 
 
이어서 아래 표를 보면서 PL와 해당 Arm 동작 모드에 대해 알아봅시다.
 
 
표 3.1 Armv7 아키텍처의 동작 모드와 PL
 
표 3.1에서 명시된 Arm 동작 모드에 대해 더 자세히 알아봅시다.
 
먼저 User 모드는 유저 애플리케이션이 실행되는 모드로 PL0입니다. 여러분이 자주 사용하는 카카오톡, 유튜브나 브라우저와 같은 애플이케이션은 User 모드에서 실행됩니다. 그런데 PL0에서는 시스템 설정에 제약이 있습니다. MMU나 인터럽트 혹은 CoProcessor 레지스터에 접근할 수 없습니다. 또한 User 모드에서는 접근할 수 있는 메모리 공간에 제약이 있습니다.
 
슈퍼바이저(Supervisor) 모드는 주로 운영체제의 커널이 구동되며, PL1으로 실행됩니다. 슈퍼바이저 모드에 진입하려면 svc 명령어를 실행해야 하는데, 주로 User 모드에서 svc 명령어를 실행해 진입합니다. 
 
User 모드에서 구동되는 유저 애플리케이션에서 svc 명령어를 실행하는 동작을 보통 시스템 콜 이라고 하는데, 이를 통해 운영체제 커널이 구동되는 슈퍼바이저 모드에 진입합니다.
 
이어서 IRQ 모드와 FIQ모드는 'IRQ 인터럽트'나 'FIQ 인터럽트'와 같은 익셉션이 발생하면 진입하는 모드입니다. 대부분 운영체제에서는 IRQ 인터럽트가 발생하면 인터럽트를 직접 처리하며, 'FIQ 인터럽트'는 트러스트존의 Secure OS에서 처리하도록 설정합니다.
 
Abort(ABT) 모드는 '프리페치 어보트(Prefetch Abort)나 데이터 어보트(Data Abort)를 유발하는 명령어를 실행하면 진입하는 모드입니다. 이어서 Undef(UND) 모드는 Arm 아키텍처에서 정의되지 않은 명령어가 실행되면 진입하는 모드입니다. 
 
Arm 아키텍처에서는 익셉션의 유형을 크게 메모리 어보트로 유발되는 익셉션과 인터럽트로 유발되는 익셉션으로 분류하는데, Abort(ABT)와 Undef(UND) 모드는 메모리 어보트를 유발하는 어셈블리 명령어가 실행되면 진입하는 모드입니다.
 
각각 Arm 동작 모드를 소개했으니 PL 관점으로 Arm 동작 모드를 분류해봅시다. PL0의 권한이 부여된 Arm 동작 모드는 User 모드이며, 이를 제외한 나머지 모드들은 PL1에서 실행됩니다. 
 
이번에는 다음 그림을 보면서 PL별로 정의된 Arm 동작 모드를 정리합시다.
 

 
 
그림 3.1 PL별로 정의된 Arm 동작 모드 
 
그림의 가장 윗 부분은 PL0을 나타내며 User 모드는 PL0 권한이 있습니다. 이어서 그림의 아랫 부분은 PL1인데, User 모드를 제외만 모든 Arm 동작 모드가 PL1 권한으로 실행됩니다.
 
어떤 Arm 동작 모드를 선택해야 할까
 
이어서 Arm 아키텍처에서 정의된 동작 모드가 운영체제나 RTOS에서는 어떻게 활용되는지 알아봅시다.
 
User 모드는 유저 애플리케이션이 실행되고, Supervisor 모드는 운영제체의 커널이나 커널 드라이버가 실행되는 모드입니다. Supervisor 모드는 svc 명령어가 실행되면 진입하는 모드이기도 합니다. 
 
그런데 Arm 동작 모드에 대한 내용을 처음 접하면 낯선데, 다양한 의문이 생깁니다. 특히 이 기능들이 실전 프로젝트에서 어떻게 활용되는지 궁금해합니다.
 
그래서 세미나에서 다음과 같은 질문을 자주 듣습니다.  
 
   "Arm 모드는 왜 존재하며, 각각 어떻게 활용될까?"
 
   "개발하려는 프로젝트에서 혹시 운영체제를 사용하지 않는 경우엔 User 모드만 
    사용하면 될까?"
 
이 질문에 대해 "시스템을 디자인하는 개발자가 자신이 개발하는 시스템에 맞게 Arm 동작 모드를 선택하면 된다"라고 말씀드리고 싶습니다.
 
어떤 시스템이 부팅을 한 다음에 디스플레이 디바이스에 'Hello, world'만을 출력하는 동작만을 실행하는 제품을 예로 듭시다. 다른 디바이스에서 인터럽트가 트리거되지도 않고 메모리를 설정할 필요가 없습니다. 이런 기능은 슈퍼바이저 모드에서 모두 프로그래밍해도 됩니다. 혹은 User 모드에서 코드가 실행되도 상관없습니다. 
 
이번에는 음악만 재생하는 소형 MP3 플레이어를 예로 들겠습니다. 그런데 이 장비에서 애플리케이션을 유저가 설치할 수 없습니다. 해당 시스템에 매우 심플한 RTOS가 구동이 되는 상황이면, 이번에도 모든 프로그램을 슈퍼바이저 모드에서 실행되도록 시스템을 디자인할 수 있을까요? 그런데 한 가지 중요한 스팩이 떠올랐습니다. '소형 MP3 플레이어에 블루투스 기능이 있어야 하는데, 블루투스를 연결할 때 인터럽트가 발생한다'고 합니다. 이럴 때는 IRQ 모드에서도 프로그램이 동작하도록 시스템을 구성해야 합니다. 
 
마지막으로 유저 애플리케이션을 설치할 수 있는 범용 운영체제를 예로 들겠습니다. 이 시스템에서는 어떤 모드에서 코드가 실행돼야 할까요?
 
User 모드에서는 유저 애플리케이션이 구동되고, 슈퍼바이저 모드에서는 리눅스 커널와 같은 운영체제의 커널이 실행되도록 시스템을 구성하면 좋을 것입니다. 물론 인터럽트가 발생하면 IRQ 모드로 진입하니 IRQ 모드에서 인터럽트를 처리하게 시스템을 설계할 수 있습니다.
 
그런데 실전 프로젝트에서 개발자가 실수로 NULL 포인터 익셉션을 유발하는 코드를 작성할 수 있습니다. 이런 코드(명령어)가 실행되면 Arm 프로세서는 '데이터 어보트'나 '프리페치 어보트' 익셉션을 유발하면서 Abort 모드에 진입합니다. 이런 조건에서는 무엇인가 시스템의 오류 정보를 출력하고 시스템을 리셋시키거나 프로세스를 종료시키는 코드가 구현되면 좋을 것입니다.
 
이처럼 Arm 동작 모드의 특징을 잘 살려서 시스템을 디자인을 하는 개발자는 적절한 모드를 선택할 수 있습니다. 
 
[중요]
시스템을 디자인할 때 Arm의 동작 모드의 특성을 잘 파악하는게 중요한데, 각 Arm 모드가 어떤 PL에서 실행되는지 염두에 둬야 합니다. User 모드는 PL0이고 나머지 모드는 PL1으로 실행됩니다.
 
Arm 동작 모드를 선택할 때 기본 원칙 
 
이어서 Arm 동작 모드를 선택할 때 고려해야 할 2가지 기본 원칙을 설명하겠습니다. 
 
첫 번째, PL0에서 PL1으로 자유스럽게 진입할 수 없습니다. PL1으로 정의된 Arm 동작 모드에서는 spsr 레지스터의 모드 필드를 이동하고 싶은 모드로 설정한 후, 'subs pc lr' 혹은 'movs pc lr'과 같은 명령어를 실행하면 PL1로 정의된 Arm 동작 모드로 바로 진입할 수 있습니다.
 
하지만 PL0으로 정의된 User 모드에서는 svc 명령어를 실행해 익셉션이 유발돼야 PL1으로 진입할 수 있습니다.
 
두 번째, PL0에서는 MMU나 IRQ나 FIQ 인터럽트를 직접 설정할 수 없습니다. 이렇게 하드웨어적으로 시스템을 설정하는 동작은 PL1에서 수행되야 합니다. 
 
Arm 동작 모드에 대해 소개했으니 이어서 Arm 동작 모드는 어떻게 진입하는지 알아봅시다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>

 

 

 

 
Arm 아키텍처의 가장 중요한 개념은 Arm 동작 모드입니다. 익셉션, 트러스트존을 비롯한 Arm 아키텍처의 많은 기능은 Arm 동작 모드 기반 위에서 설계 됐습니다. 그래서 Arm 동작 모드를 이해하는 것이 중요합니다.
 
하지만 Arm의 동작 모드를 배울 때 기계적으로 스팩의 내용을 억지로 이해하고 암기하는 분들이 많습니다. 대신 다음과 같은 질문을 던지고 답을 찾는 과정을 거치면 어떻까요?  더 많은 것을 배울 수 있고, 공부한 내용이 머릿 속에 더 오랫 동안 남을 것입니다. 
 
   ❑ Arm 동작 모드는 어떻게 바뀔까?
   ❑ Arm 동작 모드가 바뀌면 이전 Arm의 동작 모드는 어떻게 확인할까?
   ❑ 이전 Arm 동작 모드로는 어떻게 복귀할까?
   ❑ Arm 동작 모드를 활용해 운영체제 커널은 어떻게 구현돼 있을까?
 
프로그래밍을 할 때 위와 같은 질문을 던지고 코드를 분석한다면 자신이 작성한 코드가 Arm 코어에서 어떻게 실행되는지 더 잘 안다고 볼 수 있습니다.  
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
* 유튜브 소개 영상
 


 
 
 
 
ARM 프로세서의 주요 용법을 배우기 위해 리눅스 커널만큼 좋은 리퍼런스 자료가 없는 것 같다.
ARM 사의 개발자들이 리눅스 커널에 자신의 코드를 메인라인 시키기 때문이다.
 
이번에는 리눅스 커널에서 32비트 ARMv7 아키텍처에서 ARM의 모드를 읽어 제어하는 루틴을 살펴보자.
 
먼저 다음 코드를 보자.
 
arch/arm/include/uapi/asm/ptrace.h
 
#define USR_MODE    0x00000010
#define SVC_MODE    0x00000013
#define FIQ_MODE    0x00000011
#define IRQ_MODE    0x00000012
#define MON_MODE    0x00000016
#define ABT_MODE    0x00000017
#define HYP_MODE    0x0000001a
#define UND_MODE    0x0000001b
#define SYSTEM_MODE 0x0000001f
#define MODE32_BIT  0x00000010
#define MODE_MASK   0x0000001f
 
각 매크로를 선언해 놨는데, 그 동안 ARM 아키텍처에서 소개한 ARM 모드를 나타내는 매크로로 보인다.
위에서 선언된 매크로가 실제 코드에서 어떤 방식으로 사용되는지 확인해보자.
 
arch/arm/kvm/emulate.c
unsigned long *__vcpu_spsr(struct kvm_vcpu *vcpu)
{
    unsigned long mode = *vcpu_cpsr(vcpu) & MODE_MASK;
    switch (mode) {
    case SVC_MODE:
        return &vcpu->arch.ctxt.gp_regs.KVM_ARM_SVC_spsr;
    case ABT_MODE:
        return &vcpu->arch.ctxt.gp_regs.KVM_ARM_ABT_spsr;
    case UND_MODE:
        return &vcpu->arch.ctxt.gp_regs.KVM_ARM_UND_spsr;
    case IRQ_MODE:
        return &vcpu->arch.ctxt.gp_regs.KVM_ARM_IRQ_spsr;
    case FIQ_MODE:
        return &vcpu->arch.ctxt.gp_regs.KVM_ARM_FIQ_spsr;
    default:
        BUG();
    }
}
 
위 코드에서 핵심 루틴은 다음과 같다.
 
   *vcpu_cpsr(vcpu) & MODE_MASK;
 
이를 해석하면 ARM 명령어로 레지스터 CPSR를 읽어서 MODE_MASK(0x1f)와 AND 비트 연산을 하면 ARM의 모드를 알 수 있다는 것이다.
 
위 내용을 참고해서 패치를 작성해보자.
 
diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c
index 8448613..ac43177 100644
--- a/arch/arm/kernel/irq.c
+++ b/arch/arm/kernel/irq.c
@@ -47,8 +47,23 @@
 
 unsigned long irq_err_count;
 
+
+#define MODE_MASK   0x0000001f
+
+static inline unsigned long arch_arm_mode(void)
+{
+    unsigned long cpsr_value;
+
+    asm volatile(
+        "   mrs %0, cpsr"
+        : "=r" (cpsr_value) : : "memory", "cc");
+    return (cpsr_value & MODE_MASK) ;
+}
+
+
 int arch_show_interrupts(struct seq_file *p, int prec)
 {
+       unsigned long arm_mode = 0;
 #ifdef CONFIG_FIQ
        show_fiq_list(p, prec);
 #endif
@@ -56,6 +71,10 @@ int arch_show_interrupts(struct seq_file *p, int prec)
        show_ipi_list(p, prec);
 #endif
        seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
+
+       arm_mode = arch_arm_mode();
+
+       printk(" arm mode = %lx \n", arm_mode);
        return 0;
 }
 
@@ -67,6 +86,11 @@ int arch_show_interrupts(struct seq_file *p, int prec)
  */
 void handle_IRQ(unsigned int irq, struct pt_regs *regs)
 {
+       unsigned long arm_mode = 0;
+
+       arm_mode = arch_arm_mode();
+       printk(" arm mode = %lx \n", arm_mode);
+
        __handle_domain_irq(NULL, irq, false, regs);
 }
 
위 패치 코드의 핵심은 아래 함수이다.
 
static inline unsigned long arch_arm_mode(void)
{
    unsigned long cpsr_value;
 
    asm volatile(
        "   mrs %0, cpsr"
        : "=r" (cpsr_value) : : "memory", "cc");
    return (cpsr_value & MODE_MASK) ;
}
 
"   mrs %0, cpsr" 명령어를 실행해 cpsr_value 지역 변수에 레지스터 cpsr를 읽어서,
MODE_MASK(0x1f)와 AND 비트 연산을 한 결과를 반환하는 동작이다.
 
이번에는 이 코드를 빌드하면 실제 어셈블리 코드로 어떻게 변환됐는지 확인해보자.
 
c0209284 <arch_show_interrupts>:
c0209284:   e1a0c00d    mov ip, sp
c0209288:   e92dd830    push    {r4, r5, fp, ip, lr, pc}
...
c02092d0:   eb077061    bl  c03e545c <seq_printf>
c02092d4:   e10f1000    mrs r1, CPSR
c02092d8:   e59f001c    ldr r0, [pc, #28]   ; c02092fc <arch_show_interrupts+0x78>
c02092dc:   e201101f    and r1, r1, #31
c02092e0:   eb01c3d8    bl  c027a248 <printk>
 
위 코드에서 핵심 구문을 분석해보자.
 
c02092d4:   e10f1000    mrs r1, CPSR
 
레지스터 CPSR값을 r1 레지스터로 읽어 오는 동작이다.
 
c02092d8:   e59f001c    ldr r0, [pc, #28]   ; c02092fc <arch_show_interrupts+0x78>
c02092dc:   e201101f    and r1, r1, #31
 
r1 레지스터와 31(0x1f)와 AND 비트 연산을 수행한 결과를 r1 레지스터에 저장한다.
 
ARM 어셈블리 명령어로 ARM 모드를 직접 설정할 수 있는 명령어를 제공한다.
cpsr 레지스터에서 ARM의 모드가 있는 [4:0] 비트만 설정할 수 있는 기능이다.
 
이를 활용해 다시 패치를 작성해봤다.
 
diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c
index 8448613..4a1b55c 100644
--- a/arch/arm/kernel/irq.c
+++ b/arch/arm/kernel/irq.c
@@ -47,8 +47,31 @@
 
 unsigned long irq_err_count;
 
+
+#define MODE_MASK   0x0000001f
+
+static inline unsigned long arch_arm_mode(void)
+{
+    unsigned long cpsr_value;
+
+    asm volatile(
+        "   mrs %0, cpsr"
+        : "=r" (cpsr_value) : : "memory", "cc");
+    return (cpsr_value & MODE_MASK) ;
+}
+
+static inline void arch_arm_set_svc_mode(void)
+{
+       unsigned long flags = SVC_MODE | PSR_I_BIT | PSR_F_BIT;
+
+       asm volatile(
+               "   msr cpsr_c, %0"
+               : "=r" (flags) : : "memory", "cc");
+}
+
 int arch_show_interrupts(struct seq_file *p, int prec)
 {
+       unsigned long arm_mode = 0;
 #ifdef CONFIG_FIQ
        show_fiq_list(p, prec);
 #endif
@@ -56,6 +79,12 @@ int arch_show_interrupts(struct seq_file *p, int prec)
        show_ipi_list(p, prec);
 #endif
        seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
+
+       arm_mode = arch_arm_mode();
+       printk(" arm mode = %lx \n", arm_mode);
+
+       arch_arm_set_svc_mode();
+
        return 0;
 }
 
@@ -67,6 +96,11 @@ int arch_show_interrupts(struct seq_file *p, int prec)
  */
 void handle_IRQ(unsigned int irq, struct pt_regs *regs)
 {
+       unsigned long arm_mode = 0;
+
+       arm_mode = arch_arm_mode();
+       printk(" arm mode = %lx \n", arm_mode);
+
        __handle_domain_irq(NULL, irq, false, regs);
 }
 
이 패치의 핵심 코드는 다음 함수이다.
 
static inline void arch_arm_set_svc_mode(void)
{
       unsigned long flags = SVC_MODE | PSR_I_BIT | PSR_F_BIT;
 
       asm volatile(
               "   msr cpsr_c, %0"
               : "=r" (flags) : : "memory", "cc");
}
 
위 코드를 빌드한 후 어셈블리 코드를 확인한 결과는 다음과 같다.
 
c0209284 <arch_show_interrupts>:
c0209284:   e1a0c00d    mov ip, sp
c0209288:   e92dd830    push    {r4, r5, fp, ip, lr, pc}
c020928c:   e24cb004    sub fp, ip, #4
...
c02092d0:   eb077061    bl  c03e545c <seq_printf>
c02092d4:   e10f1000    mrs r1, CPSR
c02092d8:   e59f0020    ldr r0, [pc, #32]   ; c0209300 <arch_show_interrupts+0x7c>
c02092dc:   e201101f    and r1, r1, #31
c02092e0:   eb01c3d8    bl  c027a248 <printk>
c02092e4:   e121f003    msr CPSR_c, r3
 
'msr CPSR_c, r3' 명령어가 실행되서 ARM 프로세서의 모드를 SVC로 업데이트 한다.
 
(주의)
이 코드는 실험용으로 쓸만하다. 실제 시스템에 적용되면 무슨 일이 일어날 지 모른다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
ARM을 공부하면 가장 먼저 만나는 용어가 있습니다. 바로 ARM 동작 모드입니다. ARM 모드는 ARM의 세부 동작을 배우려면 반드시 알아야 하므로 잘 익혀 둘 필요가 있습니다. 
 
특히 64비트 기반의 ARMv8 아키텍처는 기존 ARM의 모드에 대응되는 익셉션 레벨이란 개념을 도입했습니다. 익셉션 레벨이란 개념이 기존 ARMv7 아키텍처의 ARM 모드에서 발전됐으니 먼저 ARM의 동작 모드부터 살펴봅시다.
 
ARM 모드의 종류
 
이 ARM 동작 모드를 잘 익혀야 시스템을 디자인하고 디버깅할 때 배운 내용을 유용하게 활용할 수 있습니다.  실행 모드를 잘 살펴 보면 유저(User) 모드는 표준 사용자 모드라고 부르는데 나머지 6가지 모드는 Privileged mode라고 분류됩니다. ARM User Manual을 열어 보면 각 모드에 대한 설명은 아래와 같이 되어 있습니다. 
 
#출처: DDI0406C_arm_architecture_reference_manual
 
User mode
An operating system to runs applications in User mode to restrict the use of system resources.
Application programs normally execute in User mode, and any program executed in User mode:
  • makes only unprivileged accesses to system resources, meaning it cannot access protected system resources
  • makes only unprivileged access to memory
 
System mode 
* Software executing in System mode executes at PL1. System mode has the same registers available
as User mode, and is not entered by any exception
 
Supervisor mode
* Supervisor mode is the default mode to which a Supervisor Call exception is taken.
* Executing a SVC (Supervisor Call) instruction generates an Supervisor Call exception, that is taken to Supervisor mode.
* A processor enters Supervisor mode on Reset.
 
Abort mode 
* Abort mode is the default mode to which a Data Abort exception or Prefetch Abort exception is taken.
 
Undefined mode
* Undefined mode is the default mode to which an instruction-related exception, including any
attempt to execute an UNDEFINED instruction, is taken.
 
FIQ mode 
* FIQ mode is the default mode to which an FIQ interrupt is taken.
 
IRQ mode 
* IRQ mode is the default mode to which an IRQ interrupt is taken.
 
Monitor mode
* Monitor mode is the mode to which a Secure Monitor Call exception is taken.
 
보시다시피 ARM 코어는 위에서 소개한 모드 중 하나를 사용합니다. 그런데 문제는 ARM 모드에 대한 내용으 읽어도 감이 잘 안온다는 점입니다. 
 
   * 무슨 모드가 이렇게 많아? 이걸 왜 쓰는 거지? 
   * 난 어떤 ARM 모드를 써야 할까?
 
의문은 끊이지 않습니다. 먼저 범용 리눅스를 기반으로 자주 사용하는 모드를 알아봅시다.
먼저 유저 모드(User mode) 입니다. 쉽게 설명하면 리눅스 애플리케이션은 유저 모드에서 동작합니다.
 
한 가지 예를 들겠습니다. 여러분이 라즈베리 파이나 우분투에서 다음과 같은 코드를 작성해 실행을 시켰습니다. 너무나 쉬운 예제 코드라 코드 내용과 컴파일을 하는 방법은 설명하지 않겠습니다.
 
#include <stdio.h>
int main() 
{
printf("Hello World! \n");
 
return 0;
}
 
위 코드는 라즈베리 파이나 우분투에서 어느 모드로 동작할 까요? ARM의 유저 모드입니다.
여기서 또 한 가지 의문이 생깁니다. 
 
   * 리눅스 애플리케이션이 ARM의 유저 모드에서 동작한다고 설명을 했다.
   * 당신은 이 사실을 어떻게 증명할 것인가?
 
좋은 질문입니다. 어떤 지식이든 그대로 답습을 하지 말고 배운 내용이 정말로 맞는지 증명해보는 습관을 갖는 것은 매우 중요합니다. '사실 - 증명'의 과정을 거치는 게 진정한 공부입니다.
 
현재 실행 중인 코드가 ARM의 어떤 모드에서 실행 중인지를 알려면 ARM에서 제공하는 CPSR(Current Program Status Register)를 읽어보면 됩니다. 보통 CPSR 레지스터의 [0:4] 비트가 0x10 이면 ARM의 유저 모드에서 동작한다고 볼 수 있습니다. 
 
어떤 ARM 모드를 써야 할까?
 
여기서 다음과 같은 의문이 생깁니다.
 
    * 평소에 애플리케이션을 실행할 때는 유저 모드를 사용하고 커널 레벨의 프로세스를 실행할 때는 시스템 모드를 사용하나?
    * 리눅스나 RTOS를 사용하지 않으면 유저 모드만 사용하면 되나?
 
이 질문에는 2가지 유형의 답이 가능하겠는데요. 
 
먼저 리눅스와 같은 범용 운영체제를 사용하는 시스템을 가정하겠습니다. 이 경우, 이미 라즈베리 파이나 안드로이드 그리고 우분투와 같은 리눅스 배포판 업체에서 리눅스 커널과 리눅스 애플리케이션을 구동할 수 있는 리눅스 유틸리티 프로그램을 미리 구성해 패키지로 배포합니다. 따라서, 리눅스 시스템 프로그래머 입장에서 일부러 유저 모드나 커널 모드를 어셈블리 명령어로 바꿀 필요가 없습니다. 유저 공간에서는 GNU C(glibc)과 커널이 알아서 바꿔주죠. 따라서 지금 실행하는 코드가 ARM의 어떤 모드에서 구동되는지 파악하면 됩니다. 유저 애플리케이션은 유저 모드, 커널의 프로세스는 시스템 모드(조금 더 정확히 슈퍼바이저 모드)에서 실행됩니다.
 
그런데, RTOS나 아예 RTOS로 탑재하지 않은 단순한 시스템에서는 디바이스의 스팩에 따라 시스템 모드를 구성하면 됩니다.
만약, SW 개발자 2명이서 시스템 전체를 개발한다면 모두 슈퍼바이저 모드에서 코드를 작성해도 됩니다. 혹은 시스템 모드에서 작성해도 되죠. 두 개발자가 메모리 맵을 다 알고 있고 메모리를 어떤 방식으로 처리하는지 이야기를 하면 끝나기 때문입니다.
 
만약 임베디드 개발자가 10명이고 애플리케이션을 다른 팀 개발자 5명이 개발한다면, ARM 모드를 어떻게 설정해야 할까요?
15명 전체 개발자가 모두 커뮤니케이션이 잘 된다면 모두 슈퍼바이저 모드에서 개발해도 되지만, 되도록 애플리케이션은 유저 모드 커널 프로세스는 슈퍼바이저 모드에서 실행되도록 구성하는 게 좋습니다. 
 
정리하면 사용하고 있는 디바이스의 스팩이나 시스템 구성에 따라 어떻게 모드를 설정할지 결정하면 됩니다.
 

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

 
 

+ Recent posts