embedded system faq

ohhara의 이미지

이 FAQ는 컴퓨터 프로그램을 개발해 본 경험이 있는 사람을 대상으로 임베디드
환경에 대해 가지게 되는 궁금증을 풀어주기 위해 작성되었습니다. 어떤 특정
임베디드 환경에 의존적이지 않고 일반적인 임베디드 환경을 대상으로 했습니다.

필자는 알티캐스트(Alticast)에 재직했을 당시 Sun Personal Java, Sun CVM과 같은
자바가상머신(Java Virtual Machine)을 여러 Settop Box에 이식(Porting)하는 작업을
한 경험이 있으며 (Skylife Settop Box 현재 탑재되어 판매중) 현재는
포항공과대학교 컴퓨터공학과 4학년에 재학중입니다.

------------------------------------------------------------------------------
Taeho Oh ( ohhara@postech.edu, ohhara@plus.or.kr ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
PLUS ( Postech Laboratory for Unix Security ) http://www.plus.or.kr
------------------------------------------------------------------------------

Q. 임베디드 환경을 지금까지 접해본 적이 없어서 어떤 환경인지도 전혀 감이 잡히지
않습니다. 어떤 것이라고 생각하면 되는지 궁금합니다.
A. 임베디드 환경은 아주 간단하게 설명하자면 아주 열악한 환경을 가진 PC라고
상상을 하시면 됩니다. 키보드, 마우스, 모니터, 하드디스크, 시디롬드라이브가
없고 매우 적은 양의 램(RAM)이 장착되어 있는 PC라고 생각하시면 됩니다. 요즘은
임베디드 환경이 고급화가 되면서 이런 경계도 모호해지고 있는 추세입니다.
임베디드 환경에 하드디스크가 장착되는 경우도 생겨나고 있으며 키보드도 장착되는
경우가 있습니다.

Q. 임베디드 운영체제는 어떤 특성을 가지고 PC용 운영체제와 어떤 점에서 차이가
있습니까?
A. 임베디드 운영체제는 우리가 PC에서 사용하는 운영체제에서 꼭 필요한 기능만
지원을 하며 ANSI C Library의 일부도 임베디드 운영체제의 일부로 포함시키기도
합니다. 그리고 전통적으로 임베디드 운영체제는 실시간성을 상당히 중요시합니다.
어떤 신호를 기다리다가 몇초간 신호가 오지 않으면 다른 행동을 하도록 만들고
싶을 때 PC용 운영체제에서는 쉽지 않은 경우가 많이 있습니다. PC에서는 몇초간
신호를 기다리다가 신호가 오지 않을 때 다른 행동을 하려고 하면 scheduler의
상황에 따라 약간의 지연이 발생하는 경향이 있습니다. 하지만 임베디드 환경에서는
이런 상황을 일반적으로 엄격하게 처리합니다.

Q. 임베디드 환경에서 프로그램을 작성할 때는 어떤 프로그래밍 언어를 사용합니까?
A. 일반적으로는 C언어를 많이 사용합니다. 요즘은 환경이 고급화가 되면서 C++이나
자바(Java)를 사용하는 경우도 있지만 C를 가장 많이 사용한다고 생각하면 됩니다.
임베디드 환경은 특성상 일반 PC에 비해 CPU가 느리고 램이 적은 경향이 있습니다.
이런 환경에서는 프로그램의 속도와 크기면에서 유리한 C언어가 가장 많이
사용됩니다.

Q. 임베디드 환경에서는 어떤 운영체제(OS)를 사용합니까?
A. 아주 여러가지의 운영체제를 사용합니다. 업계에 따라 주로 많이 사용되는
운영체제가 있을 수도 있습니다. 필자가 사용해 본 운영체제는 pSOS, VxWorks,
Windows CE, Linux, Nucleus등이 있으며 실제 찾아보면 훨씬 많은 임베디드용
운영체제를 접할 수 있습니다.

Q. 임베디드 환경에서는 어떤 CPU를 사용합니까?
A. PC에서는 intel계열 CPU를 많이 사용합니다. 하지만 이것은 에너지 소모를
최소화해야 되는 임베디드 환경에서는 사용이 거의 불가능합니다. 필자가 접해본
임베디드 환경에서 사용되는 CPU는 ARM, MIPS, PPC, MINI-SPARC등이 있으며 실제
찾아보면 훨씬 많은 임베디드용 CPU를 접할 수 있습니다.

Q. 임베디드 환경에서는 어떤 컴파일러를 사용합니까?
A. Windows CE를 사용할 때는 Visual C++를 사용할 수 있고 Linux를 사용할 때는
gcc를 사용할 수 있습니다. 그리고 gcc는 현존하는 이식성(portable)이 가장 좋은
컴파일러이기 때문에 Linux를 사용하지 않는 환경에서도 사용이 가능하도록 이식이
되어 있는 경우도 있습니다. 하지만 실제 여러 임베디드 환경에서 작업을 해 보면
gcc가 사용이 가능한 경우보다 가능하지 않고 특정 회사에서 나온 특정 버전의
컴파일러를 써야 되는 경우를 종종 만나게 됩니다. 필자가 사용해 본 임베디드
환경에서 사용되는 컴파일러는 visual c++, gcc, armcc, dcc, st20cc등이 있으며
실제 찾아보면 훨씬 많은 임베디드용 컴파일러를 접할 수 있습니다.

Q. 임베디드 환경에서도 어셈블리어를 사용할 수 있습니까?
A. 사용할 수 있습니다. 일반적으로 PC에서만 개발해본 개발자들은 CPU는 intel
CPU만이 존재하고 다른 CPU는 고려하지도 않는 경우가 간혹 있습니다. 어셈블리어는
CPU에 따라 다르며 임베디드 환경에서는 환경에 따라 다른 CPU를 사용하기 때문에
어셈블리어를 임베디드 환경에서 사용하기 위해서는 임베디드 환경에 따라 다른
어셈블리어를 사용해야 됩니다. 또한 상황에 따라서는 같은 CPU라도 어셈블러가
다르면 다른 문법을 사용해서 어셈블리어를 기술해 줘야 되는 경우도 발생합니다.
이런 이유로 어셈블리어는 꼭 필요한 경우가 아니라면 임베디드 환경에서는 사용하지
않는 것이 좋습니다.

Q. 임베디드 환경에서 C언어 책에 나오는 "Hello World"를 출력하는 프로그램을
작성해서 돌려보고 싶습니다. 어떻게 해야 합니까?
A. 일반적인 C언어 책을 보면 다음과 같은 "Hello World"를 출력하는 프로그램이
나와 있습니다.

helloworld.c
----------------------------------------
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
----------------------------------------

PC의 일반적인 개발환경에서는 위와 같이 작성해서 gcc나 visual c++같은 컴파일러를
사용해 컴파일을 한 후에 실행을 시키면 아무런 무리없이 실행이 됩니다. 하지만
임베디드 환경에서는 위와같이 단순한 프로그램을 컴파일해서 실행하는 것도 많은
어려움이 따릅니다. 일단 stdio.h가 엠베디드 환경에서 존재하지 않을 가능성이
있습니다. 그리고 여기서는 프로그램의 시작이 main 함수에서 시작한다고
가정하였지만 임베디드 환경에 따라서 main 함수가 시작지점이 아닐 수도 있습니다.
이런 이유로 임베디드 환경에서 작업하기 위해서는 임베디드 환경에 의존적인 것은
모두 분리를 해야 할 필요가 있습니다. 그냥 작업하고자 하는 임베디드 환경에
의존되도록 작성하면 되지 않느냐고 생각할 수도 있는데 그렇게 하게 되면
개발과정에서 많은 어려움에 부딪히게 됩니다. 아래와 같이 프로그램을 수정합니다.

helloworld.c
----------------------------------------
extern int em_printf();
int em_init()
{
return em_printf("Hello World\n");
}
----------------------------------------

embedded.c
----------------------------------------
extern int Print(char *str);
extern int em_printf();
extern int em_init();
int root()
{
return em_init();
}
int em_printf(char *str)
{
return Print(str);
}
----------------------------------------

프로그램을 살펴보면 helloworld.c는 임베디드 환경에 의존되지 않도록 작성이
되었고 embedded.c는 임베디드 환경에 의존되도록 작성이 되었습니다. embedded.c는
각 임베디드 환경에 맞도록 이식(porting)되어야 합니다. 예제에서는 시작 함수가
root이고 print를 위해서 Print라는 function을 써야 되는 환경에서 실행시킨다는
가정을 하였습니다. 지면관계상 header파일은 사용하지 않았습니다.

Q. 작성된 프로그램은 일반적으로 어떤 과정을 통해 임베디드 환경에서 컴파일되고
실행되게 됩니까?
A.
1) PC에서 크로스컴파일이라는 과정을 통해 해당 임베디드 환경에서 실행될 수 있는
파일을 생성합니다. 컴파일을 하게 되면 컴파일을 한 환경에서 실행될 수 있는
파일이 생성되지만 크로스컴파일을 하게 되면 컴파일을 한 환경과 다른 환경에서
실행될 수 있는 파일을 생성시킬 수 있습니다.
2) 이 파일을 임베디드 환경으로 전송합니다. 전송하는 방법은 아주 여러가지가
있습니다. serial cable을 사용해서 전송할 수도 있으며 parallel cable을 사용해서
전송할 수도 있습니다. ethernet cable이나 usb cable을 사용할 수도 있습니다.
이것은 임베디드 환경에 따라 결정되게 됩니다. 일반적으로 serial cable이나
parallel cable을 이용한 전송은 ethernet cable이나 usb cable을 이용한 전송보다
매우 느립니다. 상황에 따라서는 한번 전송을 하기 위해 1시간 이상 기다려야
됩니다.
3) 전송된 파일을 임베디드 환경에 저장합니다. 일반적으로 Flash Memory에 저장을
하지만 고급 환경에서는 하드디스크에 저장을 하기도 합니다.
4) 저장된 파일을 실행합니다. 일반적으로 임베디드 장치를 reset(reboot)을 해서
실행을 합니다.
5) 실행결과를 확인합니다. 실행결과는 파일을 전송할 때와 마찬가지로 serial,
parallel, ethernet, usb cable등을 사용해서 결과를 확인합니다. 경우에 따라서는
임베디드 환경에 부착된 LCD화면을 사용하기도 합니다.

Q. XYZ라는 임베디드 환경을 가지고 있지만 개발을 위한 관련 정보가 전혀 없습니다.
어떻게 개발을 시작해야 됩니까?
A. 일단 임베디드 환경을(!) 개발하는 개발자가 아니고 임베디드 환경에서(!)
개발하는 개발자라면 관련 정보를 관련 업체에 문의를 하는게 가장 좋습니다.
임베디드 환경은 상황에 따라 매우 다르기 때문에 관련 업체의 협조가 없이는 개발이
쉽지 않습니다. 일단 가장 기본적으로 "Hello World"를 출력하는 C 소스파일이 있을
때 이것을 어떻게 해야 컴파일을 할 수가 있고 어떻게 해야 전송할 수 있으며 어떻게
해야 실행을 할 수가 있고 어떻게 해야 실행결과를 볼 수 있는지 구체적인 방법을
알려달라고 업체에 요청합니다. 테스트를 통해 관련 정보를 확인한 후에는 OS, CPU,
Compiler, Device등에 대한 문서를 요청하고 관련 내용을 살펴본 후에 개발을
시작합니다.

Q. 임베디드 환경에서 개발을 하고 있는데 코드를 한번 고치고 컴파일을 다시 하고
전송하는 과정의 시간이 너무 길어서 개발이 매우 힘듭니다. 어떻게 좋은 방법이
없습니까?
A. 임베디드 환경에 프로그램을 전송하는 시간이 일반적으로 매우 깁니다. 이로 인해
개발 일정에 지장을 받는 경우가 많이 있습니다. 이것을 단축하기 위해서 해당
임베디드 환경과 거의 동일한 에뮬레이터(Emulator)를 가지고 개발을 하고
에뮬레이터에서 아무런 문제가 없이 작동한다는 확신이 들 때 실제 임베디드
환경에서 최종 테스트를 합니다. 에뮬레이터를 개발하는데도 시간이 만만치 않게
소모되기도 하지만 에뮬레이터를 직접 개발에 사용하게 되면 이때 소모된 시간은
전혀 아깝게 느껴지지 않습니다. 임베디드 환경의 에뮬레이터는 경우에 따라서는
직접 만들 필요가 없이 다른 곳에서 만든 것을 구할 수 있는 경우도 있습니다. 예를
들어 Sony Playstation용 게임을 개발한다고 하면 일단 Sony Playstation용
에뮬레이터를 가지고 개발을 하고 최종 테스트단계에서 실제 Sony Playstation에서
테스트를 하면 개발시간을 단축시킬 수 있습니다. 에뮬레이터는 실제 임베디드
환경과 비슷하기는 하지만 100%동일하지는 않기 때문에 에뮬레이터에서 발생하지
않던 문제가 실제 임베디드 환경에서 발생하기도 합니다. 개발을 효율적으로 하기
위해 에뮬레이터와 임베디드 환경을 최대한 비슷하게 만들어 주는 것이 좋습니다.

Q. Flash Memory는 일반 DRAM과 비교해 봤을 때 어떤 차이가 있습니까?
A. 임베디드 환경에서 작업을 해 보면 Flash Memory를 자주 접하게 됩니다. 물론
Memory는 Flash Memory와 DRAM만 있는 것은 아니지만 이 두가지를 가장 자주 접하게
됩니다. Flash Memory는 DRAM보다 가격이 비싸고 읽기 속도는 DRAM에 비해 약간
느리고 쓰기 속도는 DRAM에 비해 매우 느리며 전원이 꺼져도 Memory의 내용이
지워지지 않습니다. 그리고 쓰기를 할 때는 DRAM처럼 byte단위로는 불가능하고
sector단위로 만 가능합니다. 한 sector의 크기는 Flash Memory에 따라 다르며 보통
64에서 512byte정도 됩니다. Flash Memory는 한쪽 영역을 계속 반복적으로 쓰기를
반복하기 되면 수명을 단축시킬 수 있습니다. 그래서 쓰기를 할 때는 여러 영역을
번갈아가면서 쓰기를 하는 것이 좋습니다. 요즘 판매되는 PC용 하드웨어들이나 MP3
Player를 살펴보면 펌웨어 업그레이드기능을 지원합니다. 펌웨어는 Flash Memory에
저장이 되며 펌웨어 업그레이드는 이 Flash Memory에 저장되어 있는 프로그램을
업그레이드하는 것입니다. 예전에 ROM으로 되어 있어서 칩을 바꾸지 않는 이상에는
변경이 불가능했던 부분을 요즘은 Flash Memory로 대체해서 나중에 간단히
업그레이드가 가능하도록 하는 경향이 있습니다.

Q. 프로그램 코드는 Flash Memory에 있는 상태에서 프로그램을 실행하는게 좋습니까?
아니면 DRAM에 있는 상태에서 프로그램을 실행하는게 좋습니까?
A. 둘 다 장단점이 있습니다. 해당 임베디드 환경에 적합한 결정을 하는 것이
좋습니다. DRAM이 부족하고 성능이 매우 빠르지 않아도 되고 처음에 시작을 빨리
해야 되는 경우라면 Flash Memory에 프로그램 코드를 두는 것이 좋습니다. DRAM에
프로그램 코드를 두는 경우라면 프로그램을 처음 시작할 때 Flash Memory에서
DRAM으로 프로그램 코드를 복사하게 되는데 이때 시간이 다소 걸릴 수가 있습니다.
하지만 Flash Memory에 프로그램을 저장해 둘 때 압축해서 저장을 할 수가 있기
때문에 Flash Memory를 아낄 수가 있습니다. 그리고 DRAM이 Flash Memory보다
읽기속도가 약간 빠르기 때문에 프로그램의 전체적인 실행속도 향상에도 도움을
줍니다.

Q. 임베디드 환경에서 디버깅(debugging)을 어떻게 합니까?
A. 임베디드 환경에 따라 차이가 있지만 일단 임베디드 환경에서 디버깅은 거의
불가능하다고 생각하는 것이 좋습니다. gdb와 같은 디버거를 JTAG을 이용해서 붙여서
사용할 수 있는 환경을 접하게 되는 경우도 있는데 이것은 거의 최상의 디버깅
환경입니다. 실제로는 디버거를 사용하는 것이 불가능하고 console io만 가능한
상태인 경우가 대부분이며 더 열악한 경우에는 console io도 불가능합니다. console
io가 가능한 환경에서 디버깅을 할 때는 프로그램 중간중간에 중요한 지점에
console로 print를 해 줘서 현재 상황을 효과적으로 개발자에게 알려주는 것이
중요합니다. 일반적으로 임베디드 환경에서 지원하는 console io는 속도가 느리기
때문에 과도하게 print를 하게 되면 중간에 내용이 사라지거나 리부팅이 되기도
합니다. 개발하는 프로그램 내에서 디버깅을 하고자 하는 부분과 관련되는 것만
print를 할 수 있도록 print를 잘 정리해 놓는 것이 중요합니다. 경우에 따라서는
print가 중간에 buffering을 하는 경우도 있는데 이런 경우에는 crash가 발생했을
때 buffer를 flush하지 않고 crash가 발생하기도 합니다. 이런 경우에는 crash직전의
print내용을 볼 수가 없기 때문에 디버깅에 매우 어려움을 겪게 됩니다.

Q. 임베디드 환경에서 개발한 프로그램이 작동도중에 crash가 발생했습니다. crash가
발생한 지점을 print를 통해 찾기가 매우 힘듭니다. 좋은 방법이 없습니까?
A. 만약에 임베디드 환경에서 디버거가 사용이 가능하다면 디버거를 사용하면 매우
쉽게 crash가 발생한 지점을 찾을 수 있습니다. 하지만 이것이 불가능한 경우가
대부분입니다. 일반적으로 임베디드 환경은 crash가 발생했을 때 CPU안에 있는 모든
register의 값을 print합니다. 여기서 Program Counter(Instruction Pointer라고도
불림)의 내용을 보고 해당 주소가 프로그램 코드의 어느 function에 속한 것인지
살펴보면 crash가 발생한 곳의 위치를 알 수 있습니다. 경우에 따라서는 call
stack을 얻지 못하면 crash가 발생한 곳의 위치를 알아도 별로 도움이 되지 않는
경우도 발생하는데 이때는 crash가 발생했을 때 실행되는 exception handler를
찾아서 exception이 발생하는 순간에 stack영역을 통째로 dump를 하도록 한 다음에
dump된 내용에서 return address들이 각각 프로그램의 어느 function에 속한 것인지
살펴 보면 call stack도 얻을 수 있습니다.

Q. 전혀 다른 여러 임베디드 환경에 프로그램 하나를 이식시켜서 모두 동일하게
작동하도록 해야 합니다. 어떻게 작업하는 것이 좋습니까?
A. 임베디드 환경에서 프로그램을 개발할 때 임베디드 환경에 의존적인(dependent)
부분과 비의존적인(independent) 부분으로 나눠서 개발하고 다른 곳에 이식을 시킬
때 임베디드 환경에 의존적인 부분만 이식을 시켜줍니다. 상황에 따라서는 임베디드
환경에 의존적인 부분을 아주 자세하게 문서화를 해서 다른 업체에 외주를 주고
자신은 임베디드 환경에 비의존적인 부분만 개발하는 것도 가능합니다. 이때의
장점은 임베디드 환경에 의존적인 부분만 따로 중점적으로 테스트를 하는 것이
손쉽게 가능하기 때문에 이식이 제대로 되었는지 테스트를 하기 위해서 전체
프로그램이 반드시 필요하지는 않다는 점입니다. 또한 임베디드 환경에 의존적인
부분을 PC에 이식시켜 주면 PC에서 임베디드 환경에 비의존적인 부분을 매우
원활하게 개발하고 테스트를 할 수 있으며 여러가지 잠재적인 버그들을 각종 상용
디버거를 사용해서 고칠 수 있습니다. 이것은 위에서 언급된 에뮬레이터보다는 약간
못하지만 개발시에 상당히 에뮬레이터에 가까운 효과를 볼 수 있습니다. 이때는 OS와
CPU는 에뮬레이트(emulate)되지 않기 때문에 OS와 CPU에 의존적으로 작성된 부분이
프로그램의 비의존적인 부분에 포함에 되어 있다면 PC에서는 잘 작동하는데 실제
임베디드 환경에서는 잘 작동하지 않는 일이 발생할 수 있습니다. 실제로 이 정도
수준의 것도 에뮬레이터라고 부릅니다.

Q. C로 프로그램을 작성할 때 CPU의존적인 것은 어떤 것이 있습니까?
A. PC에서 intel계열 CPU만 사용하다가 다른 CPU를 사용하게 될 때 흔히 겪는 문제는
endian문제와 align문제입니다.

endian.c
----------------------------------------
void endian_test(void)
{
int a = 0x01020304;
char *ch = (char *)&a;
int i;
for (i = 0; i < 4; i++)
{
printf("%02x ", ch[i]);
}
}
----------------------------------------

이와 같이 프로그램을 작성해서 실행해 보면 big endian cpu의 경우에는 01 02 03 04
가 출력되고 little endian cpu의 경우에는 04 03 02 01이 출력됩니다. 그런데
이것은 이렇게 2가지 경우만 생각하면 될 정도로 단순한 문제가 아닙니다. 필자가
접한 임베디드 환경 중에서는 0x0102030405060708을 저장했을 때 04 03 02 01 08 07
06 05와 같이 다소 황당하게 저장되는 경우도 있었습니다. 그리고 int와 float의
endian이 동일하지 않은 경우도 본 적이 있습니다.

align.c
----------------------------------------
void align_test(void)
{
char *a = { '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', \x07', '\x08' };
int *i = (int *)(a + 1);
printf("%08x\n", *i);
printf("%08x\n", *i);
}
----------------------------------------

이와 같이 프로그램을 작성해서 실행해 보면 cpu가 big endian이라고 가정했을 때
02030405로 출력되는 경우도 있고 01020304로 출력되는 경우도 있으며 경우에
따라서는 그냥 crash가 발생합니다. 특이하게 처음 print에서는 출력이 되고 그 다음
print를 할 때 crash가 발생하기도 합니다. 이것은 프로그램을 align에 의존적으로
작성해서 발생한 문제입니다. 그래서 메모리에 있는 값을 읽거나 쓸 때는 align에
맞춰서 해 주는 것이 중요한데 기본적으로 align을 맞춘다는 것은 선언한 변수의
크기의 배수를 주소로 사용해서 메모리에 접근하는 것을 의미합니다. 위의 경우에서
한번은 잘 출력이 되고 그 다음에 crash가 발생하는 경우는 메모리에서는 align을
맞추지 않아도 괜찮지만 캐쉬(cache)에서는 align을 맞추지 않으면 crash가 발생하는
경우입니다. 상황에 따라서는 int와 float가 하나는 align을 맞추지 않아도 괜찮고
나머지 하나는 align을 맞춰야 되는 경우도 발생하며 64bit 데이터형의 경우에 32bit
align을 해도 괜찮은 경우도 존재합니다.

위와 같은 경우를 고려해서 endian과 align에 의존적인 프로그램은 작성하지 않는게
좋습니다.

Q. C로 프로그램을 작성할 때 컴파일러 의존적인 것은 어떤 것이 있습니까?
A. 여러 컴파일러를 사용해 보면 컴파일러마다 ANSI C 표준이 아닌 특수한 확장을
지원하는 경우가 종종 있습니다. 그래서 ANSI C 표준이 아닌 것을 ANSI C 표준인
것으로 착각하고 쓰다가 나중에 상당히 엄격한 컴파일러에서 작업을 하게 되면
당황하게 되는 경우가 있습니다. 경우에 따라서는 다른 컴파일러에서는 경고만
출력하고 넘어가는 사소한 문제를 에러로 처리하는 경우도 있습니다. 이런 문제를
미연에 방지하기 위해서는 컴파일러 option을 엄격하게 줘서 다른 컴파일러에서
작업을 할 때 문제가 거의 발생하지 않도록 하는 것이 좋습니다. 필자와 같은
경우에는 gcc를 사용할 때 아래와 같은 option을 추가해 줍니다.

----------------------------------------
-pedantic -W -Wall -Wshadow -Wpointer-arith -Wcast-align -Waggregate-return
-Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations
-Wnested-externs -Werror -Wno-unused
----------------------------------------

위와 같은 option을 주고 자신이 지금까지 개발을 해 왔던 C 프로그램을 컴파일을
시도해 보면 많은 문제가 있다는 것을 발견할 수 있을 것입니다. 그리고 의외로
사소해 보이는 것들이 문제를 일으키기도 합니다. 예를 들어 comment를 C++ 형식인
// 를 사용했는데 이게 어떤 컴파일러에서 인식이 안되는 경우도 있을 수 있고 C
소스 파일의 끝은 반드시 개행문자로 끝나야 되는데 개행문자로 끝나지 않아서
컴파일 에러가 발생할 수도 있습니다. 파일을 dos형식 txt로 저장을 해서 컴파일
에러가 발생하는 경우도 있습니다. 이와같은 문제들이 매우 많이 존재하는데
일반적으로 개발자들은 그것이 문제를 일으킬 수도 있다는 사실을 모르고 지나치게
되는데 이런 문제들은 컴파일러 option을 조정해서 탐지가 가능하도록 하는 것이
좋습니다.
위와 같이 했음에도 불구하고 컴파일러에 의존적인 코드 때문에 어려움을 겪는
경우가 있는데 그중에 가장 많이 겪는 경우가 C 구문의 evaluation 순서에 의존적인
코드를 작성하는 것입니다.

eval.c
----------------------------------------
int a;

int f(void)
{
return (a *= 2);
}

int g(void)
{
return (a += 2);
}

int h(void)
{
a = 5;
a = f() + g();
printf("%d\n", a);
}
----------------------------------------

이 코드를 살펴보면 a = f() + g(); 부분에서 f가 먼저 call이 되고 g가 call이
되는지 g가 먼저 call이 되고 f가 call이 되는지는 컴파일러에 따라 다릅니다.
위의 예제의 경우에는 f가 먼저 call이 되면 22가 출력되고 g가 먼저 call이 되면
21이 출력됩니다. 왼쪽부터 오른쪽으로 evaluation하는 컴파일러도 있고 오른쪽부터
왼쪽으로 evaluation하는 컴파일러도 있을 수 있지만 ANSI C 상으로는 어떤 순서를
따라도 상관이 없습니다.

또 하나 자주 만나게 되는 문제는 stdarg.h에 있는 va_list, va_arg, va_start,
va_end문제입니다. 가장 자주 접하게 되는 문제는 va_list가 어떤 컴파일러에서는
array로 구현되어 있고 어떤 컴파일러에서는 pointer로 구현이 되어 있어서
va_list a; va_list b; a = b; 와 같이 코드를 작성하면 array로 구현되어 있을
경우에는 컴파일 에러가 발생하는 경우입니다. 필자의 경우에는 아래와 같이 코드를
작성했을 때 a와 *ap가 어떤 컴파일러에서는 같은 값을 출력하고 어떤
컴파일러에서는 다른 값을 출력해서 상당히 당황했던 기억이 있습니다.

va.c
----------------------------------------
#include <stdarg.h>
void f(void)
{
va_list a;
va_list *ap = &a;

printf("%p %p\n", a, *ap);
}
----------------------------------------

그리고 ANSI C 표준에는 기본적인 자료형의 크기에 대해 정확한 명시가 되어 있지
않습니다. void * (pointer), char, short, int, long, float, double이 각각 몇
bit로 이루어져 있는지 알 수 없기 때문에 프로그램 작성 시에 저런 컴파일러에서
기본적으로 제공하는 것을 쓸 때는 크기에 의존적으로 프로그램을 작성해서는
안됩니다. 만약에 크기에 의존적으로 작성하고자 한다면 int16, int32처럼 임베디드
환경에 의존적이지 않은 자료형을 하나 만들고 이것들에 대한 정의를 임베디드
환경에 의존적으로 각각 해주는 방법을 사용해야 합니다. 일반적으로 컴파일러에
정의된 int 자료형이 가장 빠른 성능을 보장해 주는 경향이 있기 때문에 int16이나
int32처럼 크기를 고정시켜서 사용하는 것은 이식성을 높여줄 수는 있지만
성능저하를 유발시킬 수도 있기 때문에 주의해야 합니다.
음수의 int를 오른쪽으로 shift ( >> ) 할 때 컴파일러에 따라 산술 shift를 하는
경우도 있고 논리 shift를 하는 경우가 있습니다. 음수의 int를 오른쪽으로
shift하는 행동은 삼가하는 것이 좋습니다.
열악한 임베디드 환경에서는 float나 double을 아예 지원하지 않는 경우도 가끔
있습니다. 그렇기 때문에 이식성을 높이기 위해서는 꼭 필요한 경우가 아니면
float나 double의 사용을 삼가하는 것이 좋습니다.

Q. ANSI C 표준에 정의되어 있는 function들은 임베디드 환경에서 안심하고
사용해도 됩니까?
A. 일단 임베디드 환경에서 제공하는 모든 function은 ANSI C 표준에 정의되어
있더라도 의존적인 부분으로 분류합니다. 심각한 경우에는 ANSI C 표준에 정의되어
있는 function이 하나도 없는 환경에서 작업하게 되는 경우도 있기 때문입니다.
그리고 비록 자신이 원하는 function과 똑같은 기능을 가지는 function을 해당
임베디드 환경에서 제공을 하더라도 그 function을 100%신뢰해서는 안됩니다. 필자의
경우에는 임베디드 환경에서 제공하는 malloc과 free function을 사용했다가
malloc이 버그를 가지고 있어서 malloc을 직접 구현해서 사용한 적이 있었고 ANSI C
표준에 정의되어 있는 isalnum function을 사용했는데 이것이 어떤 환경에서는
비어있는 function으로 구현되어 있어서 결국 직접 구현을 해서 넣은 경험이
있습니다. 나중에 내린 결론은 내가 직접 작성한 부분이나 아니면 다른 곳에서
소스코드 형태로 받아와서 사용하고 있는 부분을 제외한 모든 부분은 신뢰해서는
안된다는 결론을 내렸습니다.

Q. 임베디드 환경에서 파일은 어떤 방식으로 접근하는 것이 좋습니까?
A. 파일 저장은 전원이 꺼져도 내용이 살아있어야 되기 때문에 Flash Memory에 하는
것이 좋습니다. 하지만 임베디드 환경에서는 File System이 존재하지 않는 경우가
대부분입니다. Flash Memory에서 사용할 수 있는 File System을 구해서 사용하던가
Flash Memory용 File System을 직접 구현해야 합니다.
만약에 저장할 필요는 없고 읽기만 되도 된다면 파일 내용을 C의 character array로
만들어서 그 array를 컴파일을 통해서 프로그램에 삽입하는 방법이 있을 수
있습니다. ZIP파일로 만들어서 C의 character array로 만들어서 삽입을 한다면
일종의 읽기만 되는 디렉토리도 지원하는 File System으로 사용할 수도 있습니다.
이것은 Visual C++의 resource와 비슷한 개념으로 이해하면 됩니다.

Q. 컴파일을 할 때 최적화 option을 모두 끄면 정상적으로 작동하는데 최적화
option을 주면 정상적으로 작동하지 않습니다. 컴파일러 버그입니까?
A. 이것은 컴파일러 버그일 수도 있지만 대부분의 경우는 프로그램을 잘못 작성해서
컴파일러가 최적화를 하지 말아야 되는 부분을 최적화를 해서 발생합니다. 가장 흔히
발생하는 문제는 컴파일러는 Multi Thread나 Signal(Exception) Handler와 같은 것에
대한 개념이 없고 포인터(Pointer)가 가리키는 곳은 항상 Memory라고 생각하는 데서
문제가 발생합니다.

pt.c
----------------------------------------
void f(void)
{
volatile int *led = (int *)0x00100000;
*led = 1;
*led = 0;
*led = 1;
}
----------------------------------------

예를 들어 임베디드 환경에서 주소 0x00100000에 1을 쓰면 LED가 켜지고 0을 쓰면
LED가 꺼진다고 할 때 위와 같은 프로그램을 실행시키면 LED가 켜졌다가 꺼졌다가
켜집니다. 하지만 만약에 위의 프로그램에서 volatile을 빼고 최적화 option을 줘서
컴파일을 하게 되면 컴파일러는 led가 가리키는 곳이 단순한 메모리라고 생각해서
앞의 *led = 1; *led = 0; 부분을 제거합니다. 즉 LED는 꺼지지 않고 계속 켜지게
됩니다. 이런 문제로 컴파일러가 최적화를 하지 말아야 될 부분을 개발자가 잘
살펴봐서 volatile keyword를 적절하게 추가해 줘야 최적화 option을 줘도
정상적으로 작동하는 프로그램을 만들 수 있습니다.

Q. PC환경에서 수많은 테스트와 디버거를 통해서 잠재적인 버그도 모두 고쳤고
컴파일러 cpu의존적인 부분도 모두 제거했습니다. 그런데 실제 임베디드 환경에서는
정상적으로 작동하지 않습니다. 원인이 무었입니까?
A. 이런 경우를 당하게 되면 상당히 난감합니다. 필자의 경우에는 이런 경우에는
아래와 같은 순서로 조사를 합니다.
1) function에 큰 local variable(특히 array)을 선언한 부분이 없는지 혹시 그런
부분이 있다면 그것이 재귀적으로 call되지는 않는지 살펴봅니다. 임베디드 환경은
스택(stack)이 PC에서 처럼 크지 않기 때문에 다소 큰 local variable을 사용하게
되면 memory overwrite가 일어나면서 crash가 발생합니다.
2) OS에 의존적으로 작성된 부분이 없는지 살펴봅니다. 특히 Multi Thread환경에서
작업을 하게 되면 OS마다 schedule을 다르게 해서 어느 OS에서는 특정 Thread가
예상보다 빠르게 실행되는 경우가 종종 있습니다. 임베디드 운영체제에서는 Thread
Priority가 PC용 운영체제보다 상당히 엄격하게 적용되기 때문에 이로 인해 PC에서는
deadlock이 일어나지 않는 상황인데 임베디드 환경에서는 deadlock이 일어나기도
합니다.
3) 컴파일러의 버그를 의심해 봅니다. 일단 프로그램을 최적화 option을 모두 끄고
컴파일을 해서 실행해 봅니다. 일반적으로 컴파일러 버그는 최적화 option을 켰을 때
주로 많이 발생합니다. 물론 경우에 따라서는 최적화 option을 모두 꺼도 컴파일러
버그가 나타납니다.
4) 운영체제의 버그를 의심해 봅니다. 필자의 경우에는 OS에서 여러 task가 동시에
exit될 때 OS가 crash가 발생하는 버그를 가지고 있는 OS를 본 경우가 있습니다.
5) 하드웨어를 의심해 봅니다. 필자의 경우에는 DRAM의 Refresh Rate가 잘못
설정되어 있어서 DRAM에 1로 설정되어 있던 부분이 가끔 0으로 변하는 하드웨어를
본 경우가 있습니다. 가끔은 버그가 있는 CPU를 가지고 작업해야 되는 경우도
있습니다.

Forums: 

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.