SPARC 아키텍쳐는 RISC 프로세서로써 본래 1986년 썬 마이크로시스템즈의 Sun-4/260 으로 처음 모습을 드러냈습니다. 그 이후로 프로세서는 변화하는 고객 요구에 부응하기 위해 많은 수정을 거쳐 왔습니다.

현재의 SPARC 프로세서는 고성능과 에너지 효율에 촛점을 두고 디자인되어 있습니다. 고성능 및 고 에너지 효율은 프로세서 다이 위에 여러개의 코어들을 얹고 시스템내에 여러개의 프로세서들을 둠으로써 이룩하였습니다. 이러한 시스템은 멀티프로세스 그리고 멀티쓰레드 작업에서 훌륭한 결과를 보여 줍니다. 병렬화와 효율의 수준은 원자적 연산(Atomic Operation) 없이는 이뤄 낼 수 없습니다. 이러한 연산들은 솔라리스 운영체제 와 SPARC 에 관계된 고수준의 병렬화를 이룩하는데에 필요한 동기화의 기본 방법을 제공합니다.

이 글은 SPARC 의 메모리 모델과 원자적 연산에 대해 간단히 소개하고 그 다음 IBM AIX 인터페이스를 솔라리스에서 사용하기 위해 구현해 봅니다. 여러분들이 어셈블러 언어에 친숙하다고 가정하고 글을 진행합니다. 각각의 예제들은 SPARC 의 원자적 연산과 메모리 모델의 사용법을 보여주고 IBM AIX 기반의 소스 코드를 솔라리스 / SPARC 플랫폼으로 포팅하려는 프로그래머에게 매우 유용할 작은 라이브러리를 제공합니다.


메모리 모델

SPARC 버전 9 (SPARC v9) 스펙은 3가지 메모리 모델을 정의하는데 제약사항이 적은 것부터 많은 것까지 다음과 같은 순으로 나열해 보겠습니다:

  • Relaxed Memory Order (RMO). 프로세서의 자체-일관성을 위해 필요한 메모리 참고 외에는 어떠한 순서 제약조건도 없음. 순서 보장이 필요하면 개발자는 membar 연산 을 이용해서 명시적으로 디자인하고 코딩해야 함.
  • Partial Store Order (PSO). RMO의 요구사항 전체와 덧붙여서 load 는 이전의 load 에 따라 순서화 되고 원자적인(atomic) load, store 연산은 load 의 순서에 맞게 재배열 됨. membar연산이 필요함.
  • Total Store Order (TSO). 이 것은 PSO 의 요구사항 전체와 덧붙여서 store 는 이전의 store 에 따라 순서화 되고 원자적인 load-store 연산은 load 및 store 의 순서에 맞게 재배열 됨.

SPARC 아키텍쳐는 두가지 이유 때문에 복수의 메모리 모델을 제공합니다: 고성능을 위한 메모리 연산을 스케줄 할 수 있도록 하는 것이 첫번째 이유고 프로그래머가 공유 메모리를 이용해서 동기화 기본 연산(synchronization primitive)들을 생성 할 수 있도록 하는 것이 두번째 이유입니다. 제약사항이 덜한 메모리 모델들 -- RMO 와 PSO -- 은 프로세서에 의한 어플리케이션의 퍼포먼스 향상을 좀 더 기대할 수 있습니다.

그림 1 은 이상적인 SPARC v9 프로세서의 형태 입니다.

사용자 삽입 이미지
 그림 1. SPARC v9 프로세서
 

프로세서의 Issue 유닛은 메모리에서 명령어를 읽고 프로그램 순서대로 명령을 내립니다. 프로그램 순서 는 각각의 명령어가 독립적이고 순차적으로 실행된다는 가정 하에서 어플리케이션의 실행 흐름에 따라 결정 됩니다.

reorder 유닛은 내려진 명령어들을 execute 유닛으로 전달합니다. reorder 유닛은 더 큰 효율을 위해서 몇몇 명령어 들을 병렬로 실행되도록 재배열 할 수 있는 구현을 허용 합니다. 재배치는 프로그램 순서를 유지하는 선에서 가능합니다.

execute 유닛은 명령어를 실행하고 결과를 buffer 유닛에 씁니다.

buffer 유닛은 메모리에 쓰기 작업을 스케줄 합니다. buffer 유닛의 존재는 메모리 쓰기 작업에 의해 발생하는 execute 유닛의 지체를 막아 줍니다. buffer 유닛은 또한 load 요청 시 이전의 저장 작업으로 인해서 요청된 데이타의 주소를 가지고 있으면 주소를 돌려주기도 합니다. 이러한 일은 불일치성에 대한 위험이 존재 합니다 -- 예를 들어 메모리에 대한 쓰기 요청이 buffer 유닛에 존재 할 수 있고, issue 유닛에 의해서 buffer 유닛으로 재로딩 되고, execute 유닛에서 다시 실행 되고 buffer 유닛에 의한 메모리 쓰기 이전에 쓰기 작업이 다시 큐잉 됩니다. 비록 이러한 일들이 이론적으로 싱글-프로세서 시스템 상에서 프로세스들간의 불일치성을 유발할 수 있지만 이러한 불일치성이 실제로 일어나지는 않습니다. 왜냐하면 프로세스-문맥 스위치 작업은 buffer 유닛을 메모리에 쓰는 작업도 포함하기 때문입니다.

멀티프로세서 시스템에서 불일치성은 buffer 유닛의 존재 때문에 발생할 수 있습니다. 불일치는 프로세스들 간에 공유되는 메모리가 특정 프로세서에 의해 변경되었지만 메모리에 다시 쓰여지지 않았을때 -- 즉 변경된 값이 하나 이상의 프로세서 내의 buffer 유닛에 존재 할때 입니다. 그림 2는 멀티프로세서 시스템을 나타 냅니다.

사용자 삽입 이미지
 그림 2. SPARC v9 멀티프로세서 시스템
 
 
Membar 연산

SPARC v9 아키텍쳐는 membar 연산을 포함하고 있습니다. membar 란 단어는 메모리 장벽(barrier) 에 대한 제한 입니다. membar 는 두가지 변수를 가지고 있습니다:

  • Ordering 은 프로세서에 의해 발생되는 load 와 store 의 순서를 프로그래머가 조정할 수 있습니다.
  • Sequencing 는 메모리 연산의 순서와 완료를 프로그래머가 조정할 수 있습니다.

membar 연산의 순서화는 하나의 프로세서의 연산 스트림에 대한 순서를 강제 합니다. membar 연산 이전에 존재하는 load 와 store 들은 membar 이후에 나타난 순서에 따라 재 배열 됩니다. 원자적 연산은 그것이 load 와 store 이더라도 순서화 됩니다. 왜냐하면 그들은 두가지 연산을 모두 수행하기 때문입니다. 연산은 4 비트로 암호화된 순서화 관계를 포함합니다:

  • #LoadLoad
  • #StoreLoad
  • #LoadStore
  • #StoreStore

각각의 #XY 관계는 다음과 같습니다: "프로그램 순서 내에서 membar 이전에 나타나는 모든 X 연산 은 membar 이후에 나타나는 Y 연산 이전에 완료 됩니다" 좀 더 복잡한 순서 요구사항은 비트-단위 혹은 연산자를 이용해서 만들어 질 수 있습니다.

SPARC 버전 8 (SPARC v8) 의 stbar 연산은 membar 의 일부로써 membar#StoreStore 관계 순서와 동일 합니다.

시스템이 운영하고 있는 메모리 모델에 따라서 프로그래머는 명시적으로 메모리-순서 연산을 삽입해야 프로그램의 정확성을 보장할 수 있습니다. 예를 들어 store unsigned byte (stub) 연산을 이용해서 lock 을 풀어주는 SPARC 어셈블러 코드는 다음과 같습니다:

	unlock_ldstub:
		nop                                ! TSO or 
		membar	#StoreStore                ! PSO or
		membar	#StoreStore | #LoadStore   ! RMO
		stub	%g0,[%o0]
		retl
		nop
 

lock 은 lock 을 얻어 온 이후와 풀어주기 이전 그 사이에 기간동안 변수들의 변경을 보호 하기 위한 것입니다. 변수의 변경은 프로그램에 의한 메모리로의 store 를 강제 합니다. PSO 모드 에서 #StoreStore 는 lock 에 대한 store 에 맞게 변수에 대한 store 의 순서화를 유발합니다. 만약 순서가 강제화 되지 않았다면 lock 은 메모리의 변수가 업데이트 되기 전에 풀릴 수도 있습니다.


원자적 연산

SPARC 머신 명령어들은 일반적으로 중간에 어떠한 방해도 받지 않고 실행 됩니다. 이것은 메모리 접근 명령어인 load 와 store 도 마찬가지 입니다. 멀티프로세서-멀티코어 시스템에서 두개 혹은 그 이상의 프로세스들이 동일한 메모리 공간에서 연산을 수행하는 작업은 직렬로 수행되는 것이 보장되지만 순서는 불확실합니. 이것은 메모리의 일관성을 보장하지만 연산들 간의 순서가 불확실하고, 또한 연산이 완료된 후의 메모리 내용도 불확실 합니다.

load 와 store 의 원자적 연산은 두개의 연산에 "무중단(without interruption)" 요구사항을 확장시킵니다. 이러한 연산들은 멀티쓰레드 및 멀티프로세스 어플리케이션의 생성을 도와줌으로써 현대의 고성능 시스템에서 제공되고 있는 병행성의 이득을 취할 수 있도록 합니다.

SPARC 버전 9 (v9) 은 3가지 원자적 연산을 가지고 있습니다:


Load-Store Unsigned Byte: ldstub

load-store unsigned byte (ldstub) 연산은 본래 솔라리스에서 쓰이던 원자적 기본명령으로 상호 배제적인 lock -- mutex -- 과 다른 쓰레드 동기화 기본 명령어(synchronization primitives) 를 구현하는데에 쓰였습니다. 연산은 0xff 를 바이트에 쓰고 기존에 있던 값을 리턴해 줍니다. lock 을 얻기 위해 호출자는 ldstub 를 사용하고 리턴된 값을 검사 합니다. 만약 리턴된 값이 0 이였으면 lock 이 얻어지고 lock 은 이제 0xff 라는 값을 가지고 있게 됩니다. 만약 리턴 값이 0xff 이였으면 lock 은 이전의 호출자가 들고 있는 것입니다. 호출자는 몇가지 고유의 알고리즘을 이용해서 lock 이 사용될때 까지 기다리게 될 것입니다. -- 즉 이것은 값이 0 이 되는 것을 말합니다.

아래는 이 연산의 알고리즘이 C 형태의 가상 코드로 표현되어 있습니다:

int8_t
ldstub( int8_t *lock_byte ) {
	int8_t   old_value;

	atomic {
	    old_value  = *lock_byte;
	    *lock_byte = 0xff;
	}

	return( old_value );
}
 

ldstub 은 전통적인 test-and-set 연산 입니다. 이 연산의 단점은 오직 사용 가능한 경우의 수가 2개에 불과 하므로 wait-free 방식에서는 오직 두개의 경합 프로세스들 만을 처리 할 수 밖에 없다는 것입니다.


메모리와 레지스터의 교환: swap

레지스터와 메모리의 교환 (swap) 연산은 메모리내의 word 의 내용을 레지스터와 교환 합니다. SPARC v9 메뉴얼에 따르면 swap 연산은 더 이상 사용되지 않고 프로그래머는 그 대신 casxa 연산을 사용해야 합니다.

알고리즘은 다음과 같습니다:

int32_t
swap( int32_t *word, int32_t new_value ) {
	int32_t   old_value;

	atomic {
	    old_value = *word;
	    *word     = new_value;
	}

	return( old_value );
}
 

ldstub 연산과 유사하게, swap 은 오직 2가지 경우의 수 만 존재 합니다.


비교 후 교환: cas

SPARC v9 메뉴얼은 새로운 원자적 연산을 소개 했습니다: 섹션 8.4.6.1 에 비교 후 교환(compare and swap (cas)) 이 바로 그것입니다. 이 연산은 메모리 주소와 두개의 레지스터들을 이용합니다. 먼저 메모리 내의 word 를 레지스터와 비교합니다. 만약 둘이 일치 한다면 연산은 메모리의 word 내용을 두번째 레지스터와 교환합니다.

연산은 무한한 경우의 수가 가능합니다: 이 연산은 wait-free 방법에서 거의 무한대의 경쟁 프로세스들을 처리가 가능합니다. 여러분은 cas 를 이용해서 이른바 lock-free 연산들을 사용 가능합니다 -- 링크드리스트 관리가 가장 고전적인 예입니다. 단어 lock-free 는 약간 잘못된 표기일 수도 있습니다. 프로세스는 여전히 lock 을 사용하지만 lock 이 상호배제적인 lock 을 코딩 하여 사용하는 것 대신에 lock 자체가 하드웨어 내에서 발생합니다.

cas 의 알고리즘은 다음과 같습니다:

int64_t
cas64( int64_t *word, int64_t test_value, int64_t new_value ) {
	int64_t   old_value;

	atomic {
	    old_value	= *word;

	    if ( *word == test_value )
	        *word= new_value;
	}

	return( old_value );
}
 

변환이 발생하였는지 알기 위해서 두번째 레지스터의 반환 값을 첫번째 레지스터에서 사용했었던 테스트 값과 비교 합니다. 이 글의 가상 코드 형식으로 이것을 표현 하자면 아래와 같습니다:

if ( cas64( &cas_word, testV, newV ) == testV )
	/* swap took place */
 

SPARC 어셈블러로 이 코드를 작성할 경우 약간의 성능 향상을 더 얻으실 수 있습니다.


솔라리스 OS 인터페이스

솔라리스는 병행 그리고 멀티 프로세서 시스템을 위한 다수의 인터페이스를 가지고 있습니다. 이러한 솔라리스 쓰레드 인터페이스 는 최소 솔라리스 2.4 (1993년 3월) 로 거슬러 올라갑니다. POSIX 쓰레드 인터페이스 는 솔라리스 2.5(1995년 6월) 에 추가 되었습니다. 이러한 것들은 Pthread 인터페이스로 불리며 3T 메뉴얼 섹션에 나타나 있습니다. 솔라리스10 에서 솔라리스 와 POSIX 쓰레드 인터페이스는 Multithreaded Programming Guide 에 설명되어 있습니다.

Pthread 인터페이스는 POSIX 호환과 POSIX 표준에 존재하지 않는 기능들을 추가 하기위해서 수년간 계속 확장되어 왔습니다. 쓰레드 라이브러리는 최소한 한번 이상 재작성되어 졌습니다. 이러한 재작성은 솔라리스 와 POSIX 인터페이스를 조화시키고, 인터페이스들을 libc 에 통합시켰으며, 퍼포먼스를 향상 시켰습니다.

대부분의 벤더들은 POSIX 쓰레드 인터페이스를 채용하고 있습니다. 그러므로 벤더 플랫폼 간의 어플리케이션 포팅은 대부분 쓰레드 인터페이스의 재컴파일의 문제였습니다. 그러나 간단한 재컴파일이 한가지 종류의 문제는 해결하지 못하빈다. 어플리케이션이 벤더에 종속적인 동기화 기본 변수를 사용했다면 어플리케이션의 포팅은 재컴파일 이상의 것을 요구 합니다. 몇몇 경우에 있어서 이것은 간단합니다. 예를 들어 솔라리스 와 POSIX 쓰레드 인터페이스간의 거의 완벽한 매칭이 가능하기 때문입니다. 그러나 다시한번 말해서 몇몇 인터페이스들은 구현하기에 간단하지 않을 것입니다.


IBM AIX 인터페이스

IBM AIX 플랫폼은 몇가지 벤더종속적인 동기화 기본 명령을 1992년 처음 배포되었던 3.2 버전 부터 제공하고 있습니다. Jacques Talbot 의 문서 "Turning the AIX Operating System Into an MP-Capable OS" 에서 PowerPC 프로세서에 대한 훌륭한 배경 정보와 AIX 운영체제 에서의 동기화에 대한 정보를 제공하고 있습니다. 이 문서는 USENIX 1995 Technical Conference Proceedings 의 Potpourri II 섹션에서 찾으실 수 있습니다.

문제가 의심되는 동기화 기본 명령은 다음과 같습니다:

	#include   <sys/atomic_op.h>

	int         fetch_and_add( atomic_p word_addr, int value );
	uint        fetch_and_or( atomic_p word_addr, int mask );
	uint        fetch_and_and( atomic_p word_addr, int mask );
	void        _clear_lock( atomic_p word_addr, int value );
	boolean_t   _check_lock( atomic_p word_addr, int old_val, int new_val );
	boolean_t   compare_and_swap( atomic_p word_addr, int *old_val_addr, int new_val );
	boolean_t   test_and_set( atomic_p word_addr, int mask );
 

SPARC 원자적 명령들과 약간의 SPARC 어셈블러를 앎으로써 여러분은 7가지의 AIX 동기화 기본 명령을 솔라리스에서 구현하실 수 있습니다. 이것은 AIX 어플리케이션을 솔라리스 플랫폼으로 포팅하는 호환성 라이브러리로 인해 가능 합니다. 이 라이브러리는 32-비트 모드의 솔라리스 / SPARC v9 플랫폼을 위해서도 구현될 것입니다. 프로그래머 커뮤니티는 SPARC 64-비트, AMD, 인텔 구현들을 제공해줘야 할 것입니다.

이 섹션의 각각의 인터페이스들은 C-형태 의 가상 코드로 나타내어질 것이고 SPARC 어셈블러로 구현될 것입니다. 모든 인터페이스들은 리프 함수(leaf routine) 이고 그러므로 SPARC Architecture Manual v8 (PDF) 의 섹션 D.5 와 H.1.2 에서 설명된 대로 리프 함수 최적화의 장점을 얻을 수 있을 것입니다.

fetch_and_add, fetch_and_or, fetch_and_and 인터페이스

fetch_and_OP 인터페이스들은 모두 동일한 알고리즘을 따르고 있는데 그것은, 필요에 따라 특정 작업을 교체 하는 것입니다. SPARC 플랫폼은 메모리상에서 직접 작업을 수행하지 않습니다 -- 모든 작업은 load, store 그리고 원자적 연산만 제외 하고는 레지스터 기반으로 이루어집니다. 이것은 fetch_and_OP 인터페이스가 cas 연산을 루프 안에서 사용 하는 것을 필요로 합니다. 루프내에서 원하던 결과가 계산되면 cas 를 이용해서 업데이트를 시도 합니다. 만약 업데이트가 실패 하면 루프는 계속 됩니다.

알고리즘은 다음과 같습니다:

int
fetch_and_OP( atomic_p word_addr, int value ) {
	int   result;

	atomic {
	    result       = *word_addr;
	    *word_addr OP= value;

	}
	return( result );
}
 

SPARC 어셈블러에서 이것은 다음과 같이 바뀌게 됩니다:

	fetch_and_OP:
	      ld        [%o0],%g1         ! load the current value
	loop: OP        %g1,%o1,%o2       ! compute the desired result
	      cas       [%o0],%g1,%o2     ! try to CAS it into place
	      cmp       %g1,%o2
	      bne,a,pn  %icc,loop         ! CAS failed, try again
	      mov       %o2,%g1           ! save current value for next iteration
	      retl
	      mov       %o2,%o0           ! return the old value
 

3가지 인터페이스의 차이점은 OP 연산에 and, or, 혹은 add 을 교체해 넣는 것에 불과 합니다.

두개의 조건 분기문 기능을 사용함을 기억하시기 바랍니다: 소멸, 예측. 소멸 ",a" 은 만약 분기를 타지 않으면 deloy 슬롯의 mov 연산을 스킵합니다. 예측 ",b" 은 조건 분기가 취해지지 않을 수도 있음을 프로세서에게 미리 알려 줍니다 -- 즉 프로세서가 낮은 수준의 경합을 예측할 수 있습니다.

또한 퍼포먼스 향상이 cas 연산의 리턴값에 의해서 가능할 수 있습니다. , a performance enhancement is made possible by the return value of the cas instruction. Rather than employing a load to read the word from memory, the value returned by the cas by way of the second register is used.

_clear_lock _check_lock 인터페이스

이 두가지 인터페이스들은 lock word 를 원자적인 방법으로 변경합니다. _clear_lock 인터페이스는 간단히 lock 의 값을 주어진 값으로 설정 합니다. -- 특히 런타임 초기화시에 사용됩니다. _check_lock 인터페이스는 조건적으로 lock word 의 값을 새로운 값으로 업데이트 합니다. 인터페이스 _check_lock 은 비교-후-교체 형태의 연산입니다.

알고리즘들은 다음과 깉습니다:

void
_clear_lock( atomic_p word_addr, int value ) {
	*word_addr	= value;
	return;
}

boolean_t
_check_lock( atomic_p *word_addr, int old_val, int new_val ) {
	int	result;

	atomic {
	    if ( *word_addr == old_val )
	        *word_addr  = new_val;
	        result      = FALSE;
	    } else
	        result      = TRUE;
	}
	return( result );
}
 

각각의 인터페이스들이 동기화를 위해 사용되기 때문에 메모리 장벽(memory barrier) 가 필요 합니다. 아래는 SPARC 어셈블러 코드 형태 입니다:

	_clear_lock:
	        membar  #StoreStore|#LoadStore  ! memory barrier (RMO)
	        st      %o1,[%o0]               ! store the word
        	retl
	        nop

	_check_lock:
	        cas     [%o0],%o1,%o2           ! try the CAS
	        cmp     %o1,%o2
	        mov     0,%o0                   ! assume it succeeded - return FALSE/0
	        movne   %icc,1,%o0              ! may have failed - return TRUE/1
	        membar  #LoadLoad|#LoadStore    ! memory barrier (RMO)
	        retl
	        nop
 

_check_lock 인터페이스는 조건적인 move 연산 특히 movne 형태로 사용 됩니다. 조건적인 move 는 정수 조건 코드를 사용하는데 여기서는 %icc 를 조건으로 이용하고 오직 cas 가 실패해서 cmp 연산에 의해 확인 되는 경우 1을 리턴 레지스터로 옮깁니다.

compare_and_swap 인터페이스

compare_and_swap 인터페이스는 SPARC v9 cas연산으로 두가지의 구현 요구사항과 함께 직접적으로 매핑될 수 있습니다: 리턴 값은 boolean_t 이고 예전 값은 swap 이 이루어지지 않았을 경우 두번째 매개변수에 의해 리턴 되어야 합니다.

boolean_t
compare_and_swap( atomic_p word_addr, int *old_val_addr, int new_val ) {
	int         oldV    = *old_val_addr;
	boolean_t   result;

	atomic {
	    if ( *word_addr == oldV ) {
	        *word_addr    = new_val;
	        result        = TRUE;
	    } else {
	        *old_val_addr = word_addr;
	        result        = FALSE;
	    }
	}
	return( result );
}
 

이 인터페이스를 위한 SPARC 어셈블러는 적절한 매개변수와 리턴 값 관리에 의해서 직관적으로 cas 연산과 매핑 됩니다.

	compare_and_swap:
	      ld    [%o1],%g1       ! set the old value
	      cas   [%o0],%g1,%o2   ! try the CAS
	      cmp   %g1,%o2
	      be,a  true
	      mov   1,%o0           ! return TRUE/1
	      mov   0,%o0           ! return FALSE/0
	      st    %o2,[%o1]       ! store existing value in memory
	true: retl
	      nop
 

주의해야 할 딱 한가지 것은 리턴 처리 입니다. 리턴 값은 소멸 비트(annul bit) 에 의하여 조건 분기를 사용하여 쓰여 집니다. 만약 swap 이 이루어 졌으면 분기가 취해지고 mov 1,%o0 이 delay 슬롯에서 실행될 것입니다. 만약 분기가 취해지지 않으면 소멸 비트는 첫번째 move 연산을 스킵시키고 mov 0,%o0 연산부터 실행을 계속할 것입니다.

test_and_set 인터페이스

이 인터페이스는 비트 단위의 test-and-set 연산 입니다. 비트 연산 maskor 와 메모리 내의 word 상의 내용이 테스트 대상 입니다. 만약 mask 의 어떠한 비트도 설정되지 않았다면 비트연산 or 을 이용해서 mask 가 워드에 추가 될 것입니다 -- 이것은 연산의 설정 부분 입니다.

boolean_t
test_and_set( atomic_p word_addr, int mask ) {
	boolean_t   result;

	atomic {
	    if ( *word_addr & mask )
	        result   = FALSE;
	    else {
	        *word_addr  |= mask;
	        result       = TRUE;
	    }
	}
	return( result );
}
 

실 구현에서는 루프를 사용 할 것입니다. 그러나 인터페이스가 리턴 되기 전에 성공해야 되기 때문은 아닙니다. -- 이것은 요구사항이 아닙니다. 루프는 if ( ... )*word_addr |= mask 단계의 레이스 컨디션 때문에 요구 됩니다. 이 상황을 설명하기 위해서 atomic 섹션 없이 가상 코드를 다시 써 봤습니다:

boolean_t
test_and_set( atomic_p word_addr, int mask ) {
	boolean_t   result;

	loop {
	    int oldV   = *word_addr;

	    if ( oldV & mask ) {        /* test step */
	        result = FALSE;
	        break;
	    } else {
	        int   newV = oldV | mask;

	        if ( cas32( word_addr, oldV, newV ) == oldV ) {
	            result = TRUE;
	            break;
	        } else         /* *word_addr changed between test step and cas */
	            continue;
	    }
	}
	return( result );
}
 

이전의 형태는 cas32 함수가 atomic 섹션을 포함하고 있다고 가정합니다. 이것은 안전한 가정입니다 왜냐하면 함수는 다음과 같이 작성될 수 있기 때문입니다:

	cas32:
	    cas    [%o0],%o1,%o2
	    retl
	    mov    %o2,%o0
 

그리고 함수는 정의에 따라 원자적입니다.

두번째 형태의 test_and_set 은 SPARC 어셈블러로 다음과 같이 표현될 수 있습니다:

	test_and_set:
	       ld     [%o0],%g1       ! load the current value
	loop:  andcc  %g1,%o1,%g0     ! test mask against value
	       bnz,a  done
	       mov    0,%o0           ! return FALSE/0
	       or     %g1,%o1,%o2     ! compute the desired result
	       cas    [%o0],%g1,%o2   ! try the CAS
	       cmp    %g1,%o2
	       be,a   done
	       mov    1,%o0           ! return TRUE/1
	       ba     loop            ! try again
	       mov    %o2,%g1         ! save current value for next iteration
	done:  retl
	       nop
 
 
결론

이 글은 멀티 프로세서 시스템과 공유 메모리 어플리케이션에 적합한 SPARC v9 프로세서의 메모리 모델과 원자적 연산에 대한 소개를 제공했습니다. 또한 몇몇 IBM AIX 인터페이스에 대한 솔라리스 구현을 제공함으로써 여러분이 AIX 기반의 어플리케이션을 솔라리스로 포팅 하는데에 사용 할 수 있도록 하였습니다.


감사의 인사

이 글은 java.sun.com 의 메니저 Jill Welch 와 Christine Dorffi 의 도움이 없었다면 쓰여지기 어려웠을 것입니다.


추가 정보


이 글의 영문 원본은
Atomic SPARC: Using the SPARC Atomic Instructions
에서 보실 수 있습니다.

"개발자코너" 카테고리의 다른 글

2008/06/16 11:38 2008/06/16 11:38

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/599

댓글을 달아 주세요

[로그인][오픈아이디란?]

◀ Prev 1  ... 64 65 66 67 68 69 70 71 72  ... 624  Next ▶