커널 API
글 수 18
2010.12.03 10:56:53 (*.138.143.120)
33248
Barrier 에 대한 문제의 시작은 프로그래머가 논리적으로 구성해 놓은 프로시져와
컴파일러의 최적화의 의해 수정된 프로시져의 불일치및 물리적 특성에서부터 비롯된다.
특정한 플랫폼의 상황에서 발생한 문제에 대하여 다른 버전의 컴파일러로 컴파일하거나
이기종의 플랫폼에서의 실행에서는 발생하지 않을수도 있다.
이제부터 왜 그런 형상이 발생하는지 살펴보자.
먼저 프로그래머는 일련의 순서와 같은 코딩을 하고자 한다.
이 코드들은 특정한 하드웨어 디바이스를 access 하고 있다.
A -> B -> C -> D -> E
이런 순서로 진행이 된다고 하자.
A 는 레지스터에 데이타를 쓰는 코드이다.
B , C 는 A 에서 access 하는 레지스터와 별도의 레지스터를 읽는다.
하드웨어에 대한 접근에서 A 의 수행은 C 수행에서 얻어지는 결과의 값에 영향을 주게 되어 있는데
그 결과값이 B 혹은 C 에서 읽어올때 서로 다른 디바이스의 레지스터를 읽음으로써
각 라인간의 논리적인 코드의 의존성은 없지만 우리는 그러한 순서가 반드시 지켜져야 하는 것을 알고 있다.
따라서 A -> barrier -> B -> C ... 이렇게 넣게 되면 컴파일러는 컴파일시에 최적화에 의한 실행순서가
바뀌는 것을 막을수 있게 된다.
자... 우리는 arm 을 많이 사용하니까 arm 에서 어떻게 구현이 되어 있는지 살펴보자.
include/asm-arm/system.h 파일의 내용을 살펴보자.
172#if __LINUX_ARM_ARCH__ >= 7
173#define isb() __asm__ __volatile__ ("isb" : : : "memory")
174#define dsb() __asm__ __volatile__ ("dsb" : : : "memory")
175#define dmb() __asm__ __volatile__ ("dmb" : : : "memory")
176#elif defined(CONFIG_CPU_XSC3) || __LINUX_ARM_ARCH__ == 6
177#define isb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c5, 4" \
178 : : "r" (0) : "memory")
179#define dsb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 4" \
180 : : "r" (0) : "memory")
181#define dmb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" \
182 : : "r" (0) : "memory")
183#else
184#define isb() __asm__ __volatile__ ("" : : : "memory")
185#define dsb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 4" \
186 : : "r" (0) : "memory")
187#define dmb() __asm__ __volatile__ ("" : : : "memory")
188#endif
세가지에 대한 선언이 있다. 바로 isb, dsb, dmd 명령 세가지 인다.
추가로 같은 파일에 다음과 같은 선언이 있다.
190#ifndef CONFIG_SMP
191#define mb() do { if (arch_is_coherent()) dmb(); else barrier(); } while (0)
192#define rmb() do { if (arch_is_coherent()) dmb(); else barrier(); } while (0)
193#define wmb() do { if (arch_is_coherent()) dmb(); else barrier(); } while (0)
194#define smp_mb() barrier()
195#define smp_rmb() barrier()
196#define smp_wmb() barrier()
197#else
198#define mb() dmb()
199#define rmb() dmb()
200#define wmb() dmb()
201#define smp_mb() dmb()
202#define smp_rmb() dmb()
203#define smp_wmb() dmb()
mb, wmb, rmb smp_mb, smp_rmb, smp_wmb 에 대하여 각각 dmb 를 사용하고 있다.
arm 에서는 아쉽게도 barrier 에 대한 선언이 별도로 없다.
하지만 커널에서 barrier는 컴파일러에 따라 다르게 구성할수 있도록 되어 있는데
gcc 에서는 barrier 에 대해서 아래와 같이 선언해 놓았다.
#define barrier() __asm__ __volatile__("": : :"memory")
위의 것은 우리가 위에서 보았던 것과 동일한 형태가 있다.
그럼 barrier는 arm 에서 isb, dsb, dmb 의 형태로 구현되어 있는 것이다.
각각 내용을 보자.
DMB : Data Memory Barrier
Data Memory Barrier acts as a memory barrier.
It ensures that all explicit memory accesses that appear in program order before the DMB instruction are observed
before any explicit memory accesses that appear in program order after the DMB instruction.
It does not affect the ordering of any other instructions executing on the processor.
간단히 얘기하면 DMB 명령어 이전에 data access에 대한 모든 명령어가 끝나고 나서
DMB 명령어 이후의 data access 가 발생하는 것을 보장한다는 것이다.
DSB : Data Synchronization Barrier
Data Synchronization Barrier acts as a special kind of memory barrier.
No instruction in program order after this instruction executes until this instruction completes.
This instruction completes when:
All explicit memory accesses before this instruction complete.
All Cache, Branch predictor and TLB maintenance operations before this instruction complete.
배리어의 특별한 형태로써 이 명령이 수행되기 전에 명령어의 수행이 모두 완료된다는 것이다.
추가로 캐쉬, 분기예측, TLB(가상/물리 어드레스 변환테이블의 캐쉬) 수행 명령도 모두 완료되는 것을 보장한다.
ISB : Instruction Synchronization Barrier
Instruction Synchronization Barrier flushes the pipeline in the processor,
so that all instructions following the ISB are fetched from cache or memory,
after the instruction has been completed.
It ensures that the effects of context altering operations, such as changing the ASID,
or completed TLB maintenance operations, or branch predictor maintenance operations,
as well as all changes to the CP15 registers,
executed before the ISB instruction are visible to the instructions fetched after the ISB.
In addition, the ISB instruction ensures that any branches that appear in program order
after it are always written into the branch prediction logic with the context that is visible after the ISB instruction.
This is required to ensure correct execution of the instruction stream
명령어 동기화로서 프로세서의 파이프라인을 모두 비운다.
따라서 ISB 뒤에서 실행되는 명령어는 이 명령어의 수행이 끝난후 캐쉬나 메모리로부터 다시 읽혀지게 된다.
뒤에 무엇에 의한 영향을 피하기 위해서라고 설명하는데 어쨋건 중요한 것은 이 명령어 수행하고 나면
파이프라인이 비워진다는 것이다.
기본적으로는 위의 명령어들은 armv7 이후의 프로세서에 적용되는 내용이고 그 이전 버전에서는
코프로세서의 명령어 혹은 메모리 배리어 명령으로 모두 대신하고 있다.
경험상... barrier() 의 사용으로 인한 문제의 해결을 본적은 거의 없다.
사실 그것이 버그의 원인이었던 적이 없다.
오히려 엄밀히 얘기하면 성능의 저하를 가져온다.
분명한 것은 누구에겐가는 한번쯤 몇날밤을 하얗게 태워도 해결하지 못하는 상황을 한번쯤은 막아줄수 있는 내용이 될 것이다.