/arch/arm/asm/include/atomic.h
/*
* On ARM, ordinary assignment (str instruction) doesn't clear the local
* strex/ldrex monitor on some implementations. The reason we can use it for
* atomic_set() is the clrex or dummy strex done on every exception return.
*/
#define atomic_read(v) (*(volatile int *)&(v)->counter)
#define atomic_set(v,i) (((v)->counter) = (i))
2009년 부터 현재까지 ARM에서의 atomic_set과 atomic_read의 구현은 위처럼
ldrex/strex 쌍 없이 C 언어의 기본적인 대입 구문으로 되어 있습니다.
먼저, 아시다시피 volatile 키워드는 컴파일러가 이전에 가져온 counter 값을 재사용하도록 최적화하는 것을 막기 위해 사용되었습니다. 따라서 atomic_set 매크로에는 필요가 없습니다.
대입 구문은 str 명령으로 변환될 것입니다. str 명령은 이미 원자적입니다.
하지만 다른 쓰레드에서 동시에 수행중인 ldrex/strex 쌍과 동기화되어야 합니다.
여기서 ldrex/strex 쌍은 load-link/store-conditional 매커니즘을 따르는 명령으로 read-modify-write 연산으로 분류됩니다.
ARM v6 이후로 버그가 있는 swp 명령을 대체하기 위해 나왔습니다. (현재 swp는 deprecated된 명령입니다.)
또 다른 프로세서에서 동일한 공유 메모리에 접근하는 ldrex/strex 쌍과도 동기화되어야 합니다.
또 인터럽트 서비스 루틴과도 동기화되어야 합니다.
이를 위해 각 프로세서에는 local exclusive monitor가 붙어있습니다.
local exclusive monitor는 해당 프로세서가 ldrex를 수행하면 해당 메모리 주소를 배타적 상태로 태그합니다.
해당 프로세서가 태그된 메모리 주소에 새로 값을 쓰면 태그의 상태도 갱신됩니다. 즉, 오픈 상태가 됩니다.
그래서 ldrex가 수행된 이후 strexeq나 strex를 수행하면 그동안 값이 다시 쓰여졌는지 알 수 있습니다.
다시 쓰여졌다면 strex 명령은 실패 상태를 반환합니다. 상태를 보고 원자적으로 수행하려던 로직을 재수행할 수 있습니다.
이 때, local exclusive monitor는 ldrex와 strex의 쌍을 추적하기는 하지만, 문맥 변환을 인식하지는 못합니다.
따라서 동일한 프로세서에서 여러개의 쓰레드 사이에 ldrex/strex 쌍은 꼬이게 됩니다.
Thread 1 | Thread 2 | Thread 3 |
ldrex |
|
|
| ldrex | |
| strex | |
| | ldrex |
strex |
| |
|
| strex |
예를 들어 위와 같은 경우, local exclusvie monitor는 같은 색으로 칠한 것을 하나의 쌍으로 이해할 것입니다.
즉, 1번과 3번 쓰레드의 ldrex/strex 쌍이 잘못되었습니다. 이렇게 되면 strex 결과 상태가 사실은 실패해야 해도 성공하거나, 사실은 성공해야 해도 실패하는 경우가 발생합니다.
이를 해결하기 위해 문맥 전환을 할 때마다 clrex 명령을 수행해야 합니다.
clrex를 수행하면 해당 프로세서의 local exclusive monitor의 상태를 클리어합니다. 결과적으로 원자적인 구간을 수행하는 도중에 문맥 전환을 한다면 해당 구간은 실패하도록 합니다. ARMv6k 이전은 clrex 명령이 없으므로 dummy strex 명령으로 대체합니다.
다중 프로세서 환경에서 프로세서 간 동일한 공유 메모리 접근에 대해 동기화하기 위해서 global exclusive monitor가 제공됩니다. global exclusive monitor는 각 프로세서가 베타적으로 접근하는 주소를 태그합니다. ldrex를 통해 베타적 상태를 태그해 둔 메모리에 다른 프로세서가 단순 str 연산이나 strex를 수행하면 global exclusive monitor는 해당 태그 상태를 open으로 변경합니다. 이렇게해서 베타적 접근이 실패함을 이후에 알리게 됩니다.
인터럽트 핸들러는 다른 인터럽트를 막아두거나, 또는 nested되기 때문에, ISR 문맥 사이에 clrex가 사용될 필요가 없습니다. 모든 인터럽트 핸들러가 제대로 된 ldrex/strex 쌍을 가지고 있지 않아도 더 안전함을 보장하기 위해 넣을 수도 있습니다.
결과적으로 시그널 핸들링 경로나 모든 각 예외 루틴에 clrex를 넣음으로써 atomic_set 매크로가 단순 str 연산이어도 안전하도록 할 수 있습니다.
오류 발견하면 꼭 지적해주세요.
댓글 7
-
김용욱
2012.02.14 21:59
-
Jason
2012.02.15 02:52
" 3번에 대한 상철님 답변이 어디있는거야?" 하시는 분들을 위해서 ~
http://www.iamroot.org/xe/Kernel_8_ARM_L/65971 <= 댓글 토론 내용을 읽어보시면 됩니다. ^^
-
김용욱
2012.02.15 02:33
3번은 다른 글에서 상철님이 대답해주셨으니 스킵합니다. 제 생각이 잘못된 것 같습니다.
1번 추가 부언: 루프를 도는 방식(busy lock)을 하고 있는데 현재의 LDREX와 STREX의 방식에서는 반드시 busy lock이 아니어도 될 것 같습니다. 조금 더 유연한 응용도 가능할 것 같네요.
2번: CAS(Compare-and-swap)은 기존의 값을 가져와서 메모리에 저장된 값이 같은지 확인한 후 새 값을 넣는 방식인데요. 이 방법은 기존의 값과 메모리 저장된 값이 같다면 변경이 없다고 가정하는데, 값은 같지만 변경이 있었을 수 있다는 것입니다. 이런 상황을 가짜 긍정(false positive)에 당한 것이고요. 이걸 ABA 문제라고 합니다.
조금 작위적인 예제인데 ABA문제를 설명할 때 아래의 예제를 많이 들어서 저도 똑같이 합니다.
예를 들어 리스트가 있고 그것을 TOP이 가리키고 있습니다. TOP: A -> B -> C여기에서 TOP가 A를 가리키고 있는 것을 확인해서 D를 만들어서 넣을 수 있습니다. TOP: D -> A -> B -> C
다른 프로세서에서 A와 B를 리스트에서 제거한 후 A주소에서 C를 연결하도록 변경하여 다시 TOP에 연결했을 수도 있는 거죠.
TOP : A -> C
그래서 TOP가 A를 가리키는지만 확인해서 갱신하면 아래와 같은 모양이 될 수 있죠.
TOP : D -> A -> C
만약에 LL/SC였다면 TOP에서 A와 B를 제거한 순간 체크되었을 것입니다.
(엄밀히 말해, 그렇게 안짜면 되잖아라고 말할 수도 있겠지만... CAS에서 감지하지 못하는 문제들이 있다는 것이 요지입니다.) -
Jason
2012.02.15 00:05
1. 우리가 살펴본 코드에서 보면 LDREX 를 명령어를 실행한후, Local Monitor가 open될때까지 polling방식으로 체크하면서 STREX를 계속 실행하는데, 그걸 하나의 instruction으로 구현한다는 것은, 결국 그 로직을 하드웨어로 다 구현해야 한다는 이야기이고, 당연히 복잡하고 귀찮을 거 같다는 생각이 듭니다. 결국 SWPEX 따위의 instruction을 구현한다고 해도 instruction memory를 아주~ 약간 효율적으로 사용할 수 있는 장점 빼고는 성능면에서는 장점도 없을거 같구요. <=== 여러분 다 아시죠? 제가 한말은 전부 이럴것이다라고 상상해서 작성한 소설이랍니다 ^^
2. 무슨 말인지 원 ㅡㅡ? ;;;;;
3. 상철님 답변해주삼 ^^
-
김용욱
2012.02.14 22:24
LL/SC(Load-linked/Store-conditional, ldrex와 strex 명령)가 CAS(compare-and-swap, swp 명령)에 비해 3가지 정도의 장점이 있어 보입니다.
1. load와 store의 분리. 아마도 load와 store가 구분되는게 일반적인 RISC 아키텍쳐인 arm에서 compare-and-swap 형태인 swp를 구현하기가 귀찮을 겁니다. (제 생각인데 보충해주세요.)
2. CAS (Compare-and-swap) 방식인 swp 명령의 경우에는 결국 가짜 긍정(false positive)에게 속는 ABA 문제에서 자유롭지 못할겁니다. 값을 확인해서 변경하는 것은 그 값만 같다면 그 사이에 끼어든게 없는 것 처럼 착각할 수 있죠. TOP: A->B->C에서 A를 보고 A위에 D를 추가하는 것과 A->C에서 A위에 D를 추가하는 것은 굉장한 차이가 있는데 swp 명령어는 TOP에 A가 있다는 것만 비교해서 통과시킬테니깐요. 이걸 해결할려면 해저드 포인터나 GC를 동원해야 할 겁니다.
3. AXI락 범위의 제한. swp 명령어는 일단 해당 프로세서가 AXI 버스락을 걸텐데 그것도 부담일 겁니다. 반면에 ARM의 LL/SC는 경우에 따라 버스를 안 탈 수 도 있습니다.
If the location is cacheable, the synchronization might take place without any external bus transactions, and without the result being visible to external observers, for example other processors in the system.ARM에서는 위의 이유로 더 나은 구조인 LL/SC를 권장하는 것이 아닐까요?
-
윤호정
2012.02.14 22:11
/arch/arm/include/asm/system.h에 xchg 매크로는 swp 대용으로 다양한 프로세서를 지원하기 위해 제공됩니다.
StrongARM의 경우 swp 명령에 문제가 있어 C언어로 구현되어 있고,
ARMv6 미만인 경우 swp를 사용해도 됩니다.
ARMv6 이상인 경우 swp을 사용하지 않고 ldrex/strex를 통해 인라인 어셈블리로 구현되어 있습니다.
ARM Synchronization Primitives Development Article의 Appendix A를 보면
ARMv6 이상인 경우 swp가 deprecated되어 있음을 알 수 있습니다. 이유는 모르겠네요 =3
-
윤호정
2012.02.18 21:23
http://www.mjmwired.net/kernel/Documentation/atomic_ops.txt
.
"ARM v6 이후로 버그가 있는 swp 명령을 대체하기 위해 나왔습니다" <- 이 부분은 스트롱 암에 특화된 이야기인 것 같습니다. swp 명령이 LL/SC보다 느리지만 스트롱암이 아니면 문제가 없겠죠.