본문 바로가기

Programming/C & C++

Application development on AIX

http://www-903.ibm.com/kr/techinfo/pseries/tech/tech_16.html

김세희 kseh@kr.ibm.com

어떤 UNIX시스템에서도 원시코드로부터 프로그램을 개발하여 이것을 실행할 수 있도록 실행 가능한 프로그램을 만들고 프로그램의 오류를 수정해 나가는 과정은 비슷하다고 할 수 있다. AIX에서는 일반적으로 사용하는 Open Source Environment가 아닌 AIX고유의 개발툴을 사용하므로, AIX에서 개발이나 포팅 작업을 하려는 사용자는 각각의 개발툴의 이름을 숙지하고 명령어 사용법을 알아두어야 할 필요가 있다. 그러나 전혀 다른 환경은 아니므로, 만약 Open Source Environment에 익숙한 개발자라면 AIX의 개발환경에 익숙해지는데 그다지 오랜 시간이 걸리지 않을 것으로 기대된다.


프로그램의 일반적인 개발과정은 다음과 같다.


C, C++, PASCAL, FORTRAN등으로 작성된 원시 프로그램

↓Compiler

Object Code

↓Liker

Executable Code

↓Debugger

오류를 수정한 완전한 실행파일


여기에서는 AIX에서 사용하는 컴파일러와 디버거의 사용법, 그리고 make파일을 만드는 법에 대하여 알아보겠다.



Source Code Compile


소스 코드를 compile하기위해서는 반드시 compiler가 필요하다.

AIX에서 사용할 수 있는 compiler로는 FORTRAN compiler인 xlf, PASCAL Compiler인 xlp, C++을 compile할 수 있는 xlC , 그리고 가장 널리 사용되는 C compiler인 cc,xlc,c89가 있다. 이 중 C compiler cc,xlc,c89 는 실제로는 같은 compiler를 가리키고 있지만 어떤 이름으로 부르는가에 따라 다른 식으로 source code를 해석하며, compile option 설정이 약간 다르다.

cc로 실행시키면 Kernighan and Ritchie format으로 해석하며, xlc나 c89로 실행시키면 ANSI C format으로 해석한다. 각각의 compiler를 부를 때 어떤 식으로 실행시킬 지에 대한 option은 /etc/xlC.cfg에 적혀있다. (5.0 compiler같은 경우는 /etc/vac.cfg에 있다)


a) cc 기본사용법


$ cc test.c

test.c를 compile하여 a.out으로 만든다.


$ cc -c test.c

test.c를 compile하여 test.o 로 만든다.


$ cc -o test test.c /usr/lib/libm.a

test라는 이름의 실행파일로 test.c를 compile하며 libm.a와 link시킨다.


b) cc에서 사용하는 중요한 option들


-g 소스 level의 디버깅을 가능하게 한다

-O optimize option이며 -O2와 같다

-O3 memory 와 compile time intensive optimization을 수행. 더 강력한 최적화를 위해 프로그램의 semantic을 바꾸기도 한다.

-qstrict option을 사용하여 semantic은 바꾸지 못하도록 지정할 수 있다

-O4 -O4 -O3 + -qipa 와 같다. 플랫폼에 가장 최적화된 최적화를 수행한다. 적당한 수준의 optimization

-qipa interprocedural analysis

-Q 지정된 숫자만큼의 길이의 함수는 inline을 시켜서 최적화한다.

Default로 15줄 이하의 함수는 inline시킨다 ex) -Q=12

-l link할 library이름 ex) -l libkey.a

-L library path를 지정한다 ex) -L/usr/lib

-i include file directory를 지정한다

-S assembler file을 생성한다

-v verbose option

-p prof를 사용하여 runtime profiling을 가능하도록 한다.

어떤 모듈에서 CPU time이 얼마나 걸렸는지 메모리 사용량은 얼마나 되는지 등을 측정할 수 있으므로 tuning시 사용할 수 있다

-D preprocessor에서 사용할 symbol을 정의한다 ex) -DMAX_NUM

-U symbol을 undefine한다

-M make의 dependency에 포함될 정보들을 만들어 낸다.

Output은 [filename].u file로 출력된다


Compiler에서 프로그램에 대해 만들어내는 정보를 특정 list파일로 보낼 수 있으며 정보는 sourcename.lst에 들어간다.


-qattr Symbol의 attribute정보

-qxref Cross reference listing

-qlist object code의 list

-qlistopt 실제 영향을 주는 compiler option의 list



Compile Management - Make


a) Why Make?


C로 작성된 source code를 실제 실행 가능한 프로그램으로 만들기 위해서는 크게 다음과 같은 두 단계가 필요하다.


1. c compiler로 compile과정을 거쳐 object program을 만든다.

2. library파일 및 관련 있는 파일끼리 linking작업을 한다.

이때 개발과정에서 끊임없이 source program의 수정 및 재 컴파일 작업이 발생한다. 그 뿐 아니라 현대의 커다란 프로젝트를 수행하는 프로그램은 여러 사람이 같이 작업하는 경우가 많으며, 한 개의 파일이 여러 개의 파일이 연관되어 한 부분의 수정이 다른 파일의 여러 부분에 영향을 줄 수 있다.

따라서 이 복잡한 과정을 좀더 쉽게 관리하도록 하며, 개발자가 프로그램의 실행환경과 무관하게 프로그램의 올바른 수행에만 힘쓸 수 있도록 하는 utility인 make가 필요하다. 프로그램을 구성하는 파일의 연관관계 및 구조는 또 다른 파일의 형태로 make 프로그램에 전달하고 실제 프로그램의 관리는 make가 담당하도록 한다. 이러한 목적으로 파일간의 연관성을 기록한 파일이 Makefile 혹은 makefile이다.


b) make 기본사용법

$ make

Makefile이나 makefile이 현재 디렉토리에 있는 경우 자동으로 그 파일을 읽어서 실행한다


$ make [target]

Makefile내의 특별한 target이나 action을 지정하는 경우 make 다음에 label의 이름을 쓴다.

ex) make clean ; make install


$ make -f [filename]

makefile로 Makefile이나 makefile이 아닌 다른 파일이름을 사용했을 때 사용한다.


$ make -s

makefile중간에 출력되는 output이 보기 싫으면 위의 예처럼 사용한다.


c) Makefile의 기본문법

Target : precondition

precondition은 target에 의존하는 파일을 말한다. 즉 target이 바뀌면 재 compile해야 하는 파일들을 말한다.

[TAB]action

shell로 전달되어 실행되는 행이며 하나 이상의 action은 ; 로 분리한다.

한 행의 action은 고유의 shell environment를 가지며 다른 action에 영향을 주지 않는다.

단 Directory는 HOME변수에 의존하지 않고 make를 실행한 디렉토리를 유지한다.

EX) cd /user; rm a.out;


이 경우 /user의 a.out을 지우는 것이 아니라 현재 make를 시작한 디렉토리의 a.out을 삭제하게 된다. 이렇게 하지 않으려면

cd /user; rm a.out

혹은

cd /user; \

rm a.out;

이라고 하여 directory를 유지할 수 있다.


이 두 가지의 기본 문법 외에 target부분에 label만 있다면 make의 target으로 해석하는 방법이 있다.

EX) make all , make install , make clean


주석문을 달 때는 앞에 # 을 쓰면 줄이 바뀔 때까지 주석으로 해석한다.

EX) # THIS IS A COMMENT


action의 중간중간에 나오는 메시지가 너무 많아서 해독이 어려울 때는 명령어의 앞부분에 @을 붙이면 된다.

EX) @cc -o test ${SOURCE}


d) 간단한 Makefile 작성하기


Program이라는 프로그램을 compile한다고 가정해보자.

구성파일은 input.c, sum.c, mean.c, output.c이며 output.c는 XYplane.c를 참조하며, 전체 실행파일은 /usr/lib/libc.a을 참조한다.

Program이라는 application을 작성하려면 다음과 같은 과정을 매번 일일이 타이핑해야 한다.


$ cc -c input.c

$ cc -c sum.c

$ cc -c mean.c

$ cc -o output.o output.c XYplane.c 

$ cc  -o program input.o sum.o mean.o output.o \        /usr/lib/libc.a



위의 과정을 자동화하는 Makefile은 다음처럼 작성할 수 있다.


① program : input.o sum.o mean.o output.o                                   

②                cc -o program input.o sum.o mean.o output.o \                  

③                /usr/lib/libc.a                                                                        

④ output.o : output.c XYplane.c                                                 

⑤                 cc -c output.o output.c XYplane.c                                     

⑥ input.o : input.c                                                                     

⑦                cc -c input.c                                                                  

⑧ sum.o : sum.c                                                                        

⑨              cc -c sum.c                                                                          

⑩ mean.o :mean.c                                                                                

⑪              cc -c mean.c                                                                     



이제 위의 Makefile을 한 줄씩 살펴보자.

① program을 만들기 위해 input.o, sum.o, mean.o, output.o 네 개의 파일이 필요하다.

② program을 만들기 위해서 cc compiler로 필요한 네 개의 파일을 모두 compile한다.

③ 2번째 줄에서 파일의 이름을 다 쓸 자리가 모자랐으므로 \ 을 치고 다음 줄까지 연장했다.

역시 program을 만들기 위해서는 네 개의 object file외에도 libc.a가 필요하다.

④ program을 만들기 위해 필요한 네 개의 파일 중 output.o를 만들기 위해서는 두개의 c 파일이 필요하다.

⑤ output.o를 만들기 위해서는 다음과 같은 명령어를 수행해야 한다.

cc로 output.c와 XYplance.c를 compile한다.

⑥ input.o를 만들기 위해 input.c가 필요하다.

⑦ input.o를 만들기 위해서 cc로 input.c를 compile해야 한다.

이후는 비슷한 내용의 반복이므로 생략하도록 하겠다.


참고로 Makefile은 크게 macro/명령어 정의/dependency의 세 부분으로 나누어 작성하는데, 이때 Makefile 뒷부분에 나오는 dependency를 작성할 때 파일간의 참조관계를 일일이 만들어내기가 쉽지 않다. 이 과정을 쉽게 하기 위해 compiler의 기능을 이용할 수 있다.


$ cc -M test.c

test.u로 파일의 의존관계가 출력되면, 이 파일의 내용을 그대로 cut-and-paste하여 Makefile의 끝부분에 넣어주면 된다. 그러면 test.c가 어떤 파일을 참조하는지 header file과 소스 파일의 dependency를 알 수 있으므로, 그 중의 한 개 이상의 파일이 변경되면 그때 test.c를 다시 compile하여 resource의 낭비를 막을 수 있다.

gccmakedep같은 프로그램을 사용하면 makefile에 자동적으로 dependency를 붙여 줄 수 있다.


dep :

gcc makedep $(SRCS)

$ make dep


e) 매크로


비슷비슷한 명령어나 파일의 이름을 macro로 만들어서 기억시키면 프로그램의 유지보수에 도움이 될 뿐만 아니라 복잡한 작업을 간단하게 할 수 있다.


매크로 이름 = 문자열


매크로를 사용할 때는

${매크로 이름}이나 $(매크로 이름)으로 사용하며, 위의 매크로를 정의할 때 오른쪽에 대입된 내용이 그대로 치환된다.


EX) CC=cc

      CFLAGS= -g -O

      SOURCE = a.c b.c c.c d.c 

      output : 

         ${SOURCE}           

         ${CC}        

         ${CFLAGS} -o output ${SOURCE}


매크로로 정의된 문자열은 특정부분이 다른 문자열로 바뀔 수도 있다.


SOURCE = a.c b.c c.c d.c

OBJECTS = ${SOURCE:.c=.o} <- OBJECTS = a.o b.o c.o d.o


다음 Makefile은 매크로를 사용하여 좀더 보기 좋게 만들었다.


OBJECTS = main.o read.o write.o test : $(OBJECTS)  gcc -o test $(OBJECTS) \

      main.o : io.h main.c gcc -c main.c read.o : io.h read.c  gcc -c read.c \

      write.o: io.h write.c gcc -c write.c clean : rm $(OBECTS)


참고)

중요 매크로 일일이 update하기 힘들거나 많이 사용하는 것은 특수문자를 사용하여 매크로로 쓰고 있다. Makefile을 어렵게 보이도록 하는 가장 큰 원인이므로 각각의 의미를 잘 알아두어야 한다.


$* target보다 나중에 변경된 precondition들의 파일이름.

확장자는 포함되지 않는다 .SUFFIXES부분에서만 사용하고 보통 때는 사용하지 않는다.

$< target보다 나중에 변경된 precondition들의 파일이름. .SUFFIXES에서만 사용한다.

$$@ target : dependency에서 왼쪽 label의 이름.

현재 사용하고 있는 target파일의 이름. : (colon)의 오른쪽에서만 사용가능

$@ 현재 사용하고 있는 target파일의 이름

$? target보다 나중에 변경된 precondition들의 파일이름.

SUFFIXES부분에서 사용하지 않고 보통 때 사용한다.

$% 현재의 target이 library 모듈인 경우 일치하는 '.o'파일의 이름


f) Suffix rule


UNIX에서 사용하는 일반적인 확장자 규칙관례에 따라 확장자 이름을 특별히 바꾸는 일이 없다면 make자체의 suffix rule로 작업을 수행하도록 할 수 있다.

예를 들어 대부분의 경우 [filename].c 파일은 [filename].o 파일로 compile한다. 이렇게 대부분의 경우 적용되는 rule은 make가 자체적으로 가지고 rule에 따라 처리한다.


.c.o :        ${CC} ${CFLAGS} -c $<


make내부에서 자체적으로 정의하고 있는 suffix rule을 보고 싶다면 다음과 같이 한다.

# make -p


Makefile내부에서 .SUFFIXES 매크로의 값을 세팅해 주면 내부적으로 정의된 확장자의 연산이 동작을 하게 되며, 확장자를 보고 어떤 suffix rule을 적용할지는 .SUFFIXES행의 확장자 순서를 따른다. 예를 들어 test.o를 생성하기 위해 test.c를 compile할 것인지 test.f를 compile할 것인지는 .SUFFIXES에서 앞 쪽부터 나오는 순서에 따른다.

Target부분에 확장자가 한 개만 나오는 경우는 실행파일이 단 한 개의 source file로 만들어 지는 경우 object file까지 만들 필요가 없기 때문에 바로 실행 파일을 만들 수 있도록 하는 경우이다.


EX)  .c :        ${CC} ${CFLAGS} @{LDFLAGS} $< -o $@


g) Makefile해석순서


1. precondition의 파일들을 찾는다.

2. precondition을 target으로 하는 행이 있는지 찾는다.

3. 가장 하부로 내려갈 때까지 1과 2를 반복

4. 더 이상 없으면 precondition의 확장자가 make에서 미리 정의된 .SUFFIXES행에 있는지 본다.

5. .SUFFIXES행에 있으면 확장자 규칙을 검사하고 없으면 target과 precondition의 변경시간을 비교하여 target보다 이후에 변경된 precondition이 존재하면 action을 수행한다

6. 3까지 생성된 경로를 거꾸로 올라가며 target과 precondition의 변경시간을 비교하여 target보다 이후에 변경된 precondition이 있으면 action을 수행한다.


h) Makefile예제


가) 예제 1

      .SUFFIXES : .c .o 

      OBJECTS = main.o read.o write.o 

      SRCS =  main.c  read.c  write.c 

      CC = gcc 

      CFLAGS = -g -c 

      TARGET = test$(TARGET) : $(OBJECTS)$(CC) -o $(TARGET) $(OBJECTS)\

      clean : rm -rf $(OBJECTS) $(TARGET) core main.o : io.h main.c read.o : io.h read.c write.o: io.h write.c


나) 예제 2

      .SUFFIXES : .c .o 

      OBJECTS = main.o read.o write.o 

      SRCS = main.c read.c write.c 

      CC = gcc CFLAGS = -g -c INC = -I/usr/include <- include 패스 추가

      TARGET = test$(TARGET) : $(OBJECTS)                

      $(CC) -o $(TARGET) $(OBJECTS).c.o : <-  직접 확장자 규칙을 구현

      $(CC) $(INC) $(CFLAGS) $<

      clean :

           rm -rf $(OBJECTS) $(TARGET) core main.o : io.h main.c read.o : io.h read.c write.o : io.h write.c


다) 예제 3


여러 개의 directory에 있는 makefile을 한꺼번에 수행할 때 다음처럼 쓸 수 있다.

      .SUFFIXES : .c .o

      CC = gcc

      CFLAGS = -O2 -g

      all : DataBase Test

      DataBase:  

            cd db ; $(MAKE)Test:  cd test ; $(MAKE)


라) 예제 4


보통의 Makefile은 앞부분부터 macro/명령어 정의/dependency순서로 작성하게 된다.

      .SUFFIXES : .c .o

      CFLAGS = -g

      OBJS =  main.o                 read.o                 write.o

      SRCS = $(OBJS:.o=.c)

      TARGET = test$(TARGET): $(OBJS)

                $(CC) -o $@ $(OBJS)

      dep :                

           gccmakedep $(SRCS)

      new :                

           touch $(SRCS) ; $(MAKE)

      clean :                

          $(RM) $(OBJS) $(TARGET) core


Application Debugging


AIX는 두 가지의 debugger를 제공한다. Text기반의 dbx와 dbx에 GUI interface를 씌운 xldb이다.

Debugger를 사용하려면 반드시 source code를 compile할 때 -g option을 사용하여 compile해야 debugger에서 사용할 정보를 만들어 줄 수 있다.

Debugging이 전부 완료된 다음에는 -g option을 빼고 다시 compile해야 프로그램의 크기가 줄어든다.


a) dbx 기본사용법

$ dbx [실행파일이름]

가장 간단한 실행방법이며 독립된 프로그램을 debugging할 수 있다.

만약 현재 수행되고 있는 프로세스를 debugging한다면 다음처럼 사용할 수 있다.


$ dbx -a [process id]

물론 이 프로세스도 -g option으로 compile되어 있어야 하며, process이 debugging이 끝나고 process를 종료 시키지 않은 채로 dbx를 종료하려면 detach명령어로 종료하도록 한다.


b) dbx 명령어 목록

다음은 dbx에서 유용하게 사용할 수 있는 명령어의 목록이다.

모두 dbx prompt상에서 help 를 입력하면 사용할 수 있는 명령어가 보이고 사용방법은 help [command name]이라고 입력하면 알 수 있다.


-LIST

(dbx) list 소스코드를 보여준다

(dbx) list 23,25 23번째 줄에서 25번째 줄까지 보여준다

(dbx) list foo foo 함수를 보여준다

(dbx) file [file_name] 현재 위치를 file_name으로 이동한다

(dbx) func [function_name] 현재 위치를 function_name으로 이동한다

(dbx) where 현재 위치를 알려준다


-PRINT

(dbx) print [expression] expression의 결과를 보여준다

(dbx) print [variable] variable의 값을 보여준다

(dbx) print [function_name] function의 return값을 보여준다


-ASSIGN

(dbx) assign variable = expression variable의 expression의 값을 넣는다


- BREAKPOINT

(dbx) stop at [source_line_number] [if condition]

condition이 만족되면 source_line_number에서 멈추도록 breakpoint를 설정한다.


(dbx) stop at "[file_name]":[source_line_number] [if condition]

condition이 만족되면 특정파일의 몇 번째 줄에서 멈추도록 breakpoint를 설정한다


(dbx) stop if [condition]

condition이 만족되면 멈추도록 breakpoint를 설정한다


(dbx) stop if [variable]

variable의 값이 바뀌게 되면 멈추도록 breakpoint를 설정한다


(dbx) status : 모든 breakpoint를 보여준다


(dbx) delete break_point_number

번호에 해당하는 break point를 지운다. Break point번호는 status로 알 수 있다


(dbx) cont : 다음 break point나 코드 끝까지 진행한다


(dbx) step : 코드 한 줄씩 진행한다


(dbx) next : 코드 한 줄이나 함수 안에 있으면 함수 끝까지 진행한다


Benchmark Test

Hanoi와 bubble sort두개의 프로그램을 각각의 경우에 다른 compile optimization option을 사용하여 compile하여 성능을 비교하였다.


a) Hanoi

각각 -O,-O3 -qstrict,-O4 option을 사용하여 compile한 결과이다.

hanoi

Hanoi같은 경우는 calculation-intensive 프로그램이므로 Optimize option에 따라 차이가 나고 있다.

-O4가 -O3에 비하여 그다지 좋은 편이 아닌데 procedure간의 operation을 할 일이 별로 없어서 interprocedural optimization이 그다지 영향을 주지 않는 것으로 보인다.


b) Bubble Sort

각각 -O, -O3 -qstrict, -O3, -O4 option을 사용하여 compile한 결과이다.

bubble

Bubble sort같은 경우는 memory-intensive 프로그램이므로 optimize option에 그다지 영향 받지 않는다. 이 경우 semantic을 바꿀 일이 없어서 strict option을 사용하지 않고 compile한 경우도 추가했는데 -qstrict를 사용한 경우와 많은 차이가 발생하고 있다.


부록

a) stream compile


stream bmt program에서 Makefile이 포함되어 있지 않으므로 다음 Makefile을 사용할 수 있다. 결과물로 CPU time을 재는 stream_d1과 wall clock을 재는 stream_d2두개의 실행파일이 만들어 진다.



##       stream bmt program Makefile

##               03/28/2001 Kim Sehee

#

DEFINES = -DUNIX 

CFLAGS = $(DEFINES) -O 

FILES1 = second_cpu.c stream_d.c

FILES2 = second_wall.c stream_d.c

CC = c

call : stream_d1 stream_d2

# Estimate CPU 

timestream_d1 : ${FILES1}

         $(CC) ${CFLAGS} -o $@ ${FILES1} /usr/lib/libm.a

# Estimate wall clock 

timestream_d2 : ${FILES2}

         $(CC) ${CFLAGS} -o $@ ${FILES2} /usr/lib/libm.a

clean:

        rm -rf stream_d1 stream_d2 ${FILES1:.c=.o} {FILES2:.c=.o}


참고 문헌

  • UNIX개념과 실습, 고건, 정익사
  • Managing Projects with make, 2nd Edition, Andrew Oram & Steve Talbott, O'reilly
  • Programming with GNU Software, Mike Loukides & Andy Oram, O'reilly
  • http://kldp.org/KoreanDoc/html/GNU-Make/GNU-Make.html
  • AIX Application Programming교재

한국 IBM의 글을 옮긴 것입니다.