본문 바로가기
Reset Handler에서 main까지 (Entry Point)

ARM core의 Embedded System에 Power를 넣으면 Reset Exception이 난 것과 같은 의미인데요, ARM core에 최초 Reset신호가 인가되기 때문이에요. 그러니까 SVC mode로 Reset Vector로 PC가 setting 됩니다. 좀 다른 case인 High Vector case가 있긴 하지만, 그건 우리들의 숙제로 남겨두고요. 뭐, 어쨌거나 Reset Vector는 Low Vector인 경우에는 0x0입니다요. 0x0로 PC가 setting되면 그곳에는 Reset Handler로 branch하는 code가 있기 마련이죠. 이전에 보았던 Exception Vector를 한번 볼까요

AREA    INT_VECT, CODE, READONLY
CODE32                         ; 32 bit ARM instruction set.

ENTRY

B       Reset_Handler                       ; 0x0
B       Undefined_Handler                 ; 0x4
B       SWI_Handler                         ; 0x8
B       Prefetch_Handler                   ; 0xC
B       Abort_Handler                       ; 0x10
B       Reserved_Handler                  ; 0x14
B       RAM_IRQ_VECTOR                  ; 0x18
B       RAM_FIQ_VECTOR                  ; 0x1C

0x0에는 Reset Handler로 branch하는 명령어가 자리잡고 있지요. 위에 있는 ENTRY directive를 주의해서 보시면 ENTRY는 이 실행 binary image의 진입 점이라는 의미에요. image를 하나 생성하고 나면 딱 한 개밖에 없는 곳이에요. Reset Handler가 Power on시에 거치는 곳이니까 ENTRY point로는 딱 제격이네요. 여기에서 Reset Handler가 무슨 일을 해주어야 하는가에 대해서
음미해 보시기로 하시죠. 보통 Reset Handler가 해줘야 하는 일은 크게 두 개로 나눌 수가 있는데, Hardware적인 것과 Software적인 것이라고 봐야겠죠.

Hardware적인 설정으로는
0) Hardware적으로 자동으로 IRQ/ FIQ disable이고, SVC로 이곳에 진입해요.
1) 기본적으로 사용되는 HW block들의 clock 설정
2) 기본적인 MCU의 pin 설정
3) MCU에서 bootup에 필요 없는 HW block을 죽임.
4) Memory Setting → Memory Sizer (Bus width)라든가, Wait State등
등이고요,

그러면 Software적인 설정으로는
1) 각 mode별 Stack setting
2) RW RAM에 복사. RO도 XIP가 SDRAM에서 이루어질 경우에는 복사해요. ZI는 복사할 필요 없이 영역을 아니까 그 부분 만큼 0으로 채우고요. 그러니까 RW를 복사함은 변수의 초기화하는 거고, ZI는 0으로 initialization한다고 보믄 되겠네요.

마지막으로 이런 Reset Handler의 마지막에는 C 함수로 건너뛰기 위한 main함수 호출이 있는데, 보통 __rt_entry 라는 걸 호출하면 __rt_entry가 알아서 우리가 흔히 아는 main()함수를 호출해 준답니다. 이런 순서로 진행되는 Reset Handler를 보통은 Start up code 또는 Boot up code 또는 라고 부른답니다. 우리가 만약 Entry point를 따로 구현하지 않는다면, 일반적인 ADS로 compile한 Application은 어떻게 구성이 될까요? Application이 실행되는 과정을 한 번 보시죠.


위의 그림은 ARM사 Document중에 Application Note 107에 있는 그림이에요. 이 그림이 의미하는 건 뭘까요? 왼쪽은 C Library, 오른쪽은 User Code. 왼쪽에는 __main, __rt_entry가 들어 있고요. 우리 흔히들 알고 있는 main()이 오른쪽에 들어 있죠. 그러니까, main()이 불리기 이전에 C Library에 관련된 일을 하게 되지요.

main()은 지가 제일 먼저 불리는 줄 알겠지만, 이런 복잡한 과정을 거쳐서, 결국엔 main()이 불리는 거에요. 그러니까 main()은 user의 entry point이고요, Kernel 이나 Processor에게는 __main이 Entry point인 거죠.

그러면 각각의 role을 한번 볼까요? C Library안의 __main은 뭘까요? __main은 application이 실행되기 위한 entry point에요. application이 실행되면 무조건 __main 부터 시작하는 게 약속이에요. 게다가 __main은 어떤 역할을 하느냐 하면, 실행시의 Memory Map을 setting해 주는데, SDRAM에서 XIP를 할 경우에는 Code, Data를 SDRAM에 복사해 주고, Default Memory Model의 ZI region을 0으로 initialization 해 주는 역할을 해요.

그러면 __rt_entry는? rt는 real time의 약자고요, C library 자체를 실행 가능하도록 해줘요. __main에서 자동으로 __rt_entry를 부르게 되어 있고요, C library에서 사용하는 Stack과 Heap을 잡고, library 함수와 library내부의 static data를 initialization하는 일도 합니다. 그리고 __rt_entry는 자동으로 main() 함수를 부르게 되어 있죠. 아함. 이제서야 우리가 흔히 알고 있는 Entry Point인 main() 함수가 불리게 됩니다.

Linker입장에서 보면 이 main()함수가 있으면 __main과 __rt_entry에 Memory Map 잡는 것과
C Library initialization 과정을 넣어 주라고 하는 것과 마찬가지에요. 만약에 main()이라는 함수가 존재하지 않으면, 이런 Initialization 과정을 link하지 않아서, C Library를 제대로 사용할 수가 없다는 것을 아셔야 해요.

이 부분은 뒷쪽에 Semi hosting관련된 부분에서 다시 자세히 다루게 되는데요, C Library는 Semi hosting을 사용하기 위해서 필요한 거에요. 꼭 Semi hosting만 사용한다고 하면 너무 국한 하는 거긴 하지만, 대부분은 Semi hosting과 관계 있다고 보심 됩니다.

그러니까, 일반적인 ARM을 이용한 Embedded system은 처음에 C library를 Entry로 잡지 않고, Reset Vector를 Entry Point로 잡아서 Image를 구성하는 거에요. Reset Vector를 Entry Point로 걸게 되면, __main을 대신해야 하니까, Vector Table을 만들 때 __main을 Label을 Reset_Handler 앞에다 걸어주고,
EXPORT   __main
을 해주면 Linker가 Library의 __main을 무시하고, Reset_Handler를 __main 자리에 대신 만들어 준답니다. 여기서 주의 할 점은 __main이 없는 경우에는 Linker가 _main이라는 Symbol을 찾아요. 어차피 __main이나 _main은 같은 거라 보시면 되고요,

EXPORT   __main
EXPORT   _main

AREA    INIT_VECTOR, CODE, READONLY
CODE32

__main
_main
ENTRY

B       Reset_Handler
B       Undefined_Handler
B       SWI_Handler
B       Prefetch_Handler
B       Abort_Handler
B       Nothing_Handler
B       IRQ_Handler
B       FIQ_Handler

C Library를 안 쓰고 싶으면 Reset Hander가 끝나는 시점에 __rt_entry 대신에 main() 함수를 직접 불러주면 C library를 init 안 하니까 C library를 안 쓸 수 있습니다. 뭐, 하지만 대부분 C library에 기초적인 함수들이 많이 있으니까 Entry Point는 Reset Vector로 대신 하지만, Reset Handler 끝에는 __rt_entry를 호출해 주는 게 대충의 정석이죠. 또 한가지 제대로 말하자면 __rt_entry를 쓰게 되면 main() 함수는 꼭 있어야 해요.

게다가 이런 Entry point가 없으면 Linker도 Error를 내고 Image를 만드는 것에 실패 한다니깐요.



 보통 Entry Point를 구현하는 Assembly file의 이름은 Crt0.S , init.S, startup.S, reset_handler.s 등으로 구현되는데 대충 감은 오죠? 그런데 이중에 Crt0.S는 뭘까요?  C-Runtime 0의 약자로서, 보통 우리가 만드는 PC용 프로그램이 main()함수만 만들어 놓으면 그쪽으로 Entry되는 걸로 아는데, 그게 아니고 Standard Library중 crt0.o 라는 곳에 Entry Point가 있어서,  link할 때 crt0.o라는 것도 엮이게 되고, 막상 우리가 컴파일 한 file을 실행하면 crt0.o의 Entry point를 거쳐서 main()으로 오게 되는 거랍니다. 이런 의미에서 crt0.S를 관습적으로 사용하게 된 거죠 뭐. 

 만일에 __main을 Reset Vector에 잡지 않은 경우에는 __main을 Reset Handler 끝에 불러주면 C library의 __main이 불리게 되어 자연스럽게 __rt_entry까지 불리게 됩니다. 그게 첫 번째 그림의 순서인 게죠. 예전에는 C_Entry가 Application Entry로서 __main 대신에 쓰인 적도 있어요. C_Entry가 Entry point로 사용되던 시절에는 initialization이 필요 없던 Standard Library도 아닌, Embedded Library가 쓰인 시절이었고, 그러니까 무거운 Standard Library가 link 되지 않게 하기 위해서 C_Entry를 썼었드랬죠. C_Entry로 branch 하게 되면 standard Lib 를 통한 init을 통하지 않고, 곧바로 C code로 진입해 버리게 되는 거지요.  지금은 아니지만요.

Linked at 임베디드 시스템 개발자 되기 .. at 2009/07/15 20:35

... ⓙ Bootloader와 Memory Budget (Mapfile) - 어떻게 변수를 초기화 할 것인가 ⓚ Reset Handelr에서 main (Entry point)까지 ⓛ Scatter Loading과 Booup - __user_initial_staqckheap&nb ... more

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



댓글





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