메모리·아키텍처 — 임베디드 면접 (실제 기출 기반·심화)
"메모리 맵이 뭐냐", "SRAM과 DRAM 차이", "캐시 write-through vs write-back", "DMA 썼더니 데이터가 깨진다", "MMU랑 MPU 차이", "왜 Cortex-M은 리눅스를 못 돌리나", "메모리 단편화" — 하드웨어를 아는지를 보는 단골. C 편의 메모리 영역(.data/.bss)을 물리 주소 공간·캐시·DMA로 잇는다.
임베디드 면접에서 메모리/아키텍처는 **"왜 그 구조인가 + 그래서 무슨 버그가 나는가"**로 파고든다. 정의만 외우면 DMA-캐시, MMU-실시간성 같은 꼬리에서 막힌다.
1. 메모리 맵 — 주소 공간 (★ 단골)
실제 질문: "메모리 맵이란?" · "MMIO가 무엇인가?" (velog @ebing 임베디드 시스템의 이해·@embeddedjune)
Cortex-M은 4GB(32비트) 주소 공간이 고정 분할돼 있다. 같은 버스에 코드·데이터·주변장치가 주소로 매핑된다.
| 주소 | 영역 | 무엇 |
|---|---|---|
0x0000_0000 | Code(Flash) | 벡터 테이블·.text·.rodata |
0x2000_0000 | SRAM | .data·.bss·스택·힙 |
0x4000_0000 | Peripheral | GPIO·UART·타이머 레지스터(MMIO) |
0xE000_0000 | System(PPB) | NVIC·SysTick·SCB·MPU |
깊이 — MMIO(Memory-Mapped I/O): ARM은 별도 I/O 명령(x86의 IN/OUT)이 없다. 주변장치 레지스터가 메모리 주소처럼 보여 포인터로 읽고 쓴다. 그래서 C 편의 *(volatile uint32_t*)0x40020014 패턴이 나온다 — 주소는 데이터시트의 메모리 맵에서 온다.
꼬리질문: "MMIO vs PMIO(포트 매핑) 차이?" → MMIO는 메모리 주소 공간 공유(ARM·RISC-V), PMIO는 별도 I/O 공간·전용 명령(x86). / "왜 그 레지스터 접근에 volatile?" → 하드웨어가 바꾸므로 캐싱 금지(C 편).
함정: ❌ "주변장치는 특별한 명령으로 접근한다" → ARM은 그냥 메모리 접근(MMIO).
2. 메모리 계층·SRAM vs DRAM·Flash (★ 단골)
실제 질문: "SRAM과 DRAM의 차이는?" · "메모리 계층 구조를 설명하라" (nhtranngoc Q20·velog @embeddedjune)
빠르고 비싼 것이 위, 느리고 싼 것이 아래: 레지스터 → L1/L2 캐시 → SRAM(메인) → Flash/스토리지. 지역성(아래 3절)으로 위 계층의 적중률을 높인다.
| SRAM | DRAM | Flash | |
|---|---|---|---|
| 셀 | 6 트랜지스터(플립플롭) | 1T + 커패시터 | 플로팅 게이트 |
| 휘발성 | 휘발성 | 휘발성·주기적 refresh 필요 | 비휘발성 |
| 속도/비용 | 빠름·비쌈·소용량 | 느림·쌈·대용량 | 읽기 빠름·쓰기 느림·블록 단위 |
| 용도 | 캐시·MCU 내장 RAM | 메인 메모리(PC·AP) | 코드·상수 보관 |
깊이 — 펌웨어 관점(★): MCU는 코드·상수는 Flash, 변수·스택은 내장 SRAM에 둔다. Flash는 코어 클럭보다 느려 wait state가 붙고(ART 가속기·프리페치로 보완), DRAM은 refresh 때문에 결정성이 떨어져 실시간 MCU는 SRAM을 선호한다.
꼬리질문: "DRAM은 왜 refresh가 필요한가?" → 커패시터 전하가 새어 주기적으로 다시 채워야. / "코드는 어디서 실행되나?" → 보통 Flash에서 직접(XIP), 성능 필요 시 RAM으로 복사(RAM function). / "MCU vs MPU(AP) 차이?" → MCU는 CPU+메모리+주변장치 단일 칩, AP는 CPU 중심에 외부 메모리·주변장치.
함정: ❌ "SRAM은 비휘발성이다" → 휘발성. 비휘발은 Flash/EEPROM/FRAM. ❌ "DRAM이 SRAM보다 빠르다".
3. 캐시 — write-through/back·일관성 (★★ 심화)
실제 질문: "캐시의 역할과 지역성 원리?" · "write-through와 write-back 차이?" · "캐시 일관성(coherency)이란?" (velog @ebing)
지역성(locality): 시간적(방금 쓴 걸 또 쓴다) + 공간적(근처를 곧 쓴다)을 활용해, 느린 메모리 대신 빠른 캐시에서 처리 → 적중률↑.
쓰기 정책:
- Write-Through: 캐시와 메모리를 동시에 갱신 → 일관성 단순, 쓰기 느림.
- Write-Back: 캐시만 갱신하고 dirty 비트 표시, 교체될 때 메모리에 반영 → 빠르나 일관성 관리 필요.
깊이 — 캐시 라인·false sharing: 캐시는 바이트가 아니라 라인 단위(Cortex-M7=32바이트)로 채운다. (멀티코어 전용 현상) 서로 다른 코어가 같은 라인의 다른 변수를 갱신하면 라인이 핑퐁(false sharing)돼 성능이 죽는다.
꼬리질문: "멀티코어 캐시 일관성 프로토콜?" → MESI(Modified/Exclusive/Shared/Invalid). / "캐시 미스 종류?" → cold(최초)·capacity·conflict. / "저사양 MCU에 캐시가 없는 이유?" → 결정성(캐시 미스의 비결정적 지연이 실시간성 방해) — Cortex-M0~M4는 보통 무캐시, M7은 옵션으로 L1 I/D 캐시(벤더가 활성/비활성 결정).
함정: ❌ "volatile이 CPU 캐시 일관성을 보장한다" → 아니다. volatile은 컴파일러 최적화만 막고 CPU 캐시는 별개(C 편·다음 4절).
4. DMA와 캐시 일관성 — 단골 함정 (★★★ 재검증)
실제 질문: "DMA란? CPU 없이 어떻게 전송하나?" · "폴링/인터럽트/DMA 전송 방식을 비교하라" (velog @jsomedev·@ebing)
DMA(Direct Memory Access): CPU 개입 없이 주변장치↔메모리를 블록 전송하고, 끝나면 인터럽트로 통지 → CPU는 그동안 다른 일. 데이터 입출력 방식 위계: 프로그램(폴링) < 인터럽트 < DMA < 채널. 대용량·고속(ADC·통신·디스플레이)에 필수.
⚠️ Cortex-M은 캐시-DMA 하드웨어 일관성이 없다(★ 핵심 꼬리). D-Cache를 켜면 CPU는 캐시를, DMA는 메인 메모리(SRAM)를 본다 → 불일치(stale data):
해결:
- 송신(CPU가 채운 버퍼를 DMA가 읽기 전):
SCB_CleanDCache_by_Addr()— dirty 캐시를 SRAM으로 flush(clean). - 수신(DMA가 채운 버퍼를 CPU가 읽기 전):
SCB_InvalidateDCache_by_Addr()— 캐시를 버려 SRAM에서 다시 읽게. - ⚠️ 버퍼는 캐시 라인(32B) 정렬 + 정수배 크기(
__ALIGNED(32)). 안 그러면 인접 변수가 같은 라인에 걸쳐 invalidate가 옆 데이터를 날린다. - 대안: DMA 버퍼 영역을 MPU region 속성을 Device/Normal Non-Cacheable로 지정(약간 느림·clean/invalidate 유지보수 불필요 — STM32 실무 단골).
꼬리질문: "DMA 전송 중 CPU가 같은 메모리에 접근하면?" → 버스 중재(cycle stealing)로 버스를 나눠 씀. / "왜 DMA가 CPU 폴링보다 빠른가?" → 전송을 하드웨어가 전담해 CPU 사이클·인터럽트 오버헤드 제거.
함정: ❌ "DMA는 CPU를 전혀 안 거친다" → 설정·완료 인터럽트엔 CPU가 관여. ❌ "캐시 일관성은 하드웨어가 알아서" → Cortex-M은 수동 관리.
5. MMU vs MPU (★★ 단골·재검증)
실제 질문: "MMU와 MPU의 차이는?" · "임베디드(RTOS)에서 MMU를 안 쓰는 이유는?" · "MPU의 동작 원리는?" (kimbongsik.github.io·velog @ebing)
| MMU(Memory Management Unit) | MPU(Memory Protection Unit) | |
|---|---|---|
| 주소 변환 | 가상→물리 변환(페이지 테이블·TLB) | 변환 없음(물리 주소 그대로) |
| 가상 메모리 | O(프로세스별 공간·페이징·스왑) | X |
| 기능 | 변환 + 보호 | 보호만(영역별 R/W/X·캐시 속성) |
| 탑재 | Cortex-A(앱 프로세서) | Cortex-M(MCU·보통 8~16 region) |
| OS | 리눅스·안드로이드 | RTOS·베어메탈 |
깊이 — "왜 Cortex-M은 리눅스를 못 돌리나?"(★ 단골): 일반 리눅스는 MMU(가상 메모리)가 필수인데 Cortex-M엔 MMU가 없고 MPU만 있다. MPU는 주소 변환을 안 하고 물리 메모리 영역별 접근 권한만 부여 → 스택-힙 충돌 방지·NULL(0번지) 접근 차단·코드 영역 쓰기 금지·태스크 간 메모리 격리에 쓴다. MMU 없는 MCU는 μCLinux(MMU-less)·RTOS를 돌린다.
임베디드가 MMU를 피하는 이유: ① 자원 제약(MMU·페이지 테이블·TLB 하드웨어 비쌈) ② 실시간성(주소 변환·페이지 폴트·스왑의 비결정적 지연이 데드라인을 위협).
꼬리질문: "TLB miss가 나면?" → 페이지 테이블을 메모리에서 워크(느림) → TLB에 캐싱. / "MPU region이 부족하면?" → 영역을 묶거나 우선순위로 겹쳐 설정. / "NULL 포인터 접근을 잡으려면?" → 0번지를 MPU로 접근 금지 영역 설정 → 폴트로 검출.
함정: ❌ "MPU도 가상 메모리를 제공한다" → 물리 영역 보호만.
6. 가상 메모리·페이징 (★ AP/리눅스 임베디드)
실제 질문: "가상 메모리란?" · "페이지 폴트가 무엇이고 처리 과정은?" · "페이징 vs 세그먼테이션?" (namu.wiki·velog @jsomedev CSAPP)
가상 메모리: 물리 메모리를 디스크의 캐시로 쓰고, 프로세스마다 독립된 가상 주소 공간을 준다. 변환 단위는 페이지(보통 4KB). **PTE(페이지 테이블 엔트리)**에 유효 비트·프레임 번호·R/W/X 보호 비트·참조/수정 비트가 들어간다.
페이지 폴트: 접근한 가상 페이지가 물리 메모리에 없으면(유효 비트 0) 예외 → OS가 디스크에서 프레임으로 로드 → PTE 갱신 → 재실행. 프레임이 꽉 차면 교체 알고리즘(LRU·Clock 등)으로 희생자 선택.
깊이: 임베디드 베어메탈·RTOS엔 보통 가상 메모리가 없다(MPU만). 가상 메모리는 Cortex-A급 + 리눅스에서 의미가 있다.
꼬리질문: "TLB의 역할?" → 가상→물리 변환 결과 캐시(매 접근 페이지 워크 회피). / "페이징 vs 세그먼테이션?" → 페이징=고정 크기(내부 단편화), 세그먼테이션=가변(외부 단편화). / "임베디드에서 가상 메모리를 안/못 쓰는 이유?" → 자원·실시간성.
함정: ❌ "모든 임베디드에 가상 메모리가 있다" → 리눅스 탑재 AP급만.
7. 스택 vs 힙·메모리 단편화 (★ 단골)
실제 질문: "스택과 힙의 차이는?" · "메모리 단편화란? 내부/외부 차이는?" · "임베디드에서 malloc을 기피하는 이유?" (nhtranngoc Q25·velog)
| 스택 | 힙 | |
|---|---|---|
| 할당 | 자동(컴파일러·LIFO) | 수동(malloc/free) |
| 속도 | 빠름(SP 이동만) | 느림(할당자 탐색) |
| 연속성 | 연속 | 불연속·단편화 가능 |
| 크기 | 작고 고정 | 큼·가변 |
단편화:
- 외부 단편화: 빈 공간 총량은 충분한데 잘게 흩어져 큰 블록 할당 실패.
- 내부 단편화: 할당 단위(블록)보다 작게 써서 남는 낭비.
깊이 — 왜 임베디드는 malloc을 피하나(★): ① 단편화로 장기 구동 중 할당 실패 ② 비결정적 지연(할당자 탐색 시간 가변 → 실시간성 위협) ③ 힙 손상·누수 디버깅 난이도. → 정적 할당·고정 크기 메모리 풀·RTOS 메모리 블록으로 대체.
꼬리질문: "스택 오버플로를 어떻게 감지/방지?" → 스택 가드 패턴(카나리)·MPU 가드 영역·스택 워터마크(사용 최고치 추적). / "단편화를 줄이는 할당 전략?" → 고정 크기 풀·슬랩·버디. / "RTOS 태스크 스택 크기는?" → 최악 호출 깊이 + ISR 프레임 측정·워터마크로 튜닝.
함정: ❌ "단편화는 외부만 있다" → 내부도 있다. ❌ "힙이 스택보다 항상 크다" → 설정 나름.
8. 폰노이만 vs 하버드·엔디안·정렬 (C 편 연결)
- 하버드(Harvard): 명령·데이터 버스 분리 → 동시 페치(성능). 폰노이만: 통합 버스. Cortex-M은 수정 하버드(I/D 버스 분리·메모리 맵은 통합).
- 엔디안(C 편): 리틀(ARM) vs 빅(네트워크). 외부 메모리·통신 공유 시 변환.
- 정렬(C 편 struct 패딩): N바이트 타입은 N의 배수 주소. 비정렬은 Cortex-M0 HardFault·M3/M4 성능 저하.
한국 임베디드 면접 단골 Q&A (답변 골격)
| 질문 | 핵심 답 |
|---|---|
| 메모리 맵·MMIO | 4GB 고정 분할·주변장치를 메모리 주소로(포인터 접근) |
| SRAM vs DRAM | 6T 빠름·휘발 / 1T+refresh 대용량 / Flash 비휘발 |
| 메모리 계층 | 레지스터→캐시→SRAM→Flash·지역성 |
| write-through vs back | 동시 쓰기(단순) vs 캐시 먼저 dirty(빠름·일관성) |
| 캐시 일관성 | 멀티코어 MESI·false sharing·DMA 비일관 |
| DMA란 | CPU 없이 블록 전송·완료 인터럽트·폴링<인터럽트<DMA |
| DMA 데이터 깨짐 | 캐시 stale·송신 Clean/수신 Invalidate·라인 정렬 |
| MMU vs MPU | 변환+가상메모리(A) vs 보호만(M) |
| Cortex-M 리눅스 못 도는 이유 | MMU(가상메모리) 없음·MPU만 |
| 가상 메모리·페이지폴트 | 디스크 캐시·PTE·없으면 로드·교체 |
| 스택 vs 힙·단편화 | 자동/수동·내부/외부 단편화·malloc 기피 |
| 스택 오버플로 감지 | 카나리·MPU 가드·워터마크 |
꼬리질문 대비 (상 난이도)
- "DMA로 이더넷/SD 받았더니 가끔 깨진다?" → D-Cache stale → 수신 버퍼 Invalidate·캐시 라인 정렬·또는 non-cacheable.
- "Invalidate 했더니 옆 변수가 망가졌다?" → 버퍼가 캐시 라인에 정렬 안 됨 → 32B 정렬·정수배.
- "왜 RTOS에서 동적 할당을 줄이나?" → 단편화·비결정적 지연 → 정적/풀.
- "결정적 지연이 필요한 코드는?" → 캐시 미스 없는 TCM(코어 직결 메모리)·캐시 락.
- "MPU로 뭘 잡나?" → NULL 접근·스택 가드·코드 영역 쓰기 금지·태스크 격리.
한 줄 요약 — Cortex-M은 4GB 고정 메모리 맵에 MMIO(포인터 접근). 계층은 레지스터→캐시→SRAM(6T·휘발)→Flash(비휘발). 캐시는 write-through/back·라인 단위·MESI(멀티코어)·M7부터. DMA는 캐시 비일관 → 송신 Clean·수신 Invalidate·라인 정렬. MMU(변환·가상메모리·Cortex-A·리눅스) vs MPU(보호만·Cortex-M·스택가드/격리). 가상메모리=PTE·페이지폴트(AP급). 스택/힙·내부/외부 단편화·malloc 기피(단편화·비결정성)→정적/풀.
(출처 — 한국 면접 기출·1차자료 교차검증 2026-06: velog @ebing 임베디드 시스템의 이해·kimbongsik.github.io MPU란?·velog @jsomedev DMA·메모리·nhtranngoc embedded-interview Q20·Q25 · Microchip TB3295 — Cortex-M7 캐시 일관성·ARM Cortex-M Generic User Guide(메모리 맵·MPU)·CSAPP(가상 메모리) 교차검증.)