콘텐츠로 바로가기

Bare-metal Memory Mapping

운영체제 없이 하드웨어 레지스터와 주변 장치를 CPU 주소 공간에 직접 사상하고 제어하는 베어메탈 환경의 물리적 메모리 관리 기법을 다루는 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

computer-architecture-embedded-systemscomputer-architectureembedded-systemshardware-software-interface-physicsbare-metal-memory-mappinglearningmmiodma10 min read

1. Overview

베어메탈 메모리 매핑(Bare-metal Memory Mapping)은 운영체제(OS)가 제공하는 가상 메모리의 안락한 거짓말(추상화)을 모두 찢어버리고, C 언어의 포인터(Pointer)를 통해 실리콘 다이(Die)에 하드와이어링된 물리적 주소를 직접 타격하는 극한의 로우 레벨 소프트웨어 공학입니다.

학습자는 CPU가 부팅되어 가장 처음 실행하는 0번지 주소의 **리셋 벡터(Reset Vector)**와, C 언어의 전역 변수가 램(SRAM)의 제자리로 찾아가는 스캐터 로딩(Scatter Loading) 과정을 뜯어봅니다. 나아가 하드웨어 주변 장치(LED, 센서 제어)를 통제하기 위해 특수 기능 레지스터(SFR) 번지에 1과 0을 쏘아 넣는 **메모리 맵 I/O (MMIO)**의 기하학과, 똑똑한 컴파일러의 간섭을 막아내는 volatile 키워드의 물리 법칙을 해부하여, 칩 제조사의 데이터시트를 코드로 번역하는 밑바닥 통제력을 확보합니다.

2. Scope & Boundaries

In-Scope

  • 메모리 맵 아키텍처 (Memory Map): 코드(Flash/ROM), 데이터(SRAM), 주변 장치(Peripherals) 공간 분할 물리.
  • 메모리 맵 I/O (MMIO): 특수 기능 레지스터(SFR, Special Function Register) 주소 포인터 제어, 비트와이즈 마스킹.
  • 컴파일러 억제 (Compiler Barrier): volatile 키워드의 본질적 의미, 레지스터 캐싱 방지.
  • 부트업 기하학 (Boot-up Sequence): 벡터 테이블(Vector Table), C 런타임 초기화(BSS 초기화, Data 복사), main() 진입.

Out-of-Scope

  • MMU와 가상 메모리: OS가 물리 주소를 숨기고 가상 주소를 맵핑하는 페이징(Paging) 기법 \rightarrow 03-01-02. Process Isolation & Namespaces 영역.
  • 고급 장치 드라이버: 리눅스 커널 모듈(LKM)이나 파일 시스템 관점에서의 문자/블록 디바이스 \rightarrow 03-04. I/O Devices & File System Physics 영역.

Boundaries

  • Bare-metal vs. Application (04-01): 애플리케이션 코딩이 "OS가 깔아놓은 안전한 고속도로(API) 위를 달리는 차"라면, 베어메탈은 "고속도로가 아예 없는 허허벌판에서 포인터라는 야전삽을 들고 직접 진흙길(물리 주소)을 파헤치며 전진하는 야생"입니다.

3. Counterexample

  • Volatile 누락에 의한 하드웨어 붕괴 (Compiler Optimization Trap): 특정 하드웨어 상태 레지스터가 1이 될 때까지 기다리기 위해 while(*status_reg == 0); 이라는 무한 루프를 짰는데, 최적화 모드(-O2)를 켠 컴파일러가 "어차피 메모리 안 변하네"라며 레지스터 값을 레지스터 파일(R0)에 캐싱해버려 영원히 무한 루프를 탈출하지 못하는 먹통(Deadlock). volatile 키워드를 빼먹는 순간 C 언어 컴파일러는 하드웨어를 통제할 수 없는 쓰레기를 뱉어냅니다.
  • Segmentation Fault 환상 (Physical Unbound): 리눅스에서 남의 배열 주소를 건드리면 커널이 즉시 프로그램을 사살(Segfault)해주지만, 베어메탈에서는 그런 천사 같은 보호막(MMU)이 존재하지 않습니다. 포인터 주소를 1칸 잘못 더해서 함수 리턴 주소가 깨지거나 엉뚱한 주변 장치 레지스터를 덮어쓰면, 에러 메시지 하나 없이 드론의 프로펠러가 반대로 돌거나 칩이 화염에 휩싸입니다.

4. Prerequisites

  • C 언어 포인터 (Basic): 주소를 변수에 담고(*ptr), 그 주소 안의 값을 변경하는 포인터 캐스팅 개념이 머릿속에 시각화되어 있어야 합니다. (05-01-02 C Pointer Physics)
  • 컴퓨터 구조 기본 (Basic): 폰 노이만 아키텍처와 버스(Bus)의 개념을 알아야 메모리 맵 I/O가 왜 가능한지 알 수 있습니다. (02-01-01 Digital Logic)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 Memory Map Geometry 32비트 칩의 4GB 공간 중, 어디가 플래시고 어디가 램이며 어디가 기계 팔을 움직이는 부위인지 지도를 펼칩니다. P1
2 MMIO & Pointers 데이터시트에 적힌 0x40020000 번지를 C 언어 포인터로 찌른 뒤 비트(Bit) 연산으로 1을 욱여넣어 하드웨어를 구동합니다. P5
3 Volatile & Compilers 하드웨어를 제어하는 변수에 volatile 족쇄를 채워, 컴파일러가 건방지게 코드를 최적화(삭제)하지 못하게 멱살을 잡습니다. Industry
4 Boot-up & C-Runtime 전원이 켜지자마자 0번지 벡터 테이블에서 출발해, 전역 변수들을 램으로 퍼 나르고 드디어 main() 함수로 진입하는 시공간을 해부합니다. Industry

6. Learning Topics

Basic

Core Topic 01: 4GB 우주 지도, 메모리 맵 아키텍처 (Memory Map Geometry)

  • Why to Learn: 칩(MCU) 설계자가 미리 정해놓은 절대적인 4GB 물리 주소 템플릿(어디가 ROM이고 어디가 RAM인지)을 머릿속에 박아 넣어야만 엉뚱한 곳에 데이터를 쓰다 칩을 멈추게 하는 삽질을 막을 수 있기 위함입니다.
  • What to Learn:
    • Concepts: 물리적 메모리 맵(Physical Memory Map), 하버드 아키텍처(명령어/데이터 버스 분리), 32비트 주소 공간(0x00000000 ~ 0xFFFFFFFF).
    • Skills: 플래시(Flash) / SRAM / Peripheral / System 영역 경계 식별.
    • Tools: ARM Cortex-M 데이터시트, 링커 스크립트(Linker Script).
    • Trade-offs: x86 같은 복잡한 칩은 장치를 I/O 전용 명령어(in, out)로 분리해 메모리 맵을 아끼는 타협을 하지만, ARM 기반 칩은 장치 레지스터마저 일반 램(RAM)처럼 4GB 안에 모조리 욱여넣는 MMIO(Memory-Mapped I/O)를 채택하여 프로그래밍 일관성을 극대화한 대신 주소 공간이 난도질당하는 단점.
  • How to Learn:
    • 1단계: ARM 칩 데이터시트를 펴고, 0x00000000 번지부터 512MB는 코드(Flash), 0x20000000 번지부터는 변수(SRAM), 0x40000000부터는 타이머/센서 제어 포트(Peripherals)로 칼같이 잘려있는 칩 내부 부동산(Die) 배치를 해부합니다.
    • 2단계: C 코드로 컴파일된 바이너리가 칩에 구워질 때 링커(Linker)가 어떻게 각 영역의 시작 주소를 코드에 하드코딩해 주는지, ROM_START, RAM_START 매직 넘버 맵핑을 뜯어봅니다.
  • Implement: 파이썬으로 가상 4GB 메모리 맵 딕셔너리를 작성. 0x20000000(RAM)에 쓰기를 시도하면 정상 작동하고, 0x00000000(ROM)에 쓰기를 시도하면 하드웨어 보호에 의해 HardFault 예외를 뱉어내는 메모리 컨트롤러 파이프라인 시뮬레이션.

Core Topic 02: 포인터 멱살잡이와 메모리 맵 I/O (MMIO & Bitwise)

  • Why to Learn: 운영체제의 장치 드라이버(API) 껍데기를 모두 걷어내고, LED를 켜거나 모터를 돌리기 위해 칩의 특정 물리 번지를 포인터로 강제 캐스팅해 1과 0의 펄스를 직접 꽂아 넣는 쾌감을 장악하기 위해서입니다.
  • What to Learn:
    • Concepts: MMIO(Memory-Mapped I/O), SFR(Special Function Register).
    • Skills: 포인터 캐스팅((volatile uint32_t*)), 비트 클리어(AND ~), 비트 셋(OR |), 비트 토글(XOR ^).
    • Tools: 매크로(Macro) 정의.
    • Trade-offs: 특정 핀 하나만 켜기 위해 비트 OR 연산을 쓰면 주변 비트가 안전하지만 CPU가 Read-Modify-Write 3단계를 거치며 속도가 3배로 느려지는 반면, 냅다 32비트 전체를 = 기호로 덮어씌우면 빛의 속도지만 다른 장치 핀까지 모조리 초기화되어 시스템이 작살나는 치명적 딜레마.
  • How to Learn:
    • 1단계: 데이터시트에 "GPIOA 포트의 5번 핀을 켜려면 0x40020014 번지의 5번째 비트를 1로 둬라"라고 적혀있을 때, 이를 C 언어 포인터 *(volatile uint32_t *)0x40020014 |= (1 << 5); 로 직역하여 하드웨어 전선을 쇼트시키는 물리를 해부합니다.
    • 2단계: 이런 매직 넘버를 코드 곳곳에 박아두면 유지보수가 지옥이 되므로, C 언어의 구조체(struct)와 포인터 오프셋(Offset)을 이용해 깔끔하게 레지스터 맵을 객체지향적으로 포장하는 스킬을 뜯어봅니다.
  • Implement: 0x40020000 번지를 가리키는 파이썬 가상 레지스터(32비트)를 만들고, SetBit(5)ClearBit(5) 함수를 비트 연산(&, |, ~)으로 구현. 상태 출력 시 0b00000000000000000000000000100000 처럼 비트 마스킹의 물리적 스크래치 변화를 시각화.

Practical

Core Topic 03: 컴파일러 족쇄, Volatile 역학 (Compiler Barrier)

  • Why to Learn: 1나노초라도 연산을 줄이려는 최적화 컴파일러(GCC -O3)가, 하드웨어를 제어하는 프로그래머의 소중한 루프문을 "불필요한 중복 연산"으로 취급해 맘대로 삭제해 버리는 참사를 원천 차단하기 위함입니다.
  • What to Learn:
    • Concepts: volatile 한정자(Qualifier), 컴파일러 최적화(Compiler Optimization), 사이드 이펙트(Side Effects).
    • Skills: 레지스터 변수 캐싱 방지, 메모리 배리어(Memory Barrier)의 소프트웨어적 의미.
    • Tools: objdump 어셈블리 분석.
    • Trade-offs: 모든 전역 변수에 volatile을 떡칠하면 컴파일러가 변수를 읽을 때마다 무조건 램(RAM)까지 다녀오게 되어 안전빵은 챙기지만, 레지스터 캐싱을 아예 못 쓰게 되어 프로그램 전체 성능이 거북이처럼 박살 나는 최악의 튜닝.
  • How to Learn:
    • 1단계: 센서에서 값이 들어올 때까지 while(Sensor_Reg == 0); 이라며 무한 대기하는 코드를 짰을 때, 컴파일러가 코드를 분석하더니 "이 루프 안에서는 Sensor_Reg를 아무도 안 바꾸네? 그럼 무한 루프 while(1); 이랑 똑같네!" 하고 코드를 날려먹는 논리적 오만을 해부합니다.
    • 2단계: 포인터 선언에 volatile이라는 족쇄를 달면, 컴파일러에게 "이 변수는 CPU가 아니라 외부 물리 우주(센서, 인터럽트)가 언제든 맘대로 바꿀 수 있으니, 네 머리로 예측하지 말고 무조건 메모리(주소)에서 새로 읽어와라"라고 강제하는 메모리 강제 참조 역학을 뜯어봅니다.
  • Implement: C 언어 컴파일러의 최적화 단계를 파이썬으로 모사. 루프 바디(Body)에서 값이 변하지 않는 변수 x를 기다리는 while x == 0: 루프에 대해, is_volatile=False면 최적화기가 해당 코드를 while True:로 치환해 버려 데드락에 빠지고, True면 매 틱마다 메모리 딕셔너리에서 x를 새로 폴링(Polling)해 루프를 무사히 빠져나오는 최적화기 붕괴 데모.

Advanced

Core Topic 04: C 런타임 초기화와 리셋 벡터의 우주 (Boot-up & CRT)

  • Why to Learn: 전원 버튼을 누르면 대체 어떻게 main() 함수가 실행되는지, 그 0.1초의 짧은 순간 동안 칩 내부에서 벌어지는 전역 변수 복사와 셋업의 피 튀기는 물리학을 꿰뚫어 아키텍트의 시야를 얻기 위해서입니다.
  • What to Learn:
    • Concepts: 리셋 벡터(Reset Vector), 부트스트래핑(Bootstrapping), C 런타임 초기화(C-Runtime, CRT), BSS 영역 / Data 영역.
    • Skills: 스캐터 로딩(Scatter Loading), 스택 포인터(SP) 초기화.
    • Tools: 스타트업 어셈블리 코드(startup_*.s).
    • Trade-offs: 펌웨어가 켜질 때 램(SRAM)은 전원이 꺼지면 증발해 버린 백지상태이므로, 초기값을 가진 전역 변수(int a = 5;)들을 플래시(ROM)에서 램(RAM)으로 수작업 복사하는 시간이 소모됨 vs 이 복사 코드를 빼먹으면 전역 변수들이 전부 쓰레기(Garbage) 값으로 튀어나와 런타임 붕괴가 일어나는 초기화 헌법.
  • How to Learn:
    • 1단계: 칩에 전원이 튀는 순간 하드웨어는 묻지도 따지지도 않고 주소 0x00000000(벡터 테이블)을 읽어 '스택 끝단 주소'를 SP 레지스터에 세팅하고, 0x00000004에 있는 '리셋 핸들러' 주소로 무조건 PC를 강제 점프(Jump)시키는 실리콘의 멱살잡이를 해부합니다.
    • 2단계: 리셋 핸들러(어셈블리) 코드가 0으로 초기화할 변수(BSS) 구역을 쫙 밀어버리고, 초기값이 있는 변수(DATA)들을 ROM에서 RAM으로 복사(Scatter Loading)한 뒤, 마침내 C 언어의 첫 관문인 main()을 호출(Call)하여 우주를 여는 빅뱅의 순간을 뜯어봅니다.
  • Implement: 0으로 채워진 가상 RAM(0x20000000)과 초기화 데이터를 가진 ROM(0x08000000) 객체 생성. 리셋 인터럽트 발동 시 파이썬 startup_routine()이 실행되어 ROM의 .data 섹션을 RAM으로 1바이트씩 덤프(복사)하고, 빈 .bss 섹션 영역을 0으로 꽉 채운 후 최종적으로 main() 콜백 함수를 쏴주는 부트 로더 시뮬레이션.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
MMIO 하드웨어 장치의 레지스터를 메모리 주소 공간에 할당하여 표준 메모리 명령어로 제어하는 기법입니다. 기본 제어 방식 Bus Port-mapped IO '일반 RAM'과 물리적 다름 P1:CS2023/ES core
volatile 컴파일러에게 해당 변수의 값이 외부(하드웨어)에 의해 언제든 변할 수 있음을 알려 최적화를 금지하는 키워드입니다. 추천 최적화 제약 Register Atomic '멀티스레드 동기화'와 혼동 Industry C core
Linker Script 목적 파일들의 섹션을 칩의 실제 물리 메모리 주소에 어떻게 배치할지 명시하는 설계도 파일입니다. 실무 메모리 배치 Section Makefile '소스 코드'가 아닌 빌드 설정 Industry Binutils core
Reset Vector CPU 하드웨어가 리셋 후 가장 먼저 실행할 명령어를 찾기 위해 하드웨어적으로 고정된 물리 주소입니다. 심화 시작점 Bootstrap Entry point main()과 시작점이 다름 P1:CS2023/ES core

8. References

Primary

Secondary

  • [Making Embedded Systems] Elecia White — Practical guide to BMM.
  • [The Definitive Guide to ARM Cortex-M3/M4 Processors] Joseph Yiu — Linker & Startup details.

Industry

  • [STMicroelectronics: RM0090 Reference Manual] — Real-world register maps.
  • [GNU Linker (ld) Documentation - Linker Scripts] — The industry tool standard.

9. Final Checklist

Primary

  • 데이터시트의 특정 주소(예: 0x40021000)에 값을 썼을 때, 왜 이 요청이 RAM으로 가지 않고 통신 장치(UART)로 가는지 물리적 원리를 설명 가능한가? (P1)
  • 링커 스크립트에서 .bss 섹션을 명시적으로 정의하지 않았을 때, 초기화되지 않은 전역 변수가 왜 쓰레기 값을 갖게 되는지 물리적 인과관계를 입증할 수 있는 가? (P1)

Secondary

  • volatile uint32_t *reg 선언에서 volatile이 빠졌을 때, 반복적으로 상태를 확인하는 'Busy-wait' 루프가 왜 무한 루프나 삭제로 이어지는지 어셈블리 수준에서 소통 가능한가?
  • 칩의 SRAM 영역이 0x20000000부터 64KB라면, 스택 포인터(SP)를 최초에 어느 주소로 설정해야 메모리를 최대한 활용할 수 있는지 수리적으로 도출할 수 있는 가?

Industry

  • 신규 하드웨어 보드 브링업(Bring-up) 시, 링커 스크립트의 메모리 맵 오류로 인한 HardFault 발생 시점을 디버거 심볼을 통해 찾아낼 수 있는 가? (SFIA)
  • 펌웨어 보안 요구사항에 따라 실행 코드 영역(.text)을 'Read-Only' 물리 영역으로 강제 배치하는 링커 설정을 제안할 수 있는 가?

Hardware-Software Interface Physics

1 / 6