Notice
Recent Posts
Recent Comments
Today
Total
05-04 00:14
Archives
관리 메뉴

Jeongchul Kim

Makefile 기반 리눅스 프로그래밍 본문

Embedded Linux

Makefile 기반 리눅스 프로그래밍

김 정출 2015. 12. 21. 19:17

10.1 Makefile 이란?    

Makefile이란 “컴파일을 위한 쉘 스크립트 파일”이다.


비교적 매우 간단한 하나의 C 소스 파일을 컴파일하기 위해서는 컴파일 명령어로 바로 컴파일이 가능하겠으나, 리눅스 커널 소스들처럼 한번에 컴파일해야 할 소스 파일들이 매우 많거나, 커널 모듈 혹은

디바이스 드라이버와 같이 컴파일 과정에 필요한 옵션들이 길게 필요할 경우 프롬프트 상에서 명령어로

처리하기에는 한계가 있다.

이 경우 스크립트 형식으로 컴파일해야 할 소스 파일들과 옵션들을 정리하여 “make”라는 유틸리티가

자동적으로 한 줄 씩 해석하여 컴파일하도록 만든 파일이 “Makefile”이다.


Makefile 예제

mymain.c main() 함수가 있는 메인 소스

mylib_func_1.c mymain.c 파일을 지원하기 위한 라이브러리 소스

mylib_func_2.c


File Name: Makefile

1 ## Makefile Example

2

3 all : mymain

4

5 mymain : mymain.0 mylib_func_1.o mylib_func2.o

6 gcc -o mymain mymain.0 mylib_func_1.o mylib_func2.o

7

8 mymain.o : mymain.c

9 gcc -c mymain.c -c 옵션은 오브젝트(Object) 코드 파일 생성

10

11 mylib_func_1.o : mylib_func_1.c

12 gcc -c mylib_func_1.c

13

14 mylib_func_2.o : mylib_func_2.c

15 gcc -c mylib_func_2.c

16

17 clean :

18 rm -f *.o mymain


File Name: mymain.c

1 /** mymain.c **/

2

3 #include <stdio.h>

4

5 void lib_func_1(char *);

6 void lib_func_2(char *);

7

8 int main()

9 {

10 printf(“EXAMPLE mymain \n”);

11 lib_func_1(“Library lib_func_1 called”);

12 lib_func_2(“Library lib_func_2 called”);

13 return0;

14 }

File Name: mylib_func_1.c

1  /** mylib_func_1.c **/

2

3 #include <stdio.h>

4

5 void lib_func_1(char* str)

6 {

7 printf(“lib_func_1() : %s \n”,str);

8 }


File Name: mylib_func_2.c

1  /** mylib_func_2.c **/

2

3 #include <stdio.h>

4

5 void lib_func_2(char* str)

6 {

7 printf(“lib_func_2() : %s \n”,str);

8 }


Makefile에서 사용되는 문법

[결과물]: [결과물을 만드는 데 필요한 재료들] ← Tab → [결과물을 만드는(컴파일하는 )방법

* 주의 탭(Tab) 키로 간격을 띄워야 한다. 스페이스(Space)로 간격을 벌리는 것은 인식하지 못함.


Makefile 에서 정의해 놓은 키워드

키워드

설명

all

Makefile을 통해 최종적으로 얻고자 하는 최종 결과물을 지정하는 키워드

clean

Makefile을 통해 생성된 결과물들을 삭제하기 위한 키워드

CC

C 컴파일러(Compile)를 지정하기 위한 키워드

참고로 C++ 컴파일러를 지정하기 위한 키워드 : CXX

CFLAGS

컴파일할 때 필요한 옵션들을 지정하기 위한 키워드

-I (대문자 아이)

소스 파일에서 #include 한 헤더 파일들을 검색할 때 우선적으로 검색할 디렉토리를

지정하기 위한 키워드

-L

소스 파일을 컴파일할 때 필요한 라이브러리를 검색할 때 우선적으로 검색할 디렉토리를

지정하기 위한 키워드

-l (소문자 엘)

소스 파일을 컴파일할 때 필요한 라이브러리를 검색할 때 검색할 라이브러리 이름을 지정하기 위한 키워드









Makefile을 컴파일하는 법, 생성된 프로그램 실행

[root@ ]# make

gcc -c mymain.c

gcc -c mylib_func_1.c

gcc -c mylib_func_2.c

gcc mymain.0 mylib_func_1.o mylib_func_2.o -o mymain

[root@ ]#

[root@ ]# ls

Makefile Makefile.sharedlib mylib_func_1.o mymain

Makefile.config Makefile.staticlib mylib_func_2.c mymain.c

Makefile.include mylib_func_1.c mylib_func_2.o mymain.o

[root@ ]#

[root@ ]# ./mymain


앞서 작성한 Makefile 예제에서는 확장성이 없다. 즉 소스 코드 파일의 이름이 변하거나 컴파일러가 PC용

컴파일러인 “gcc”에서 타겟 보드용 컴파일러인 “arm-linux-gcc”로 변경될 경우 일일이 해당 라인 내용들을 수정해주어야 하는 불편함이 있다.


File Name: Makefile

1 ## Makefile Example version2

2

3 PLATFORM =

4

5 CC = $(PLATFORM)gcc

6

7 MAIN_SRC = mymain

8 LIB_SRC1 = mylib_func_1

9 LIB_SRC2 = mylib_func_2

10

11 all : $(MAIN_SRC)

12

13 $(MAIN_SRC) : $(MAIN_SRC).o $(LIB_SRC1).o $(LIB_SRC2).o

14 $(CC) -o $(MAIN_SRC) $(MAIN_SRC).o $(LIB_SRC1).o $(LIB_SRC2).o

15

16 $(MAIN_SRC).o : $(MAIN_SRC).c

17 $(CC) -c $(MAIN_SRC).c

18

19 $(LIB_SRC1).o : $(LIB_SRC1).c

20 $(CC) -c $(LIB_SRC1).c

21

22 $(LIB_SRC2).o : $(LIB_SRC2).c

23 $(CC) -c $(LIB_SRC2).c

24

25 clean :

26 rm -f *.o $(MAIN_SRC)


타겟 보드용 컴파일러인 arm-linux-gcc를 사용하고자 한다면 변수 “PLATFROM”의 값을 “arm-linux-”로

변경하면 된다.



10.2 Makefile 응용 - 자신만의 Library 만들기

<그림> Linux System Architecture


Linux에서는 User Application 이 실행되기 위해 시스템 상에서 제공하는 라이브러리를 호출하는데,

이 때 호출 되는 라이브러리 형태는 두 가지 형태이다.



왼쪽의 형태(Static Library) 는 이미 컴파일할 때 User Applicaton과 함께 컴파일 되어 하나의 실행 파일로 생성되었기 때문에, User Application 이 실행될 때에는 해당 Library 파일이 없어도 상관이 없다. 이미 내부에 포함되어 있기 때문이다.


그러나 오른쪽의 형태(Shared Library) 는 두 파일이 별도 분리되었기 때문에, User Application이 실행될 때 해당 Library가 반드시 있어야만 User Application의 실행이 가능하게 된다. 또한 해당 Library가 어떤 경로(PATH)에 위치하고 있는지도 알고 있어야 실행이 가능하게 된다.


Linux에서는 Shared Library를 더 선호하고 있어서 실제로 같은 디렉토리에 두 개의 라이브러리가 공존하고 있다면, Linux에서는 Shared Library를 호출해준다.


Linux라는 운영체제는 특성 상 실행시키고 관리해주어야 할 User Application의 개수가 매우 많을 것이다.

Static Library의 경우, 각 User Application 마다 Static Library를 내부에 포함해야 하기 때문에 중복되는 라이브러리가 많을 경우 메모리 낭비를 초래할 수 도 있다. 그러나 Shared Library의 경우 라이브러리가 별도로 분리되어 있기 때문에 메모리 효용성을 높일 수 있다.


File Name: Makefile.staticlib

1 ## Makefile Example : Static Library

2

3 PLATFORM =

4

5 CC = $(PLATFORM)gcc

6 AR = $(PLATFORM)ar <- ar : Linux에서 아카이브 파일 생성할 때 사용되는 명령어

7

8 MAIN_SRC = mymain

9 LIB_SRC1 = myib_func_1

10 LIB_SRC2 = mylib_func_2

11

12 LIB_OUT = libjungchul.a <- *.a 파일의 확장자 - Static Library

13

14 all : $(MAIN_SRC)

15

16 $(MAIN_SRC) : $(MAIN_SRC).o $(LIB_OUT)

17 $(CC) -o $(MAIN_SRC) $(MAIN_SRC).o -L. -ljungchul

18

19 $(LIB_OUT) : $(LIB_SRC1).o $(LIB_SRC2).o

20 $(AR) ruv $(LIB_OUT) $(LIB_SRC1).o $(LIB_SRC2).o <- 실제로 Static Library를 만드는 과정

21

22 $(MAIN_SRC).o : $(MAIN_SRC).c

23 $(CC) -c $(MAIN_SRC).c

24

25 $(LIB_SRC1).o : $(LIB_SRC1).c

26 $(CC) - c $(LIB_SRC1).c

27

28 $(LIB_SRC2).o : $(LIB_SRC2).c

29 $(CC) -c $(LIB_SRC2).c

30

31 clean :

32 rm - f *.o *.a $(MAIN_SRC)


아카이브(Archive) 파일이란 여러 개의 파일을 디렉토리 구조 그대로 하나의 파일로 만들어 놓은 파일을 뜻하며, Linux에서 많이 사용되는 “ *.tar ”라는 파일이 대표적


*.a 라는 확장자가 아카이브 파일임을 뜻한다.


모든 라이브러리는 이름을 붙일 때 “lib”를 접두사(Prefix)로 붙이고 있으므로, 여기서 생성하려는 라이브러리 이름은 확장자와 접두사를 제외한 jungchul이 된다.


“_L.” 과 “-ljungcul”의 내용은 앞서 표에 내용이 나와있다.

“현재 디렉토리(“.”), 즉 Makefile이 있는 현지 디렉토리에서 “jungchul”이란 이름을 가진 라이브러리를 찾아라”

찾은 라이브러리와 함께 컴파일 하라.


[root@ ]# make clean

rm -f *.o mymain

[root@ ]#

[root@ ]# make -f Makefile.staticlib

gcc -c mymain.c

gcc -c mylib_func_1.c

gcc -c mylib_func_2.c

ar ruv libfunfun.a mylib_func_1.o mylib_func_2.o

ar: creating libjungchul.a

a - mylib_func_1.o

a - mylib_func_2.o

gcc -o mymain mymain.o -L. -ljungchul

[root@ ]#

[root@ ]# rm -f libjungchul.a

[root@ ]#

[root@ ]# ./mymain


기존 컴파일 과정에 생성된 결과 파일들을 삭제하기 위해 “make clean”을 먼저 실행시킨다.


컴파일하게 되면, Static Library를 삭제해도 제대로 실행이 될 것이다!



File Name: Makefile.sharedlib

1 ## Makefile Example : Shared Library

2

3 PLATFORM =

4

5 CC = $(PLATFORM)gcc

6

7 MAIN_SRC = mymain

8 LIB_SRC1 = mylib_func_1

9 LIB_SRC2 = mylib_func_2

10

11 LIB_OUT = libjungchul.so <- Shared Object란 뜻의 약자

12

13 all : $(MAIN_SRC)

14

15 $(MAIN_SRC) : $(MAIN_SRC).o $(LIB_OUT)

16 $(CC) -o $(MAIN_SRC) $(MAIN_SRC).o -L. -ljungchul

17

18 $(LIB_OUT) : $(LIB_SRC1).o $(LIB_SRC2).o

19 $(CC) -shared -o $(LIB_OUT) $(LIB_SRC1).o $(LIB_SRC2).o <- “-shared” 옵션

20

21 $(MAIN_SRC).o : $(MAIN_SRC).c

22 $(CC) -c $(MAIN_SRC).c

23

24 $(LIB_SRC1).o : $(LIB_SRC1).c

25 $(CC) -fPIC -c $(LIB_SRC1).c

26

27 $(LIB_SRC2).o : $(LIB_SRC2).c

28 $(CC) -fPIC -c $(LIB_SRC2).c

29

30 clean:

31 rm -f *.o *.so $(MAIN_SRC)


Shared Library 파일에서는 확장자가 “*.so”가 사용됨. Shared Object란 뜻의 약자로, Shared Library는 다른 User Application들과 공유할 수 있다.


Shared Library는 단순히 하나의 아카이브 파일로 만들어지는 게 아니라 “-shared”라는 옵션을 주어 별도의 바이너리 파일로 다시금 컴파일하는 방법으로 생성되는 파일.


-fPIC 이라는 옵션. 여기서 “PIC”란 “Position Independent Code”를 뜻하는 약자로, 메모리 주소와 상관없이 호출될 수 있는 바이너리 오브젝트 파일을 생성할 때 사용되는 옵션이다.

일반적으로 Linux와 같은 운영체제에서 실행되는 User Application들은 매번 실행될 때마다 메모리에 로딩(Loading)되는 주소 값이 바뀌게 되므로, 실행될 때마다 호출될 Shared Library는 이와 상관없이 호출될 수 있도록 컴파일할 때 반드시 “-fPIC” 옵션을 줘야 함


[root@ ]# make clean

rm -f *.o mymain

[root@ ]#

[root@ ]# make -f Makefile.sharedlib

gcc -c mymain.c

gcc -fPIC mylib_func_1.c

gcc -fPIC mylib_func_2.c

gcc -shared -o libjungchul.so mylib_func_1.o mylib_func_2.o

gcc -o mymain mymain.o -L. -ljungchul

[root@ ]#

[root@ ]# ls

Makefile Makefile.sharedlib mylib_func_1.c mylib_func_2.o mymain

Makefile.config Makefile.staticlib mylib_func_1.o mylib_func_2.c

Makefile.include libjungchul.so mymain mymain.c

[root@ ]#

[root@ ]# ./mymain

./mymain: error while loading shared libraries : libjungchul.so: cannot open shared object file: No such file

or directory


오류 발생 - 해당 라이브러리 파일을 찾을 수 없다.

앞서 우리는 쉘 환경 변수 값을 수정하기 위해 “.bash_profile” 파일을 수정하거나, Aliasing 기능을 수정하기 위해 “.bashrc” 파일을 수정한 후 반드시 “source”라는 명령어를 실행시켜 주었음을 기억


Shared Library의 경우, User Application이 이를 호출하게 되면 호출 요청을 받고 실제 Shared Library가 있는 메모리 주소로 찾아가 이를 실행시키는 것은 운영체제이다. 이미 실행 중인 운영체제는 지금 내가 작업 중인 현재 디렉토리 위치에 Shared Library가 있음을 당연히 알지 못하므로 알려주어야 한다.


1. /etc/ld.so.conf.d/ 디렉토리 밑에 “라이브러리.conf” 파일 생성

2. ldconfig 명령어 실행


이전 Linux Kernel 2.4 버전까지는 “/etc/ld.so.conf” 파일에 직접 Shared Library 파일이 있는 현재 디렉토리의 절대 경로(PATH) 값을 추가해주면 되었는데, 2.6부터는 “/etc/ld.so.conf.d/” 디렉토리 밑에 해당 라이브러리 별로 별도의 “*.conf” 파일을 만들도록 변경

“pwd” 명령어를 수행시켜 보면 현재 디렉토리의 절대 경로(PATH)가 나올 것이다.

이 경로 값을 “/etc/ld.so.conf.d/” 디렉토리 밑에 “jungchul_lib.conf라는 이름으로 저장하도록 한다.

단, 한 가지 주의할 점은 설정 파일 이름을 정할 때 파일의 이름은 어떤 이름을 주어도 상관없으나, 확장자는 반드시 “*.conf”로 끝나야 한다.


해당 파일을 생성하여 Shared Library 경로 값을 추가해 주었다면, 이제 이를 현재 실행 중인 운영체제에 알려주기 위해 ldconfig 명령어를 수행시킨다.


[root@ ]# ./mymain

./mymain: error while loading shared libraries : libjungchul.so: cannot open shared object file: No such file

or directory

[root@ ]#

[root@ ]# pwd

/root/JUNGCHUL/howto_lib_make

[root@ ]#

[root@ ]# vi /etc/ld.so.conf.d/jungchul_lib.conf

[root@ ]# ldconfig

[root@ ]#

[root@ ]# ./mymain



Comments