캐시 플러시와 메모리 일관성 관리

캐시 플러시의 동작 방식과 DMA, 동적 코드 생성, 다중 프로세서 환경에서의 일관성 관리 및 성능·보안 고려사항을 정리한다.

2026-08-14 · 최초 발행 2026-01-04

캐시의 복사본을 메모리와 맞추는 작업

캐시는 메모리 데이터의 사본을 보관해 접근 성능을 높인다. 대신 수정된 값이 캐시에만 머무르면 메인 메모리와 서로 다른 데이터를 갖게 된다. 캐시 플러시(Cache Flush)는 이 차이를 다루기 위해 더티 캐시 라인을 메모리에 기록하고, 필요에 따라 캐시 라인을 무효화하는 작업이다.

플러시는 단순히 캐시를 비우는 동작이 아니다. 어떤 데이터를 메모리에 반영할지, 캐시의 기존 내용을 폐기할지, 여러 처리 장치가 같은 값을 관찰하게 할지를 함께 결정한다.

캐시 플러시FlushInvalidateClean더티 데이터 메모리 기록 +무효화캐시 라인만 무효화더티 데이터 메모리 기록만

처리 범위도 상황에 따라 달라진다. 전체 캐시를 대상으로 할 수 있고, 특정 캐시 라인·주소 범위·세트의 모든 웨이만 지정할 수도 있다. L1, L2, L3 가운데 특정 레벨만 대상으로 삼는 방식도 있다.

DMA와 I/O가 캐시 상태를 문제로 만드는 지점

DMA 출력에서는 CPU가 버퍼에 기록한 뒤 장치가 메모리를 직접 읽는다. 캐시에만 최신 데이터가 있다면 장치는 이전 값을 전송할 수 있으므로, DMA 시작 전에 캐시 데이터를 메모리에 반영해야 한다.

CPU캐시에 쓰기DMA 시작캐시 플러시메모리에 반영DMA 컨트롤러최신 데이터 전송

반대로 DMA 입력은 디스크나 네트워크에서 받은 데이터를 메모리에 쓴다. 이때 CPU 캐시에 이전 값이 남아 있다면 캐시 무효화가 필요하다. 플러시나 무효화 없이 진행하면 오래된 데이터를 사용할 수 있다.

MMIO(Memory-Mapped I/O)는 하드웨어 레지스터를 메모리 주소처럼 접근한다. 하드웨어 레지스터는 캐시되면 안 되며, 일부 시스템에서는 접근 전후 명시적 플러시가 필요하다. 명령어 재정렬에 따른 부작용을 막고 최신 하드웨어 상태를 읽기 위해 접근 순서도 보장해야 한다.

프로세스 전환과 코드 생성에서의 선택

컨텍스트 스위칭 때 이전 프로세스의 데이터가 캐시에 남아 캐시 오염(Pollution)을 일으킬 수 있다. 다만 성능을 위해 일반적으로 캐시 전체를 플러시하지는 않는다. ASID/PCID 같은 주소 공간 식별자는 플러시 없이 주소 공간을 구분하는 데 쓰인다. 일부 보안 컨텍스트에서는 플러시가 필요할 수 있다.

런타임에 새 코드를 만드는 JIT 컴파일러와 동적 로더는 데이터 캐시와 명령어 캐시의 관계를 고려해야 한다. 데이터로 기록한 코드를 메모리에 반영한 뒤, 이전 명령어를 보유할 수 있는 I-Cache를 무효화해야 한다. ARM은 이를 명시적으로 처리하며, x86은 I/D 일관성을 자동으로 처리한다.

프로그램 코드메모리에 코드 쓰기D-Cache에 저장됨D-Cache 플러시I-Cache 무효화 코드 실행

다중 프로세서에서는 MESI, MOESI 등의 캐시 일관성 프로토콜이 많은 부분을 자동 관리한다. 그럼에도 특정 상황에는 소프트웨어의 명시적 플러시가 필요하며, 메모리 배리어와 원자적 연산은 공유 메모리 및 락 프리 알고리즘의 접근 순서를 유지하는 데 사용된다.

아키텍처와 운영체제가 제공하는 인터페이스

하드웨어마다 캐시 제어 명령은 다르다. x86에는 WBINVD, INVD, CLFLUSH, CLFLUSHOPT, CLWB가 있고, ARM에는 DC CIVAC, DC CVAC, DC IVAC, IC IALLU가 있다. MIPS는 다양한 오퍼랜드를 갖는 CACHE 명령어를, PowerPC는 dcbf·dcbi·dcbst·icbi를 제공한다. RISC-V는 명령어 캐시용 FENCE.I를 제공하며 표준 플러시 명령은 없다.

WBINVD: Write-Back and Invalidate (전체 캐시)
INVD: Invalidate (전체 캐시, 더티 데이터 미기록 - 위험)
CLFLUSH: Cache Line Flush (특정 주소)
CLFLUSHOPT: CLFLUSH 최적화 버전 (순서 완화)
CLWB: Cache Line Write-Back (무효화 없이 기록만)
DC CIVAC: Data Cache Clean and Invalidate by VA to PoC
DC CVAC: Data Cache Clean by VA to PoC
DC IVAC: Data Cache Invalidate by VA to PoC
IC IALLU: Instruction Cache Invalidate All to PoU
PoC: Point of Coherency
PoU: Point of Unification

소프트웨어에서는 Linux의 cacheflush() 시스템 콜, Windows의 FlushInstructionCache() API, 리눅스 커널의 flush_cache_range()flush_dcache_page() 등을 사용할 수 있다. 임베디드 환경에서는 어셈블리 명령어를 직접 호출하기도 하며, 대부분의 경우 운영체제가 처리를 자동화한다.

플러시 범위를 줄여야 하는 이유

전체 플러시는 더티 라인을 찾고 메모리 버스를 점유해 데이터를 기록한 뒤 캐시 라인을 무효화한다. 이후 접근은 캐시 미스를 일으켜 데이터를 다시 불러와야 한다.

캐시 플러시 시작모든 더티 라인 검색메모리 버스 점유메모리에 쓰기캐시 라인 무효화후속 접근 미스성능 저하

전체 플러시에는 수백 사이클 ~ 수천 사이클이 소요될 수 있고, 캐시 라인 플러시는 수십 사이클이 걸린다. 플러시 중에는 다른 메모리 접근도 지연되며, 완료를 기다리는 동안 파이프라인이 정체될 수 있다.

이 비용을 줄이려면 필요한 주소 범위만 플러시하고, 여러 작업을 모아 지연 플러시로 처리하거나 가능한 경우 비동기 플러시를 사용한다. 메모리 배리어로 순서만 보장해 플러시를 피할 수 있는지 검토할 수도 있다. DMA 버퍼를 캐시 불가 영역에 할당하거나 Write-Through 캐시를 사용하는 방법, 하드웨어 일관성 프로토콜과 적절한 페이지 속성(WT, UC 등)을 활용하는 방법도 있다.

부채널 공격의 관찰 대상이 되는 캐시

캐시의 접근 시간 차이는 보안 문제로 이어질 수 있다. Flush+Reload는 캐시를 플러시한 뒤 접근 시간을 측정해 정보를 탈취하고, Evict+Time은 캐시 라인을 퇴출한 뒤 시간을 측정한다. Prime+Probe는 캐시를 채운 뒤 다른 프로세스의 실행을 감지하는 방식이다. Spectre는 추측 실행과 캐시 타이밍을 조합하며, Meltdown은 권한 검사 우회와 캐시 관찰을 이용한다.

완화에는 프로세스별 캐시 영역을 분리하는 캐시 파티셔닝, 사용자 모드의 플러시 명령어 제한, 정밀 타이머 제한을 통한 타이밍 노이즈 적용이 있다. Spectre 패치와 레지스터 클리어 같은 추측 실행 완화, 타이밍에 독립적인 상수 시간 암호화 구현도 함께 고려할 수 있다.

커널·임베디드·영구 메모리에서의 활용

리눅스 커널은 주소 범위 플러시와 DMA 동기화 인터페이스를 제공한다.

// 특정 주소 범위 플러시
void flush_cache_range(struct vm_area_struct *vma,
                       unsigned long start, unsigned long end);

// DMA 준비
dma_sync_single_for_device(dev, dma_handle, size, direction);

// DMA 완료 후
dma_sync_single_for_cpu(dev, dma_handle, size, direction);

임베디드 시스템에서는 ARM 코드 업데이트, JIT, 동적 로더, 네트워크·스토리지 드라이버의 DMA 전송, 센서·액추에이터 제어를 위한 메모리 매핑 하드웨어 접근에서 캐시 처리가 필요하다. 부트로더는 커널을 로드한 뒤 I-Cache를 무효화하며, RTOS에서는 빠른 인터럽트 핸들러에서의 플러시 비용을 주의해야 한다.

Persistent Memory를 사용하는 데이터베이스는 NVM에 데이터를 반영하기 위해 CLWB를 활용할 수 있다. 트랜잭션 커밋 시 플러시로 내구성을 확보하고, 배치 플러시로 오버헤드를 낮춘다. 플러시 여부는 데이터 복구에도 영향을 준다.

캐시 플러시메모리 일관성운영체제DMA캐시