부팅
- 모든 초기화 작업을 마치고 펌웨어가 대기 상태가 될때 까지 상태
- ARM 코어가 리셋 익셉션 핸들러를 모두 처리한 다음 C언어 코드로 넘어가기 직전까지
실행 파일은 메모리를 크게 세 가지로 나누어 사용한다.
- text 영역
- 코드가 있는 공간
- 임의로 변경하면 안됨
- data 영역
- 초기화한 전역 변수가 있는 공간
- 전역 변수 선언시 초기 값을 할당해서 선언하면 해당 전역 변수가 점유 하는 공간이 할당된됨
- BSS영역
- 초기화하지 않은 전역 변수가 있음
- 초기화 하지 않은 전역 변수이므로 빈드 완료되어 생성된 바이너리 파일에는 심벌과 크기만 들어 있음
해당 프로젝트에서는 text 영역의 크기를 1MB (0x00000000 ~ 0x000FFFFF), data영역과 bss영역의 할당은 각 모드별로 1MB씩 그냥 할당한다. (크기를 그렇게 신경 안쓰고 일단 할당) RTOS의 위에서 동작할 task stack 영역도 필요하다. 총 64개의 task를 지원할 것이고 task마다 1MB씩 stack 영역을 할당하므로 총 64MB 할당한다. 그 다음 전역 변수용으로 1MB를 할당한다. 남은 공간은 heap으로 사용한다.
영역 | 주소 (총 128MB) |
동적 할당 영역 | 0x04900000 ~ 0x07FFFFFF (55MB) |
전역 변수 영역 | 0x04800000 ~ 0x48FFFFF (1MB) |
태스크 스택 영역 | 0x00800000 ~ 0x047FFFFF (64MB) |
UND 모드 스택 | 0x00100000 ~ 0x007FFFFF (7MB) |
ABT 모드 스택 | |
FIQ 모드 스택 | |
IRQ 모드 스택 | |
SVC 모드 스택 | |
USY/SYS 모드 스택 | |
text 영역 | 0x00000000 ~ 0x000FFFFF (1MB) |
.text
.code 32
.global vector_start
.global vector_end
vector_start:
//exception vector table이 작성됨
LDR PC, reset_handler_addr
LDR PC, undef_handler_addr
LDR PC, svc_handler_addr
LDR PC, pftch_abt_handler_addr
LDR PC, data_abt_handler_addr
B .
LDR PC, irq_handler_addr
LDR PC, fiq_handler_addr
//변수를 선언해 놓았고 이 변수를 exception vector table에서 사용함
reset_handler_addr: .word reset_handler
undef_handler_addr: .word dummy_handler
svc_handler_addr: .word dummy_handler
pftch_abt_handler_addr: .word dummy_handler
data_abt_handler_addr: .word dummy_handler
irq_handler_addr: .word dummy_handler
fiq_handler_addr: .word dummy_handler
vector_end:
reset_handler:
//reset exception handler이다.
LDR R0, =0x10000000
LDR R1, [R0]
//dummy로 해놓은 것이고 무한 루프를 돌게끔 구현함
dummy_handler:
B .
.end
부팅 초기시 exeption handler로 처리되며 해당 부분을 위해 Entry.S를 위와 같이 수정하자. 코드는 exception vector table에 각 handler로 jump 하는 코드만 작성되었다. 각 handler의 동작은 구현되지 않고 무한루프로 동작된다.
$ cd project_directory
$ make clean && make all
$ make debug
$ (다른 console에서) make gdb
(gdb) target remote:1234
(gdb) continue
컨트롤 + C로 정지
(gdb) info registers
2020/01/11 - [Engineering/운영체제] - [임베디드OS개발프로젝트] 3장. 일단 시작하기
지난 글 마지막 결과에서 확인할 수 있던 것 처럼 동일한 동작을 수행 했으므로 R1에 0x1780500값이 들어가 있음을 확인할 수 있다.
보드가 부팅하면서 가장 먼저 수행되어야 할 것은 reset exception handler이다. 이 reset exception handler는 메모리 맵을 설정하는 작업을 수행해줘야 한다. SP 레지스터에 정해진 값을 넣는 작업을 수행하며 초기화를 수행한다.
$ cd project_directory
$ mkdir include
$ vim MemoryMap.h
$ vim ARMv7AR.h
<Memory.h 내용>
#define INST_ADDR_START 0
#define USRSYS_STACK_START 0x00100000
#define SVC_STACK_START 0x00300000
#define IRQ_STACK_START 0x00400000
#define FIQ_STACK_START 0x00500000
#define ABT_STACK_START 0x00600000
#define UND_STACK_START 0x00700000
#define TASK_STACK_START 0x00800000
#define GLOBAL_ADDR_START 0x04800000
#define DALLOC_ADDR_START 0x04900000
#define INST_MEM_SIZE (USRSYS_STACK_START-INST_ADDR_START)
#define USRSYS_STACK_SIZE (SVC_STACK_START-USRSYS_STACK_START)
#define SVC_STACK_SIZE (IRQ_STACK_START-SVC_STACK_START)
#define IRQ_STACK_SIZE (FIQ_STACK_START-IRQ_STACK_START)
#define FIQ_STACK_SIZE (ABT_STACK_START-FIQ_STACK_START)
#define ABT_STACK_SIZE (UND_STACK_START-ABT_STACK_START)
#define UND_STACK_SIZE (TASK_STACK_START-UND_STACK_START)
#define TASK_STACK_SIZE (GLOBAL_ADDR_START-TASK_STACK_START)
#define GLOBAL_ADDR_SIZE (DALLOC_ADDR_START-GLOBAL_ADDR_START)
#define DALLOC_ADDR_SIZE (55*1024*1024)
#define USRSYS_STACK_TOP (USRSYS_STACK_START+USRSYS_STACK_SIZE-4)
#define SVC_STACK_TOP (SVC_STACK_START+SVC_STACK_SIZE-4)
#define IRQ_STACK_TOP (IRQ_STACK_START+IRQ_STACK_SIZE-4)
#define FIQ_STACK_TOP (FIQ_STACK_START+FIQ_STACK_SIZE-4)
#define ABT_STACK_TOP (ABT_STACK_START+ABT_STACK_SIZE-4)
#define UND_STACK_TOP (UND_STACK_START+UND_STACK_SIZE-4)
<ARMv7AR.h내용>
#define ARM_MODE_BIT_USR 0x10
#define ARM_MODE_BIT_FIQ 0x11
#define ARM_MODE_BIT_IRQ 0x12
#define ARM_MODE_BIT_SVC 0x13
#define ARM_MODE_BIT_ABT 0x17
#define ARM_MODE_BIT_UND 0x1B
#define ARM_MODE_BIT_SYS 0x1F
#define ARM_MODE_BIT_MON 0x16
MemoryMap.h와 ARMv7AR.h는 C언어 헤더 파일이다. GCC로 컴파일 할 경우 asm 파일에서도 사용할 수 있다. Entry.S에서 사용하기 위해 헤더를 추가해 준다.
#include "ARMv7AR.h"
#include "MemoryMap.h"
.text
.code 32
.global vector_start
.global vector_end
vector_start:
LDR PC, reset_handler_addr
LDR PC, undef_handler_addr
LDR PC, svc_handler_addr
LDR PC, pftch_abt_handler_addr
LDR PC, data_abt_handler_addr
B .
LDR PC, irq_handler_addr
LDR PC, fiq_handler_addr
reset_handler_addr: .word reset_handler
undef_handler_addr: .word dummy_handler
svc_handler_addr: .word dummy_handler
pftch_abt_handler_addr: .word dummy_handler
data_abt_handler_addr: .word dummy_handler
irq_handler_addr: .word dummy_handler
fiq_handler_addr: .word dummy_handler
vector_end:
reset_handler:
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_SVC
MSR CPSR, R1
LDR SP, =SVC_STACK_TOP
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_IRQ
MSR CPSR, R1
LDR SP, =IRQ_STACK_TOP
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_FIQ
MSR CPSR, R1
LDR SP, =FIQ_STACK_TOP
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_ABT
MSR CPSR, R1
LDR SP, =ABT_STACK_TOP
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_UND
MSR CPSR, R1
LDR SP, =UND_STACK_TOP
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_SYS
MSR CPSR, R1
LDR SP, =USRSYS_STACK_TOP
dummy_handler:
B .
.end
위의 코드는 .h 파일 두개를 추가 후 SP 설정 및 초기화를 위해 reset_handler 부분을 수정한 것이다. 모든 동작 모드를 한 번씩 순회하면서 스택 꼭대기 메모리 주소를 SP에 설정하는 코드이다. SP에 해당모드의 시작 주소를 넣는 것이 아닌 TOP의 주소를 넣는 것은 스택이 높은 주소에서 낮은 주소로 자라는 특징을 가지고 있기 때문이다.( 하지만 이것은 설계자 마음이다. 꼭 Stack이 위에서 아래로 heap은 밑에서 위로 자라는 방향은 아니다.)
<Makefile 내용>
ARCH = armv7-a
MCPU = cortex-a8
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OC = arm-none-eabi-objcopy
LINK_SCRIPT = ./navilos.ld
ASM_SRCS = $(wildcard boot/*.S)
ASM_OBJS = $(patsubst boot/%.S, build/%.o, $(ASM_SRCS))
INC_DIRS = include
navilos = build/navilos.axf
navilos_bin = build/navilos.bin
.PHONY: all clean run debug gdb
all: $(navilos)
clean:
@rm -fr build
run: $(nvailos)
@qemu-system-arm -M realview-pb-a8 -kernel $(navilos)
debug: $(nvailos)
@qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4
gdb:
arm-none-eabi-gdb
$(navilos): $(ASM_OBJS) $(LINK_SCRIPT)
$(LD) -n -T $(LINK_SCRIPT) -o $(navilos) $(ASM_OBJS)
$(OC) -O binary $(navilos) $(navilos_bin)
build/%.o: boot/%.S
mkdir -p $(shell dirname $@)
$(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
ASM 컴파일러를 산용해서 컴파일 할 경우 전처리기가 없기 때문에 .h파일을 읽어올 수 없다. 따라서, 컴파일러를 gcc로 변경한다. 추가적으로 .h 파일들이 존재하는 include 디렉토리도 추가한다. 위의 makefile 내용으로 수정한다.
make all을 수행한 결과이다. 이후 QEMU <-> gdb를 이용해 sp의 메모리 위치를 확인해 보자.
34번째 라인까지 실행되고 첫번째 SVC 동작 모드 스택이 설정된다. SVC 모드 스택은 0x00300000 ~ 0x003FFFFF까지 메모리 주소 영역이다. 스택과 스택 경계에 4B를 비워두도록 크기를 설정 했으므로 0x003FFFFC여야 한다. 위 그림의 info register 결과에서 볼수 있듯이 sp의 값이 해당 값을 가리키고 있음을 알수 있다.
CPSR을 보면 마지막 바이트가 0xd3이다. 이것은 11010011(2)이며, 마지막 하위 5비트만 보면 10011이다. 이것은 16진수로 0x13을 나타낸다. SVC 동작 모드를 설정하는 값이다.
ARM의 CPSR 레지스터의 bit 필드를 나타낸다. 하위 5bit는 해당 모드의 동작 상태를 나타내며 0b10011은 SVC를 나타낸다. 이렇게 모든 모드들에 대해서 sp를 정해 준다. 실제 보드에서는 sp뿐만이 아닌 하드웨어 시스템 클럭 설정, 메모리 컨트롤러 초기화 같은 일들도 필요하다. 이것은 추후에 Cortex-M3를 공부하며 진행하도록 하겠다.
<Entry.S 부분>
...
MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_SYS
MSR CPSR, R1
LDR SP, =USRSYS_STACK_TOP
BL main
BL main을 추가해준다. 이것을 통해 asm의 동작이 끝난후 C언어의 main문으로 함수가 점프하게 된다.
build/Mainc.C를 생성 후 아래 내용 입력
#include "stdint.h"
void main(void)
{
uint32_t* dummyAddr = (uint32_t*)(1024*1024*100);
*dummyAddr = sizeof(long);
}
BL 브랜치 명령으로 점프 하려면 점프 대상 레이블이 같은 파일 안에 있어야 한다. 다른 파일에 있다면 링커가 링킹할 수 있도록 레이블을 .global로 선언해야 한다. 컴파일러는 C언어 함수 이름을 링커가 자동으로 접근할 수 있는 전역 심벌로 만든다. 전역 심볼은 어셈블리로 사용할떄 .global 지시어로 선언하고 C언어로 할때는 extern으로 사용한다. 반대로 어셈블리에서 .global로 선언한 이름은 c언어에서 함수 호출로 진입할 수 있다.
새롭게 Make 파일을 수정해 준다.
ARCH = armv7-a
MCPU = cortex-a8
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OC = arm-none-eabi-objcopy
LINKER_SCRIPT = ./navilos.ld
MAP_FILE = build/navilos.map
ASM_SRCS = $(wildcard boot/*.S)
ASM_OBJS = $(patsubst boot/%.S, build/%.os, $(ASM_SRCS))
C_SRCS = $(wildcard boot/*.c)
C_OBJS = $(patsubst boot/%.c, build/%.o, $(C_SRCS))
INC_DIRS = -I include
navilos = build/navilos.axf
navilos_bin = build/navilos.bin
.PHONY: all clean run debug gdb
all: $(navilos)
clean:
@rm -fr build
run: $(navilos)
qemu-system-arm -M realview-pb-a8 -kernel $(navilos)
debug: $(navilos)
qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4
gdb:
arm-none-eabi-gdb
$(navilos): $(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Map=$(MAP_FILE)
$(OC) -O binary $(navilos) $(navilos_bin)
build/%.os: $(ASM_SRCS)
mkdir -p $(shell dirname $@)
$(CC) -march=$(ARCH) -mcpu=$(MCPU) $(INC_DIRS) -c -g -o $@ $<
build/%.o: $(C_SRCS)
mkdir -p $(shell dirname $@)
$(CC) -march=$(ARCH) -mcpu=$(MCPU) $(INC_DIRS) -c -g -o $@ $<
make all 수행후 gdb를 통해 실행파일을 run한 후 정지 시키자. 그럼 펌웨어가 main() 함수를 실행하고 나면 다시 reset handler로 돌아가서 무한루프는 도는 것을 확인할 수 있다. 그래서 '컨트롤+c' 명령을 입력하기 전까진 종료되지 않는다. 이후 x/8wx 메모리 주소 명령을 입력한다. 이 명령은 입력된 메모리 주소부터 8개를 4바이트씩 16진수로 값을 출력하라는 명령이다. 그래서 0x6400000 메모리 주소의 값을 4바이트씩 8개 출력을한다. 출력 값은 4가 나온다.
'Operating System' 카테고리의 다른 글
[파일시스템] FAT16 파일시스템 (1/3) (0) | 2020.05.26 |
---|---|
[파일시스템] 파티션 (1) | 2020.05.26 |
[파일시스템] 파일시스템의 이해 (0) | 2020.05.24 |
[임베디드OS개발프로젝트] 5장. UART (0) | 2020.01.20 |
[임베디드OS개발프로젝트] 3장. 일단 시작하기 (0) | 2020.01.11 |