Linux v6.18.37 소스 · Linux 4.18 문서와 API 차이 확인

메모리 배리어: 다른 CPU와 장치는 무엇을 보게 됩니까?

READ_ONCE, release·acquire, DMA와 MMIO, cache와 TLB를 구체적인 값의 변화에 연결합니다.

누가 어떤 두 동작을 순서대로 보아야 합니까?

“배리어를 넣었다”는 말만으로는 충분하지 않습니다. 데이터를 읽는 주체가 다른 CPU인지, DMA 장치인지, 주소 변환을 수행하는 하드웨어인지부터 정해야 합니다. 같은 주소처럼 보여도 일반 RAM과 장치 register는 접근 방법이 다릅니다.

상황맞춰야 할 관계함께 확인할 것
CPU가 다른 CPU에 값을 공개합니다.데이터 준비가 공개 표시보다 먼저 보이고, 표시를 확인한 뒤 데이터를 읽어야 합니다.release·acquire 또는 맞는 잠금과 객체 수명
CPU와 DMA 장치가 descriptor를 공유합니다.필드 준비와 소유권 표시, 완료 표시와 결과 읽기의 순서입니다.DMA API, coherency, 장치 protocol
CPU가 MMIO register를 씁니다.장치에 명령이 전달되는 순서와 필요한 도착 조건입니다.readl·writel 계열 API와 bus의 posted write
커널이 페이지 테이블을 바꿉니다.새 PTE와 남아 있는 옛 주소 변환 상태 사이의 관계입니다.TLB 무효화 범위·완료·메모리 수명

배리어는 데이터를 대신 복사하거나 여러 writer의 충돌을 자동으로 막는 기능이 아닙니다. 순서가 맞아도 객체를 너무 일찍 해제하면 잘못된 메모리를 읽습니다.

READ_ONCE만 있으면 준비된 값을 읽습니까?

다음은 Linux 커널 안의 설명용 예입니다. producer와 consumer는 일반 cacheable RAM의 같은 payload·ready를 공유합니다. 두 변수는 실행을 시작하기 전에 0으로 초기화됐고, producer는 한 번만 공개하며 이후 payload를 바꾸지 않습니다. consumer가 끝날 때까지 변수의 수명도 유지합니다.

/* producer: 한 번만 실행합니다. */
WRITE_ONCE(payload, 0x55);
smp_store_release(&ready, 1);

/* consumer: out은 호출자가 제공한 유효한 출력 공간입니다. */
if (!smp_load_acquire(&ready))
    return false;
*out = READ_ONCE(payload);
return true;
값과 보장
WRITE_ONCE(payload, 0x55)payload에 0x55를 기록합니다. 이것만으로 다른 변수 ready와의 CPU 간 순서까지 정하지는 않습니다.
smp_store_release(&ready, 1)준비한 값을 공개하는 표시를 1로 씁니다. 앞선 메모리 접근과 이 저장 사이에 release 순서를 둡니다.
smp_load_acquire(&ready)ready를 읽고 반환합니다. acquire는 이 읽기와 뒤의 접근 사이의 순서를 정합니다.
if (...) return false0을 읽었다면 이번 호출에서는 payload를 사용하지 않습니다. acquire가 1이 될 때까지 기다려 주는 함수는 아닙니다.
*out = READ_ONCE(payload)위 공개 저장의 1을 acquire로 읽었다는 조건 아래, 이 예의 값 0x55를 가져옵니다. 주소가 아니라 정숫값을 출력 공간에 복사합니다.

ready를 읽는 쪽도 같은 공개 규칙에 참여해야 합니다. ready를 다시 0으로 돌려 여러 메시지에 재사용하거나 producer가 여러 개라면 이 짧은 예제만으로 충분하지 않습니다. 새 값과 이전 값을 구별하는 protocol과 덮어쓰기·회수 조건을 추가해야 합니다. release·acquire의 순서

표시와 데이터가 따로 보이는 문제를 그려 봅니다

payload = 0x55를 한 번 공개하는 경우

1. 공개 전

공유 상태consumer가 아는 것
ready의 초기값은 0입니다.아직 읽어도 된다는 표시를 얻지 못했습니다.
payload는 아직 사용 대상이 아닙니다.현재 보이는 값을 임의로 결과로 사용하지 않습니다.

producer가 payload 쓰기를 시작했더라도 consumer가 곧바로 안전하게 사용할 수 있다는 뜻은 아닙니다.

2. 공개 표시를 취득합니다

consumer가 읽은 ready이 예에서의 처리
0false를 반환합니다. 준비 완료를 아직 관찰하지 못했습니다.
release 저장의 1을 acquire로 읽음이후 payload를 읽을 때 앞선 0x55 쓰기를 반영해야 합니다.

그림의 연결은 같은 ready에 대한 release 저장과 acquire 읽기가 짝을 이루는 관계입니다. 임의의 서로 다른 변수에 acquire·release를 붙이는 것으로 대체하지 않습니다.

3. 순서가 빠진 경우와 비교합니다

양쪽에서 ready와 payload를 READ_ONCE·WRITE_ONCE로만 다루면, Linux 메모리 모델에서는 ready가 1인데 payload는 초기값으로 관찰되는 경우를 일반적으로 배제할 수 없습니다. “각 접근이 한 번씩 있었다”와 “여러 접근의 순서가 연결됐다”는 서로 다른 조건입니다.

단계는 값 공개와 관찰의 순서입니다. 특정 CPU에서 반드시 이 순서로 오류가 재현된다는 뜻은 아닙니다. 숫자 0x55와 1은 예제 데이터와 공개 표시이며 실제 주소가 아닙니다.

v6.18.37의 공식 message-passing litmus 파일도 순서 없는 예에는 Sometimes, 쓰기·읽기 배리어를 짝지은 예에는 Never라는 기대 결과를 적고 있습니다. 여기서는 그 소스와 보장 조건을 대조했으며, 이 장치에서 herd7이나 실제 CPU 시험을 수행한 결과로 제시하지 않습니다. 순서 없는 공식 예 배리어를 짝지은 공식 예

SMP 설정과 빈 어셈블리를 읽는 방법

CONFIG_SMP는 여러 CPU가 함께 실행되는 커널을 구성하는 빌드 설정입니다. 지금 online CPU가 몇 개인지 검사하는 if 문이 아닙니다. include/asm-generic/barrier.h는 이미 architecture 쪽에서 정의한 구현을 우선 사용하고, 없는 정의를 보충합니다.

v6.18.37의 정의·조건읽는 방법
barrier(): 빈 asm과 memory clobber컴파일러의 메모리 접근 재배치를 제한합니다. 빈 문자열 자체가 CPU의 DMB 같은 명령을 생성하지는 않습니다.
CONFIG_SMP가 꺼진 smp_mb/rmb/wmb일반 fallback은 compiler barrier로 축약됩니다. 장치 접근에 필요한 모든 배리어까지 사라진다는 뜻은 아닙니다.
#ifndef mb아직 mb가 정의되지 않았을 때만 뒤의 fallback을 씁니다. 이 부분만 보고 모든 architecture가 빈 배리어를 쓴다고 읽으면 안 됩니다.
arm64의 __smp_wmb / __wmb각각 dmb(ishst)와 dsb(st)입니다. CPU 공유 순서와 일반 쓰기 배리어의 정의를 구분합니다.
x86의 __smp_wmbcompiler barrier로 정의됩니다. 해당 일반 메모리의 하드웨어 순서 보장을 전제로 하며 모든 MMIO·memory type에 확대하지 않습니다.

CPU가 하나인 커널에서도 DMA 장치는 CPU와 별도로 동작합니다. 장치에 보이는 접근 순서와 MMIO 규칙을 고려해야 하므로 “UP이면 compiler barrier 외에는 필요 없다”는 결론은 맞지 않습니다. 가상 머신도 guest의 CPU 수와 host의 공유 메모리 처리가 다를 수 있어 virt_* 계열을 구분합니다. SMP·UP·virt 배리어 선택 compiler barrier 정의 arm64 구현 x86 구현

descriptor 준비, 장치 알림, 작업 완료는 다릅니다

descriptor는 장치가 수행할 작업의 주소·길이·상태 등을 담는 설명 구조체입니다. 여기서는 CPU와 장치가 coherent DMA 메모리의 descriptor를 공유하고, status가 소유권을 나타내는 장치를 가정합니다. streaming DMA buffer의 cache 처리는 별도의 mapping·sync 규칙을 따릅니다.

coherent descriptor를 장치에 넘기는 예
  1. CPU가 작업 정보를 준비합니다.

    장치가 아직 소유하지 않은 descriptor의 길이와 buffer 정보를 채웁니다.

    필드 쓰기를 소유권 표시보다 앞서게 합니다.

  2. dma_wmb 뒤에 소유권을 넘깁니다.

    장치가 status를 보고 필드를 읽는 protocol에 맞춰 순서를 정합니다.

    필요한 장치에는 MMIO로 새 작업을 알립니다.

  3. writel로 doorbell을 씁니다.

    doorbell은 새 descriptor가 있음을 알리는 장치 register입니다.

    장치가 작업한 뒤 완료 상태를 공개합니다.

  4. CPU가 완료 상태를 확인합니다.

    protocol에 맞는 완료 확인과 dma_rmb 등의 순서 처리 뒤 결과를 읽습니다.

화살표는 이 장치 protocol의 준비·소유권·알림·완료 순서입니다. dma_wmb가 DMA를 실행하거나 전송 완료까지 기다린다는 뜻은 아닙니다.

dma_* 배리어가 MMIO 접근 순서까지 대신 보장하는 것은 아닙니다. 장치 register에는 적절한 I/O accessor를 사용합니다. coherent는 CPU와 장치의 cache 일관성 조건이며, descriptor 필드의 순서와 사용 권한까지 자동으로 정하지 않습니다. DMA 배리어와 I/O accessor의 구분

writel이 반환하면 장치의 일도 끝났습니까?

bus에 따라 write를 중간에서 받아 두고 나중에 전달하는 posted write가 있습니다. 이 경우 CPU 함수의 반환과 장치가 쓰기를 받는 시점을 구분해야 합니다. 전달을 확인해야 하는 지점에서는 해당 장치·bus 규칙에 맞는 안전한 register를 readl로 읽는 방법 등이 사용됩니다.

상태무엇을 확인했습니까?
writel 호출·반환I/O accessor를 통해 쓰기를 발행했습니다. 장치의 긴 작업 완료까지 의미하지 않습니다.
규약에 맞는 readback앞선 posted write의 전달을 확인하는 데 사용할 수 있습니다. 읽으면 지워지는 상태 register 등은 임의로 고르지 않습니다.
완료 interrupt·status·응답장치가 요청한 작업을 끝냈는지는 별도 protocol로 확인합니다.

spinlock을 잡았다는 사실만으로 posted write가 장치에 도착한 시점까지 자동으로 정해지지는 않습니다. writel_relaxed로 바꾸는 것도 단순한 속도 조정이 아닙니다. 생략되는 순서 보장을 다른 코드가 어떻게 충족하는지 먼저 확인합니다. I/O write와 posted write 잠금 구간과 안전한 readback

cache 처리와 TLB 무효화를 혼동하지 않습니다

D-cache는 데이터, I-cache는 실행할 명령, TLB는 주소 변환과 권한에 관한 상태를 다룹니다. 메모리 배리어를 실행했다고 이 상태들이 모두 갱신되는 것은 아닙니다. PTE는 페이지 테이블 항목이며 RAM에 저장된 값입니다. 그 값을 고쳐도 하드웨어에 남은 옛 변환은 별도 처리 대상입니다.

가상 주소 V의 매핑을 제거하는 경우

1. 매핑이 유효합니다

위치상태
페이지 테이블의 V 항목물리 페이지 P를 가리킵니다.
CPU의 TLBV → P 변환을 보관할 수 있습니다.
물리 페이지 P현재 매핑의 수명 규칙에 따라 사용 중입니다.

V와 P는 주소 이름입니다. V가 가리키는 실제 데이터와 변환 정보는 별도입니다.

2. PTE를 제거합니다

페이지 테이블 항목을 무효 상태로 바꾸어도 옛 TLB 변환이 남아 있을 수 있습니다. 필요한 범위에 무효화를 요청하고 완료 조건을 만족해야 합니다. 이때 일반 배리어만 넣고 끝내면 옛 변환 제거를 대신할 수 없습니다.

3. 옛 매핑의 사용을 끝냅니다

관련 CPU와 보조 변환기의 동기화·참조 수명 조건을 모두 만족한 뒤에야 해당 메모리를 다른 용도로 회수할 수 있습니다. TLB 무효화 완료만으로 다른 종류의 참조까지 모두 사라졌다고 가정하지 않습니다.

단계는 매핑 제거와 수명 확인의 순서입니다. 화살표 V → P는 가상 주소와 물리 주소의 변환 관계이며 데이터 복사를 뜻하지 않습니다. 실제 잠금·batch·architecture helper는 기존 TLB 분석에서 이어집니다.

기존 주소 매핑으로 cache 작업을 해야 하는 architecture를 고려해 Linux의 공통 문서는 cache 처리 → PTE 변경 → TLB 처리를 설명합니다. 모든 CPU에서 모든 cache helper가 실제 cache line을 비우는 명령을 낸다는 뜻은 아닙니다. 새 실행 코드를 쓴 경우의 I-cache 동기화 역시 별도로 확인합니다. cache·PTE·TLB 처리 순서 arm64 cache helper의 목적

Linux 4.18 문서를 현재 코드와 대조할 때

4.18의 Cache and TLB Flushing 문서는 역할을 이해하는 데 도움이 되지만 함수 이름과 인자까지 현재 코드로 옮기면 안 됩니다. v6.18.37 문서는 update_mmu_cache_range(vmf, vma, address, ptep, nr)를 설명하며, nr은 연속된 페이지 수입니다. 예전 문서의 update_mmu_cache와 인자 목록이 다릅니다. 이 API를 모든 architecture에서 TLB 항목을 강제로 채우는 함수로 단정하지도 않습니다.

TLBI·INVLPG·SFENCE.VMA의 상세 명령 분석은 기존 글과 연결했습니다. 이 글에서 보충한 부분은 누가 값을 관찰하는지, 어느 저장과 읽기를 연결해야 하는지, 요청·도착·완료를 어디서 구분하는지입니다. v6.18.37 API 설명 실제 arm64 TLB helper