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
- 1 Embedded 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
- 10 CPU 파이프라인 분석 — 5-stage·Cortex-M·Cortex-A 비교
- 11 Pipeline Stall 분석 — Data·Structural·Control Hazard·Forwarding
- 12 Branch Prediction 분석 — Static·2-bit·BTB·BHT·Mispredict 비용
- 13 Speculative Execution 분석 — OoO·Reorder Buffer·Register Renaming
- 14 CPU Cache 기초 — L1·L2·L3·Set Associative·Replacement Policy
- 15 Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict
- 16 Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
- 17 메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정
- 18 SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2
- 19 PMU·HPM 하드웨어 카운터 분석 — 정밀 성능 진단
- 20 임베디드 Bus Architecture — AHB·AXI·CHI 진화와 5-Channel
- 21 Bus Contention 진단 — Arbitration·QoS·Starvation 측정
- 22 DMA 성능 최적화 — Burst·Scatter-Gather·Chain·Cache 일관성
- 23 DMA vs CPU Copy 성능 비교 — Break-even·Setup Overhead 실측
- 24 Interrupt Latency 분석 — 진입·종료·Tail-Chaining·Late Arrival
- 25 Interrupt Storm 처리 — NAPI·Rate-Limit·Polling 전환
- 26 MMIO 접근 성능 — Cache Policy·Write-Combining·Volatile·Barrier
- 27 Peripheral Clock 분석 — PLL·Divider·Gating·DVFS
- 28 Power vs Performance 트레이드오프 — DVFS·Race-to-Idle·Big.LITTLE
- 29 Thermal Throttling 분석 — Junction Temp·Trip Point·냉각
- 30 CXL Interconnect 분석 — AI 시대 메모리 대역폭 확장
- 31 Concurrency 기초 — Concurrency vs Parallelism·Race·Memory Model
- 32 False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
- 33 Lock Contention 분석 — Wait·Hold·Convoy·측정 기법
- 34 Spinlock 성능 분석 — Spin-Wait vs Context Switch·Ticket·MCS
- 35 Mutex 성능 분석 — Futex·Adaptive·Priority Inheritance
- 36 Reader-Writer Lock 성능 — Reader/Writer Priority·RCU·Seqlock
- 37 Lock-Free 자료구조 성능 — CAS·ABA·Hazard Pointer·Epoch Reclamation
- 38 Memory Ordering 분석 — Acquire·Release·Seq-Cst·ARM Relaxed Model
- 39 Cache Coherency 프로토콜 — MESI·MOESI·Snoop·Directory
- 40 SMP 성능 분석 — Per-Core·Affinity·Load Balance·Scalability
- 41 Linux perf 기초 — stat·record·report 활용
- 42 Linux perf 고급 — Raw Event·Tracepoint·perf script
- 43 ftrace 활용 — function·function_graph·latency tracer
- 44 eBPF·bpftrace 동적 트레이싱 — 커널 무수정 관측
- 45 Flamegraph 분석 — On-CPU·Off-CPU·Differential
- 46 ARM DS·Lauterbach 분석 — Hardware Trace 전문 도구
- 47 Bare-metal 프로파일링 — GPIO·DWT·SysTick·ITM 활용
- 48 NVIDIA 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로 떨어지는 이유
- 55 CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
- 56 CXL 성능 프로파일링 도구 — 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의 지연·대역폭 비용 측정.
같은 시리즈에서 이어 읽기