링커 스크립터의 .text
에는 가장 먼저 IVT에 대한 부분이 들어가야 한다.
.text : {
*(.isr_vector),
*(.text),
*(.rodata)
} > FLASH // Flash에 할당되는 읽기전용 내용들
시스템이 부트업 하기 위해서는 가장 먼저 IVT가 실행돼 reset handler 함수가 실행돼야 한다. 그래야 .bss
, .data
section에 정의된 심볼들이 초기화가 되기 때문이다. 따라서 .text
의 최상단에는 .isr_vector
에 대한 포인터가 들어가야 하고, 이 내용은 flash에 읽기 전용으로 기록된다.
section
을 사용해서 IVT를 정의한다. __attribute__ ((section(".isr_vector")))
void (* const IV[])(void) {
(void (*)(void))(END_STACK), // Stack pointer 초기값 지정
isr_reset,
isr_nmi,
...
}
// ISR은 parameter도 return도 없다.
void isr_reset(void) {
/*...구현...*/
while(1) {}
}
// 사용자 정의 ISR 또는 빈 ISR은 오버라이드 될 수 있도록 __weak 심볼을 사용한다.
void isr_user(void)__weak {
/*...구현...*/
while(1) {}
}
MCU에 전원이 들어오면 IVT의 상단에 정의된 reset handler가 가장 먼저 실행된다.
.data
와 .bss
section의 초기화를 수행한다!.bss
section을 0
으로 초기화하고, .data
와 .bss
section을 RAM의 실제 section에 복사하는 작업을 수행한다.main()
함수를 호출할 수 있다.링커 스크립터는 VMA (Virtual Memory Address)와 LMA (Load Memory Address) 를 분리하는 메커니즘을 제공한다. 물론 MCU는 MMU가 없어서 모두 physical address로 동작하지만, flash에서 정의된 .data
의 주소들이 실제 RAM의 어디로 매핑될지는 컴파일 타임에 결정할 수 없기 때문에 이 메커니즘은 꼭 필요하다. AT
키워드와 _stored_data
라는 링커 스크립터 변수를 이용한다. (내용이 복잡하고 번역이 이해하기 어려워 이정도만 언급하고 넘어간다.)
END_STACK
이라는 미사용 RAM 영역의 끝 주소가 저장돼있다. IVT는 flash에 저장되므로 런타임에 계산될 수 없기 때문에 사전에 정의된 상수값이어야 한다.(다중 부트와 다중 시스템 디버깅에 대한 내용 그리고 .utils
공유 라이브러리에 대한 내용 생략함.)