메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

설계와 구현을 통한 임베디드 OS의 이해와 응용(2) - 개발환경 구성 및 부트로더 작성(2)

한빛미디어

|

2005-12-08

|

by HANBIT

16,904

저자: 서민우
출처: 임베디드월드

* 설계와 구현을 통한 임베디드 OS의 이해와 응용(1) - Overview
* 설계와 구현을 통한 임베디드 OS의 이해와 응용(2) - 개발환경 구성 및 부트로더 작성(1)


다음으로 led.S 파일의 내용은 다음과 같다.

led.S
#define rGPFCON 0x56000050
#define rGPFDAT 0x56000054
#define rGPFUP  0x56000058

#define dGPFCON 0x000055aa

.globl led_init
led_init:
    ldr r0,=rGPFCON
    ldr r1,=dGPFCON
    str r1,[r0]

    ldr r0,=rGPFUP
    mov r1,#0x000000ff
    str r1,[r0]

    mov pc,lr

.globl led_off
led_off:
    ldr r0,=rGPFDAT
    ldr r1,[r0]
    orr r1,r1,#0x000000f0
    str r1,[r0]

    b   delay

.globl led_on
led_on:
    ldr r1,=rGPFDAT
    ldr r2,[r1]
    bic r2,r2,r0
    str r2,[r1]

delay:
    mov r0,#0x8000
1:
    subs r0,r0,#1
    bne 1b

    mov pc,lr

led.S 파일에는 세 가지 함수가 있다. led_init, led_off, led_on의 세 함수가 그것들이며, 각각 led를 초기화하고, 끄고, 켜는 역할을 한다.

그럼 led.S 파일의 내용을 자세히 들여다보자.

led_init:
    ldr r0,=rGPFCON
    ldr r1,=dGPFCON
    str r1,[r0]

    ldr r0,=rGPFUP
    mov r1,#0x000000ff
    str r1,[r0]

    mov pc,lr

이 부분은 led를 초기화 하는 함수이다. 필자가 사용하는 보드에는 led가 GPF(F port)의 4,5,6,7 번 비트에 물려있다. GPF는 총 8 개의 외부 핀으로 연결되며, 각각의 핀을 input, output 또는 외부 인터럽트를 받을 수 있는 용도 중 하나로 사용할 수 있다. 이 중 필자가 사용하는 보드에서는 GPF의 4,5,6,7 번 비트에 led를 연결하고 output 용으로 사용한다. GPF를 제어하기 위해서는 GPFCON, GPFDAT, GPFUP의 세 레지스터를 사용한다. 각각의 레지스터는 다음에서 정의하는 것처럼 0x56000050, 0x56000054, 0x56000058 번지에 있다.

#define rGPFCON 0x56000050
#define rGPFDAT 0x56000054
#define rGPFUP 0x56000058

위의 코드에서는 GPFCON 레지스터를 이용하여 GPF의 4,5,6,7 번 비트를 output 용으로 설정하고, GPFUP 레지스터를 이용하여 풀업 저항을 비활성화 시키고 있다. 풀업 저항은 GPIO 핀을 input 용으로 사용할 경우, input 값이 불안정하게 들어오는 것을 막는 역할을 한다. 즉, input 값이 0 과 1 사이의 어중간한 값으로 들어올 때 0 또는 1로 고정시켜 주는 역할을 한다. 따라서 GPIO 핀을 output 용으로 사용할 경우에는 풀업 저항을 사용할 필요가 없다. 그래서 여기서는 풀업 저항을 비활성화 시키고 있다.

led_off 함수는 GPFDAT 레지스터의 4,5,6,7 번 비트에 이진수 1111을 써 줌으로써 led를 끄는 역할을 한다.

led_on 함수는 r0 레지스터로 넘어온 값에 해당하는 led를 켜 주는 역할을 한다. 즉, r0의 값이 0x00000010일 경우 GPF의 4 번 비트에 해당하는 led를 켜주고, r0의 값이 0x00000020일 경우 GPF의 5 번 비트에 해당하는 led를 켜 주는 역할을 한다. delay 루틴은 led가 꺼져있거나 켜져 있는 시간을 일정하게 유지시켜 주는 역할을 한다.

마지막으로 Makefile의 내용을 들여다보자.

Makefile
cute-boot: start.o  led.o
    arm-linux-ld  start.o  led.o  -o  cute-boot  -Ttext  0x00000000
    arm-linux-objcopy  cute-boot  cute-boot.bin  -O  binary

start.o: start.S
    arm-linux-gcc  start.S  -c

led.o: led.S
    arm-linux-gcc  led.S  -c

clean:
    rm  -f  *.o
    rm  -f  cute-boot.elf
    rm  -f  cute-boot.bin

먼저 Makefile의 작성 요령을 다음 예를 통해서 간단하게 알아보자.

start.o: start.S
    arm-linux-gcc  start.S  –c

여기서 mmlt;start.o: start.Smmgt;와 같이 콜론 ”:”을 포함하고 있는 부분을 dependency line이라 한다. 콜론을 중심으로 왼쪽 부분을 타겟이라 하고, 오른쪽 부분을 타겟을 만들기 위한 소스라고 한다. dependency line은 다음과 같은 의미를 갖는다.

target 부분은 source에 의존한다.

즉, 위에서 start.o는 start.S에 의존한다. make 명령어를 이용하여 Makefile의 내용을 수행할 경우 make 명령어는 start.o 파일이 바뀐 시간을 start.S 파일이 바뀐 시간과 비교한다. 만약 start.S 파일이 start.o 파일보다 더 최근에 바뀌었다면, make는 mmlt;arm-linux-gcc start.S –cmmgt; 부분을 수행한다. 즉, start.o 파일을 start.S 파일의 내용에 맞게 새롭게 컴파일을 수행한다. 주의할 점은 mmlt;arm-linux-gcc start.S –cmmgt;와 같은 명령어 부분은 반드시 탭키로 한 칸 띄어 주어야 한다. 즉, 다음과 같이 명령어 부분을 작성해야 한다.

mmlt;탭mmgt;arm-linux-gcc start.S –c

탭이 아닌 스페이스 키 등으로 띄어 줄 땐 에러가 발생하니 주의하기 바란다.

위의 Makefile을 이용하여 make 명령어를 수행할 경우 다음과 같이 나타난다.

$ make
arm-linux-gcc start.S -c
arm-linux-gcc led.S -c
arm-linux-ld start.o led.o -o cute-boot -Ttext 0x00000000
arm-linux-objcopy cute-boot cute-boot.bin -O binary

여기서는 arm-linux-gcc를 이용하여 start.S와 led.S 파일을 각각 start.o와 led.o 파일로 만든다.

다음으로 arm-linux-ld를 이용하여 이 두개의 목적 파일로 cute-boot 결과 파일을 만든다. arm-linux-ld 명령어의 –o 옵션은 outupt의 약자이다. 결과 파일의 맨 앞 부분이 놓일 메모리 위치는 0x00000000 번지가 된다. -Ttext 옵션은 “text” 세그먼트의 시작위치를 링커에게 알려주는 옵션이다. cute-boot 파일은 elf 포맷의 파일이며, 다음과 같이 file이란 명령어를 이용하여 cute-boot 파일의 형식을 알 수 있다.

$ file cute-boot
cute-boot: ELF 32-bit LSB executable, ARM, version 1 (ARM), statically linked, not stripped

여기서 cute-boot 파일이 elf 포맷의 32-bit ARM 용 실행 파일임을 알 수 있다.

마지막으로 arm-linux-objcopy 명령어는 파일의 포맷을 바꾸는 역할을 하며, 여기서는 elf 포맷의 cute-boot 파일을 순수 바이너리 이미지로 바꾸는 역할을 한다. 왜냐하면, 우리가 작성한 프로그램을 타겟 보드상에서 수행하기 위해서는 elf 포맷의 파일을 순수 바이너리 이미지 파일로 만들어야 한다. 즉, arm-linux-objcopy 명령어는 입력파일로 cute-boot를 받아 출력 파일로 cute-boot.bin 을 만들어 내는데 그 포맷은 mmlt;-O binarymmgt; 옵션에 의해 순수 바이너리 이미지 파일이 된다.

참고로 다음과 같이 명령어를 수행해 보자.

$ make clean
rm -f *.o
rm -f cute-boot
rm -f cute-boot.bin

이렇게 명령어를 사용할 경우 make는 Makefile에서 clean에 해당하는 target을 찾는다. 여기서는 *.o , cute-boot, cute-boot.bin 등 make를 수행하는 과정에서 생성된 파일을 모두 제거하는 역할을 한다.

이상에서 Makefile의 내용을 알아보았다.

최종적으로 생성된 cute-boot.bin 이미지 파일을 JTAG를 이용하여 보드상에 다운로드 받아 실행해 보고 의도된 대로 동작하는지 확인해 본다.

다음은 arm-linux-nm 명령어와 arm-linux-objdump 명령어를 이용하여 cute-boot 파일의 내용을 좀 더 자세히 살펴보자. 이러한 명령어들은 실제 개발하는 과정에서 디버깅용으로 유용하게 사용할 수 있는 명령어들이며, 이 후 기사에서도 종종 사용할 것이다.

먼저 arm-linux-nm 명령어는 실행 파일로부터 심벌에 대한 정보를 보여 주는 역할을 한다. 참고로 순수 바이너리 이미지에는 심벌에 대한 정보가 존재하지 않는다. 따라서 여기서는 elf 포맷의 cute-boot 파일을 이용하여 심벌의 정보를 본다.

다음과 같이 arm-linux-nm 명령어를 수행해 본다.

$ arm-linux-nm cute-boot -n
00000000 T _start
00000010 t reset
00000070 T led_init
0000008c T led_off
000000a0 T led_on
000000b0 t delay
000080d0 A __bss_end__
000080d0 A __bss_start
000080d0 A __bss_start__
000080d0 D __data_start
000080d0 A __end__
000080d0 A _bss_end__
000080d0 A _edata
000080d0 A _end

여기서 우리는 _start가 0x00000000 번지에 놓여 있는걸 알 수 있다. 또한 reset이 0x00000010 번지에 놓여 있는걸 볼 수 있다. 참고로 __bss_end__, __bss_start, __bss_start__, __data_start, __end__, _bss_end__, _edata, _end 등의 심벌들은 ld가 자체적으로 정의해서 사용하는 심벌들이다. 궁금하면 다음과 같이 명령어를 수행해 보고 나온 결과를 자세히 살펴 보기 바란다.

$ arm-linux-ld --verbose

다음은 arm-linux-objdump 명령어에 –D 옵션을 이용하여 cute-boot 파일을 역어셈블해 보기로 하자. 다음과 같이 명령어를 수행한다.

$ arm-linux-objdump  cute-boot  -D

cute-boot:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:   ea000002        b       10 
   4:   deadbeef        cdple   14, 10, cr11, cr13, cr15, {7}
   8:   deadbeef        cdple   14, 10, cr11, cr13, cr15, {7}
   c:   deadbeef        cdple   14, 10, cr11, cr13, cr15, {7}
00000010 :
  10:   e10f0000        mrs     r0, CPSR
  14:   e3c0001f        bic     r0, r0, #31     ; 0x1f
  18:   e38000d3        orr     r0, r0, #211    ; 0xd3
  …

그러면 역어셈블한 내용을 좀 더 자세히 들여다보자.

cute-boot: file format elf32-littlearm

이 부분에서 우리는 cute-boot 파일이 little endian 방식의 ARM 용 32 비트 elf 포맷이라는 정보를 얻을 수 있다.

Disassembly of section .text:

이 부분은 .text 섹션에 대한 역어셈블을 의미하며 아래에 실제 역어셈블한 내용이 나온다. .text 섹션은 일반적으로 함수 부분이 컴파일 되어 위치하게 되는 부분이다.

00000000 <_start>:

이 부분은 0x00000000 번지에 _start가 위치한다는 의미이다.

0: ea000002 b 10

역어셈블한 결과는 세 부분으로 나뉜다. 먼저 맨 왼쪽에 주소부분, 중간에 기계어, 맨 오른쪽에 기계어에 대응하는 어셈블리어로 나타난다. 이 부분은 0 번지에 ea000002 이라는 기계어가 있으며, 이 기계어를 역어셈블한 결과가 다음과 같다는 의미이다.

b       10 
 
   4:   deadbeef        cdple   14, 10, cr11, cr13, cr15, {7}
   8:   deadbeef        cdple   14, 10, cr11, cr13, cr15, {7}
c:   deadbeef        cdple   14, 10, cr11, cr13, cr15, {7}

이 부분은 다음 부분을 컴파일하고 링크하여 나온 결과이다.

.balignl 16,0xdeadbeef

앞에서 이 부분에 대한 설명을 하였으니 다시 한 번 확인하기 바란다.

00000010 :

이와 같이 0x00000010 번지에 reset이 위치하는 점도 참고하기 바란다.

이상에서 개발 환경을 구성하는 방법과, 부트로더의 앞 부분을 작성해 보았다. 다음 번 기사에서는 cache와 MMU 설정을 포함하여 부트로더의 나머지 부분을 완성해 보기로 하겠다.

[참고 자료]

http://sourceforge.net/projects/u-boot
u-boot-1.1.2.tar.bz2

http://emlinux.co.kr/index.php" target="_blank
u-boot-1.0.0-emlinux.tgz

S3C2440A 32-BIT CMOS MICROCONTROLLER USER"S MANUAL Revision 1
TAG :
댓글 입력
자료실