MMIO 접근 성능 — Cache Policy·Write-Combining·Volatile·Barrier
#한 줄 요약
“MMIO는 peripheral register를 메모리처럼 다루는 방식입니다.” 다만 cache와 reorder는 금지입니다.
#MMIO Cache Policy
ARM v7/v8 Memory Type:
| Type | Cache | Order | Speculation | 사용 |
|---|---|---|---|---|
| Normal (cacheable) | yes | weak | yes | DRAM |
| Normal (non-cacheable) | no | weak | yes | DMA buffer |
| Device (nGnRnE) | no | strict | no | MMIO register |
| Device (nGnRE) | no | strict | early-write OK | MMIO 일부 |
| Strongly-Ordered | no | very strict | no | critical config |
nGnRnE는 no Gather, no Reorder, no Early Write의 약자입니다.
MMIO는 보통 Device-nGnRE를 씁니다. Read는 cache와 prefetch가 없고, write는 순서가 보장됩니다.
#Linux ioremap
void __iomem *mmio = ioremap(0xC0000000, 0x1000); /* phys */ /* size *//* Returns virtual address mapped to Device-nGnRnE */iowrite32(0x12345678, mmio + 0x10);val = ioread32(mmio + 0x20);iounmap(mmio);ioremap은 MMIO mapping을 만듭니다. ioremap_wc는 write-combining 매핑이며 PCIe BAR 등에 씁니다.
#Write-Combining
일반 MMIO write:
- 매 store → 즉시 transaction
- → 32 × 4-byte write = 32 transaction
Write-combining:
- store buffer가 근접 주소 합침
- → 32 × 4-byte → 4 × 32-byte burst transaction
PCIe BAR에서 GPU framebuffer 같은 큰 sequential write를 할 때 bandwidth가 8배로 늘어납니다.
mmio = ioremap_wc(phys, size); // Write-combining#Volatile - 컴파일러 차단
/* 잘못된 예 - 컴파일러가 두 read 중 하나를 제거할 수 있습니다 */uint32_t *reg = (uint32_t*)0x40000000;uint32_t a = *reg;uint32_t b = *reg; // 같은 주소여서 컴파일러가 변수에 *cache*합니다
/* Good */volatile uint32_t *reg = (uint32_t*)0x40000000;uint32_t a = *reg;uint32_t b = *reg; // 두 번 read가 보장됩니다volatile은 컴파일러 최적화만 차단합니다. CPU의 OoO 실행이나 write buffer에는 영향이 없으므로 barrier가 별도로 필요합니다.
#ARM Memory Barrier
__DMB(); // Data Memory Barrier - memory access 순서 보장__DSB(); // Data Synchronization Barrier - 모든 memory access 완료 대기__ISB(); // Instruction Synchronization Barrier - pipeline flush#언제 어떤 barrier?
/* Peripheral 활성화 후 사용 */RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;__DSB(); // clock enable 완료를 기다립니다TIM2->CR1 = 1; // 이제 안전합니다
/* Self-modifying code */flash_write(code_buf);__DSB(); __ISB(); // cache flush와 pipeline refillcall_new_code();
/* DMA 시작 전 cache flush */SCB_CleanDCache_by_Addr(buf, len);__DSB();HAL_DMA_Start(...);#Read-Modify-Write Race
GPIO->ODR |= (1 << 5); // read + OR + writeARM 명령으로:
ldr r0, [r1] ; readorr r0, #0x20 ; modifystr r0, [r1] ; writeISR이 중간에 다른 bit를 바꿔도 ISR의 변경이 사라집니다.
해결 방법은 다음과 같습니다.
- Atomic set/clear register (ARM Cortex-M bit-band)
- BSRR (Bit Set/Reset Register) - STM32 GPIO
GPIO->BSRR = (1 << 5); // atomic setGPIO->BSRR = (1 << 5) << 16; // atomic reset#Bit-Banding (Cortex-M3·M4)
SRAM bit-band region: 0x20000000 - 0x200FFFFFAlias region: 0x22000000 - 0x23FFFFFF
bit_addr = alias_base + (byte_offset × 32) + (bit × 4)#define BITBAND(addr, bit) \ ((__IO uint32_t*)(0x22000000 + ((((uint32_t)(addr)) - 0x20000000) << 5) + ((bit) << 2)))
*BITBAND(&flag_byte, 3) = 1; // atomic set bit 3Cortex-M7부터는 제거되었습니다 (cache 동작이 복잡해지기 때문입니다).
#Strongly-Ordered vs Device
/* GIC register, system control */- Strongly-Ordered: 매 access 완전 직렬화 (next 시작 전 이전 완료)- Device: gather 안 함, reorder 안 함, but speculation 일부 OKGIC와 CPU control은 Strongly-Ordered로, 일반 peripheral은 Device로 매핑합니다.
#PCIe MMIO 특수성
/* Posted vs Non-posted */- Memory write (PCIe) - posted (응답 없음, 빠름)- Memory read - non-posted (round-trip latency)- Config read/write - non-posted (sequenced)PCIe MMIO write를 flush하려면 다음과 같이 합니다.
iowrite32(val, mmio); // postedioread32(mmio + STATUS); // read로 flushRead는 post된 write가 완료된 뒤에 응답하므로 write 효과가 보장됩니다.
#DMA와 MMIO Ordering
/* 보내려는 buffer 준비 */fill_buf(tx_buf, len);__DSB(); // memory write 완료
/* DMA setup register */DMA->SRC = (uint32_t)tx_buf;DMA->LEN = len;DMA->CR = DMA_EN; // startDSB가 없으면 DMA가 비어 있는 buffer를 read할 수 있습니다.
#Word-Sized Access 강제
/* 32-bit register는 8-bit access 시 잘못 동작합니다 */volatile uint8_t *reg8 = (uint8_t*)0x40000010;*reg8 = 0x12; // 일부 칩은 4 byte를 한꺼번에 처리하면서 나머지를 0으로 만들어 faultARM v7에서는 word-aligned word access만 안전합니다. iowrite32 / iowrite16 / iowrite8로 폭을 명시해야 합니다.
#STM32 register 비트 매크로
GPIO->MODER &= ~(GPIO_MODER_MODER5_Msk); // clearGPIO->MODER |= (0b10 << GPIO_MODER_MODER5_Pos); // set AF mode_Msk는 mask, _Pos는 shift를 의미합니다. CMSIS 표준입니다.
#자주 하는 실수
⚠️ Volatile 없이 register access
*(uint32_t*)0x40000000 = 1;/* 컴파일러가 dead store로 판단해 삭제할 수 있습니다 */⚠️ Cache enable 잊고 빠르다고 착각
Cortex-M7에서는 D-cache가 enable된 뒤에는 MMIO 영역도 cacheable이 될 수 있습니다. 그러면 register read가 stale해집니다.
MPU로 MMIO 영역을 non-cacheable로 명시해야 합니다.
⚠️ Barrier 누락
clock_enable();peripheral_use(); // clock이 안정되기 전에 access하면 fault사이에 DSB를 두어야 합니다.
⚠️ Bit-band region 외에 사용
*BITBAND(&heap_var, 0) = 1; // heap_var는 bit-band 영역이 아니어서 미정의 동작Bit-band는 0x20000000-0x200FFFFF와 0x40000000-0x400FFFFF에서만 동작합니다.
#정리
- MMIO는 Device memory type으로 매핑하며 uncached + strict order로 동작합니다.
- Linux는 ioremap(Device)과 ioremap_wc(combining)를 제공합니다.
- volatile은 컴파일러용, barrier(DMB, DSB, ISB)는 hardware용입니다.
- BSRR이나 bit-band로 RMW race를 회피합니다.
- DMA buffer를 준비하고 DSB를 거친 뒤에 start합니다.
- PCIe에서는 read로 posted write를 flush합니다.
다음 편은 Peripheral Clock을 다룹니다.
#관련 항목
Embedded Performance Engineering · 26 of 57
- 1Embedded Performance Engineering — 임베디드 성능 엔지니어링 시리즈 소개
- 2임베디드 성능 분석 방법론 — Measure → Analyze → Optimize 사이클
- 3성능 지표 정의 — Latency·Throughput·Utilization 분석
- 4성능 측정의 기본 — Wall-Clock·CPU Cycle·Instruction Count
- 5성능 데이터 통계적 분석 — Percentile·Histogram·평균의 함정
- 6실시간 성능 분석 — WCET·Jitter·Deadline Miss 측정
- 7임베디드 벤치마킹 기초 — 재현성·Warmup·노이즈 제거
- 8성능 모델링 — Amdahl·Gustafson·Roofline Model 적용
- 9프로파일링 기법 개요 — Sampling vs Instrumentation·PGO·LTO
- 10CPU 파이프라인 분석 — 5-stage·Cortex-M·Cortex-A 비교
- 11Pipeline Stall 분석 — Data·Structural·Control Hazard·Forwarding
- 12Branch Prediction 분석 — Static·2-bit·BTB·BHT·Mispredict 비용
- 13Speculative Execution 분석 — OoO·Reorder Buffer·Register Renaming
- 14CPU Cache 기초 — L1·L2·L3·Set Associative·Replacement Policy
- 15Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict
- 16Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
- 17메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정
- 18SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2
- 19PMU·HPM 하드웨어 카운터 분석 — 정밀 성능 진단
- 20임베디드 Bus Architecture — AHB·AXI·CHI 진화와 5-Channel
- 21Bus Contention 진단 — Arbitration·QoS·Starvation 측정
- 22DMA 성능 최적화 — Burst·Scatter-Gather·Chain·Cache 일관성
- 23DMA vs CPU Copy 성능 비교 — Break-even·Setup Overhead 실측
- 24Interrupt Latency 분석 — 진입·종료·Tail-Chaining·Late Arrival
- 25Interrupt Storm 처리 — NAPI·Rate-Limit·Polling 전환
- 26MMIO 접근 성능 — Cache Policy·Write-Combining·Volatile·Barrier
- 27Peripheral Clock 분석 — PLL·Divider·Gating·DVFS
- 28Power vs Performance 트레이드오프 — DVFS·Race-to-Idle·Big.LITTLE
- 29Thermal Throttling 분석 — Junction Temp·Trip Point·냉각
- 30CXL Interconnect 분석 — AI 시대 메모리 대역폭 확장
- 31Concurrency 기초 — Concurrency vs Parallelism·Race·Memory Model
- 32False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
- 33Lock Contention 분석 — Wait·Hold·Convoy·측정 기법
- 34Spinlock 성능 분석 — Spin-Wait vs Context Switch·Ticket·MCS
- 35Mutex 성능 분석 — Futex·Adaptive·Priority Inheritance
- 36Reader-Writer Lock 성능 — Reader/Writer Priority·RCU·Seqlock
- 37Lock-Free 자료구조 성능 — CAS·ABA·Hazard Pointer·Epoch Reclamation
- 38Memory Ordering 분석 — Acquire·Release·Seq-Cst·ARM Relaxed Model
- 39Cache Coherency 프로토콜 — MESI·MOESI·Snoop·Directory
- 40SMP 성능 분석 — Per-Core·Affinity·Load Balance·Scalability
- 41Linux perf 기초 — stat·record·report 활용
- 42Linux perf 고급 — Raw Event·Tracepoint·perf script
- 43ftrace 활용 — function·function_graph·latency tracer
- 44eBPF·bpftrace 동적 트레이싱 — 커널 무수정 관측
- 45Flamegraph 분석 — On-CPU·Off-CPU·Differential
- 46ARM DS·Lauterbach 분석 — Hardware Trace 전문 도구
- 47Bare-metal 프로파일링 — GPIO·DWT·SysTick·ITM 활용
- 48NVIDIA Nsight Systems — GPU·NPU 포함 시스템 분석
- 49모던 프로파일러 비교 — Tracy·Hotspot·uftrace·Coz
- 50연속 프로파일링 — Parca·Pixie·Pyroscope·Tetragon
- 51실전 사례 — ISR Latency 100µs Deadline Miss 추적
- 52실전 사례 — Matrix Multiply가 예상의 10배 느린 이유
- 53실전 사례 — 8-core가 4-core를 넘으면 throughput이 떨어지는 이유
- 54실전 사례 — 카메라 1080p 60fps가 30fps로 떨어지는 이유
- 55CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
- 56CXL 성능 프로파일링 도구 — cxl-cli·DAMON·perf-mem 활용
- 57실전 사례 — CXL.mem 추가로 LLM inference KV cache 처리량 회복
관련 글
실전 사례 — CXL.mem 추가로 LLM inference KV cache 처리량 회복
70B 모델 KV cache가 HBM 한계를 넘어 throughput이 무너졌을 때, CXL.mem 256 GB pool 추가로 회복한 실전 케이스.
CXL 성능 프로파일링 도구 — cxl-cli·DAMON·perf-mem 활용
CXL.mem 환경 성능 도구 — cxl-cli 토폴로지·DAMON page activity·perf-mem로 보는 CXL 트래픽·numastat 통계.
CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
CXL.mem 토폴로지별 실측 — Direct attach·Single switch·Multi-host pool의 지연·대역폭 비용 측정.