2009년 12월 19일 토요일

중소규모 프로젝트를 위한 Makefile 만들기

대규모 프로젝트에서는 재귀적 Makefile을 사용함으로써 다양한 디렉토리 구조에 유연하게 대처할 수 있다. 하지만 중소규모의 프로젝트에서는 매 디렉토리마다 Makefile을 넣는것은 좀 번거로운 일이 아닐수 없다. 이에 아래와 같이 중소규모 프로젝트에 적용가능한 디렉토리 구조 및 Makefile을 만들어 보았다. Makefile은 ./와 ./obj 두군데만 있어 관리하기 용이하도록 하였으며, ./Makefile이 ./obj/Makefile을 호출하는 구조이다.

먼저 디렉토리 구조는 아래와 같다

./
 |---main.c
 |--Makefile

./include
 |--- func1.h
 |--- func1.h

./srcdir1
 |--- func1.c

./srcdir2
 |--- func2.c

./obj
 |--- *.obj files
 |--- Makefile
 |--- depend file

아래와 같이 동작되도록 Makefile을 작성할 것이다.
 - 실행 파일은 ./ 디렉토리에 생성되도록 한다.
 - object파일은 모두 ./obj 디렉토리에 생성되고 make clean시 모두 지워지도록 한다.
 - make depend로 dependency file 설정하여 수정된 파일만 컴파일 되도록 한다.

./Makefile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1 OBJDIR = ./obj
2
3 all:
4   cd $(OBJDIR) && make
5
6 clean:
7   cd $(OBJDIR) && make clean
8
9 depend:
10   cd $(OBJDIR) && make depend

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

./obj/Makefile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1 CC = gcc
2 LD = gcc
3
4 INCDIR = ../include
5 SRCDIR1 = ../srcdir1
6 SRCDIR2 = ../srcdir2
7
8 VPATH = $(SRCDIR1) $(SRCDIR2) ..
9
10 CFLAGS = -O2 -I$(INCDIR)
11
12 TARGET = testapp
13
14 SRCS = $(foreach dir, .. $(SRCDIR1) $(SRCDIR2), $(wildcard $(dir)/*.c))
15 SRCS := $(notdir $(SRCS))
16
17 OBJS = $(SRCS:.c=.o)
18
19 all: $(TARGET)
20
21 $(TARGET) : $(OBJS)
22   $(LD) $^ -o$(TARGET) $(LIBS)
23   mv $(TARGET) ../
24
25
26 %o:%c
27   $(CC) $(CFLAGS) -c $< -o $@
28
29 clean:
30   -rm -rf $(OBJS)
31   -rm -f ../$(TARGET)
32
33 depend: $(SRCS)
34   $(CC) -M $(CFLAGS) $^ > $@
35
36 -include depend

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Line:14  모든 디렉토리에서 c파일을 골라낸다.
Line:15  c파일 리스트에서 경로명을 제거한다.
Line:17  각 c파일에 해당하는 o파일 리스트를 만든다.
Line:22  $^ 는 현재 타겟의 종속 항목 리스트 전체로 치환된다.  
Line:26  $< 는 확장자 규칙에서만 사용되며 사용되며 현재 타겟보다 최근에 변경된 종속 항목 중 하나로 대체된다. 아래에서 makefile 내부 매크로 관련 정보를 더 찾아볼 수 있다.    http://haneul0318.springnote.com/pages/14554?print=1

Line:34  모든 파일의 의존성리스트를 만든다.
Line:36 depend파일을 include하도록 하였으므로 이 Makefile이 불릴때마다 depend target에 자동으로 실행된다. 이때 obj디렉토리에 depend 파일이 만들어지며, 이 파일은 Makefile의 일부로 간주되게 된다. 이 파일로 인하여, 최근에 수정된 c파일이 자동으로 새로 컴파일 되며, h파일의 경우 해당 h파일을 사용하는 모든 c파일이 다시 컴파일 되게 된다. 하지만 변경되지 않은 파일들은 새로 컴파일 되지 않아 매번 모든 파일을 새로 컴파일하는 수고를 덜 수 있다. 명령문 앞에 "-"를 붙이면 오류발생시 무시하고 나머지 명령을 계속 진행한다. "-"가 없을 경우에는 오류 발생시 make가 중단된다.

아래는 위 메이크 파일 실행시 결과이다.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

dooeui@dooeui-laptop:~/test$ make
cd ./obj && make
make[1]: Entering directory `/home/dooeui/test/obj'
gcc -O2 -I../include -c ../srcdir1/file1.c -o file1.o
gcc -O2 -I../include -c ../srcdir1/main.c -o main.o
gcc -O2 -I../include -c ../srcdir2/file2.c -o file2.o
gcc file1.o main.o file2.o -otestapp
mv testapp ../
make[1]: Leaving directory `/home/dooeui/test/obj'

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Makefile관련 추가 정보는 아래에서 찾을 수 있다.

  http://wiki.kldp.org/KoreanDoc/html/GNU-Make/GNU-Make.html#toc1

도서 중에서 make에 관련 사용법을 속시원히 설명한 책은 "유닉스-리눅스 프로그래밍 필수 유틸리티"- 한빛 미디어를 추천한다.

2009년 12월 6일 일요일

안드로이드 시작하기

임베디드 개발 플래폼의 대세가 안드로이드가 될 것임을 너무 늦게 - 모두가 다 알아챈 후에 - 깨닫게 되었다. 늦었지만 지금이라도 시작하지 않으면 곤란해질 것임을 느끼면서, 일단 안드로이드 이미지 빌드부터 시도해 보기로 한다.

1. 리눅스 설치

안드로이드 커널 컴파일을 위해서 노트북에 우분투를 설치하여 시도하기로 한다. 마침 ubuntu 9.10 iso 파일이 있으므로, VirtualBox에 우분투 9.10을 아래와 같이 설치한다. 안드로이드 커널 컴파일에 시간이 오래 걸리고 꽤 많은 용량이 필요하다고 하므로, 메모리와 하드 디스크를 넉넉하게 할당하였다.

호스트 컴퓨터는 Windows7-64bit-Pro 이다.
- Memory : 2G
- HardDisk : 25G

2. 커널 컴파일 따라하기

일단 아는게 없으므로, 인터넷을 찾아본다. 일단은 아래 국내 사이트에서 정보를 조금 얻을 수 있었다.
http://www.kandroid.org/
http://forum.falinux.com/zbxe/
http://www.androidpub.com/


하지만, 빌드는 공식 사이트를 참고한다.
http://source.android.com/download

영어라 편하지는 않지만, 구글이 제공하는 동영상과 슬라이드 자료에서 가장 정확하고 많은 정보를 얻을 수 있었다.
http://source.android.com/documentation

특히 Anatomy & Physiology of an Android 동영상 및 강연 자료가 나의 궁금증을 가장 속 시원히 풀어주었다.

만일 플래폼 빌드보다 안드로이드 어플리케이션에 더 관심이 많다면 아래 사이트를 참조하자.
http://developer.android.com/

아래는 이미지 빌드를 시도하면서 얻은 몇가지 정보 사항이다.
- JDK 5.0 update 12 or higher가 필요하다. 하지만 JDK 6.0은 지원하지 않는다.
- 15G HDD, 512MByte에서도 빌드에 성공하였다.
- .bashrc에 아래와 같이 path를 지정(Repo를 설치한 디렉토리에 대한 path임)
export PATH="~/bin:$PATH"

가장 큰 문제는 apt-get install시 sun-java5-jdk 패키지를 찾지 못하는 것었다.
JDK 5.0을 수동으로 설치할 수도 있겠지만, 아래 사이트에서 쉬운 해결책을 찾을 수 있었다.
http://www.androidpub.com/android_porting_info/38814

간단히 설명하면,
1. /etc/apt/sources.list에 아래 두줄을 추가.
deb http://us.archive.ubuntu.com/ubuntu/ jaunty multiverse
deb http://us.archive.ubuntu.com/ubuntu/ jaunty-updates multiverse
2. sudo apt-get update 실행
3. sudo apt-get sun-java5-jdk 실행

그 외에는 큰 문제 없이 빌드가 성공!!!

일단 첫 발에 의미를...

참고:
만약 java 1.6와 java1.5를 모두 설치하였다면 아래 명령으로 java 1.5를 사용하도록 지정할 수 있다.

sudo update-java-alternatives -s java-1.5.0-sun

2009년 11월 20일 금요일

Priority Inversion Protection

우선순위가 낮은 테스크가 소유한 자원을 우선순위가 높은 자원이 요청할 경우 대부분의 최신 OS는 우선순위 상속을 사용하여 문제를 해결한다. 우선순위 상속이 발생 시 낮은 우선순위 테스크의 우선순위가 높아지므로, 또다른 우선순위 역전이 발생할 수 있음을 주의해야 한다.

예를들어 flash driver에서 우선순위 상속이 발생하여 flash recalim등과 같이 CPU를 오래 소모하는 작업의 우선순위가 상승할 경우, 예기치 못한 우선순위 역전이 발생할 수 있다. flash reclaim자체는 write 작업에서만 발생하지만, 우선순위가 높은 task에서 요청한 flash read가 낮은 우선순위 task의 flash reclaim으로 지체 될 경우, OS는 priority inheritance로 이를 해결하려 하고, 이 때문에 flash reclaim이 높은 우선순위에서 실행되어, 예기치 못하게 오랜 시간동안 다른 테스크들의 실행을 막을 수 있는 것이다.

이와 같은 경우를 고려하여 우선순위 역전이 발생하지 않도록 appliacation들의 우선순위를 조절하고, flash driver의 서비스 처리 테스크 수를 늘려, 동시 접근에 대한 처리를 가능하도록 하여 문제를 해결할 수 있다.

2009년 10월 23일 금요일

Shell과 Makefile에서 PATH수정하기

PATH 앞 혹은 뒤쪽에 다른 PATH를 추가하고자 할때 아래와 같이 하면 된다.

Shell에서는...


export PATH=/added_path:$PATH


Makefile에서는...

TEMP=/added_path:$(PATH)
PATH:=$(TEMP)# :=를 사용해서 $(PATH)가 recusive하게 해석되지 않도록 함.
PATH=$(TEMP) # 이렇게 하면 error, $(PATH)가 recusive하게 해석됨.

혹은
PATH=/added_path:$(PATH) #이렇게 해도 상관 없음
PATH:=/added_path:$(PATH)
Makefile에서 매크로를 참조할때는 꼭 괄호를 사용할 것.
Shell에서는 괄호가 없어도 됨.

2009년 10월 2일 금요일

State Patten을 C로 짜려면?

C에서는 상속을 사용하지 못하지만, 그렇다고 State Patten을 구현하지 못하는 것은 아니다. 아래와 같은 구현으로 C에서 State Patten의 장점을 사용할 수 있을 것 같다. C에서는 인터페이스나 상속이 지원되지 않으므로 function table을 사용하여 이벤트 발생시 각 상태별 실행 함수를 지정하도록 하였다. 사실상 이 function table이 구현의 핵심으로, 상태 변경을 발생시키는 이벤트가 추가 할 때마다, 모든 function table의 해당 함수에 대한 함수 포인터 혹은 null function pointer를 추가해야 한다. 바로 이 부분이 상속과 가상 함수를 지원하지 못하는 C에서 State Pattern을 구현할 때 발생하는 불편함이기는 하지만 여전히 State Pattern의 장점을 그대로 누릴 수 있다. 또한 (당연히) 동일 방법을 사용하여 Strategy Pattern 역시 구현 가능하다.

아래 예에서는 구현을 단순화하기 위해서 상태는 ON/OFF두가지 상태만을 가정하였고, 'n' 입력시 ON상태로, 'f' 입력시 OFF상태로 상태 천이를 하도록 하였다.



#include <stdio.h>

/**********************************************************************
*
* 1. Type defination for Power State management
*
**********************************************************************/

typedef enum {ON,OFF} PowerState;

typedef struct
{
  PowerState currPS;
  void (*on_received_fptr)(struct PwrState **ps );
  void (*off_received_fptr)(struct PwrState **ps );
}PwrState;

/**********************************************************************
*
* 2. Function table for each State.
*
**********************************************************************/
//Prefix ON/OFF means current status when these functions are called.
void ON_off_received(PwrState **ps );
void OFF_on_received(PwrState **ps );
void null_func(PwrState **ps);

//C에서는 가상 함수를 사용할 수 없기 때문에, 아래와 같은 각 상태별
//함수 테이블을 별도로 유지해야 한다.

PwrState psON= {ON,   null_func,      ON_off_received};
PwrState psOFF={OFF,  OFF_on_received,  null_func};


void ON_off_received(PwrState **ps)
{
  printf("Currnt State =%d : 0:ON 1:OFF \n",(int)(*ps)->currPS);
  printf("%s is called\n",__func__);

  printf("change curr power state from ON to OFF\n");
  *ps=&psOFF;
}

void OFF_on_received(PwrState **ps)
{
  printf("Currnt State =%d : 0:ON 1:OFF \n",(int)(*ps)->currPS);
  printf("%s is called\n",__func__);

  printf("change curr power state from OFF to ON\n");
  *ps=&psON;
}

void null_func(PwrState **ps){}

int main(void)
{
  char keyin;
  PwrState *ps=&psOFF;

  printf("Current power state is OFF\n");
  printf("type \'n\' to change power state to ON\n");
  printf("type \'f\' to change power state to OFF\n");

  while(1)
  {
    keyin = getchar();

    switch(keyin)
    {
      case 'n':// change state to ON
        ps->on_received_fptr(&ps);
        break;
      case 'f':// change state to OFF
        ps->off_received_fptr(&ps);
        break;
      default:
        break;
    }
  }
}

2009년 10월 1일 목요일

원하는 폴더에서 마우스 오른쪽 버튼으로 커맨드 창 열기

아래 내용을 filename.reg기울임꼴로 저장한 후 실행하면 레지스트리에 해당 내용이 추가되며, 추가된 이후 실행된 탐색기에서는 특정 폴더에서 마우스 오른쪽 버튼 클릭시 뜨는 팝업 메뉴에 "Dos Prompt(Z)"가 추가된다. &Z는 팝업메뉴에서의 단축키를 Z로 지정한 것이며, "Dos Prompt" 대신 원하는 문자열을 사용할 수 있다.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\OpenNew]@="Dos Prompr t(&Z)"
[HKEY_CLASSES_ROOT\Directory\shell\OpenNew\Command]@="cmd.exe /k"


아래 블로그에서 관련 내용을 포함한 추가 정보를 얻을 수 있습니다.
http://youngsam.kr/

2009년 7월 3일 금요일

ELF Header

ELF Header 혹은 ELF File Header는 보통 52바이트로 아래와 같이 내용을 살펴 볼 수 있다.

--------------------------------------------------------
Dukeru>readelf -h testApp
ELF Header:
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: PowerPC
Version: 0x1
Entry point address: 0x48041110
Start of program headers: 52 (bytes into file)
Start of section headers: 275611 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 29
Section header string table index: 28
--------------------------------------------------------

0x0000-0x0003 (EI_MAG) Magic
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
이 파일이 ELF 파일임을 나태냄. 45 4C 46이 ASCII로 E L F임.

0x0004 (EI_CLASS) Class
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
32bit 환경에서 실행되는 ELF파일임을 뜻함.
01: 32bit
02: 64bit


0x0005 (EI_DATA Encoding)
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Data: 2's complement, big endian
이 파일에 데이터가 Big Endian 포맷임.
01: Little Endian
02: Big Endian

0x0006 (EI_VERSION)
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Version: 1 (current)
ELF Header Version을 뜻함. EV_CURRENT가 값으로 사용되며 일반적으로 EV_CURRENT는 1으로 정의되어 있음.

0x0007 - 0x000F (EI-PAD)
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
OS/ABI: UNIX - System V :ABI형식
ABI Version: 0
몇몇 가지 부가 정보가 있으나 일반적으로 모두 0임.

0x0010-0x0011 (E-TYPE)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Type: EXEC (Executable file)
1: Relocatable file
2: Executable file
3: Shared object file
4: Core file

0x0012-0x0013 (E-MACHINE)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Machine: PowerPC
0x03: Intel386
0x14: PowerPC

0x0014-0x0017 (E_VERSION)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Version: 0x1
Object file version
0:Invalid
1:Current version

0x0018-0x001B (E_ENTRY)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Entry point address: 0x48041110
Virtual Address Starting Process, 프로그램의 시작 주소, main이 아니라 _start 함수의 주소임. main은 _start에서 호출된다.

0x001C-0x001F (E-PHOFF) Program Header Offset
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Start of program headers: 52 (bytes into file)
파일 시작위치에서 Program Header Table까지의 offset.

0x0020-0x0023 (E-SHOFF) Section Header Offset
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Start of section headers: 275611 (bytes into file)
파일 시작위치에서 Section Header Table까지의 offset.

0x0024-0x0027 (E_FLAGS)
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Flags: 0x0
Process Specific Flags.

0x0028-0x0029 (E_EHSIZE) ELF Header Size.
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Size of this header: 52 (bytes)
ELF header의 크기이면서 Program Header의 시작 위치

0x002A-0x002B (E-PHENTSIZE) Program Header Entry Size
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Size of program headers: 32 (bytes)
Program Header Table의 각 item의 크기. Program Header는 각 Segment당 한개씩 있음.

0x002C-0x002D (E_PHNUM) Program Header Number
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Number of program headers: 6
6개의 Segment, 즉 6개의 Program Header가 있다는 뜻.

0x002E-0x002F (E_SHENTSIZE) Section Header Entry Size
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Size of section headers: 40 (bytes)
Section Header Table의 각 item의 크기.

0x0030-0x0031 (E_SHNUM) Section Header Number
0000030 00 1d 00 1c 00 00 00 06 00 00 00 34 48 04 00 34
Number of section headers: 29
29개의 section header가 있다는 뜻.

0x0032-0x0033 (E_SHSTRNDX) Section Header String Index
0000030 00 1d 00 1c 00 00 00 06 00 00 00 34 48 04 00 34
Section header string table index: 28
Section Header table에서 String section의 header가 있는 index.
E-SHOFF + (E_SHSTRNDX * E_SHENTSIZE) 가 파일에서 String Section의 header의 위치가 된다.

해당 섹션의 이름을 얻는 방법:
원하는 Section Header의 sh_name은 section name이 string section의 시작으로부터 몇바이트 위치에 있는지 offset값을 가지고 있다.
따라서 section header table을 참조해서 string section의 시작주소를 구하고 여기에 sh_name을 더하면 된다. (section header에 대해서는 다음에 자세히 설명한다.)

string section의 시작주소는 section header table에서 구할 수 있다.

따라서 string section header에서 sh_offset을 읽어 원하는 section header의 sh_name을 더하면, 원하는 section의 이름을 얻을 수 있다.

Addr. of a section name = "sh_name" of the section + "sh_offset" of string section

ELF? 엘프?

ELF파일은 유닉스 계열 OS에서 가장 많이 사용되는 실행파일 형식으로 아래와 같은 파일을들이 이 형식을 사용한다.
  • Object file.
  • Executable file.
  • Shared Library.
  • core파일.
.a 확장자를 가지는 일반 Library파일은 단순한 archive파일로 ELF형식이 아니다.

아래 ELF파일의 구조에서 살펴보았듯이 ELF파일은 ELF file header, program header table, sections 그리고 section header table로 구성되어 있다.


ELF file구조 및 readelf 사용법.

ELF file구조
  • ELF file Header
  • Program Header Table
  • Section1
  • Section2
  • ...
  • Section Header Table
- Program Header Table은 오브젝트 파일에서는 optional이다. Program Header는 세그먼트에 대한 정보를 가지고 있는데, 오브젝트 파일은 이러한 정보가 없으므로 Program Header가 필요하지 않다.
- Section Header는 실행파일에서는 optional이다. Section header가 없거나 손상되어도 프로그램의 실행에는 지장이 없으나 이런 파일은 디버거를 사용하여 디버깅을 할 수 없다.

--------------------------------
ELF header
--------------------------------
Program Header 1
-----------------------
Program Header 2
-----------------------
Program Header 3
-----------------------
...
-----------------------
Program Header N
--------------------------------

sections...

--------------------------------
Section Header 1
-----------------------
Section Header 2
-----------------------
...
-----------------------
Section Header M
----------------------------------

readelf사용법
    Usage: readelf elf-file(s)
      Display information about the contents of ELF format files
        Options are:
        • -h Display the ELF file header
        • -l Display the program headers
        • -S Display the sections' header
        • -d Display the dynamic segment (if present)
          부연설명:
          ELF는 unix계열에서 현재 가장 널리 사용되는 실행 파일 포맷이다. a.out과 COFF는 이제 많이 사용되지 않는다. 단, MS Windows계열에서 사용하는 실행 파일 포맷인 PE(Potable Executable) 포맷이 COFF를 약간 수정한것이라고 한다.

          2009년 6월 7일 일요일

          디버깅은 근성이다!!!

          개발을 하다보면 문제의 원인을 찾지 못해 문제를 바로 해결하지 못하고 짧게는 며칠에서 길게는 몇 달 씩 고생하는 경우가 생기곤 한다. 최근 1년동안에는 두 번 정도 이런 문제에 발목을 잡혀 여러날을 맘고생하며 보내야 했다. 먼저 부딪친 문제는 플래시 메모리용 파일 시스템이 망가지는 문제였고, 이 문제가 해결되고 몇 달이 지나지 않아 메인 어플리케이션이 SIGSEV로 terminated 되는 문제가 발생하였는데, 프로그램이 죽는 위치가 직접 작성한 코드가 아니라 Rose RT가 제공하는 코드고, 직접 재현이 불가능하고 바다건너 미국에서만 발생한다고 하여 도무지 디버깅을 하기 쉽지 않은 문제였다. 두 문제 모두 몇 달에 걸친 디버깅 과정끝에 다행이 원인을 찾을 수 있었지만, 문제를 찾은 후 좀 더 빨리 문제해결하지 못한것에 대한 아쉬움이 남는다.

          문제를 해결하기위한 첫번째 과정은 문제를 재현하는 것이다. 문제 재현, 이것이 문제 해결에 가장 중요한 항목이지만, 실제 개발중에는 여러가지 이유로 문제 재현이 용이하지 않는 경우가 발생하곤 한다. 따라서 재현이 잘 되지 않는 문제가 발생하면, 해결하는데 오랜 시간이 걸리는 골치거리로 자라날 가능성이 크다. 따라서 문제 재현이 어려울 경우에는 문제를 재현하는 방법을 찾는 것이 문제를 해결하는 것이 된다.

          앞에서 언급한 두 문제 모두, 역시 해결의 열쇠는 "문제 재현"이었다. 첫번째 문제는 수만대의 제품이 시장에 팔려나간 상태에서 한달에 한번꼴로 문제 발생이 보고 되었지만 개발실에서는 좀처럼 문제를 재현시킬 수 없었고, 두번째 문제는 테스트 벤치에서는 발생하지 않고, 멀리 미국에 서만 재현되는 문제였다. 결국 두 문제 모두 결국 문제를 재현할 수 있는 환경이 갖추어진 후에야 해결되었는데, 이 과정에서 적잖은 근성을 소모해야 했다.

          운동 선수만 근성이 필요한게 아니다. 개발자들에게도 직장인에게도 근성이 필요하며, 적절한 실력을 인정받기 위해서 꼭 필요한 요소중의 하나로 감히 근성이 한자리를 차지할 것 같다. 사람들이 어려운 문제에 이르렀을때 이를 해결해 나가는 저마다의 노하우가 있지만, 때론 자신의 지식으로 바로 해결할 수 없는 문제에 부딪치게 된다. 이때 문제를 해결하는 열쇠는 다름아닌 "근성"이다. 근성이 없는 사람은 여러가지 핑게를 대며 문제를 회피하거나 외면하는 수순에 들어가게 되는데, 이때 그들은 나약함과 비굴함을 얼핏 밖으로 비치기도 한다.

          사람을 평가할 때 사용하는 중요한 지표로 attitude, 즉 태도를 사용하는데, 근성이 여기에 포함될 것이다. 즉 실력있는 사람으로 인정받기 위해서는 근성이 중요하다는 말이다. 박지성이 높이 평가받는 이유도 "근성"이 아닐까? 물론 근성만있고 문제 해결을 못한다면 문제가 있지만, 근성이 있는 사람이 문제 해결을 위한 지식을 쌓아왔을 가능성이 더 높지 않을까 ?

          앞에서 말한 골치거리 문제들을 해결할 때도 "근성"이 나의 친구가 되어 주었다. 아래에 나와 나의 친구 "근성"이 어떻게 문제를 해결했는지 정리하고자 한다.

          첫번째 문제는 수만대의 제품이 풀린 시장에서 한달에 한번정도만 보고되는 재현 빈도가 낮은 문제였다. 하지만 문제가 꾸준히 발생하고 있었고, file system이 corrupt되면 그 이 후 정상적으로 시스템을 사용할 수 없었기 때문에 꽤나 치명적인 문제였다.

          우리는 당연히 file system이 깨지는 것은 flash device driver나 file system library에 문제가 있는 것이라고 생각했지만, OS업체는 해당 driver와 library가 여러 업체에서 사용하지만 문제가 거의 보고된 적이 없기 때문에 s/w문제 보다는 flash device의 불량일 것이라고 추정하며 책임을 회피하고 있었다.

          당연한 수순으로, 문제 재현을 위해 우리 프로그램의 파일 접근 과정을 시뮬레이션하는 작은 테스트 프로그램을 만들어 며칠씩 반복해서 돌려보았지만 문제를 재현시킬 수 없었다.

          결국 문제를 해결하기 위해서 캐나다에 있는 OS개발 업체로 우리측 개발자 3명이 파견되었고, OS개발 업체 개발자 2명 및 여러명의 테스터가 할당되었다. 우리 개발자들이 우리쪽 s/w의 file access 하는 디자인을 OS쪽 개발자들에게 설명하여 주고, 반대로 OS쪽 개발자들도 flash device driver와 file system 구현 코드를 우리에게 설명하여 주었다. 재미나게도 우리는 서로 상대방의 프로그램에서 문제를 찾아내는데 집중하였다. (자신이 더 잘 알고 있는 자신의 코드에서 문제를 찾아내는게 더 쉬울것임에도 불구하고...)

          상호 프로그램에 대한 디자인 리뷰를 통해서 서로의 시스템을 대강 이해하게 되었다. 하지만 그 정도로 복잡한 s/w내에 잠자고 있는 문제를 찾을 수는 없었다. 이러면서 몇달이 흘러갔고 결국은 디버깅용 출력 코드를 삽입한 바이너리를 여러개의 제품에 넣어 expose 테스트를 하며 문제가 재현되기를 기다리기로 하였다. 그러나 문제가 몇 대의 테스트 제품에서 재현되기에는 너무 발생 빈도가 낮았고, 문제가 재현되어도 디버깅용 출력 코드가 충분한 정보를 제공한다는 보장은 어디에도 없었다.

          해결의 실마리는 의외로 엉뚱한 곳에서 나왔다. 해당 제품을 함께 개발한 다른 개발자들과 파일 접근 관련 경험을 문답하던 중 마지막 데이터 몇바이트를 유실하는 경험을 한 프로그래머가 있었는데, 마침 유실되는데이터가 꼭 필요한 데이터가 아니었기에, 아예 그 데이터를 저장하지 않는 방법으로 문제를 피해갔다는 것이었다. 그리고나서 나중에 자신이 알아낸 것은 fclose()를 하지 않고 프로그램을 종료시키면 문제가 발생하는 것 같다는 정보도 함께 주었다.

          확인 결과 fclose()를 호출하지 않으면 프로그램 종료시 마지막에 저장한 몇바이트 정도가 유실 되었고 이는fclose를 호출하지 않으면 flash write cache의 내용이 file system에 commit되지 않는 것으로 추정되었다. 하지만 이 경우 유저 데이터만 유실될 뿐 file system에는 문제가 없었다.

          하지만, 뭔가 이런 오동작과 이번 문제가 관련이 있을 것만 같아 이전에 작성한 시뮬레이션용 프로그램을 fclose를 하지 않도록 수정하여 프로그램을 돌렸고, 채 1분이 지나지 않아서 거짓말 처럼 해당 문제가 재현되었다.

          문제를 재현하는 테스트 프로그램을 OS업체에 보내자 OS업체는 하루만에 문제를 해결한 flash filesystem driver를 보내왔다. 문제는다소 복잡한 조건하에서만 발생하였는데, fclose()를 빼먹는 것이 필수 조건이었다. OS는 프로그램 종료시 어플리케이션이 사용한 모든 자원을 안전하게 반납하여야 하기 때문에 fclose() 하지 않아도 이를 안전하게 처리해 주어야 하지만, flash file system의 버그로 문제가 발생한 것이었다.

          해당 문제는 거의 몇개월이나 걸려서 해결이 되었고, 문제 해결에 대한 성취감도 대단하였다. 문제를 해결하기 위해서 여러가지 과거의 경험이 총 동원되었지만, 모두가 문제 해결에 지쳐가고 회의적으로 생각하는 시점에서 계속 문제 재현을 시도했던것은 나의 친구 "근성"이었던 것 같다.

          더 빨리 문제를 해결하지 못한것에 대한 아쉬움이 어쩔수 없이 내 뒤에 붙어있다. 하지만 아이러니한 것은, 문제가 오래끈 만큼 해결에 대한 주위의 인정이 커졌다는 것이다. 문제를 손쉽게 해결하였다면 누가 알아주었을까?


          두번째 문제는 미국의 테스트 환경에서만 발생하는 문제였다. 재현도 최소 30분~ 2시간 정도가 필요한 것으로 보고가 되었고, 한국에서는 재현이 전혀 되지 않았기 때문에, 몇 달동안 별다른 분석을 할 수 없었다.

          매주 고객은 업데이트된 보고서를 요청하는데, 미국에서 테스트한 결과 자료만 가지고는 도무지 원인을 찾을 수 없었다. SIGSEGV로 terminated될 때 출력되는 IP주소를 map파일에서 추적하여 문제 발생 함수명을 찾았지만, 해당함수는 Rose RT쪽 코드로 상용으로 널리 사용되는 부분이라 문제가 발생할 가능성이 아주 적은 곳이었다. 미국에 상주하는 테스트 팀에 요청하여 문제 발생시 core file을 dump하여 문제 발생 위치를 뒤진 결과 스택에 있는 포인터 변수에 이상한 주소 값이 들어가 있어, 해당 포인터 변수에 접근시 SIGSEGV가 발생하는 것이 확인 되었다. 하지만 core file에서 memory를 dump해 보아도 stack overflow의 흔적은 찾을 수 없었고, 해당 변수에 이상한 값이 들어가 있었다. 프로그램의 어디에선가 포인터를 잘못 사용한 것으로 추정되었지만, 수십만 라인의 코드에서 해당 위치를 찾기란 불가능에 가까웠다.

          결국은 문제가 재현되는 미국으로 출장을 갈수 밖에 없었고, 출장을 와서도 특별히 뾰족한 수가 있는 상황이 아니었다. 최후로 선택한 방법은 divide and conquer로 프로그램내의 어플리케이션 모듈을 하나씩 실행되지 않도록 수정하여 재 컴파일 하고 이를 실행하여 문제 발생 여부를 테스트 하였다.

          오랜 시각의 재현 테스트(한번에 40분정도 걸리는...) 끝에 18번째 재현 테스트만에 GPS관련 application을 disable하였을 때 문제가 발생되지 않음이 확인되었고, 해당 모듈을 디자인 리뷰한 결과 해당 프로그램 코드에서 구조체의 항목중 배열로된 어떤 항목을 사용하면서 배열의 index를 무한히 증가시킴을 확인하였다. 따라서 구초체 크기만큼씩 건너뛰면서 메모리에 값이 덮어써졌고, 이 과정이 20분정도 반복되면 그때가 되서야 시스템이 SIGSEGV로 죽었던 것이다.

          이 문제를 발견하기 위해서 한국에서 내가 알고 있는 모든 급 디버깅 기법을 사용하여 문제를 분석하였지만, 전혀 문제의 핵심에 접근하지 못하였다. 메모리 오류를 잡아주는 각종 디버깅용 라이브러리를 사용하여 미국에 테스트를 요청하여도 전혀 문제는 검출되지 않았고, 결국 문제 해결은 문제가 재현되는 환경에서 "노가다"에 가까운 반복 재현 테스트로 문제 발생 원인을 좁혀가는 과정을 통하여 이루어졌다. 다행이도(?) 문제가 한시간 안에 한번은 재현이 되었기 때문에 이러한 방법이 가능하기는 했지만, 그래도 여전히 지난한 과정이었고 "근성"으로 이를 진행할 수 밖에 없었다.

          (개발 환경이 리눅스등의 환경이었으면 좀 더 강력한 메모리 오류 검출 툴들을 사용해 볼 수 있었겠지만, 애석하게도 이런 툴이 돌아가지 못하는 환경이었다. )

          아마도 문제 재현이 더 어려원다면, 문제를 더 자주 재현시키는 다른 방법을 찾아야 했을 것이고, 이는 더욱 지난한 과정이 되었을 것이다. 이런 경우가 발생하지 않은 걸 신에게 감사할 수밖에...

          2009년 3월 2일 월요일

          do(...}while(0)의 또다른 용도

          [#define에서 do{...}while(0)을 사용하는 이유는?] 에서 소개한 이유 외에 do{...}while(0)이 유용하게 사용될 수 있는 경우가 있다. 바로 goto를 사용하지 않고 중첩된 조건문을 탈출하기 위해서 do{...}while(0)을 응용할 수 있다. (옳건 그르건 간에)많은 사람들이 goto를 사용하는 것을 금기시 하기 때문에 do{...}while(0)문은 goto를 써야만 하는 경우에 좋은 대안이 될 수 있다.

          바로 예제를 보고 이해하자.

          if(...)
          {
            if(...)
            {
             ....
             goto ESCAPE;
            }
          }
          ESCAPE:


          위코드는 아래와 같이 goto없이 작성이 가능하다.

          do{
            if(...)
            {
              if(...)
              {
                 ....
                break;
              }
            }
          }while(0);

          2009년 1월 21일 수요일

          라이브러리 이해하기 2 - 공유 라이브러리 버전 관리.

          공유 라이브러리는 보통 아래와 같이 명명된다.

          libMyLibrary.so.1.2.3

          라이브러리는 용도에 따라 몇가지 서로 다른 이름을 가지고 있는데 혼돈되기 쉬우니 주의가 필요하다.

          1. 링커 이름.(linker name)

          링커 이름은 라이브러리 파일 명에서 버젼을 나타내는 숫자를 뺀 so까지의 이름이다. 즉 위에서는 libMyLibrary.so까지가 linker name이 된다. 링커 이름은 해당 라이브러리를 사용하는 프로그램을 빌드 할 때 사용되는 이름이다. 더 정확히 말하면, 프로그램이 빌드될 때 해당 프로그램이 사용하는 공유 라이브러리를 찾기 위해 링커 이름을 가진 파일이 빌드하는 시스템에 있어야 한다. 예를들어 어떤 어플리케이션이 libMyLibrary.so.1.2.3을 사용하여 컴파일 할 경우 빌드 옵션에 -lMyLibrary를 추가하게 되는데 이때 기본 라이브러리 경로나 혹은 –L 옵션을 사용하여 지정한 디렉토리에 libMyLibrary.so 파일, 즉 링커 이름으로 된 라이브러리 파일이 있어야 한다. (보통은 libMyLibrary.so.1.2.3에 대한 심볼링 링크로 이 파일을 만든다 이렇게 하는 이유는 호스트 개발 환경에 해당 라이브러리의 여러 버젼이 존재할 경우 이를 관리하기 위함이다. ) 크로스 컴파일을 하는 환경에서는 빌드시에만 링커 이름을 가진 파일이 필요하며, 타겟에서 실행 시에는 이 파일이 필요치 않다. 즉 개발하는 호스트 컴퓨터에는 라이브러리 파일 이름이 링커 이름으로 되어 있거나 이를 심볼링 링크로 만들어야 한다. 컴파일러/링커는 링커 이름을 가진 파일에서 이 라이브러리의 soname을 읽어오게 된다. 따라서 공유 라이브러리는 빌드시 실행 파일에 포함되지는 않지만 soname을 읽어와야 하기 때문에 공유 라이브러리가 빌드하는 호스트 시스템에 없으면 빌드가 되지 않는다.

          2. soname

          이 이름은 로더가 라이브러리를 실행할 때 찾는 이름으로 타겟에는 soname을 가진 파일이 반드시 있어야 한다. 때때로 이 파일은 심볼릭 링크로 다른 파일로 링크되어 있다.

          일반적으로 soname은 so뒤에 숫자 하나를 추가하여 만든다.  예를 들어  libMyLibrary.so.1 가 위 라이브러리의 soname으로 적합하다. 아래와 같은 명령으로  soname을 지정할 수 있다.

          gcc –shared –Wl,-soname libMyLibrary.so.1 –o libMyLibrary.so.1.2.3 MyLibrary.o

          so뒤에 오는 첫번째 숫자는 라이브러리의 Major version number로 일반적으로 호환성이 변경되는 경우에 증가시킨다. 이렇게 라이브러리를 빌드하고 또 이 라이브러리를 사용하는 프로그램을 빌드하면 해당 프로그램 실행 시 로더에 의해서 soname을 가진 파일을 라이브러리 경로에서 찾게 된다. 즉 프로그램이 실행될 때에는 soname을 가진 라이브러리 파일이 필요하다. 크로스 컴파일을 하는 환경에서는 빌드 시에는 soname을 가진 파일이 아닌 링커이름을 가지는 파일(일반적으로 xxx.so로 끝나는 파일)이 필요하고 실행시에는 soname을 가지는 라이브러리(혹은 이에 대한 심볼릭 링크 )가 필요한 것이다.

          라이브러리 full name에서 두번째 숫자는 라이브러리의 minor version number로 호환성에는 변경이 없지만 새로운 함수등이 추가되는 경우에 변경되며, 세번째 자리는 release 버전으로 버그 수정 등으로 라이브러리가 새로 릴리즈되는 경우에 변경된다.

          따라서 크로스 컴파일 환경에서, 호스트에서 공유 라이브러리 libMyLibrary.so.1.2.3 를 만들어 타겟 시스템에 넣을 경우 타켓 시스템에는 아래와 같이 soft link file을 만들어 soname을 가진 파일을 만들어야 한다.

          ln –s libMyLibrary.so.1.2.3 libMyLibrary.so.1
          or
          mv libMyLibrary.so.1.2.3 libMyLibrary.so.1


          또한 해당 라이브러리를 사용하여 프로그램을 만드는 모든 개발자는 자신의 빌드 환경에 아래와 같이 공유 라이브러리가 링커 이름을 가지도록 설정하여야 한다. (즉 .so.1 파일은 호스트(빌드)시스템에서는 필요 없다)

          ln –s libMyLibrary.so.1.2.3 libMyLibrary.so
          or
          mv libMyLibrary.so.1.2.3 libMyLibrary.so

          특정 라이브러리의 soname을 확인하기 위해서는 아래와 같은 명령을 사용할 수 있다.

          objdump -p libMyLibrary.so.1.2.3 | grep SONAME

          더 자세한 내용은...
          http://wiki.kldp.org/wiki.php/DocbookSgml/Program-Library-HOWTO#DL-LIBRARIES

          od와 xxd

          윈도우 환경에서 사용 가능한 몇가지 hex editor가 존재하듯 유닉스/리눅스 환경에서도 몇가지 방법으로 바이너리 파일을 hex 모드로 읽고 편집할 수 있다. emacs가 이를 지원하는 대표적인 유닉스 환경에서의 에디터이지만, 여기서는 다른 방법을 소개한다.

          먼저 바이너리 파일을 hex 모드로 읽기만 하는 경우에는 xdd나 od를 사용하면 된다.

          xxd -g1 filename
            or
          od -tx1 -Ax filename

          두 프로그램은 거의 동일한 포맷으로 출력을 보여주지만 od가 좀 더 포맷을 자유롭게 지정할 수 있다. -tx1는 type을 16진수로 해서 1바이트 단위로 표시하라는 뜻이며, -Ax는 Address표시를 16진수로 지정하는 옵션으로 위 예와같이 데이터와 어드레스의 표시 형식을 서로 다르게 지정할 수도 있다. od는 octal dump의 약자로 옵션을 주지 않을 경우 8진수로 출력된다.

          od가 여러가지 타입으로 출력을 지원하는 것과 달리 xxd는 오직 16진수 형식만 지원한다. -g옵션으로 출력 바이트 단위를 지정해줄 수 있으며 기본값은 2바이트 단위이다.

          바이너리 파일을 수정하기 위해서는 vi와 xxd를 함께 사용하면 된다. xxd는 -r 옵션을 제공하는데 이는 reverse 기능으로, 원래 xxd가 바이너리 파일을 16진수 텍스트 형태로 변경해주는 프로그램인데 -r옵션을 사용하여 실행하면 16진수 텍스트를 그대로 다시 바이너리로 변경해 주게 된다. 따라서 바이너리 파일을 헥사 에디팅하려면 vi에서 xxd를 사용하여 바이너리를 16진수 텍스트 형태로 바꾸에 표시하여 주고, 이를 수정한 후 xxd -r을 사용하여 다시 바이너리 형태로 바꾸어준 다음 저장하면 된다.

          먼저 vi 실행 시 -b옵션을 사용하여 수정하고자 하는 바이너리 파일은 연다. 이 옵션을 사용하게 되면 vi에서 binary 파일을 열 때 문제를 야기할 수 있는 여러가지 vi 기능들이 모두 turn off 된다.

          vi -b filename

          이후 아래와 같이 파일을 hex 포맷으로 변환한다.

          :%!xxd

          이제 헥사 포맷으로 변환된 텍스트를 수정한다. 수정시에는 데이터를 insert나 delete하면 안된다. (아쉬운 제약 사항이지만 어쩔 수 없다.) 따라서 r 명령을 사용하여 데이터를 원하는 값으로 한자씩 수정하는 것이 좋다. 수정이 끝나면 아래 명령으로 이를 다시 바이너리 데이터로 바꾸면 된다.

          :%!xxd -r

          -r 옵션으로 xxd를 실행시에는 16진수로 표시되는 데이터 영역의 값들만 참조되기 때문에 vi에서 16진수 데이터영역 외에 주소 영역이나 아스키로 표시되는 영역을 수정하더라도 이는 무시된다.

          이제 데이터를 저장하고 나가면 된다.

          :wq

          수정사항을 xxd나 od로 확인해 볼 수 있다.

          od -tx1 -Ax filename more +20 -10

          라이브러리 이해하기 1.

          유닉스나 리눅스 환경에서 라이브러리는 크게 세가지로 나눌 수 있다.

          - 정적 라이브러리
          - 공유 라이브러리
          - 동적 적재 라이브러리

          정적 라이브러리는 .a로 끝나는 파일로 빌드 시 실행 파일에 포함되게 된다. 정적 라이브러리를 사용하는 프로그램을 만들기 위해서는 해당 라이브러리가 extern하는 함수가 선언된 헤더 파일과 해당 라이브러리가 필요하다.

          공유 라이브러리는 .so로 끝나는 파일로 빌드 시 실행 파일에 포함되지 않는다. 따라서 여러 프로그램이 공유하는 기능을 공유 라이브러리로 만들 경우 디스크 공간을 아낄수 있고 공유 라이브러리에 버그가 존재할 경우 공유 라이브러리만 재배포하여 문제를 수정할 수 있다. 또한 공유 라이브러리가 이를 사용하는 프로세스에 의해 메모리에 로드되면, 해당 공유 라이브러리를 사용하는 또다른 프로세스가 실행되었을 때 이전 프로세스가 로딩해 놓은 내용을 공유하여 사용하기 때문에 메모리 사용량 또한 절약된다.
          공유 라이브러리를 사용하는 프로그램을 만들기 위해서는 해당 라이브러리가 extern하는 함수가 선언된 헤더 파일과 해당 공유 라이브러리가 필요하다. 공유 라이브러리는 실행 파일이 실제로 시스템에서 실행되는 시점에 프로그램 링커 로더가 /lib, /usr/lib, 그리고 LD_LIBRARY_PATH등에서 해당 파일의 존재유무를 확인하고 이를 사용되게 된다. 공유 라이브러리는 앞서 말한바와 같이 링크되는 시점(해당 공유 라이브러리를 사용하는 어플리케이션을 빌드하는 시점)에 공유 라이브러리의 헤더와 라이브러리( .so)파일이 필요하다. 왜 실행파일에 포함되지 않는 라이브러리 파일이 필요한 것인까? 이는 빌드되는 어플리케이션이 실행될 때 찾아야 할 라이브러리의 이름이 공유 라이브러리 파일 안에 적혀있기 때문인데 이를 soname이라고 하고 이 soname은 라이브러리의 파일명하고 다를 수 있으므로 서로 구분하여야 한다. 공유 라이브러리를 컴파일할 때에는 아래와 같이 –fPIC (Position Independent Code)옵션이 필요하다.

          gcc –fPIC –c MyLibrary.c
          gcc –shared –Wl,-soname libMyLibrary.so.1 –o libMyLibrary.so.1.2.3 MyLibrary.o


          동적 적재 라이브러리는 실행 중에 동적으로 로딩되는 라이브러리며, 프로그램 실행시가 아닌 해당 라이브러리에 포함된 함수가 사용되는 시점에 불려와 사용되게 된다. 따라서 동적 라이브러리를 사용하는 프로그램은 첫 실행이 정적 혹은 공유 라이브러리를 사용할 때 보다 빠르다는 장점이 있다. 또한 동적 적재 라이브러리는 실행 시 동적으로 라이브러리에서 직접 정보를 얻어와 실행한다. 따라서 동적 적재 라이브러리를 사용한 어플리케이션을 빌드하는 시점에서는 해당 라이브러리에 대한 헤더 파일이나 심지어 해당 라이브러리 자체도 필요 없다. 리눅스에서는 일반 공유 라이브러리를 동적으로 적재가 가능하므로 공유 라이브러리와 동적 적재 라이브러리가 같다고 할 수 있다. 다만 이를 사용하는 방법으로 구분될 뿐이다. 즉 so파일을 공유 라이브러리도 사용할 수도 동적 적재 라이브러리로 사용할 수도 있으며 동적 적재 라이브러리로 사용하려면 라이브러리를 사용하고자 하는 프로그램에서 dlopen, dlsym등을 사용하여 원하는 심볼을 불러오도록 처리하면 된다.

          대부분의 unix/linux 시스템에서 LD_LIBRARY_PATH가 사용 가능하지만 모든 시스템에서 그런 것은 아니다. 리눅스에서는 LD_LIBRARY_PATH 사용이 가능하지만, /etc/ld.so.conf 파일과 ldconfig을 사용하여 /etc/ld.so.cache 파일을 생성하는 방법이 더 권장된다.

          또다른 방법으로는 rpath를 지정하는 방법이 있다. rpath는 실행 파일을 compile할때 지정해 주면 된다.

          gcc -Wall -o myexefile -Wl,rpath,. main.o -L. -lMyLibrary

          위 예제에서는 rpath를 현재 디렉토리 [.]로 설정하였으며 이는 실행할 때 MyLibrary를 찾는 위치에 현재 디렉토리를 추가한 것이다.  -L옵션은 링크시에 MyLibrary를 찾는 위치를 지정하는 옵션으로 역시 [.] 현재 디렉토리로 지정하였다. 굳이 -l 옵션으로 라이브러리를 지정할 필요 없이 경로를 포함한 파일 이름을 사용하여도 된다.

          gcc -Wall -o myexefile -Wl,rpath,. main.o ./libMyLibrary.so.1.2.3

          2009년 1월 11일 일요일

          MTD 이해하기...

          MTD는 memory technology device의 약자로, char. device, block device와 같은 별도의 디바이스그룹이라고 간주하는것이 맞을 것 같다. 전통적으로 OS에서 장치를 char. device 와 block device 로 나누어왔기 때문에 flash memory용 디바이스 드라이버를 둘 중 어떤 것으로 분류 시켜야 하는지 고민이 생기게 된다, MTD는 분명 hdd를 대체하는 장치로 사용되고 있지만, 그 동작 특성이 block device와는 현저히 다르기 때문에 굳이 이를 block device 로 부르는 것은 옳지 않으므로 그냥 MTD 라고 부르는 것이 맞을 것 같다. MTD가 선보이기 시작하였을 때 개발자들은 hdd를 대신하여 MTD를 사용하기 위해서 MTD에 기존의 파일 시스템을 올리는 방법을 궁리하였고 가장 손쉬운 방법으로 고안한 것이 FTL 이다. FTL은 Flash devcie 를 block device처럼 보이도록 변환해주는 Layer로 이를 통해서 기존의 block device용으로 개발된 파일 시스템을 변경없이 flash memroy위에서 사용할 수 있게 되었다. 하지만, FTL 을 얼마나 잘 구현했는지에 따라서 성능이나 flash device의 수명이 큰 영향을 받게 된다. 실제로 가장 손쉬운 FTL구현을 생각해 보자, 특정 data를 overwrite하기 위해서 flash는 해당 sector를 모두 지우고 새 데이터를 포함한 sector전체를 다시 write해야 한다. 단지 한바이트의 데이터를 바꾸기 위해서 매번 해당 바이트가 포함된 sector전체를 지우고 다시쓰도록 FTL을 구현한다면, 손쉬운 구현이 되겠지만 flash device의 수명(10만~100만번 erase)이 아주 짧아질 수 밖에 없을 것이다. 따라서 다양한 방법을 사용해서 모든 flash sector가 균등하게 사용되도록 FTL을 구현해야 하며, 이를 Wear Leveling이라고 부른다.

          USB drive같은 몇몇 flash devices는 FTL을 아예 H/W로 구현하여 제품에 내장하여 출시하고, 이런 장치는 MTD가 아닌 block device로 보는 것이 타당하다. 그렇지 않은 Flash device에서는 FTL을 소프트웨어로 구현하게 된다.

          MTD는 때로는(오히려 더 자주, 일반적으로) device차체가 아닌, device driver를 지칭하는 말로 사용된다. 이 경우에는 MTD는 FTL이 해당 Device를 control 할 수 있도록 도와주는 Low Level Driver라고 생각하면 된다. 제조사 별로, device별로 device를 제어하는 방법이 조금씩 다르기 때문에 MTD Layer에서 이를 일반화하여 주는 것이다. 따라서 FTL은 하위 device의 제조사나 특정 제품에 상관 없이 구현 될 수 있는 것이다. 아래는 FTL 을 사용하는 시스템의 구조를 표현한 것이다.

          File System for block device
          -----------------------------
          FTL
          -----------------------------
          MTD Layer

          최근에는 flash device를 위해서도 몇몇 파일 시스템이 개발되었으며, 이 경우 FTL이 없이 file system이 MTD Layer위에 올리기도 한다.

          File System for flash device
          -----------------------------
          MTD Layer

          하지만 필요에 따라서 flash file system이 FTL을 포함하고 있는 경우도 있으며, 이 경우의 FTL은 위에서 설명한 FTL과는 약간 다른 일을 수행하게 된다. 즉 최초의 FTL은 flash device를 block device로 변환시켜주기 위해 사용한 library 나 h/w를 의미하였으나, 최근에는 좀더 광범위한 의미로 사용되고 있다.

          읽어볼만한 자료들
          http://www.linux-mtd.infradead.org/faq/general.html
          http://blog.naver.com/idkiss?Redirect=Log&logNo=50018755390
          http://lsea.tistory.com/160
          http://www.hongikcom.com/entry/4FTLFlash-Translation-Layer

          2009년 1월 10일 토요일

          #define에서 do{...}while(0) 을 사용하는 이유는?

          #define을 사용한 매크로 함수에서 do{...}while(0) 문을 사용하는 경우를 볼 수 있다. 왜 굳이 의미없는 do{...}while(0)를 사용하는지 아래 예제를 보면서 살펴보자.

          //do{...}while(0)문을 사용하지 않은 경우
          #define Inc2Each(x,y) { x+=2;y+=2;}

          //do{...}while(0)문을 사용한 경우
          #define Inc2Each(x,y) do{ x+=2;y+=2}while(0)

          do{...}while(0)을 사용하지 않은 첫번째 방식의 경우 어떤 문제가 사용하는지 살펴보자

          if ( x > y )
           Inc2Each(x,y);
          else
           x=y;

          위 코드는 아래와 같이 확장될 것이다.

          if ( x > y )
           { x+=2;y+=2;};
          else
           x=y;

          즉 if 와 else 사이에 원치 않는 ; 가 포함됨으로써 예상치 못했던 오류를 만들어 내게 된다.
          하지만 do{...}while(0)을 사용하면 아래와 같이 문제가 깨끗하게 해결된다.

          if ( x > y )  
           do{ x+=2;y+=2;}while(0);
          else  
           x=y;

          혹시 아직도 뭐가 문제인지 모르겠다면...

          if ( x > y )
          { 
           x+=2;
           y+=2;
          }     //if 문은 여기서 끝난다.
          ;     //빈 라인이 되고...
          else    //이 else는 if문이 없는 else가 되므로 컴파일 에러 발생할 것임.
           x=y;

          자세한 내용은 http://kernelnewbies.org/FAQ/DoWhile0