Table of Contents

SCV/OS 를 실제로 개발하면서 느낀 점을 일기형식으로 적은 곳이다.

2007. 5. 15

SCV/OS 의 함수 명세를 확정했다.

void SCV_Main()
void SCV_Task_Init()

이런 식이다. 모든 함수의 이름에는 SCV 가 대문자로 명기되고, 뒤에는 맨 앞의 글자만 대문자로 명기한다.

2007. 5. 18

오늘은 하루 종일 SCV/OS 에만 매달렸다.
그 결과, 명령어를 사용할 수 있는 shell 을 만들었고, 히스토리 기능을 추가했다. X5 부트로더 코드를 참조했다.
자동완성 기능을 추가하려고, readline 이라는 라이브러리를 사용하기로 했다. gdb 에서 역시나(?) 사용하고 있었다. readline 디렉토리가 바로 그것이다. 일단 사용법을 알기 위해서,

#cd gdb/readline
#configure
#make     // x86 용 gcc 로 컴파일

컴파일 후에 .a 형태의 라이브러리가 생성되었다. 테스트 프로그램을 컴파일하기 위해 아래와 같이 수행했다.

#cd examples
#make

컴파일 시에 앞에서 만든 라이브러리를 참조한다. 내가 원하는 명령어 자동완성 기능은 fileman.c 파일에 구현되어 있다.
이 파일을 컴파일해서 어떻게 해서든 현재의 scv/os 코드에 집어 넣으려고 삽질을 했었다.
내가 했던 방법들은,

  1. 크로스 컴파일러로 라이브러리를 만들고, fileman.c 파일과 라이브러리를 scv/os 코드로 가져와서 컴파일 시에 함께 되도록 시도.
  2. fileman.c 파일만 가져와서, 필요한 루틴들만 기존의 코드에서 가져오는 방법

들이다.
먼저 첫 번째 방법은 수정(make 파일 수정)을 통해서 컴파일은 해결했고, 실행(exec) 파일을 만들 때, termcap 라이브러리를 필요로 했다. 내가 사용하는 크로스컴파일러에는 포함되어 있지 않기 때문에, termcap 소스코드를 받아서 컴파일한 뒤에 생성된 라이브러리 파일을 크로스 컴파일러의 lib 디렉토리에 복사했다.
그랬더니, 크로스 컴파일러로 컴파일하고, 또 실행파일까지 만드는 데 성공했다.
하지만, 기존의 scv/os 파일과 filman 파일을 같이 링킹하는 과정에서 세크먼트 폴트 에러가 발생했다. 정확한 지점을 알 수가 없어서 어쩔 수 없이 두 번째 방법을 선택했다.
fileman 파일에 소스 코드를 추가하던 중에, malloc() 함수를 사용하는 것을 알았다. 결국 나중에 malloc() 함수가 구현되면 그 때 시도하기로 했다.

2008. 5. 16

거의 딱 1 년 만에 scv/os 를 만지는 것 같다. 한 것 없이 1 년이 지나가 버렸다. 그동안 계속 미루던 것들을 논문 작성을 앞두고 부랴부랴 시도하기로 했다.

  1. 디렉토리 구조 변경
  2. 그동안의 버그와 추가된 사항 업데이트
  3. 컴파일 환경과 프로그램 구조의 명확화 등

위의 사항들을 구현하기 위해, 기존의 scv/os 의 구조를 거의 버리고 새롭게 시작했다. 먼저 유연한(configurable) 컴파일 환경을 구축해야 했기 때문에 예전과 마찬가지로 busybox-1.00-pre1.tar.gz 을 가지고 시작했다.
컴파일 환경을 구축하면서 겪은 문제는 다음과 같다.

어셈 파일(.S) 이 컴파일되지 않는 문제

부트로더 파일과 커널 초기화 파일을 컴파일하려고 디렉토리에 소스 파일을 추가하고 Makefile.in 을 추가했다. 하지만, .c 파일은 컴파일 되는 반면, .S 파일은 컴파일 되지 않았다.
한참의 삽질 끝에 원인을 알아냈다.

...
BOOTLOADER_SRC:=start_boot.S
BOOTLOADER_OBJ:= $(patsubst %.S,$(BOOTLOADER_DIR)%.o, $(BOOTLOADER_SRC))     // patsubst 가 포함된 문장의 경우, .S 확장자를 .o 로 바꾸는 역할을 한다
...

위와 같이 수정하고 나서, 비로소 무사히 컴파일 되었다.

ld 를 이용한 링킹과정에서 발생하는 오류 메세지 문제

이 문제는 정말로 주의해야 할 문제이다. 거의 하루 왠종일 삽질을 한 것 같다.
각각의 파일을 컴파일하고, 마지막 단계에서 ld 를 이용하여 ELF 파일을 생성하는 과정에서 이름 모를 오류 메세지가 출력되었다.

#arm-none-eabi-ld -p -X -Ttext 0x00000000 -N -T ./bootloader/start-ld-script -o ./scvos.elf ./bootloader/start_boot.o ./kernel.bin.o  // 모두 절대경로 사용
./bootloader/start_boot.o: In function `reset':
(.text+0x0): multiple definition of `reset'
bootloader/start_boot.o:(.text+0x0): first defined here
./kernel.bin.o: In function `_binary___kernel_bin_start':
(.data+0x0): multiple definition of `_binary___kernel_bin_start'
kernel.bin.o:(.data+0x0): first defined here
./kernel.bin.o: In function `_binary___kernel_bin_end':
(.data+0xfc): multiple definition of `_binary___kernel_bin_end'
kernel.bin.o:(.data+0xfc): first defined here
make: *** [scvos] Error 1

이 문제의 결론 부터 얘기하면, 상대경로와 절대경로의 문제이다. 위의 주석에도 언급했지만, 모든 컴파일이나 링킹 하는 과정에서 상대 경로 또는 절대 경로로 수행해야 한다. 만일 상대 경로와 절대 경로를 같이 사용하게 되면, 위와 같은 문제가 발생하게 된다.
또한 위에서 사용하는 링크 스크립트 파일 역시, 같이 사용해야 한다.

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(reset)
SECTIONS
{
        . = 0x00000000;
 
        . = ALIGN(4);
        .text : {./bootloader/start_boot.o(.text) *(.text)}     // 모두 절대 경로 사용
 
        . = ALIGN(4);
        .rodata : {*(.rodata)}
 
        . = ALIGN(4);
        .data : {*(EXCLUDE_FILE(./kernel.bin.o).data)}   // 모두 절대 경로 사용
 
        . = ALIGN(4);
        __bss_start = .;
        .bss : {*(.bss)}
        _end = .;
 
        . = ALIGN(4);
        _os_start = . ;
        .cuteOS : {./kernel.bin.o}           // 모두 절대 경로 사용
        . = ALIGN(4);
        _os_end = . ;
}

위에서는 모두 절대 경로를 사용했다. 만일 2 개는 절대 경로, 1 개는 상대 경로를 사용한다면, 앞에서 발생한 에러가 발생한다.
또한 소스 코드를 컴파일 할 때, 지정한 경로 방식과 다를 때 역시, 같은 에러가 발생한다. 나의 경우, 모두 절대 경로 방법을 사용함으로서 문제를 해결했다.

2008. 5. 17

어셈 파일에서 .h 파일을 include 할 때

임베디드 프로그램과 일반 애플리케이션 프로그램과 다른 점을 꼽으라면, 'volatile' 이라고 말하고 싶다.
volatile 의 경우, 헤더 파일에서 특정 레지스터 주소를 아래와 같이 define 해놓고 c 코드에서 r/w 하는 데 자주 사용된다.

#define MEMORY_CFG_BASE	(*(volatile unsigned *)0x48000000)

하지만, .c 코드에서는 위와 같은 방법이 정상적으로 컴파일되지만 아래와 같이 어셈 파일(.S) 에서는 컴파일 오류가 발생한다.

ldr	r0, =MEMORY_CFG_BASE

volatile 은 C 언어에서만 지정된 예약어(?)이기 때문에 어셈블리 파일(.S) 에서는 당연히 이해할 수 없다. 그러므로 어셈블리 파일에서 사용할 define 값은 아래 처럼 주소만 적어준다.

#define MEMORY_CFG_BASE	0x48000000

2008. 5. 18

나눗셈 연산 루틴 사용시 링킹 에러

다음과 같이 나눗셈 연산이 들어간 코드가 있다고 하자. 파일명이 serial.c 라고 한다.

UBRDIV0 = ((40000000 / (BaudRate * 16)) - 1);

그리고 링킹 시에 에러가 발생했다.

arm-linux-ld -p -X -Bstatic -Ttext 0x30000000 -N -o ./kernel.elf ./hal/start_kernel.o ./kernel/kernel_main.o ./kernel/shell.o ./error/error.o ./dev/serial/serial.o -Igcc
./dev/serial/serial.o(.text+0x60): In function `Serial_Init':
dev/serial/serial.c:27: undefined reference to `__udivsi3'
make: *** [scvos] Error 1

c 코드에서 나온 '/'(나누기 연산)을 컴파일러는 '_udivsi3' 함수를 호출한다. 처음의 c 코드를 컴파일하여 디스어셈블링 해보면, 알 수 있다.

#arm-none-eabi-objdump -S serial.o
...
     UBRDIV0 = ((40000000 / (BaudRate * 16)) - 1);
  5c:   e3a04205        mov     r4, #1342177280 ; 0x50000000
  60:   e2844028        add     r4, r4, #40     ; 0x28
  64:   e51b3014        ldr     r3, [fp, #-20]
  68:   e1a03203        lsl     r3, r3, #4
  6c:   e3a00626        mov     r0, #39845888   ; 0x2600000
  70:   e2800b96        add     r0, r0, #153600 ; 0x25800
  74:   e2800c02        add     r0, r0, #512    ; 0x200
  78:   e1a01003        mov     r1, r3
  7c:   ebfffffe        bl      0 <__aeabi_uidiv>      // 바로 이 부분이다
  80:   e1a03000        mov     r3, r0
  84:   e2433001        sub     r3, r3, #1      ; 0x1
  88:   e5843000        str     r3, [r4]
...

사칙 연산 중에 오로지 나눗셈만 에러가 발생한다. 나는 처음에 컴파일러에서 나눗셈 연산과 관련한 라이브러리가 빠져있다고 생각했다.
그래서 여러가지 다른 툴체인들을 가지고 실험을 해봤다. 하지만 결국 원인은 간단한 곳에 있었다.
나눗셈 관련 라이브러리는 gcc 가 가지고 있다. 그러므로 툴체인이 생성될 때 만들어진다. 단지 컴파일 시에 위치를 지정해주지 않았기 때문에 에러가 발생하는 것이었다.

arm-none-eabi-ld -p -X -Bstatic -Ttext 0x30000000 -N -o ./kernel.elf ./hal/start_kernel.o ./kernel/kernel_main.o ./kernel/shell.o ./error/error.o ./dev/serial/serial.o -L/usr/local/arm-2007q3/lib/gcc/arm-none-eabi/4.2.1 -lgcc

libgcc.a 라이브러리가 있는 경로를 컴파일 시에 '-L/usr/local/arm-2007q3/lib/gcc/arm-none-eabi/4.2.1' 추가해 주어야만 에러를 해결 할 수 있다.

printf 선언하기

애플리케이션의 경우에는 컴파일에 내장되어 있는 printf 같은 함수들을 그냥 호출해서 사용하면 된다. 하지만, 커널 개발에 있어서는 이러한 도움을 받을 수 없거나 용량의 문제 때문에 따로 필요한 부분만 선언하여 사용한다.

int printf(void)
{
...
}

일반적으로 위와 같이 선언하고 컴파일하면, 경고가 발생한다.

language/c/printf.c:5: warning: conflicting types for built-in function 'printf'

에러의 뜻은 내장함수와 충돌이 난다는 것이다. 충돌을 방지하기 위해서 내장함수를 사용하지 않도록 해야 한다. 컴파일 옵션을 사용해서 간단하게 처리할 수 있다.

arm-none-eabi-gcc -g -Wall -fno-builtin    -Ihal/arm9 -Ierror -c -o language/c/printf.o language/c/printf.c   // -fno-builtin  옵션을 추가한다

-fno-builtin 옵션 설명

이 옵션을 지정하면 GCC 는 접두사로 '_builtin_' 으로 시작하지 않는 내장 함수를 인식하지 않는다.
GCC 로 컴파일한 C++ 응용 프로그램의 경우에는 항상 이러한 규칙이 적용된다. 직접 GCC 의 내부 함수를 호출하려면 '_builtin_' 접두사가 붙은 함수를 호출해야 한다.
보통 GCC 는 내장 함수를 좀 더 효율적으로 다루는 특별한 코드를 생성한다.
몇몇 경우에 내장 함수는 인라인 코드로 바뀌어서, 디버깅할 때 브레이크 포인트를 잡거나 똑같은 함수를 제공하는 외부 라이브러리와 링킹할 때 어려운 경우가 있다.
C, Objective C 응용 프로그램에서 덜 제한적인 -fno-builtin-function 옵션을 사용해서 특정 내장 함수를 선택적으로 사용하지 않을 수 있다.
이 옵션은 그러한 내장 함수가 없다면 무시된다. 비슷하게 -fno-builtin 옵션을 사용해서 모든 함수를 사용하지 않고, 다음 예처럼 함수 호출을 특정 내장 함수로 선택할 수 있다.

#define strcpy(d,s)     __builtin_strcpy((d), (s))

2008. 5. 19

지금 현재까지로는 최대한 최신 버전의 툴체인을 사용하려고 했었다. 하지만, 최신의 툴체인은 구하기도 어려울 뿐더러 직접 만들기도 어렵다.
또한 구하더라도, scvos 개발 과정에서 툴체인으로 인한 오류나 경고 메세지가 발생했다. 이러한 문제들은 한단계 예전 버전의 툴체인을 사용하면 자연스럽게 사라졌다.
데탑이 아닌 임베디드 개발에서 최신 툴체인을 사용하지 않는 이유는 어느 정도의 안정성이 요구되기 때문일 것이다.
오늘을 시점으로 사용하고 있는 툴체인은 http://www.codesourcery.com/gnu_toolchains/arm/download.html 에서 받은 'arm-2007q1-21-arm-none-eabi-i686-pc-linux-gnu.tar.bz2' 이다.

2008. 5. 20

다음의 코드는 에러일까? 아닐까?

#define RDURXH0()   (*(volatile unsigned char *)0x50000024)

언뜻 보면, 에러 같지만 아니다. 'volatile unsigned char' 인 것도 좀 이상하다.
위의 구문을 이해하기 위해서는 레지스터 주소에 대한 특징을 알아야 한다. s3c2410 데이터 시트를 찾아보면, 다음과 같이 설명하고 있다.

There are three UART receive buffer registers, URXH0, URXH1, and URXH2, in the UART block. URXHn has an 8-bit data for received data.

2008. 5. 28

현재 이 시점에서 가장 문제가 되는 것은 크게 2 가지다.

  1. printf 와 관련한 인터럽트에서의 출력 여부
  2. 실험의 방향을 어떻게 잡을 것인가

지금으로서 나의 생각은 다음과 같다.

printf 와 관련한 인터럽트에서의 출력 여부

기본적인 타이머 인터럽트의 경우, 거의 대부분 serial 레지스터를 직접 액세스하는 루틴에 걸리기 때문에, 로그 리스트에서 선택 시에 타겟이 멈춰버린다.
그래서 생각한 것이, printf 루틴이 실행되는 함수를 2 가지(커널과 애플리케이션)로 만들어서 replay 하는 방법이다.
이 방법의 경우, 기존과 같이 선택하는 순간 타겟이 멈추는 문제는 발생하지 않는다.
하지만, 출력문이 제대로 출력되지 않거나, data abort exception 이 발생한다.

2008. 6. 12

디버깅 명령어 중에서 백트레이스 명령어는 구현 불가..
이유는 일반 프로그램의 경우 유저 스택을 사용하지만, 실제 명령어가 실행되는 모든 svc 스택을 사용하기 때문이다.

2008. 6. 18

계속 아직 해결하지 못한 문제들에 대해서 생각하고 있다. 논문 1 차 초안을 쓰면서 정말 말이 안되는 엉터리 라는 생각이 든다.
2년 동안 했던 게 아무것도 없다는 생각이 든다. 가장 큰 문제는 하드웨어 인터럽트의 처리 문제다.