Linux Kernel 에서의 Memory Barrier 구현

일반적인 명령어(instruction) 뿐만 아니라, Barrier 를 위한 명령어 또한 아키텍쳐마다 다릅니다.
그리고, 컴파일러 Barrier 로 사용되는 Directive 도 컴파일러마다 다를 수 있습니다.
이 때문에, Linux Kernel 에서는 몇가지 카테고리의 메모리 Barrier 를 정의해두고, 이러한 카테고리 별로 각 아키텍쳐에 맞게 포팅가능한 형태로 구현되어 있습니다.
Barriers and the Linux Kernel“을 보면 Barrier 를 5 가지 카테고리로 분류한 것을 볼 수 있습니다.
이 중에서 가장 주의 깊게 보아야할 3 가지 카테고리는 다음과 같습니다.

  • 컴파일러 Barrier : barrier()
  • 시스템 차원의 Barrier : mb, rmb, wmb
  • SMP 전용 Barrier : smp_mb, smp_rmb, smp_wmb

rmb 와 wmb 는 모두 mb 와 유사하지만, rmb 의 경우 읽기(Load) 연산들 사이의 Ordering 을 보장하기 위해 사용되고, wmb 는 쓰기(Store) 연산들 사이의 Ordering 을 보장하기 위해 사용됩니다.
상위 Layer 에서는 mb, smp_mb 등의 Wrapping 된 Barrier 함수들을 용도에 맞게 사용하면 되는 것이고, 밑단에서 Linux Kernel 은 이들 함수들을 아키텍쳐에 맞게 구현하여 가지고 있는 것입니다.
각 카테고리의 이름을 통해 짐작할 수 있듯이, 위의 3 가지 카테고리의 Barrier 는 해당 Barrier 들이 영향을 미치는 범위를 통해 분류한 것입니다.
영향을 미치는 범위는 기본적으로 다음과 같이 컴파일러 Barrier 가 가장 좁고, 시스템 차원의 Barrier 가 가장 넓을 것입니다.
하지만, 이는 상황에 따라 각각이 같은 범위와 역할을 수행할 수도 있는데, 아래쪽에 아키텍쳐마다 구현된 Barrier 코드를 해석하다보면 알게 될 것입니다.

Compiler Barrier <= SMP Barrier <= System Barrier

  • 컴파일러 Barrier : 컴파일러의 재배치 제한
  • 시스템 차원의 Barrier : 명령어 스트림의 메모리 연산에 대해 시스템 차원에서 Ordering 보장
  • SMP 전용 Barrier : 시스템 차원의 Barrier 와 유사하지만 캐쉬 일관성 구조의 SMP 환경에서 코어/프로세서 사이의 Ordering 보장

컴파일 과정에서의 Reordering 최적화나 CPU 상에서의 Out-of-Ordering 등이 없다면, 명령어가 항상 개발자의 예상대로 순차적(Sequential Execution Model)으로 수행되니 얼마나 좋을까요?
이러한 Reordering 기술에다가 캐쉬, 레지스터까지 가세하게 되니까, 프로세서 사이 또는 시스템의 각 요소들 사이의 메모리 연산의 순서에 대한 보장이 굉장히 어렵고 중요한 문제가 됩니다. 그래서, 자신의 개발환경(Architecture)이 재배치로 인한 문제를 발생시킬 가능성이 있는지를 정확하게 예측하고, 적절한 지점에 Barrier 를 사용하여 이러한 Ordering 의 문제가 발생하지 않도록 해야 합니다.
사실 극단의 Latency 를 요구하는 환경이 아닌 대부분의 경우는 되도록 넓은 범위로 Ordering 을 보장할 수 있는 Barrier 를 사용하게 되겠죠.
예를 들면, SMP 전용 Barrier 를 사용해도 되는 곳에 시스템 Barrier 를 사용하더라도 효과는 같습니다. (물론 성능에서 손실을 볼 수 있습니다.) SMP 전용 Barrier 는 항상 시스템 Barrier 를 대체할 수 있는 것은 아니지만, 시스템 Barrier 는 SMP 전용 Barrier 를 대체할 수 있습니다.
그리고, 참고로 SMP 전용 Barrier 와 시스템 Barrier 를 사용하게 되면, 그 자체로 컴파일러 Barrier 의 역할 또한 수행하게 됩니다.
(아래 쪽의 ARM 구현 코드에서 isb, dsb, dmb 매크로를 보면 volatile 키워드와 memory clobber 를 사용했는데, 이 때문에 컴파일러 Barrier 의 역할을 수행할 수 있는 것입니다.)

Barrier 의 구현은 아키텍쳐 별로 다음의 파일을 통해 확인할 수 있습니다. mb, rmb, wmb, smp_mb, smp_rmb, smp_wmb 와 같은 Barrier 가 매크로 형태로 각 아키텍쳐에 맞는 barrier.h 파일에 구현되어 있는 것입니다.

  • $KERNEL_ROOT/arch/$ARCH/include/asm/barrier.h

ARM Barrier

arch/arm/include/asm/barrier.h 파일을 보면 ARM 아키텍쳐에서의 Memory Barrier 에 대한 구현을 볼 수 있습니다.

/*
 * ARM instructions for memory barrier
 */
#if __LINUX_ARM_ARCH__ &gt;= 7
#define isb(option) __asm__ __volatile__ ("isb " #option : : : "memory")
#define dsb(option) __asm__ __volatile__ ("dsb " #option : : : "memory")
#define dmb(option) __asm__ __volatile__ ("dmb " #option : : : "memory")
...
/*
 * SMP Barrier
 */
#ifndef CONFIG_SMP
#define smp_mb()    barrier()
#define smp_rmb()   barrier()
#define smp_wmb()   barrier()
#else
#define smp_mb()    dmb(ish)
#define smp_rmb()   smp_mb()
#define smp_wmb()   dmb(ishst)
#endif
...
/*
 * System Barrier
 */
#elif defined(CONFIG_ARM_DMA_MEM_BUFFERABLE) || defined(CONFIG_SMP)
#define mb()        do { dsb(); outer_sync(); } while (0)
#define rmb()       dsb()
#define wmb()       do { dsb(st); outer_sync(); } while (0)
#else
#define mb()        barrier()
#define rmb()       barrier()
#define wmb()       barrier()
#endif

위의 코드를 보면 다음의 4 가지가 핵심적인 내용이 되겠네요.

  • 시스템 Barrier(mb, rmb, wmb)는 CONFIG_SMP 가 설정되어 있지 않으면 그냥 컴파일러 Barrier 로 정의된다. SMP 전용 Barrier도 마찬가지다. 즉, UP 환경에서는 컴파일러 차원의 Barrier 이외에는 Reordering 관련하여 신경쓸 것이 없다는 말과 같을 것이다.
  • ARM 아키텍쳐에서는 Barrier 용 명령으로 isb, dsb, dmb 를 지원한다.
  • SMP 전용 Barrier 는 dmb 명령 + ish, ishst 옵션의 조합을 통해 구현되었다.
  • 시스템 Barrier 는 dsb 명령으로 구현되었다.

위의 코드를 SMP 환경이라는 가정 하에 ARM 명령어로 풀어보면 아래와 같습니다. (참고로 위의 코드에서는 제가 빼버렸지만, ARMv6 의 경우는 dmb, dsb 대신 mcr 명령을 이용하여 구현되어 있습니다.)

  • mb : dsb = dsb sy
  • rmb : dsb = dsb sy
  • wmb : dsb st = dsb syst
  • smp_mb : dmb ish
  • smp_rmb : dmb ish
  • smp_wmb : dmb ishst

dmb, dsb, isb 명령어와 옵션에 대한 설명은 이곳이나 이곳을 참조하면 됩니다.
dsb, dmb 모두 Shareability domain 을 지정하는 sy, ish, osh 같은 옵션을 통해 그 영향 범위가 결정됩니다. sy 가 Default 이기 때문에 만약 옵션이 생략되었다면 영향 범위(Shareability domain)가 전체 시스템임을 의미합니다.
그리고, Shareability domain 은 아래와 같이 정의되어 있습니다.
어설프게 번역을 하는 것보다 원문을 읽는 것이 이해가 더 잘되는 것 같아서 원문을 옮깁니다.

The ordering of memory accesses in the ARM architecture takes place within what is called a Shareability domain. Shareability domains define “zones” within the bus topology within which memory accesses are to be kept consistent (taking place in a predictable way) and potentially coherent (with hardware support). Outside of this domain, observers might not see the same order of memory accesses as inside it.

간단히 말하면, Shareability domain이란 버스 토폴로지 내에서 메모리 접근이 일관성과 순서 보장을 가지도록 정의한 “Zone”이라는 것이네요. 즉, 메모리 접근 Ordering 이 발생하는 범위를 말합니다.
만약, dmb 를 통해 데이터 접근에 대한 순서 보장을 하고자 하는데, 옵션으로 ish 을 제공한다면 이는 inner shareable domain 내에서만 그러한 순서를 보장하겠다는 의미인 것입니다. 다른 domain 에는 영향을 주지 않습니다.
결국, 위의 Barrier 들에 대한 명령어들을 잘 보면 다음의 결론을 내릴 수 있습니다.

  • 시스템 Barrier 의 경우는 dsb 를 사용하여 데이터 뿐 아니라 캐쉬, 분기예측, TLB 수행 명령도 모두 완료되는 것을 보장하여 시스템 차원의 메모리 Ordering 을 보장하고 있다.
  • SMP 전용 Barrier 의 경우는 dmb 명령을 이용하여 (그리고, 캐쉬 일관성 구조를 이용하여) inner shareable domain 범위 내에서 메모리 ordering 을 보장하고 있다.

x86 Barrier

x86 의 경우는 Barrier 가 어떻게 구현되어 있는지 잠깐 볼까요 ? arch/x86/include/asm/barrier.h 파일에 아래와 같이 구현되어 있습니다.

#define mb()   asm volatile("mfence":::"memory")
#define rmb()  asm volatile("lfence":::"memory")
#define wmb()  asm volatile("sfence" ::: "memory")

#ifdef CONFIG_SMP
#define smp_mb()   mb()
#ifdef CONFIG_X86_PPRO_FENCE
# define smp_rmb()  rmb()
#else
# define smp_rmb()  barrier()
#endif
#ifdef CONFIG_X86_OOSTORE
# define smp_wmb()  wmb()
#else
# define smp_wmb()  barrier()
#endif
#define smp_read_barrier_depends() read_barrier_depends()
#define set_mb(var, value) do { (void)xchg(&var, value); } while (0)
#else
#define smp_mb()   barrier()
#define smp_rmb()  barrier()
#define smp_wmb()  barrier()
#define smp_read_barrier_depends() do { } while (0)
#define set_mb(var, value) do { var = value; barrier(); } while (0)
#endif

x86 도 역시 ARM 과 마찬가지로 mb, rmb, wmb, smp_mb, smp_rmb, smp_wmb 가 모두 설정에 맞게 구현되어 있습니다. 또한 SMP 환경이 아닐 때는 smp_mb 같은 SMP 전용 Barrier 가 컴파일러 barrier 로 정의되어 있네요.
ARM 과 다른 점은 다음과 같습니다.

  • 시스템 Barrier 인 mb, rmb, wmb 는 명시적으로 x86 에서 제공하는 mfence(store+load), lfence(load), sfence(store) 명령을 사용한다.
  • SMP 전용 Barrier 의 경우 별다른 명령어가 존재하는 것이 아니라 mb, rmb, wmb 와 같은 명령어를 수행한다.

공용 Barrier

아키텍쳐 별로 구현된 Barrier 이외에 $KERNEL_ROOT/include/asm-generic/barrier.h 파일에도 Barrier 들이 구현되어 있는데, 내용을 보면 특정 아키텍쳐의 명령어들을 통해 구현한 것이 아니라 아래와 같이 모두 컴파일러 Barrier 로 정의되어 있습니다.
이는 아마도 Barrier 용 명령어를 별도로 지원하지 않는 아키텍쳐의 경우, 시스템 차원의 Barrier 와 SMP 전용 Barrier 를 모두 컴파일러 Barrier 로만 정의하여 사용하기 위한 것으로 보입니다.

#define mb()    asm volatile ("": : :"memory")
#define rmb()   mb()
#define wmb()   asm volatile ("": : :"memory")

#ifdef CONFIG_SMP
#define smp_mb()    mb()
#define smp_rmb()   rmb()
#define smp_wmb()   wmb()
#else
#define smp_mb()    barrier()
#define smp_rmb()   barrier()
#define smp_wmb()   barrier()
#endif

You may also like...