임베디드 OS 개발 프로젝트를 읽고 작성하였습니다.
A.1 ARM 아키텍처 기초 지식
ARM 기반으로 펌웨어를 만들기 위해 기본적으로 알아두어야 할 ARM 아키텍처 내용!
A.1.1 익셉션 벡터 테이블
전원이 켜지면 ARM은 익셉션 벡터 테이블의 리셋 벡터를 읽음
익셉션 벡터 테이블의 위치는 ARM이 정한 기본 위치인 0x00000000(메모리 시작 위치) 메모리 주소에 배치함
오프셋 | 이름 | 설명 |
0x00 | Reset | 전원이 켜지면 실행됨 |
0x04 | Undefined Instruction | 잘못된 명령어를 실행했을 때 |
0x08 | SVS(Supervisor Call) | SVC 명령으로 발생시키는 익셉션 |
0x0C | Prefetch Abort | 명령어 메모리에서 명령어를 읽다가 문제가 생김 |
0x10 | DataAbort | 데이터 메모리에서 데이터를 읽다가 문제가 생김 |
0x14 | Not used | 사용하지 않음 |
0x18 | IRQ interrupt | IRQ 인터럽트가 발생했을 때 |
0x1C | FIQ interrupt | FIQ 인터럽트가 발생했을 때 |
< 표 A.1 익셉션 벡터 테이블의 구성 >
ARM은 익셉션 벡터 테이블에 정의된 상황이 발생하면
-> 프로그램 카운터(PC)를 익셉션 벡터 테이블에 정의된 오프셋 주소로 강제 변환함
-> 익셉션 벡터 테이블에 있는 명령을 바로 실행함
익셉션 벡터 테이블의 익셉션
각각 4바이트씩 할당
32비트 머신이니까 한 익셉션에 명령어 한 개만 실행할 수 있음
익셉션 핸들러 (exception handler)
: 익셉션을 처리하는 코드
명령어를 쓸 수 있는 최소 크기(4바이트)를 배정하고 거기에 브랜치 명령어를 써 놔서 익셉션을 처리하는 코드로 점프하게 만듦
정리
익셉션이 발생하면 ARM 코어는 프로그램 카운터를 익셉션 벡터 테이블의 익셉션 벡터 오프셋으로 변경
거기에 브랜치 명령을 쓰고 브랜치 명령에 따라 다시 점프하면 익셉션 핸들러로 진입하고, 이 익셉션 핸들러에서 익셉션을 처리함
* 익셉션 벡터 오프셋의 크기는 명령어 한 개만 쓸 수 있는 크기
A.1.2 익셉션의 종류
익셉션 (Exception)
: 주변 장치(peripheral) 등에서 발생한 신호를 처리하기 위해 순차적으로 진행하던 프로그램의 수행을 끊는 것단순히 프로그램 카운터의 값을 바꾸는 것과 다른 의미임
프로그램 카운터는 제어문이나 조건문을 만나지 않는 한 항상 값이 명령어 한 개의 크기만큼 증가함제어문이나 조건문을 만나면 값이 줄어들거나 명령어 한 개의 크기보다 더 크게 변할 수는 있지만,R0부터 R14까지의 레지스터 값은 실행 중인 코드에서 유효성을 유지해야 함유효성을 유지해야 한다는 말은 쉽게 말해 값이 깨지지 않아야 한다는 말
익셉션이 발생하면 진행 중인 프로그램 흐름(context)과 상관없이 익셉션 벡터를 거쳐 익셉션 핸들러로 무조건 들어감=> 모든 레지스터의 유효성이 깨짐
익셉션 핸들러에서 익셉션을 다 처리하고 나서는 원래 진행하던 위치로 복귀를 해야 함레지스터 값의 유효성이 익셉션 발생 시점에서 깨지므로 익셉션 핸들러 처리 후에 원래 위치로 복귀할 수 없는 상황이 생김그래서 익셉션이 발생했을 때 익셉션 핸들러를 처리하고 나도 프로그램이 망가지지 않고 제대로 동작하도록 작업을 해 줘야 함
익셉션이 발생하면 프로그램 흐름이 다시 원래 위치로 복귀할 수 있도록 R14에 복귀할 주소를 자동으로 저장
익셉션이 발생했을 때 ARM이 자동으로 수행하는 동작을 순서
1. ARM 모드일 때는 익셉션에 따라 PC+4 혹은 PC+8을 R14_x(x는 각 익셉션별 동작 모드)에 저장. 만약 Thumb 모드라면 PC+2 또는 PC+4를 R14_x에 저장.
2. CPSR을 익셉션별 동작 모드에 연결된 SPSR_x에 저장.
3. CPSR의 동작 모드 비트와 I, T 비트의 값을 각 익셉션과 동작 모드에 맞게 변경.
4. SCTLR(System Control Register)의 EE 비트 값에 따라 E 비트를 설정.
5. SCTLR의 TE 비트 값에 따라 T 비트를 설정.
6. PC의 값을 익셉션 벡터 위치로 강제 변경.
A.1.3 인터럽트
: 프로그램의 흐름을 누군가 가로채는 것을 말함
: 외부 요인에 의해서 발생하는 것
EX) 사용자의 행동(버튼이 눌림, 액정 화면 터치), 시간의 흐름 (1ms마다 인터럽트 발생)
* ARM에서는 인터럽트와 익셉션이 차이 없이 동작함
* 인터럽트가 발생했을 때 이를 처리하는 인터럽트 핸들러나 익셉션 핸들러를 같은 개념으로 생각해도 됨
인터럽트 지연(latency)
하드웨어가 인터럽트를 감지해서 ARM에 인터럽트 신호가 입력되는 순간부터 펌웨어에서 인터럽트 핸들러가 수행되기 직전까지 걸리는 시간
인터럽트는 필연적으로 인터럽트 지연(latency)을 발생시킴
거의 찰나에 가까운 매우 짧은 시간이긴 하지만 임베디드 시스템의 목적에 따라서 이 시간이 문제가 되기도 함
=> sol : 벡터 인터럽트 컨트롤러(Vectored Interrupt Controller, VIC) 등의 별도 하드웨어로 인터럽트 지연을 최대한 줄이려는 여러 가지 시도를 함
ARM의 인터럽트
- IRQ / FIQ 제공
- IRQ보다 FIQ가 더 빠른 처리를 목적으로 설계되었음
- 인터럽트가 발생하면 익셉션 처리에 해당하는 동작을 수행하고 IRQ 익셉션 혹은 FIQ 익셉션 벡터로 PC가 변경됨
인터럽트에 대한 다섯 가지 개념
1. Interrupt Request (IRQ)
IRQ는 FIQ보다 우선순위가 낮으므로 만약 IRQ와 FIQ가 동시에 발생하면 ARM은 FIQ에 대한 처리 요청을 펌웨어에 먼저 보냄
CPSR의 I 비트를 1로 설정하면 IRQ 익셉션을 비활성화함
-> 자동으로 IRQ에 해당하는 모든 인터럽트 요청도 펌웨어로 처리되지 않음
2. Fast Interrupt Request (FIQ)
IRQ와 동작 특성 동일
IRQ보다 빠른 이유 : FIQ 익셉션 동작 모드는 별도로 R8에서 R12까지의 레지스터를 가지고 있기 때문
펌웨어에서 인터럽트 처리를 할 때 R8에서 R12까지만 사용하도록 코드를 작성하면 레지스터를 백업하고 복구하는 시간을 사용하지 않아도 됨.
-> 빨라짐 (컨텍스트 스위칭 오버헤드 줄임)
3. Non-Maskable Fast Interrupt (NMFI)
NMFI를 켜면 FIQ를 비활성화 할 수 없음. FIQ는 마스크할 수 없는 인터럽트라는 의미
NMFI를 켰을 때 CPSR의 F 비트가 1이 되는 경우는 FIQ 익셉션이 발생했거나 리셋 익셉션이 발생했을 경우뿐
펌웨어가 하는 것이 아니라 하드웨어가 자동으로 처리하는 것
4. Low Interrupt Latency (LIL)
인터럽트 지연을 줄이기 위한 기능 중 하나로 ARM의 기본 설정 기능
SCTLR의 21번째 비트인 FI 비트로 동작 여부를 알 수 있으며, 항상 1로 설정되어 있음
LIL은 인터럽트가 발생했을 때 현재 수행 중인 명령의 실행이 아직 끝나지 않았다 하더라도 실행 중인 명령어를 취소해 버리고 인터럽트를 먼저 처리함
실행이 끝나지 않은 명령어는 인터럽트 핸들러의 처리가 모두 끝난 다음에 원래 프로그램의 진행 흐름으로 복귀할 때 SUBS PC, r14, #4로 인터럽트가 발생했던 시점에서 한 명령어 뒤로 다시 돌아가도록 해서 처리함
이 말은 인터럽트 처리를 위해서 같은 명령어를 두 번 실행한다는 뜻이지만 인터럽트의 지연을 줄이고 그 시간을 인터럽트 처리 후에 보상한다는 개념으로 이해할 수 있음
5. Interrupt Controller (IC)
VIC (Vectored Interrupt Controller)를 포함하는 Interrupt Controller는 인터럽트 처리를 전담하는 일종의 주변장치
ARM에는 인터럽트를 감지하는 핀이 IRQ와 FIQ 딱 두 개임
따라서 인터럽트가 발생했다는 것만 알 수 있지 어떤 인터럽트가 어떤 하드웨어에서 발생했는지를 알 수 없음
이를 알려면 인터럽트 컨트롤러에게 물어봐야 함
(1) Interrupt Controller의 기능
- 인터럽트가 발생했을 때 해당 인터럽트의 종류를 레지스터에 기록함
- 인터럽트가 발생했을 때 ARM의 IRQ 혹은 FIQ에 맞춰서 인터럽트 신호를 줌
- 특정 인터럽트를 골라서 마스킹할 수 있음. 마스킹된 인터럽트는 비활성화됨. 인터럽트 컨트롤러의 종류에 따라서 마스킹이 인터럽트를 켜는 것을 의미할 수도 있음. 인터럽트 컨트롤러의 메뉴얼을 보고 맞춰서 사용해야 함
- 여러 인터럽트 간에 우선순위를 설정할 수 있음
(2) 인터럽트가 발생하면 펌웨어가 세 단계를 처리해 인터럽트 서비스 루틴으로 진입
1. 인터럽트 컨트롤러에서 인터럽트 소스가 어떤 것인지를 판별
2. 인터럽트 소스에 따라 실행해야 할 인터럽트 서비스 루틴을 선택
3. 해당 인터럽트 소스를 비활성화하고 인터럽트 서비스 루틴으로 진입
(3) 인터럽트 서비스 루틴
: 인터럽트 핸들러의 하위 개념으로 인터럽트 핸들러에서 인터럽트의 종류를 판별한 다음 해당 인터럽트만 전담으로 처리하는 코드로 이동
이 해당 인터럽트만 전담하는 코드가 인터럽트 서비스 루틴
(4) VIC (Vectored Interrupt Controller)
VIC를 사용한다면 위의 세 단계를 하드웨어가 모두 처리한 다음 곧바로 펌웨어의 인터럽트 서비스 루틴으로 진입VIC는 인터럽트에 대해서 인터럽트 서비스 루틴의 시작 메모리 주소(펌웨어가 지정)를 직접 저장 (=함수 포인터를 하드웨어가 직접 가지고 있다고 생각하면 됨)인터럽트가 발생하면 VIC에 의해서 인터럽트 서비스 루틴으로 PC 값이 바로 바뀜
A.1.4 Abort
: 어디서 문제가 일어났는지 보고하지 않고 프로그램의 동작이 더 이상 진행되지 않도록 하는 것
ARM에서 Abort
: 인터럽트와 함께 익셉션의 한 종류로 정의
인터럽트 = 데이터 처리를 위해서 정상적인 프로그램의 흐름을 끊는 익셉션
abort = 비정상적인 동작으로 인해 정상적인 프로그램의 진행을 더 이상 진행할 수 없을 때 발생하는 익셉션
ARM에서 Abort가 발생하는 경우
- MPU (Memory Protection Unit)로 보호되는 메모리 영역에 접근 권한 없이 접근했을 때- AMBA (SoC의 주변장치 연결 및 관리를 위한 공개 표준) 메모리 버스가 에러를 응답했을 때- ECC (Error Correcting Code) 로직에서 에러가 발생했을 때
** 주로 메모리와 관련된 익셉션임을 알 수 있다ARM이 메모리에서 어떤 정보를 읽는 경우는 딱 두 가지임1. 명령어를 읽는 것 => abort가 발생하면 prefetch abort 익셉션으로 진행2. 데이터를 읽는 것 => abort가 발생하면 data abort 익셉션으로 진행
undefined instruction 익셉션
: 메모리나 다른 것들이 모두 정상인데 정작 읽어온 명령어가 ARM이 모르는 명령어인 경우 발생하는 익셉션 (일종의 abort)
이 익셉션은 오류이기도 하지만 익셉션 핸들러로 진입했을 때 익셉션 핸들러를 통해서 ARM에서 지원하지 않는 명령어를 처리할 수도 있음=> 이런 식으로 소프트웨어적으로 기능을 확장 가능
소프트웨어적으로 기능을 만들어서 처리할 수도 있고 코프로세서 (co-processor)라는 보조 프로세서 하드웨어를 연결하여 기능을 확장할 수도 있음
=> undefined unstruction 익셉션 핸들러에서 디코딩하지 못한 명령어를 코프로세서로 보내고 코프로세서가 처리 결과를 리턴하면 해당 결과를 펌웨어가 이용하는 식
A.1.5 동작 모드와 뱅크드 레지스터
동작 모드 (Operating Mode)
: ARM에는 익셉션과 관련하여 동작 모드라는 개념이 있음
특정 익셉션이 특정 동작 모드에 연결되어 있기도 하고 익셉션과 상관없이 존재하는 동작 모드도 있음
1. User 모드 (USR)
- 일반적으로 사용하는 모드
- ARM 상태 / Thumb 상태
- 운영체제를 사용한다면 사용자 프로그램은 일반적으로 USR 모드에서 동작함
2. Fast Interrupt 모드 (FIQ)
- FIQ 익셉션이 발생하면 FIQ 모드로 전환됨
- ARM 상태일 때만 동작
- 뱅크드 레지스터(banked register) : 빠른 처리를 위해서 별도로 레지스터를 몇 개 더 가짐
3. Interrupt 모드 (IRQ)
- IRQ 익셉션이 발생하면 IRQ 모드로 전환
- ARM 상태일 때와 Thumb 상태일 때 모두 동작 가능
4. Suervisor 모드 (SVC)
- 운영체제 등에서 시스템 코드를 수행하기 위한 보호 모드
- 보통 운영체제에서 시스템 콜(system call)을 호출하면 SVC 익셉션을 발생시켜 SVC 모드로 전환한 후 커널 동작을 수행함
- SVC 익셉션은 메모리나 하드웨어에 상관없이 순수하게 소프트웨어에 의해서 발생하는 익셉션
5. Abort 모드 (ABT)
- Data abort나 Prefetch abort가 발생했을 때 전환되는 동작 모드
6. System 모드 (SYS)
- 운영체제 등에서 사용자 프로세스가 임시로 커널 모드를 획득해야 하는 경우 SYS 모드를 사용
- 사용자 모드와 커널 모드를 구분하는 운영체제가 아닌 운영체제라면 SYS 모드가 기본 동작 모드인 경우가 많음
- SYS 모드는 익셉션과 연관되어 있지 않고 소프트웨어로 모드 전환을 하여 진입 가능
7. Undefined 모드 (UND)
- Undefined instruction이 발생했을 때 진입하는 동작 모드
레지스터
각 동작 모드에 따라 ARM은 각각 다른 레지스터를 가지고 있기도 하고 동작 모드들이 같은 레지스터를 공유하기도 함.
ARM은 작업 레지스터를 모두 37개 가지고 있는데, 32비트 범용 레지스터 31개와 상태 레지스터 6개.
R0 ~ R12, 범용 레지스터 (general purpose register) : 펌웨어가 데이터를 일반적으로 처리할 때 사용하는 레지스터
R13, R14, R15 : 특수 목적 레지스터
R13, 스택 포인터 (Stack Pointer, SP) : 스택의 위치를 추적하고 있는 레지스터, 스택에 데이터를 넣거나(push) 가져올(pop) 때 스택 포인터 값만 알고 있으면 그 값을 증가시키거나 감소시키면서 관리 가능
R14, 링크 레지스터 (Link Register, LR) : 서브 루틴 혹은 함수 호출이 끝난 후 리턴 어드레스(return address)를 저장함
R15, 프로그램 카운터 (Program Counter, PC) : 다음 명령어의 주소 혹은 branch or jump시 해당 명령어의 주소를 담음, ARM모드이면 4바이트 증가 Thumb모드이면 2바이트 증가
FIQ 모드의 R8부터 R12까지는 FIQ 모드에서만 쓸 수 있게 배정됨
이전 레지스터의 값을 백업할 필요 없음
개별 동작 모드는 모두 SP와 LR을 뱅크드 레지스터로 가지고 있음
각 동작 모드가 독립된 스택 영역을 유지할 수 있고 각 동작 모드가 다른 동작 모드의 영향을 받지 않으면서 다른 동작 모드로 원활하게 복귀할 수 있음
각 동작 모드에서 공유하지 않고 전용으로 사용하는 레지스터는 같은 이름을 가지고 있지만, 실제로 독립된 공간에 데이터를 저장함
-> 뱅크드 레지스터(banked register) : 각 동작 모드별로 SP, LR, SRSR
A.1.6 ARM 모드와 Thumb 모드
ARM 모드 명령어 : 32비트 명령어 집합
Thumb 모드 명령어 : 16비트 명령어 집합
동일한 C 언어로 코드를 각각 ARM 모드로 컴파일해 보고 Thumb 모드로 컴파일해 보면, 결과로 생성되는 바이너리의 크기가 다름
일반적으로 Thumb 모드가 ARM 모드에 비해서 70% 작음
ARM은 ARM 모드와 Thumb 모드를 하나의 펌웨어에서 섞어 쓰는 것을 허용함
속도가 별로 중요하지 않은 부분은 Thumb 모드로 컴파일해서 링크
속도가 중요한 부분은 ARM 모드로 컴파일해서 링크
ARM 모드와 Thumb 모드는 동작 모드에 따라 자동으로 바뀌기도 함.
Thumb 모드가 동작 중이여도 익셉션이 발생 해 익셉션 핸들러로 들어갈 때는 ARM 모드로 전환됨.
그래서 익셉션 핸들러는 ARM 모드로 컴파일되어야 함.
그리고 Thumb 모드에서는 R8 이상의 높은 번호의 레지스터 사용이 제한됩니다,
SP, LR, PC는 사용한데 반면 범용 레지스터 R8~R12는 몇 가지 명령어에서만 사용이 가능함.
A.1.7 프로그램 상태 레지스터
프로그램 상태 레지스터 (Program Status Register, PSR)
: 프로그램이 동작하면서 생기는 상태를 관리하는 레지스터
- CPSR (Current Program Status Register) : 현재 상태를 저장하는 레지스터
- SPSR (Saved PSR) : 상태를 저장하는 레지스터
CPSR, SPSR은 구조가 같음.
Cortex-A8의 PSR 구성
N : 계산 결과가 음수일 때 1로 변경됨
S : 계산 결과가 0일 때 1로 변경됨
C : 계산 결과에 자리 내림(carry)이 발생하거나 나눗셈을 할 때 자리 빌림(borrow)이 발생하면 1로 변경됨
V : 계산 결과에 오버플로(overflow)가 발생하면 1로 변경됨
Q : 곱셈을 할 때 32비트가 넘어가면 올림수에 이용함
J : Cortex-A 이상 프로세서에서 Jazelle 상태로 전환 시 1로 변경됨
DNM : Do Not Modify의 약자로, 확장을 위해서 비워 둠
GE[3:0] : SIMD(Single Instruction Multi Data) 명령을 사용해서 연산을 할 때 하프워드(half word) 단위로 크거나 같은지를 표시하는 비트
IT[7:2] : ITSTATE로 Thumb-2에 포함된 IT(if-then) 명령을 처리할 때 참조하는 비트, Thumb 모드에서 조건부 실행이 가능해짐
E : 데이터의 엔디안을 표시하는 비트
A : 예측 가능한 data abort만 발생하도록 함, 이 비트를 끄면 예측 불가능한 비동기 데이터 abort를 허용함
I : 이 비트가 1이면 IRQ가 비활성화됨
F : 이 비트가 1이면 FIQ가 비활성화됨
T : Thumb 모드일 때 1로 변경됨
M[4:0] : 모드 비트
- 10000 : USR
- 10001 : FIQ
- 10010 : IRQ
- 10011 : SVC
- 10111 : ABT
- 11011 : UND
- 11111 : SYS
'개발 > 임베디드 os 개발 프로젝트' 카테고리의 다른 글
6장 인터럽트 (1) | 2023.06.17 |
---|---|
5장 UART (3) | 2023.06.15 |
4장 부팅하기 (0) | 2023.06.04 |
3장 일단 시작하기 (0) | 2023.04.17 |
2장 개발 환경 구성하기 (0) | 2023.04.16 |