리셋에서 제어까지: ARM 베어 메탈에서 인터럽트 비활성화

발행: (2026년 1월 2일 오후 03:59 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

ARMv7에서 베어‑메탈 실행은 C 환경이 전혀 존재하기 전에 리셋 벡터에서 시작됩니다. QEMU의 vexpress‑a9 모델에서 Cortex‑A9가 리셋을 벗어나면 프로세서는 인터럽트가 마스크된 상태와 MMU가 비활성화된 상태의 Supervisor 모드로 진입합니다. 스택 포인터는 정의되지 않았으며, 메모리 섹션은 초기화되지 않고 핸들러도 설치되지 않습니다. 실행은 정의된 프로그램 카운터와 CPSR 값만으로 시작됩니다.

이 글에서는 실행 초기 단계(가장 처음)를 살펴보고, 최소한의 스타트업 어셈블리 블록이 리셋 후 어떻게 제어를 잡는지 보여줍니다: 인터럽트를 명시적으로 마스크하고, 프로세서 모드를 확인하며, 알려진 상태에서 정지합니다. 이는 스택, 메모리 초기화, 그리고 궁극적으로 C 런타임을 도입하기 전에 예측 가능한 기준점을 마련합니다.

Source: https://dev.to/ripan030/reset-on-armv7-42p7?preview=faa923959e61c5f6237c34a0aff32b99be91f7f7de66dc73eef40ecb1add9326590b15aee947a94e9e7c942f17734e55b219de281290336a1fc991a2

Reset Vector에서 _start까지

리셋 시, ARMv7은 다음과 같은 초기 상태를 정의합니다:

  • 모드: Supervisor (0b10011)
  • IRQ 마스크 (CPSR.I): 1 (비활성화)
  • FIQ 마스크 (CPSR.F): 1 (비활성화)
  • 명령어 집합: ARM 상태
  • MMU: 비활성화

리셋 시 인터럽트가 아키텍처적으로 마스크되어 있지만, 초기 시작 코드에서는 이 암시적인 상태에 의존해서는 안 됩니다. 인터럽트를 명시적으로 비활성화하면 벡터 테이블이나 예외 핸들러를 설치하기 전까지 결정론적인 환경을 보장할 수 있습니다.

이전 글 Bare Metal ARM Boot: Understanding the Reset Vector and First Instructions에서는 최소한의 벡터 테이블과 리셋 벡터를 설정했습니다:

  • 플래시의 주소 0x0에 배치된 벡터 테이블
  • 리셋 벡터가 _vectors에서 _start로 분기
  • _start에 무한 루프 포함

이번 글에서는 그 예제를 확장하여 _start에 실제 첫 번째 역할을 부여합니다: 인터럽트 마스킹을 강제하고, 그 후에 정지하도록 합니다.

CPSR 개요

Current Program Status Register (CPSR) 은 실행의 주요 측면을 제어합니다:

  • Bits [31:28] – 조건 플래그 (N, Z, C, V)
  • Bit 7 – IRQ 비활성화 (I)
  • Bit 6 – FIQ 비활성화 (F)
  • Bits [4:0] – 모드 비트

일반적인 모드 인코딩:

모드
User0x10
FIQ0x11
IRQ0x12
Supervisor0x13
Abort0x17
Undefined0x1B
System0x1F

인터럽트 명시적 비활성화

cpsid 명령어(Change Processor State, Interrupt Disable)는 인터럽트 마스킹을 직접 제어할 수 있게 해줍니다:

  • cpsid i – IRQ 비활성화
  • cpsid f – FIQ 비활성화
  • cpsid if – 두 가지 모두 비활성화

cpsid는 특권 명령어이므로 Supervisor와 같은 모드에서만 실행할 수 있습니다. 시작 시 cpsid if를 실행하면 유효한 핸들러가 준비될 때까지 실수로 예외가 발생하는 것을 방지할 수 있습니다.

최소 스타트업 어셈블리

    .section .vectors, "ax"
    .global _vectors
_vectors:
    b _start            @ Reset vector: branch to startup

    .section .text
    .global _start
_start:
    @ Disable IRQ and FIQ interrupts
    cpsid   if

    @ Infinite loop
halt:
    b   halt

GDB로 동작 확인하기

브레이크포인트를 설정하면 실행이 리셋 벡터에서 _start 로 흐르는 것을 확인할 수 있습니다:

(gdb) break _start
Breakpoint 1 at 0x4: file startup.s, line 9.
(gdb) continue
Continuing.

Breakpoint 1, _start () at startup.s:9
9       cpsid   if

cpsid if 명령을 실행하기 전후의 CPSR을 확인해 봅니다:

(gdb) info registers cpsr
cpsr           0x400001d3          1073742291
(gdb) stepi
10      b .
(gdb) info registers cpsr
cpsr           0x400001d3          1073742291

CPSR 값이 변하지 않는 이유는 리셋 시 이미 인터럽트가 마스크되어 있었기 때문입니다.

이진 형태 보기:

(gdb) print/t $cpsr
$1 = 1000000000000000000000111010011

해석

  • Bits [4:0] = 10011 → Supervisor 모드
  • Bit 6 = 1 → FIQ 비활성화
  • Bit 7 = 1 → IRQ 비활성화

이는 시작 코드가 특권 모드에서 실행되고 있으며 두 인터럽트 소스가 모두 마스크되어 있음을 확인시켜 줍니다.

Conclusion

프로세서는 이제 리셋에서 시작 어셈블리로 깔끔하게 전환되어 제어된 실행 상태를 설정합니다. 인터럽트 마스킹이 명시적으로 적용되고, 프로세서 모드가 검증됩니다. 이러한 기반이 마련되면 이후 단계인 스택 설정, .data.bss 초기화, 그리고 main() 진입을 안전하게 도입할 수 있습니다.

다음 포스트에서는 이 최소 부트스트랩을 기반으로 베어‑메탈 ARM 시스템용으로 사용할 수 있는 C 런타임을 구축할 것입니다.

Back to Blog

관련 글

더 보기 »