본문 바로가기
R13, R14에게 반했다?

뭘 알아야 이해를 하지

6개의 모드마다 모두 R13과 R14 레지스터는 공통적으로 있는데, 그 이유가 무엇일까요? 분명 이유가 있겠죠? 왜 R13과 R14일까요?

ARM core에서는 R13과 R14 레지스터는 특별한 일을 한답니다. 이제부터 소개하죠.


R13은 스택 포인터

R13은 스택 포인터 레지스터라고 불린답니다.

스택이라... 어디선가 말이 들어본 말이지 않나요? 갑자기 Push, Pop이라는 용어가 떠오르면서 자료 구조가 생각나죠? ㅋㅋㅋ

모두들 오래 전에 배운 내용이라 기억이 가물가물 하실겁니다.



스택이란 데이타를 잠시 보관하는 메모리 공간이랍니다. 스택에 잠시 보관할 때는 Push, 보관된 데이타를 꺼낼 때는 Pop이라는 명령어가 사용됩니다. C 언어와 스택은 아주 각별하답니다. C 언어는 스택 메모리 공간이 없으면 프로그램 자체가 동작하지 않습니다. 어떤 CPU이든 C 언어로 만든 프로그램이 동작한다면 반드시 스택 공간을 사용하게 되어 있답니다. 이 공간은 언제든지 읽고, 쓰기가 가능한 SDRAM 이여야 한답니다. 왜?? 데이타를 잠시 보관하는 공간이라서요. ㅋㅋ

임베디드 시스템을 만들 때 반드시 필요한 하드웨어를 뽑아야 한다면, CPU와 SDRAM이랍니다. MCU에 따라 CPU와 SDRAM이 내장된 형태가 있는 반면, CPU와 SDRAM이 따로 있는 경우가 있죠. USN이라고 8bit MCU가 있는데 아주 작은 RAM이 내장되어 있답니다. 32bit 프로세서에도 내장된 SDRAM이 있어요. 주로 인터널에스램(Internal SRAM,I-RAM)이라고 부르죠. I-RAM은 주로 부트로더를 올리거나 인터럽트 벡터 테이블 코드 등 프로그램이 동작한답니다.

이제 스택이 어떻게 동작하는지 동작원리를 알아보고, C 언어로 만든 프로그램이 ARM core에서 동작할 때 스택이 어떻게 동작되는지를 알아보아요.



스택에 데이타를 저장될 때는 Push, 저장된 데이타를 다시 꺼낼 때는 Pop이랍니다. 스택은 김치와의 인연도 있답니다. 한국사람이라면 누구나 김치를 좋아하죠. 이 김치를 장독대에 보관하면 맛이 정말 일품이죠. (갑자기 무진장 배가 고파지는군요. ㅋㅋ)

장독대에 김치 한 포기씩 정성껏 넣을 때 스택이 동작하는 것처럼 Push를 한답니다. 이렇게 쌓아 두었던 김치를 꺼낼 때 Pop을 하죠. 김치가 저장 되는 순서는 ① -> ② -> ③ -> ④, 꺼내는 순서는 반대로 되죠.



그럼 ARM core에서는 Push와 Pop이 어떻게 동작되는지 알아보죠.

Push① 명령어가 실행되면 SDRAM의 상위 주소②에서부터 데이타가 저장된답니다. 그리고 스택 포인터④는 0x30010000 - 4번지로 이동하죠. 다음 Push① 명령어가 실행되면 스택 포인터 위치 주소③ 에다 데이타를 저장한답니다. Push 명령어가 모두 끝나고, 이제 Pop명령어가 실행되면 스택 포인터 위치 주소에 있는 데이타를 꺼내 오고 스택 포인터 위치는 Stack point +4 주소로 이동된답니다. 그래서 ARM core에서는 스택 이동 방향이 Push 경우는 상위 주소에서 하위 주소로, Pop경우는 하위 주소에서 상위 주소로 이동한답니다.



방금 설명드린 내용이 도대체 왜...왜...R13하고 어떤 연관성이 있을까요?

개발자가 C 언어로 프로그램을 했다면 분명히 Push, Pop 명령어가 있답니다. 언제 명령어를 만들었냐고요? 아니 발뺌하실 생각은 아니죠? ㅋㅋ 과학 수사로 DNA 분석하면 다 나온 다니깐요. ㅋㅋ

제가 분석을 해 보니 바로 함수 콜 할 때 있더라구요.

사실 개발자는 직접 Push와 Pop명령어를 사용하지는 않구요, 컴파일을 하고 나면 Push, Pop 같은 명령어를 볼 수가 있답니다. Push, Pop 명령어는 32bit ARM 명령어와 16bit THUMB 명령어로 나누어 진답니다. 먼저 32bit ARM 명령어에서 Push는 stmdb이고 Pop은 ldmia 이랍니다. 16bit THUMB 명령어에서는 Push는 push이고 Pop은 pop 명령어랍니다.

32bit ARM Instruction 16bit THUMB Instruction Push stmdb Push Pop ldmia Pop

그럼 직접 소스 코드를 보죠. 먼저 32bit ARM 명령어랍니다.

main함수에서 func2c 함수① 호출을 했어요. func2c함수의 시작 위치에서 보면 r13을 사용한 어셈블리 명령어②가 실행된답니다. 스택에 데이타 값들을 push 한 후 func2c 함수의 프로그램들이 실행된답니다. func2c함수의 마지막에는 스택에 저장된 데이타를 pop하는 어셈블리 명령어③가 실행되고 main 함수로 돌아간답니다.

다음으로는 16bit THUMB 명령어랍니다.

main함수에서 func2c 함수① 호출을 했어요. func2c함수의 시작 위치에서 보면 r13을 사용한 어셈블리 명령어②가 실행된답니다. 스택에 데이타 값들을 push 한 후 func2c 함수의 프로그램들이 실행된답니다. func2c함수의 마지막에는 스택에 저장된 데이타를 pop하는 어셈블리 명령어③가 실행되고 main 함수로 돌아간답니다.

아래와 같은 예제를 통해 Push와 Pop 동작 될 때, R13 레지스터와 메모리 값들 변화에 대해 알아보죠.


Push 할 때 R13과 스택의 변화
main 함수에서 func2 함수로 진입해서 Push 명령어인 stmdb가 실행①될 때 R13의 레지스터 값④이 변한답니다.


1. PC값이 0x0288일 때 R13은 0x30FFD7E8 주소를 가리키고 있고,

2. 명령어가 실행①되면 R13 주소②에서 부터 하위 주소로 r3, r4, r14값③들을 Push⑤ 하고,

3. R13은 0x30FFD7DC 주소④ 를 가리키게 됩니다.

Pop 할때 R13과 스택의 변화


func2 함수의 마지막 주소에 Pop 명령어인 ldmia가 실행①될 때 R13의 레지스터 값④이 변한답니다.


1. PC값이 0x02C4일 때 R13은 0x30FFD7DC 주소를 가리키고 있고,

2. 명령어가 실행①되면 R13 주소에서부터 상위 주소로 r3, r4, r14값②들을 pop③ 하고,

3. PC값은 R14 주소를 저장한 ④ 주소로 점프를 한 다음,

4. R13은 0x30FFD7E8 주소를 가리키게 됩니다.


지금까지 R13의 역할과 동작 원리에 대해 살펴봤어요.

이처럼 R13은 함수 콜을 할 때 스택이 사용된다는 것을 알게 되었는데, ARM에는 6개 모드가 있다고 했잖아요. 각 모드 별로 R13이 있기 때문에 스택 위치가 각각 다른 주소를 가리키게 된답니다. 각 모드 별로 스택 위치를 언제 지정할까요? 그건 Startup.s 파일에서 지정하도록 되어 있답니다. Startup 파일에서 스택 영역 할당이라는 부분이 있지요.


Startup.s 소스 코드로 살펴보면,



▶코드 Startup.s STACK_BASEADDR EQU 0x31000000
UserStack EQU (STACK_BASEADDR-0x3800)
SVCStack EQU (STACK_BASEADDR-0x2800)
UndefStack EQU (STACK_BASEADDR-0x2400)
AbortStack EQU (STACK_BASEADDR-0x2000)
IRQStack EQU (STACK_BASEADDR-0x1000)
FIQStack EQU (STACK_BASEADDR-0x0)
;----------------------
InitStacks
;----------------------
MRS r0,cpsr
BIC r0,r0,#MODEMASK
ORR r1,r0,#USRMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=UserStack
ORR r1,r0,#UNDEFMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=UndefStack
ORR r1,r0,#ABORTMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=AbortStack
ORR r1,r0,#IRQMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=IRQStack
ORR r1,r0,#FIQMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=FIQStack
BIC r0,r0,#MODEMASK|NOINT
ORR r1,r0,#SVCMODE
MSR cpsr_cxsf,r1
LDR sp,=SVCStack
MOV pc,lr
LTORG


스택 포인터가 SDRAM의 상위 주소라는 걸 명심하세요. 자세한 내용은 링커 스크립트(303) Unit에 자세히 설명을 해 놓았답니다.


마지막으로 왜 C 언어는 반드시 스택을 사용해야 하는지 그 이유를 말씀 드리자면, Startup.s 파일에서 Main.c의 main 함수로 진입할 때, 반드시 스택을 사용하게 되어 있답니다.


Stack에 대한 ARM 명령어와 THUMB 명령어를 비교해 보도록 하죠.

ARM 명령어 THUMB 명령어 PUSH STMDB R13!,{r1,r2,(R14) } PUSH {r1,r2,r14} POP LDMIA R13!,{r1,r2,(R15) } POP {r1,r2,r15}

R14는 링크 레지스터
C 언어로 프로그램을 만들다 보면 자주 서브함수를 호출하곤 합니다. 스택에는 주로 변수의 값이나 서브함수 실행이 끝나고 나면 복귀해야 될 주소가 저장되어 있답니다.

하지만 서브함수라고 해서 무조건 스택을 사용하는 것은 아니랍니다. 그렇다면 서브함수 중 스택을 사용하지 않을 경우, 서브함수 실행이 끝나고 복귀해야 될 주소를 ARM core는 어떻게 알게 될까요? 지금부터 그 궁금증을 풀어보아요.

R14는 링크 레지스터(Link Register)라고 해서 스택을 사용하지 않는 서브함수 에서 복귀해야 될 주소를 저장하는 역할을 한답니다. 예제 프로그램을 보면서 설명 드리죠.

main 함수에서 func2d 함수를 호출했답니다. func2d 함수 진입 시점①에 Stack 명령인 Push 명령어가 없답니다. 이와 같은 경우는 func2d 함수 실행이 끝나고 나면 다시 main함수의 어느 주소로 돌아가야 할지 모른답니다.


main 함수에서 func2d 함수로 진입할 때 ARM core에서는 어떤 일이 일어나는지 알아보죠.func2d 함수로 진입①했을 때, R14②는 func2d 함수를 호출한 다음의 주소 값(0x20C4)을 저장한답니다.

R14에는 복귀할 주소가 저장되었는데, 스택과 마찬가지로 함수 마지막에 복귀하는 명령어도 있겠죠. func2d 함수의 마지막 부분에는 r14 값을 pc값으로 바꾸는 명령어①가 있답니다. 명령어가 실행되면 r14->pc로 변하고②, func2d 호출한 주소의 다음 번지③로 점프하게 된답니다.

R13과 R14에 대해 모두 알아보았어요.

R13 같은 경우에는 프로그램 실행을 하는 코드 주소가 올 수가 없고 스택 포인터 주소만 올 수 있으며, R14같은 경우에는 스택 포인터 주소가 올 수 없고 프로그램 코드의 주소만 온다는 것을 알았답니다. 이 말은 하드웨어 디버깅 장비를 사용해서 디버깅을 하다 보면 R13과 R14의 레지스터 값만으로도 프로그램 실행이 비정상적으로 동작한다는 것을 예측할 수가 있답니다.

SDRAM - "205" startup – “301”


Written By Soto

Linked at 친절한 임베디드 시스템 개발자.. at 2012/02/12 22:57

... 501 ARM Mode와 PSR..너희들은 누구냐? 502 R13, R14에게 반했다? 503 스타트업(Startup.S) 어떻게 만들죠? 504 스케 ... more

Commented at 2012/02/13 10:48
비공개 덧글입니다.
Commented by 히언 at 2012/02/13 13:29
Soto님이 답변 다셔야 되는데, 비밀글이라 ㅎㅎㅎ. 코멘트 감사드리고, 조만간 저의 소견두 ㅎㅎ 피력해 보겠사옵니다. ^^ 감사합니다.
Commented by Soto at 2012/02/20 11:09
Soto입니다.


답변,
관심가져 주셔서 감사합니다.
님의 코멘트가 모두 맞는것 같네요. 정확한 표현인것 같아요. 님 계서 작성한 글을 적극 참고하도록 할께요.
그렇지만 이 내용이 완전히 틀리다고 표현하는 것은 아닌 것 같습니다.


우선,


없으면 없는데로 동작되도록 컴파일러를 구현하면 됩니다. (물론 ARM처럼 register를 조작하므로써...


컴파일러도 역시 사람이 만들기에 없으면 없으면 없는데로 동작을 하도록 만들면 됩니다. 하지만 ARM이 가진 r13 레지스터를 최대한 활용하고, 코드를 최적하기 위해 컴파일러를 그렇게 만든거죠. 즉, ARM cpu가 만들어지고 나서 컴파일러가 만들어지는거죠. 그래서, ARM과 스택 그리고 c 언어까지도 연관성이 있됩니다.
Start.s 코드에서 c의 main으로 갈때 가장 먼저하는 일이 스택에 관한 겁니다. 님의 말씀처럼 c언어가 직접 스택을 관여하는게 아니라 컴파일러가 관여하는 거죠.
왜 가장 먼저 스택에 관한 걸까요? 다른 코드들도 많을 텐데.... c언어에서 함수만 사용했다하면 그 함수의 시작 부분과 마지막 부분에 대부분 스택에 관한 코드를 컴파일러가 왜 생성시킬까요? 스택이 뭐길래? 스택에는 대부분 지역변수의 값을 비롯해서 기타의 여러 값들을 저장하기 위함인데 ...
코드 생성을 쉽게 하는 것 못지 않게 ARM cpu 성능 향상을 위해 생성됩니다. 컴파일러 기준이기 보다는 ARM cpu 성능 향상이 먼저죠. 어짜피 ARM에는 R13이라는 스택 관한 레지스터가 있으니 사용 안 할 이유가 없죠. MIPS, Power PC 같은 cpu도 마찬가지 입니다. 앞서 말씀 드린 것 같이 ARM cpu 가 만들어지고 컴파일러가 만들어 지니깐요.(꼭 닭이 먼저냐 달걀이 먼저냐 같네요. 서로 같은 얘기를 하는 것 같은데...ㅎㅎ)


STMDB명령어와 LDMIA명령어는 Stack을 위한 명령어가 아닙니다.


맞습니다. 원래 이 코드는 스택만을 위한 것이 아니라 멀티 store 와load 이죠. 단지 원리가 스택에서 store 와 load되는 것과 비슷하다보니 컴파일러에서 이 코드를 사용한 것 뿐입니다.
구지 ARM모드에서 push, pop 같은 새로운 코드를 만들지 않아도 될 만큼 비슷한 원리죠.
이 부분의 설명이 미흡했나보네요. 코멘트 감사합니다.

즐거운 하루 되세요.

Commented at 2012/02/14 16:11
비공개 덧글입니다.
Commented by 히언 at 2012/02/14 17:58
아하하~ 이건 제가 코멘트 할 수 있는 내용이라 다행입니다~~
근데 제가 SDRAM 16 bit로 코멘트 했던가요? -,.-a

일단 이거는 ARM사에서 갖다 붙인 이유들 인데요,
심지어는 ARM에서 제시한 Thumb의 장점으로 이것저것이 좋아진다는 comment까지 덧붙인 경우가 있습니다. 근데 해보니까 갖다 붙인게 아니라 진짜 좋네? 라는 결론이라고나 할까요?

ARM 32bit가 세상에 나왔을 때 가장 많이 쓰이던 메모리는
16bit dataline을 가진 NOR flash + PSRAM combination이 가장 많았던건 사실입니다.
크기 자체도 bit로 따지던 시절이었고 (머 지금도 그렇지만요)
32bit data line을 가진 메모리를 장착하기에는 단가측면에서 맞지 않아서
비즈니스에서 살아남을 수 없는게 사실이었어요.
Mass MCP를 적용해야 당연히 Product로써의 단가 유지가 되는 시대였었었었지요 ^^
- 말씀하신대로 ARM7TDMI도 32bit dataline을 가지고 있어요. -

그러다 보니까, 코멘트 된 대로 16bit에 맞는 방법을 찾게 되었고,
Thumb이라는 걸 개발하고 보니까, 오! 이런저런면이 좋네? 아, 계속 가져갈 수도 있겠다.
특히나 Code density에서 유리한 면이 많구만! 어허허! 라는 둥의 스토리가 된거라고 생각합니다.
오히려 그시대에는 ARM mode를 쓰는 날이 오긴 하는걸까? 하는 어이 없는 질문을 하던 시기이기도 합니다.

ARM11을 사용하는 시기에 들어와서야 32bit dataline을 full로 쓰는 MCP (SDRAM+NAND combination)가 대중화되어 실제 32bit짜리 SDRAM를 장착했지만, Thumb의 용도가 워낙 code density 부분에서 강력해서 아직까지도 버리지 않고 잘 사용하고 있다~고 해야겠지요.

어쨌든, 이게 필요한 이유는 갖다 붙이기 나름이라고 생각하고
저 나름대로는 대단한 ARM이다. 라고 생각하고 있습니다. 흐흐.

이 부분이야 이러면 어떻고 저러면 어떻겠습니까~? 냐호호.

저, 그런데, 제가 작가 구하고 있는데, 혹시 posting 보셨나요?
http://recipes.egloos.com/5631769
나름 웹진으로 거듭나고자 하는데, 관심있으면 알려주세요.

어쨌거나 엔지니어강국 대한민국을 만들어 보는거에요.

감사한 코멘트, 또 한번 잘 받았습니다.

히언배상.
Commented by 공부하자! at 2012/02/25 18:54
안녕하세요. 먼저 이렇게 좋은 강좌글 올려주셔서 감사드립니다!
제가 아직 앞부분을 보고있는데 comment를 못 달게 되어있어서 여기에 질문하나만 올리겠습니다!

http://recipes.egloos.com/5276602 이 게시물에서 이해가 안되는게 있습니다

여기에서 노어메모리가 낸드 원낸드보다 상대적으로 실행속도가 느리다고 말씀 하셨는데요~!
낸드와 원낸드는 SD메모리로 복사한 후 실행이 되기에 복사하는 시간이 있을텐데 어떻게 노어메모리가 좀 더 느린것인가요~? 궁금합니다!
Commented by soto at 2012/02/26 07:20
안녕하세요. 좋은 질문 남겨 주셔서 감사합니다.
먼저 NAND에 대해 말씀 드릴께요. NAND에 코드는 부팅할 당시 바로 SDRAM으로 실행 코드를 복사합니다. NAND 플래시 메모리를 지원하는 CPU는 NAND에 있는 첫번째 블럭을 무조건 SDRAM으로 복사를 합니다. 첫번째 블럭에는 보통 부트로더가 들어 있답니다. 이 부트로더는 실제 타겟을 동작시키는 실행 코드를 다시 한번 더 SDRAM으로 복사하죠. 이 과정을 NAND로 접근해서 데이타를 읽어오는 Read time이라고 해요. 데이타를 읽는 단위는 블럭 단위이고, 읽는 속도는 정말 빠르답니다.
님의 질문은,
NAND를 읽는 시간이 있기 때문에 실행 속도가 느리다고 생각하시는 것 같네요.
NAND Read time과 실행 속도를 다르게 생각해 보시면 어떨까요?
즉, 프로그램이 실행되고 있는 시간이 실행속도 부팅 시간이 아닌....
실제 NAND에 있는 실행 코드가 SDRAM으로 복사 끝나고 난 이후 코드가 실행되고 있는 속도요.
NOR에서 실행되는 속도보다 SDRAM에서 실행되는 속도가 더 빠르다는 표현입니다.
잘 아시다시피 NAND에서는 코드를 실행할 수가 없잖아요. ^^;
감사합니다.
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.
친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





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