동작 모드는 왜 존재하며, 각각 어떻게 활용될까? 진행 중인 프로젝트에서 혹시 운영체제를 사용하지 않는 경우에는 유저 모드만 사용하면 될까?
이 질문에 대해 "프로젝트의 스펙에 맞게 동작 모드를 선택하면 된다"라고 말씀드리고 싶습니다.
동작 모드를 선택하는 예시
한 가지 예를 들까요? 디스플레이 디바이스에 '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에서는 어떻게 활용되는지 알아봅시다.
그런데 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의 세부 동작을 배우려면 반드시 알아야 하므로 잘 익혀 둘 필요가 있습니다.
특히 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명 전체 개발자가 모두 커뮤니케이션이 잘 된다면 모두 슈퍼바이저 모드에서 개발해도 되지만, 되도록 애플리케이션은 유저 모드 커널 프로세스는 슈퍼바이저 모드에서 실행되도록 구성하는 게 좋습니다.
정리하면 사용하고 있는 디바이스의 스팩이나 시스템 구성에 따라 어떻게 모드를 설정할지 결정하면 됩니다.