null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
☆ Star
캐시와 메모리 일관성
#cache
#coherency
#dsync
#isync
#memory-barrier
@devpc
|
2026-04-03 23:46:46
|
GET /api/v1/nodes/232?nv=1
History:
v1 (2026-04-03) (Latest)
0
Views
2
Calls
# 캐시와 메모리 일관성 ## TriCore 캐시 구조 TC37x의 각 코어는 독립적인 캐시를 가진다. ``` ┌───────────────────────────────────────────────┐ │ CPU0 │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ I-Cache │ │ D-Cache │ │ │ │ (명령어 캐시) │ │ (데이터 캐시) │ │ │ │ 16 KB │ │ 16 KB │ │ │ └────────┬───────┘ └────────┬───────┘ │ │ │ │ │ └───────────┼────────────────────┼──────────────┘ │ │ └─────────┬──────────┘ │ SRI 버스 → RAM / 플래시 ``` > DSPR / PSPR (스크래치패드 RAM)는 캐시를 **거치지 않는다**. > 캐시 일관성 문제는 주로 **LMU(공유 RAM)** 접근 시 발생한다. --- ## 캐시 일관성 문제 ### 시나리오 1: DMA → RAM, CPU 읽기 ``` 1. DMA가 LMU의 0x1000 주소에 새 데이터를 씀 RAM[0x1000] = 0xDEAD 2. CPU0의 D-Cache에는 이전 값이 남아 있음 Cache[0x1000] = 0xBEEF (stale) 3. CPU0이 0x1000을 읽으면 캐시에서 0xBEEF를 반환 → 잘못된 값 사용! ``` ### 시나리오 2: CPU 쓰기, DMA 읽기 ``` 1. CPU0이 tx 버퍼를 채움 — 데이터가 캐시에만 있음 Cache[0x2000] = 0x1234 (dirty), RAM[0x2000] = 구버전 2. DMA가 0x2000에서 데이터를 읽음 → RAM에 있는 구버전 데이터를 전송! ``` --- ## 해결책 1: Non-Cacheable 영역 사용 DMA와 교환하는 버퍼를 처음부터 캐시 불가 영역에 배치한다. TC37x에서 LMU는 두 가지 주소로 접근 가능하다: ``` 캐시 가능 주소: 0x9000_0000 (LMU 캐시드 미러) 캐시 불가능 주소: 0xB000_0000 (LMU 언캐시드 미러) ``` 링커 스크립트에서 DMA 버퍼를 언캐시드 영역에 배치: ```ld .dma_buffers (NOLOAD) : { *(.dma_buf*) } > lmu_uncached /* ORIGIN = 0xB0000000 */ ``` ```c /* DMA 버퍼 선언 */ __attribute__((section(".dma_buf"))) static uint8_t dma_rx_buf[1024]; ``` 이 방식은 가장 안전하지만 캐시 이점을 포기한다. 버퍼 크기가 크고 접근 빈도가 높다면 성능 저하가 발생할 수 있다. --- ## 해결책 2: 캐시 무효화 / 플러시 캐시 가능 영역을 DMA 버퍼로 쓰되, DMA 전후에 캐시를 명시적으로 관리한다. ```c /* DMA 수신 전: 해당 영역 캐시 무효화 (Invalidate) */ /* CPU 캐시의 오래된 내용을 버리고, 다음 접근 시 RAM에서 새로 읽도록 */ void cache_invalidate_range(void *addr, uint32_t size) { uint32_t start = (uint32_t)addr & ~0x1FU; /* 캐시 라인(32B) 정렬 */ uint32_t end = ((uint32_t)addr + size + 0x1FU) & ~0x1FU; for (uint32_t a = start; a < end; a += 32U) { __asm__ volatile ("cachea.i [%0], 0" :: "a"(a) : "memory"); } } /* DMA 송신 전: 해당 영역 캐시 플러시 (Clean/Flush) */ /* CPU 캐시의 dirty 데이터를 RAM에 반영 */ void cache_flush_range(void *addr, uint32_t size) { uint32_t start = (uint32_t)addr & ~0x1FU; uint32_t end = ((uint32_t)addr + size + 0x1FU) & ~0x1FU; for (uint32_t a = start; a < end; a += 32U) { __asm__ volatile ("cachea.w [%0], 0" :: "a"(a) : "memory"); } } ``` 사용 예: ```c void dma_receive(uint8_t *buf, uint32_t len) { cache_invalidate_range(buf, len); /* DMA 시작 전 무효화 */ dma_start(buf, len); /* ... 완료 대기 ... */ /* DMA 완료 후 CPU가 buf를 읽으면 새 데이터를 RAM에서 가져옴 */ } void dma_transmit(uint8_t *buf, uint32_t len) { cache_flush_range(buf, len); /* DMA 시작 전 플러시 */ dma_start_tx(buf, len); } ``` --- ## 메모리 배리어: dsync / isync 캐시 일관성 외에도 **명령어 재정렬** 문제가 있다. TriCore 파이프라인은 성능을 위해 메모리 쓰기를 버퍼링하거나 재정렬할 수 있다. ``` dsync (Data Synchronization Barrier) → 이전 모든 데이터 메모리 접근이 완료될 때까지 대기 → 쓰기 버퍼 플러시 포함 → ARM의 DSB(Data Synchronization Barrier)에 해당 isync (Instruction Synchronization Barrier) → 이후 명령어 페치를 I-Cache에서 새로 읽음 → 자가 수정 코드, 인터럽트 벡터 변경 후 사용 → ARM의 ISB(Instruction Synchronization Barrier)에 해당 ``` ```c /* 레지스터 설정 후 반드시 dsync */ PERIPHERAL_CTRL.B.ENABLE = 1U; __asm__ volatile ("dsync" ::: "memory"); /* 설정이 실제로 반영됨을 보장 */ /* ISR 벡터 테이블 수정 후 */ vector_table[10] = new_handler; __asm__ volatile ("isync" ::: "memory"); /* 파이프라인 플러시 */ ``` > **ARM과의 비교** > ARM Cortex-M: `__DMB()`, `__DSB()`, `__ISB()` (CMSIS 매크로) > TriCore: `dsync`, `isync` (어셈블리 명령어) > 개념은 동일하며, 둘 다 컴파일러 최적화를 막는 `memory` 클로버가 필요하다.
// COMMENTS
Newest First
ON THIS PAGE