본문 바로가기
Inline Assembly와 INTLOCK의 구현

가끔은 Software를 만들면서 Assembly로 짜야 하는 부분이 나올 때도 있어요. 가끔은 멋들어져 보이고 싶을 때도 있으니까, Assembly로 직접 짜고 싶을 때도 있는 거지요. 하나도 멋지지 않은데, 한번쯤은 그러고 싶을 때 어찌하면 좋을 까요.

Assembly (.s) file을 만들어서, 그걸 Assembler로 compile한 후, 기존 C로 짜던 object와 link를 걸어서 사용하면 되겠네요? 아~ 머리 아파.. 그럴 때 간단한 함수 하나 정도만 Assembly로 짤 거면 간단한 방법이 있다오. Inline Assembly라는 걸 이용해 BoA요.

잠깐 잠깐. 그런데 도대체 어떨 때, 이런 Assembly가 필요할까요? C로 뭐든지 가능한 건 아닌가요..? 라는 질문이 생기죠. 도대체.. 어떨 때..

뭐. 이럴 때가 있겠죠.
1. Low Level을 직접 다룰 때 : Coprocessor를 다뤄야 할 때 이럴 때는 C로는 초 난감입니다.
2. ARM을 직접 다룰 때 : PSR을 다뤄야 할 때. Interrupt Lock을 걸고 싶을 때 ~ C로는 또 한번 좌절 초 난감... 이죠.
3. Register를 직접 다룰 때 : Register를 직접 다루고 싶을 때는 어떻게 해야 할지.. R0, R1을 내 맘대로 다루고 싶다~
뭐 이럴 때 꼬오옥~ 필요한 셈이에요.

아 머리 아파. 그러면 Inline Assembly를 사용하기 위해서는 그 문법을 쬐깐 알아야겠죠. 그 문법은 별거 없어요.

__asm

이라고 알려주면 C compiler가 아~ 이 다음부터는 Assembly구나~ 하고 눈치밥 3년에 서당개 풍월을 읊으며 룰루랄라~ 넘어가 준다는 스토리죠. 오케이, 그럼 정확히 Inline Assembly를 어떻게 하는지 자세히, 샅샅이 살펴 보겠사옵니다. Inline Assembly의 용법은 일반 C와 비슷해요. {}으로 시작하고 닫으면 되고요. {} 사이에 원하는 Assembly를 끼워 넣으면 됩니다.

예를 들어서, 지가 만들어서 유용하게 써먹었던 stack pointer를 가져올 수 있게 하는 함수를 예제로 들어 보겠사와요. 보통 C나 C++에서는 현재의 stack pointer를 가져올 수 있는 방법이 있던가요? 전 잘 모르겠데 말인뎁쇼.  뭐 잔소리 집어 치우고 실제적인 코드를 들여다 보는 게 제일 감이 잘 오지요.

getsp.c ----------------------------------------------------------------

typedef unsigned long word;

word *get_StackPointer(word *stackPointer)
{
__asm
{
mov stackPointer, sp;
}

return;
}

요런 식으로 구현 가능 하겠죠.

------------------------------------------------------------------------

자, 요로코롬 get_StackPointer()라는 함수를 만들어 놓으면, 일단, 전달 받는 인자가 1개 R0로 전달 될 것이고, 그 이외에는 분명 당연코 stack을 쓸 일이 없을 것이니, get_StackPointer()함수를 부르는 시점에서 Stack Pointer가 변할 일이 없으니, 정확히 현재 시점의 Stack pointer를 가져올 수 있겠죠. 진짜로 그런지는 언제나~ Compile 해보면 아니까, 아래처럼 Assembly로 get_StackPointer()를 컴파일해 보면. (getsp.c에 위의 함수만 하나 덜렁 넣어놓고 컴파일 해 보면)

tcc -o getsp.s -S getsp.c

getsp.s ----------------------------------------------------------------------------------------

; generated by Thumb C Compiler, ADS1.2 [Build 805]

; commandline [-O2 -S -IC:\apps\ADS12\INCLUDE]
CODE16                                                 ; 16bit Thumb code 야~

AREA ||.text||, CODE, READONLY               ; RO, .text 속성이구~

get_StackPointer PROC                                     ; 함수이름은 get_StackPointer야.
MOV      r0,sp                                         ; return값이 들어갈 r0에 sp를 넣고서~
BX       lr                                                ; Linked Register의 값으로 돌아가~
ENDP                                                     ; 함수의 끝이여.

EXPORT get_StackPointer                          ; get_StackPointer()함수는 어디선가

가져다 쓸꺼니까 참고하고, 알겄냐.

------------------------------------------------------------------------

오호라. 의도한대로그대로 Assembly가만들어졌네요. 뭐간단하죠? 한가지참고할만한사항은 Inline assembly에서도 local 변수, 즉여기에서는 stackPointer를직접참조할수있다는사실. 요것만으로도상당히편리하게이용할수있겠어요. 음흐흐.xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /그럼, 이번엔 CPSR을만져서 Interrupt가발생하지못하도록하는함수하나를Inline으로한번만들어볼까요? 함수이름은 Interrupt_lock이고, FIQ와 IRQ를모두 disable 할수있으면하는바램이있고요. 이때, CPSR에서 IRQ mask는 0x80, FIQ mas는 0x40이고요, 두개를 OR 한것은 0xC0이고요. 가장중요한건Privileged Mode에서만 PSR을만질수있고요, CPSR을만질수있는건Thumb mode가아닌 ARM mode에서만가능하기때문에, Compiler는 tcc가아닌 armcc를사용해야하겠습니다. 일단, CPSR의 interrupt 관련 bit를 1로 set하기위한코드를짜보았어요.

intlock.c --------------------------------------------------------------------------

#define PSR_Irq_Mask 0x80
#define PSR_Fiq_Mask 0x40
#define PSR_INT_Mask 0xC0

void Interrupt_lock(void)
{
__asm
{
mrs     a1, CPSR
orr     a2, a1, #PSR_INT_Mask
msr     CPSR_c, a2
}
return;
}

-----------------------------------------------------------------------------------

Register Convention을 r0, r1대신 a1, a2라고 썼고요, 일부러 이렇게 해도 된다는 점을 부각하기 위해서., ㅋ a1에 CPSR 값을 가져 온후, a1과 interrupt bit field를 OR해서 a2에 집어 넣은 후, a2값을 MSR을 이용해서 CPSR에 넣어주는 뭐 이런 간단한 mechanism인게죠. CPSR_c는 바로 뒤에 설명 나오니까, 넘어가시고요.

역시나 마찬가지로 Assembly로 만들어 보면 다음과 같은 결과가 튀어 나와요.

armcc -o intlock.s -S intlock.c

; generated by ARM C Compiler, ADS1.2 [Build 805]

; commandline [-O2 -S -IC:\apps\ADS12\INCLUDE]
CODE32                                                             ; 여기서 부터는 32bit ARM code이지.

AREA ||.text||, CODE, READONLY                           ; Symbol 단위 특성은 CODE야.

Interrupt_lock PROC                                                     ; 난 함수고, 궁금할지도 모르겠지만

; 이름은 Interrupt_lock()이야
MRS      r0,CPSR                                                  ; CPSR값을 r0에 loading해
ORR      r1,r0,#0xC0                                             ; loading한 r0값과 0xC0을 OR해서 r1에 넣고요
MSR      CPSR_c,r1                                               ; r1을 CPSR에 넣고
MOV      pc,lr                                                      ; 돌아가자~ 으흐흐 이런 거죠.
ENDP

EXPORT Interrupt_lock



어때요. 흥미롭죠. 뭐 너무나 당연 간단한 예제 두 개를 해보았더니, Inline Assembly 별거 아닙니다~고~ 외칠 수 있겠습니다만, 여기에서 재미있는 퀴즈 하나 가시죠. 그러면 다음 코드에서 return 값은 뭐가 될까요~? 예를 들어, a = 0, b = 1이 들어왔다고 치면?

int add (int a, int b)
{
__asm
{
add r0, r0, #1
}
return a+b;
}



뭘까요? 이때의 return 값은? 2가 될 거라는 생각 안 드세요?  r0로 a가 넘어 들어 왔을 거고, r1으로는 b가 타고 들어와서, r0는 1이 늘어나고, 이때 r0와 r1을 더하니 2가 되어야 할 거 같지 않나요?라고 말하면 퀴즈가 안되겠죠. 으허허.

실은 컴파일러는 이런 문제를 해결하기 위해서 a와 b를 다른 register에 넣고, inline 안에서 만지는 레지스터들은 inline 내부에서만 조물딱 거리고, 함수의 argument로 넘어온 값들과는 inline 내부에서는 완전 별개로 관리할 수 있도록 backup을 하여 도와 줍니다. 그러니까 inline assembly에서의 r0, r1등의 register의 사용법은 inline에 진입하기 직전에 backup되므로, APCS와 상관없다고 보면 되고, inline된 register는 컴파일러가 자동으로 stack에 save하고 restore되거나 다른 register에 할당된다고 이해 하면 되겠습니다.

그러니 위의 add() 의 경우에는 실제로 inline assembly 내부의 r0가 따로 쓰일 일이 없으니, 컴파일 해보면 컴파일러가 알아서 그 내용을 최적화 하여 없애 버리는 결과가 초래 됩니다. 자, 볼까요?

CODE16

AREA ||.text||, CODE, READONLY

add PROC
ADD      r0,r0,r1                               ; a + b
BX       lr                                        ; a는 return값이 되고 lr로 돌아가자~
ENDP

오호, 간단하네요. ㅋㅋ. 결국 inline으로 넣은 코드는 아무 소용 없어져서 없어져 버렸습니다.이런 편리한 In-line Assembly도 나름의 restriction이 있으니, 그 restriction을 이해 하지 못하면, 문제를 가진 inline Assembly code를 양산할 수 있으니 꼭 이해해 두셔야 합니다.

Inline Assembly는..
ⓐ 의사 명령어인, LDR Register,= Expression과 ADR의 형식을 사용할 수 없습니다.
ⓑ Label을 달 수 없습니다. Label을 쓸 수 없고, BL, BLX등의 brach명령어를 못쓰니까요.
ⓒ BL. BLX등의 branch 명령어는 허용이 안됩니다.
(뭐 당연하죠 inline에서 다른 데로 jump해 버리면.. 안되죠)
ⓓ Processor Mode나 Coprocessor 의 상태를 바꿀 수 있으나,
compiler는 이 변화를 알아채지 못합니다. 사용자가 끝까지 책임지는 무한 책임주의가 되어야 합니다.
ⓔ 안타깝께도 Thumb mode에서 inline 된 Assembly는 Thumb mode의 한계와 같이
r0~r7까지만 Access 가능합니다.
ⓕ 중요한 건 inline된 Register는 컴파일러가 Stack에 저장하고 Restore하는 걸 자동으로
만들어 줘요.

뭐 이런 여러가지 restriction도 존재합니다.. 만.
Low Level의 Control에는 편리한 부분이 있어서, 이런 편리함의 중독성에서 못벗어 날 때도 있습니다.
냐하하.

■ CPSR macros, CPSR_c
CPSR setting부분에 뜬금없이 CPSR_c이라는게 나왔는데, 이건 또 뭐냐.. 하면,
다시한번 예전의 CPSR의 구조를 그려놓고 주섬주섬 살펴보면,

요렇게 생기신 32bit짜리 register인데요, 요 녀석을 8bit씩 4개로 나누어 볼 수 있겠네요. 코딩을 하다 보면 요런 영역들을 따로 관리해야 할 일들이 많은데, 이 녀석들을 그때 그때  mask 씌워서 값을 쓰려다 보니 귀찮더란 말이죠. 그래서 CPSR_c 라는 Macro를 미리 Compiler와 약속하여, 아래 control 즉 0~7 bit 까지의 영역에만 값을 써주는 역할을 해 주는 거죠.
그럼 CPSR_c만 있느냐.. 나머지 영역들이 서운해 하니까.

CPSR_c : 0~7 bit 즉 Mode, ARM/Thumb, IRQ/FIQ Enable/Disable (Control Field)
CPSR_x : 8~15 bit Unused  (Extension Field)
CPSR_s : 16_23 bit Unused (Status Field)
CPSR_f : 24~31 bit NZCV flag와 unused (Flags Field)

이런 식으로 8bit씩 4개의 Mask들이 준비되어 있어요. 이 녀석은 CPSR_cx, CPSR_sf  이런식으로 붙여서 사용할 수도 있고요. 결국 CPSR_cxsf = CPSR 이라고 봐야겠죠? 보통은 CPSR_cf가 가장 많이 쓰이겠죠.

CPSR만 이게 되느냐! 하면 SPSR도 똑~같이 사용할 수 있어요. SPSR_c, SPSR_x, SPSR_s,  SPSR_f. 이렇게 말이죠. 으후훗!

■ Inline의 의미 : Inline이라는 건 말이죠. 컴파일러에게 마치 Macro처럼 이 함수를 사용하는 곳에다가 Inline으로 선언된 함수를 직접 끼워 넣어달라는 말이에요. 그러니까 Inline으로 선언된 함수를 사용하게 되면 Inline 함수로 분기하는 게 아니고, Inline 컴파일 된 함수의 코드 자체가 호출하는 함수에 삽입 되는 거지요.

Linked at 친절한 임베디드 시스템 개발자.. at 2009/10/01 12:32

... ⓔ Inline Assembly와 INTLOCK()구현 ... more

Commented by kimyow at 2009/07/21 11:31
Inline assembler not permitted when generating Thumb code
라는 compile error 메세지가 뜨네요 ;;

tcc 로 compile 시 __asm 을 쓰지 못하는 것 같은데...

시원한 답변 부탁드립니다.^^
Commented by 히언 at 2009/07/21 15:40
요게 ADS tcc로 compiler하면 아래와 같은 meessage가 나와요.
"get.c", line 8: Warning: C2624W: Thumb inline assembler will not be supported i
n future releases of the compiler
"get.c", line 10: Warning: C2211W: non-value return in non-void function
get.c: 2 warnings, 0 errors, 0 serious errors

머 더이상 지원 안한다고 하지요?
지금 현재 쓰시는 compiler가 아마도 RVCT2.2 이상이지 싶습니다.
RVCT2.2 이상에서는 inline assembly 지원 안하니까, 직접 Assembly로 짜서 만들어라.. 어쩌구 해 놨어요.
Commented by kimyow at 2009/07/22 16:51
RVCT2.2 쓰는거 맞습니다. 결국 컴파일러 버젼 문제였네요..ㅎㅎ
답변 감사합니다.
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.
친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -