336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
초보자 아닌 분은 보지 마십시오...

1. 글을 시작 하며

이글은 임베디드 리눅스의 초보자를 위한 글입니다.
(이미 알고 계신분은 물러 가십시오... ^^ )

이글의 내용 중 일부는 저의 잘못된 경험에 의해서 오류가 있을 수
있읍니다.

오류를 발견하시면 바로 지적을 부탁 드립니다.

이글은 임베디드 시스템에 리눅스를 사용하기 위해서 사용되는
크로스 컴파일 환경에 대한 간단한 소개 글이지 설치 방법에
대한 글이 아님을 미리 말씀 드립니다.

2. 크로스 컴파일러란?

대부분의 프로그래머들은 PC 라는 동일한 환경에서 프로그램을
작성하실 겁니다.

PC에서 프로그램을 짜고 컴파일 하고, 실행 화일을 PC에서 수행 합니다.

이렇게 동일한 환경에서 동작 되는 컴파일러와 이 컴파일러에서
생성된 실행화일을 동일한 환경에서 수행 한다면 이때의 컴파일러를
네이티브( native ) 컴파일러라고 합니다.

이와 반대로 컴파일러가 동작하는 시스템과 컴파일러에 의해서 생성된
실행화일이 동작하는 시스템이 다를 때 이 컴파일러를 크로스(cross)
컴파일러라고 합니다.

여러분이 임베디드 시스템에 동작하는 프로그램을 작성한다면
당연히 크로스 컴파일러 환경을 구축해야 합니다.

개발 환경은 PC 일것이고 실행 화일이 동작하는 시스템은
다른 CPU 구성을 갖는 임베디드 시스템일 것이기 때문입니다.

그렇다고 모든 임베디드 시스템에 동작하는 프로그램들이 크로스
컴파일 환경을 필요로 하는 것은 아닙니다.

PC와 같은 구조를 갖는 임베디드 리눅스 시스템을 만든다면
PC의 네이티브 컴파일러에서 만들어진 실행화일이 그대로
수행될 수 있기 때문입니다.

하지만 임베디드 시스템 대부분이 저렴한 가격대를 요구하거나,
또는 특수한 기능을 수행하는 구조를 가지기 때문에 이런 경우는
조금 드믄 편입니다.

그래도 개발 프로세스를 빠르게 진행하기 위해서 익숙한 PC 구조를
사용하는 경우도 요즈음은 많아 지는 추세인것 같습니다.

어찌되었든 크로스 컴파일 환경에서의 개발은
윈도우 개발자 입장에서 보면 매우 열악한 개발 환경이 됩니다.

돈이 많은 회사야 좋은 개발 툴을 사서 이런 열악한 환경을
일부 개선하기는 하지만 그래도 열악하기는 마찬가지 입니다.

가장 큰 이유는 컴파일이 끝난후 실행 화일을 즉시 시험하지
못하기 때문입니다.

그외에도 개발하기 위해서 손가락이 무척 바빠지는 것도
한 요인입니다. 단순하게 마우스 클릭 한번으로 수행되는
GUI 컴파일러에 익순한 분들이 타자를 치는 것은 무척 힘든
것입니다.

그래도 리눅스에서 크로스 컴파일러를 사용하는 것에는 이유가
있읍니다.

3. 리눅스와 gcc

지금은 많은 분들이 임베디드 시스템 개발에 리눅스를
사용되고 있다는 것을 알고 있읍니다.

하지만 그 이유가 리눅스 커널을 임베디드 시스템에 탑제하기
때문이라고 알고 계신다면 잘못 알고 계신 겁니다.

임베디드 시스템 개발 환경으로 리눅스를 사용하는 근본적인
이유는 gcc라는 막강한 컴파일러가 있기 때문입니다.

리눅스만 사용하시는 분이라면 리눅스 프로그램 개발을 위해서
당연히 gcc라는 컴파일러를 사용합니다.

그래서 gcc라는 것이 그냥 리눅스용 컴파일러라고 알고 있읍니다.

하지만 gcc는 그렇게 단순한 컴파일러가 아닙니다.

또한 gcc는 그냥 i386 프로세서에서 동작하는 실행화일을 만들어
내는 컴파일러도 아닙니다.

현재 존재 하는 컴파일러 중에서 가장 많은 프로세서를 지원하는 컴파일러가
바로 gcc입니다.

gcc 컴파일러의 패케지는 이미 전세계에 동작되는 대부분의 프로세서를
지원하기 위한 준비를 갖추고 있읍니다.

더구나 공짜입니다. !!!

하지만 여러분은 이런 gcc가 어떻게 다른 CPU를 지원하게 할 수 있는지에
대해서는 모를 겁니다.

그냥 gcc 명령을 치면 i386 코드가 생성되기 때문입니다.


3. 크로스 컴파일러와 gcc 소스 패케지

gcc에서 i386 이외의 실행화일을 만들기 위해서는 리눅스에 이미 설치된
gcc란 컴파일러는 소용이 없읍니다.

몇 가지 옵션만 바꾸어서 gcc 수행한다고 i386 이외의 프로세서를 지원하는
코드가 생성되는 것은 아닙니다.

애초에 다른 프로세서에서 동작 될 수 있는 실행화일을 만들수 있겠금 gcc를
만들어야 하는 것입니다.

그렇다고 기존에 i386에서 동작하는 gcc를 아예 다른 프로세서에서 동작하는
실행화일을 만드는 gcc로 바꾸어 버리면 문제가 됩니다.

그 뒤로는 i386에 동작하는 어떤 프로그램도 만들 수 없기 때문입니다.

그래서 보통은 gcc의 이름을 조금 바꿉니다.

예를 들어 arm 계열의 프로세서에서 동작하는 실행 프로그램을 만들기 위해서
사용하는 gcc는 arm-linux-gcc라는 이름을 가집니다.

이름이 다른 이 두 컴파일러는 전혀 다른 컴파일러가 아닌 똑같은 gcc인 것입니다
단지 gcc는 i386 프로세서에서 수행되는 코드를 만들어 내는 컴파일러이고
arm-linux-gcc라는 것은 arm 프로세서에서 동작되는 코드를 만들어 내는 컴파일러
일 뿐입니다.

관행적으로 네이티브용 컴파일러가 보통 gcc가 되고 크로스 컴파일러는 gcc
앞에 접두사를 붙입니다.

만약 알파 프로세서 동작하는 gcc에서 i386 계열의 실행 코드를 만드는 크로스
컴파일러는 i386-linux-gcc가 될 것입니다. ( 확인하지 않았읍니다. ^^ )

어찌되었든 둘 다 동일하게 gcc란 패케지에서 파생한 컴파일러인 것입니다.

자....

제가 자꾸 gcc 패케지라는 말을 쓰고 있읍니다.
이 gcc 패케지라는 것이 무엇일까요?

윈도우에서 델파이라는 개발 툴을 구매해서 설치하면 단순하게 델파이 컴파일러
만 설치되는 것이 아닙니다. 개발하기 위한 이것저것들이 설치 됩니다.

이와 동일한 개념으로 gcc 패케지란 컴파일을 하기 위한 이런 저런 것을 모두 담은 것을
말 하는 것입니다.

윈도우에서와 달리 리눅스에서는 패케지들이 소스로 구성되어 있읍니다.
그래서 이런 것을 gcc를 예를 들면 gcc 소스 패케지 라고 말합니다.

또 여러분이 착각하는 것중 하나가 gcc가 단순하게 c 컴파일러라고 알고 있는데
정확하게 이야기 하면 c 컴파일러가 아닙니다. c++ 소스를 컴파일
할때도 역시 gcc 를 사용합니다. 어셈블러를 컴파일 할때도 gcc를 사용할수
있읍니다.

그래서 gcc 소스 패케지에는 c 이외의 몇가지 컴파일러들이 더 들어 있읍니다.

어찌되었든 gcc 소스 패케지란 gcc 컴파일러를 만들수 있는 소스로 구성된 것을
말 합니다.

이 gcc 소스 패케지를 이용하여 원하는 프로세스 지원 크로스(또는 네이티브)
컴파일러를 만드는 것입니다.

그런데 아주 모순 된 것 중 하나는 gcc를 만들기 위해서는 gcc가 필요하다는
점입니다. 왜냐하면 gcc 소스 패케지이기 때문입니다.

하지만 이점은 여기에서 문제 삼지 않으려 합니다.
여러분의 리눅스에는 당연히 gcc 컴파일러가 이미 설치되어 있기 때문입니다.

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
APK003 print를 잘 쓰자
==============================================

1. 개요

이 문서는 ESP-NS에서 동작하는 응용 프로그램에서
printf 를 쓰는 방법에 대한 소개 입니다.

작성자 : 유영창
frog@falinux.com
작성일 : 2004년 9월 10일
수정일 :

관련된 ADK( Application Developer Kit ) 디렉토리

adk/sample/printf

2. 최강의 디버거 printf

응용 프로그램을 작성하는 과정에 반드시 수반되는 것이
디버깅입니다. 이 과정은 프로그래머에게는 숙명입니다.
이걸 피해갈 사람은 없읍니다.

윈도우 프로그래머라면 통합 환경에서 제공하는 디버깅기능을
즐겨 사용하게 됩니다.

이런 분들이 임베디드 리눅스에서 동작하는 프로그램을 디버깅
하면 갑갑함을 느낍니다.

왜냐?

임베디드 리눅스에서 사용할만한 통합환경이 없다는 것이
가장 큰 이유입니다

그렇다면 리눅스에는 쓸만한 디버거가 없을까요?

있읍니다 그것도 엄청 막강한 gdb라는 툴이 있읍니다.

그러나

제 주위에서 이 프로그램을 사용하는 사람 본적이
별로 없읍니다.

왜냐하면 준비하는데 엄청난(?) 작업이 필요하기 때문입니다.
특히 저같이 게으른 사람에게는 gdb를 이용해서 작업하는
과정은 무척 어려운 일입니다.

라인 명령을 사용해야하고 디버깅을 위한 준비작업 또한
만만치 않기 때문입니다.

그래서 저는 printf 라는 막강한 디버거를 사용합니다.

사실 임베디드 시스템에서 printf 함수는 목적하는 기능구현을
위해서 사용할 필요가 없는 함수입니다.

뭐 .... PC 리눅스에서도 마찬가지죠..

하지만

이 printf 함수 만큼 즐겨 사용하는 함수가 없읍니다.

주로 디버깅을 위해서 사용합니다.

이 printf 디버거는 무척 강력합니다.

일단 준비할 것이 없읍니다

그냥 printf 만 사용하면 됩니다.

그리고 내가 원하는 모든 변수값들을 찍어 볼수 있읍니다.
내가 원하는 함수의 문장을 통과하는지를 관찰할수 있읍니다.

디버깅이라는 것이 이게 다 아니겠읍니까?

리눅스 프로그램머들은 고수가 될수록 이 printf 함수를
자유자재로 사용합니다.

저 스스로도 고수라고 칭하므로 당근 이 printf 함수를
아주 적절히(?) 절묘하게(?) 사용합니다.
이건 고수들만의 노하우도 됩니다. ( 퍽~~ ㅜㅜ )

그런데 이 printf 를 그냥 사용하는 것은 조금 불편합니다.

그래서 조금 변경해서 사용하는데 이것을 소개할까 합니다.
그리고 더불어 주의점도요...

3. printf함수도 시간을 빼앗아 간다.

printf 를 그냥 사용해도 되지만 디버깅 단계에서는 유용하겠지만
나중에는 수많은 메세지들이 콘솔에 난무하는 엄청난 혼란을
발생합니다. ( 당해본 사람만 압니다.)

그래서 고수(?)들은 이 printf 를 그냥사용하지 않고 매크로 함수로
변형해서 사용합니다.

간단하게 소개 하면 이렇게 어딘가에 선언을 합니다.

//#define NDEBUG

#ifndef NDEBUG
#define dp(fmt,args...) printf( fmt, ## args )
#define dlp(fmt,args...) printf( "[%s %d]" fmt, __FILE__,__LINE__, ## args )
#else
#define dp(fmt,args...)
#define dlp(fmt,args...)
#endif


이것은 NDEBUG 라는 것이 선언되어 있지 않으면
dp 라는 것은 printf 함수로 대치 됩니다.
( dlp 라는 것은 조금 나중에 설명하겠읍니다. )

만약 NDEBUG 라는것이 선언되면 printf 는 아무것도 하지 않고
프로그램 소스에서 제거 됩니다.

예를 들어

printf( "hello world\n" );

이라고 쓰는 것은

dp( "hello world\n" );

라고 쓰면 됩니다.

#define NDEBUG 라는 문장이 없으면
위 문장들은 출력을 발생하게 됩니다.

그러나

#define NDEBUG 를 선언하게 되면

dp( "hello world\n" );

는 아무런 일도 하지 않습니다. 더구나 컴파일러의 최적화에 의해서
실행코드의 크기도 작아 먹지 않습니다.

위에서 소개한 dp 문은 보통 전체 소스 파일들이 사용하는 공통 헤더파일에
정의해 써 놓고 사용하는 것이 좋습니다.

평소에는 dp 문을 이용하여 출력을 하다가

NDEBUG 라는 문자열만 사용하면 싹~ 없어지기 때문에 나중에 한꺼번에
출력이 안될수도 있기 때문에 추척 편리한 함수입니다.

하나의 소스파일에서 메세지 출력을 없애고 싶다면
해당 소스파일에 위 정의문의 바로 위나 위 정의문을 포함한 헤더파일을
선언하는 #include 문 앞에 NDEBUG 라는 문자열을 정의하면 됩니다.

정말 정말 편리한 기능입니다.

만약 프로그램 전체에서 dp를 사용한 문자열의 출력을 제거하고 싶다면
그냥 컴파일 옵션에 -DNDEBUG 라는 것만 추가 하면됩니다.

APK 에 있는 샘플에 소개한 Makefile을 사용한다면
Makefile 에 있는

CFLAGS += -Wall -O2 -g



CFLAGS += -Wall -O2 -g -DNDEBUG

로 바꾸면 됩니다.

4. printf함수를 이용한 위치 추적

혹시 다음과 같은 문장의 의미를 여러분은 아시는 지 모르겠읍니다.

printf( "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__ );

이것은 이 문장이 적혀진 라인을 포함하는 파일명과 함수명 그리고
파일의 라인번호를 출력하게 합니다.

그래서 이것을 이용해서 dlp라는 함수를 선언합니다.

dp는 단순히 어떤 메세지나 값을 표출하는데 사용한다면
dlp는 해당 메세지가 표출된 위치를 함께 표출하기 위해서 사용합니다.

5. \r 을 출력하는 문자열 라인의 앞에 추가 하자!

초기에 보드에 프로그램을 작성 하다 보면 디버깅을 위한 메세지를
출력하면서 동작상태를 관찰하게 됩니다.

그런데 응용 프로그램을 자동으로 실행하게 했다면 로긴이 안된 상태이기
때문에 문자열의 '\n' 문자 때문에 다음과 같은 현상이 발생합니다.

message line 1
message line 2
message line 3

원래는 줄 앞에 나란히 정렬해야 하는데 삐뚤 삐뚤하게 출력되기 때문에
애써 예쁘게 출력하도록 한 내용이 보기 어렵게 됩니다.

그래서 한 라인의 시작 문자열 맨 앞에서는 '\r' 문자를 사용하는 것이 좋습니다.

예를 들어

printf( "message line 1\n" );
printf( "message line 2\n" );
printf( "message line 3\n" );

이라고 한다면 이것을 다음과 같이 고쳐 주어야 합니다.

printf( "\rmessage line 1\n" );
printf( "\rmessage line 2\n" );
printf( "\rmessage line 3\n" );

6. \n을 사용하기 전까지는 화면에 출력되지 않는다.

printf 문장을 사용해서 다음과 같이 특정 변수명값을 추적한다고 합시다.

while(1)
{
:
printf( "%d ", test_v );
:
}

이렇게 하고 출력이 나올걸 기대하면 아마도 전혀 의도하지 않는 현상이
발생할겁니다.

리눅스에서 printf 문은 '\n' 을 만나기 전까지는 출력 처리가 되지 않는
다는 점을 기억합시다.!!!!

왜 이렇게 될까요?

그것은 여러 프로세스들이 서로 출력을 할때 보기 편하라는 의미가 담겨
있읍니다

A 프로그램도 출력도 하고 B 프로그램도 출력을 하고 있다면 아마도
먼저 보낸 순서대로 출력이 되어야 할겁니다. 그러면 서로 짬봉이
되는 바람에 무슨 내용인지 알수가 없죠...

그래서 '\n'을 먼저 출력하는 프로그램의 출력 내용을 표출하는 것입니다.

그래서 출력 결과를 보시고자 한다면 항상 출력하고자 하는 내용의 마지막에는
'\n'을 꼭 추가하시기 바랍니다.

7. printf 는 실제로 시리얼로 출력하는 것이다.

ESP 보드는 printf 가 메인 콘솔에서 본다면 시리얼로 데이터가 전송되는 것입니다.
메인 콘솔의 속도가 115200 으로 되어 있지만 이 속도가 그렇게 빠르지 않다는 점을
명심하시기 바랍니다.

나중에 printf 가 블럭 되기도 하고 아주 빠른 처리를 요구하는 부분에서 printf를
남발하면 큰 코 다치게 됩니다.

특히 printf 는 그 처리가 무척 복작한 함수입니다. 그래서 한번만 호출해도
엄청난 수행시간을 필요로 합니다.

그래서 가끔 아주 간단한 지연이 필요한 경우에는 이 printf 함수를 사용하기도
한다는 점 기억하시기 바랍니다. 그만큼 처리 속도를 잡아 먹는 다는 이야기 입니다.

'Programming > Linux' 카테고리의 다른 글

Linux find grep 명령사용하기  (0) 2008.06.24
Fedora8사전에서 Microsoft를 검색하면...  (0) 2008.05.26
리눅스 디렉토리  (0) 2008.05.21
Data Types  (0) 2008.04.03
유닉스/리눅스 명령어 레퍼런스  (0) 2008.03.26

+ Recent posts