Embedded : : Linux/: : Linux Kernel

Linux : Build System (Makefile, CMake)

Jay.P Morgan 2023. 7. 14. 16:59

 

  Intro : C 프로그래밍 작업 순서




Ⅰ. 프로그램을 작성

Ⅱ. 컴파일러를 통해 컴파일 진행

Ⅲ. 링크 진행

Ⅳ. 실행파일 생성

 

 

 

 

  Ⅰ. 프로그램 작성

  자신이 구현하고자 하는 것을 C언어를 사용하여 논리적으로 작성한다.(이렇게 작성된 것을 Source-Code라 한다.)

  이렇게 프로그램을 작성하는 과정을 흔히 코딩(coding)이라 한다.

 

C/C++에서 우리가 작성한 소스 코드를 실제 실행할 수 있는 실행 파일로 변환하기 위해서는 빌드라는 과정이 필요하다.
이 과정은 전처리(Preprocessing), 컴파일(Compile), 어셈블링(Assemble), 링킹(Linking) 순서로 이루어진다.

 

 

 

  Ⅱ. 컴파일러를 통해 컴파일 진행

  C언어를 이용해 작성한 코드를 컴퓨터가 이해할 수 있도록 기계어로 번역하는 과정이다.

  

  소스 코드(Source Code) : C로 작성하여, 아직 컴파일되지 않은 코드

  소스 파일(Source File) : 소스 코드가 저장되어 있는 파일

  오브젝트 파일(Object File) : 소스 코드를 컴파일하여 새로 생성되는 파일

 

  컴파일러는 MSVC, GCC, CLANG 등 다양한 컴파일러가 있다.

 

     • 전처리(Preprocessing)

  전처리기는 #include, #define, #ifndef 같은 전처리 구문을 처리하며, 이는 컴파일이 이루어지기 전에 수행된다. 또한, 주석을 제거하고, 매크로를 확장하며, 조건 컴파일 지시어를 처리하는 등의 작업을 수행하며, 컴파일 전에 컴파일러가 해석할 수 있는 소스 코드로 치환한다.

 

     • 컴파일(Compile)

  컴파일러 전처리가 완료된 코드를 어셈블리 코드로 변환한다.

이 과정에서 소스 코드의 문법을 체크하고, 타입 검사를 수행하며, 최적화를 진행한다.

이때, 프로그래머가 전역 변수를 초기화하지 않으면 컴파일러에 의하여 0으로 초기화됩된다.

 

     • 어셈블링(Assemble)

  어셈블러는 컴파일러가 생성한 어셈블리 코드를 컴퓨터가 읽을 수 있는 목적 코드(기계어 코드)로 변환한다.

이렇게 생성된 목적 코드는 이진 형태로 되어 있다.

 

 

  Ⅲ. 링크 진행

     • 링킹(Linking)

  링커는 앞서 만들어진 목적 코드라이브러리의 목적 코드시동 코드(프로그램의 시작과 종료를 처리하는 코드)등을 링크해서 최종 실행 파일을 만든다.

  링크시 아무런 에러가 발생하지 않으면 Windows 시스템에서는 .exe라는 확장자를 갖는 실행파일이 만들어진다.

  한편, Unix 또는 Linux 시스템에서는 실행 가능한 바이너리 파일이 생성된다.

 

  Windows Visual Studio에서는 'F7' 버튼을 통해 Build 할 수 있고, Linux나 Mac OS의 Terminal 환경에서는 gcc, make 등을 통해 Build 할 수 있다.

 

 

  Intro : 함수의 구성

   함수란 적절한 입력과 그에 따른 출력이 존재하는 것을 말한다.

 

 

     • 함수 호출이란?

  위 그림에선 입력 x를 전달하여 그에 따른 출력 y를 요구한다.

  여기서 입력 x를 전달하는 것 인자 전달이라 하며, 입력 x를 전달하며 정의된 함수의 실행을 요청하는 것 함수 호출이라 한다.

  수학에서의 함수와 유사하다고 볼 수 있으며, void부터 1개, 2개 .. 등 여러개의 인수를 전달할 수 있다.

  (단, 함수의 선언된 형태로 인자의 수를 전달해야 한다.)

 

  입력의 형태 (전달 인자)는 변수, 포인터 등 다양한 형태로 인자 전달이 가능하다.

 

  지역변수가 갖는 한계 때문에, 필요시 입력의 형태로 포인터를 사용한다.

 

※ C 언어에서는 연산을 수행하는 문장의 끝을 나타내기 위해 세미콜론 ' ; '을 사용한다.

  C / C++에서는 각 문장의 끝에 세미콜론 ' ; '을 붙여 수행하는 문장의 끝을 나타낸다.

 

     • 표준 라이브러리 함수

  누구나 가져다 쓸 수 있도록 기본적으로 제공하는 함수들을 말한다.

  C POSIX 라이브러리는  C 표준 라이브러리 POSIX에 대한 시스템 사양이다.

  

     • 헤더 파일

  표준 라이브러리 함수를 사용하기 위해서는, 그 함수를 포함하는 헤더 파일의 선언을 프로그램 제일 앞에 작성해야 한다.

 

     • 함수에서 return의 의미

  1. 함수의 실행이 끝났으므로 함수를 빠져 나온다.

  2. 함수를 호출 한 영역으로 값을 반환(리턴, 출력)한다.

 

 

  0. Linux : Build System

 

  0.1 build (빌드)

    - 소스 코드를 실행 가능한 Software로 변환하는 과정

  0.2 C언어 빌드 과정 (gcc 기준)

 

  1. Compile & Assemble

    - 하나의 소스 파일이 0과1로 Object 파일이 만들어짐

gcc -c 파일이름

 

  2. Linking

    - 만들어진 Object파일들 + Library들을 모아 하나로 합침

      File 파일이름 으로 파일 개요를 볼수 있다. (elf)

gcc 파일 파일 -o 실행파일이름
gcc -o 실행파일이름 파일 파일 . . .

 

 

  0.3 Build System

  Build System : 빌드 할 때 필요한 여러 작업을 도와주는 프로그램들

 

  0.4 빌드 자동화 스크립트별 속도

    1. Bash Shell Script : Build 느림

    2. Python Script : Build 느림

    3. Make Build System : 빠름

 

  0.5 Build.sh 제작 예시

#! /bin/bash
gcc -c ./red.c
gcc -c ./blue.c
gcc -c ./main.c
gcc main.o red.o blue.o -o ./purple
rm -r ./*.o

 



 

  0.6 .sh 파일 실행방법

  1. source **.sh

  2. .**.sh

    모든 파일을 컴파일 & 어셈블 수행 - 오래 걸림

    make build system으로 해결가능

 

 

 

 

 

  1. Make

   

  1.1 make의 필요성


  만약 main.c, A.c, B.c처럼 필요한 파일 수가 많지않으면 상관없겠지만, 프로젝트의 크기가 커진다면 필요한 명령어의 수가 증가하기 때문에 이를 직접 하나씩 추가하기 어렵습니다. 또한, 필요한 모든 명령어를 쉘 스크립트에 넣고 실행하게 된다면 한 파일만 넣고 컴파일 하면 되는데, 쉘스크립트를 실행하면 모든 파일을 컴파일하기 때문에 효율이 떨어집니다.

 

Linux System에서는 여러 개의 .c 파일을 컴파일&링크하여 하나의 실행 파일로 생성하는 데 사용하는 Tool로 Makefile과 make명령을 제공한다.

 

  Makefile

  컴파일 명령, 소스파일을 컴파일 하는 방법, 링크할 파일, 실행 파일명 등을 설정하는 파일

 

  Make 명령

Makefile을 읽고, 작성된 내용대로 컴파일을 실행하여 실행 파일을 생성한다.

 

리눅스에 make가 설치되어있지 않은 경우, Ubuntu Linux는 아래와 같이 먼저 make를 설치한다.

$ sudo apt install make

 

 

  make명령 사용하기

 

 

  1.2 make 설치

sudo apt install make -y

    Makefile을 만들고 나서, make 명령어를 입력하면 Makefile 문서에 작성한 대로 빌드가 이루어진다.

    ※ 장점

      (1) 빌드 자동화 : 기술된 순서대로 Build 작업을 수행하는 자동화 스크립트 지원

      (2) 빌드속도 최적화 : 변화된 파일에 대해서만 작동하므로, 불필요한 Compile & Assemble을 피하여 효율적이다.

 

 

  1.3 Makefile의 구성

  make를 터미널 상에서 실행하면 해당 위치에 있는 Makefile을 찾아서 읽어들입니다. 따라서 makefile을 작성해야하는데, 이 때 필요한 세가지 구성요소가 있습니다.

     • Target, Dependencies, Command

1. Target : 빌드 대상으로 명령어가 수행된 최종결과를 저장하는 파일

2. Dependencies : 주어진 target make할 때 필요한 파일 목록

3. Command : 실행할 명령어

 

이 때 커멘드 앞은 스페이스바가 아닌, 반드시 tap을 써야합니다.

=> dependencies를 가지고 command를 수행하여 target을 생성함.

 

     • 쉘스크립트를 makefile로 작성한 예

Test.out: main.o A.o B.o
	gcc -o test.out main.o A.o B.o

main.o: A.h B.h main.c
	gcc -c main.c

A.o: A.h A.c
	gcc -c A.c

B.o: B.h B.c
	gcc -c B.c

첫번째 문단을 살펴보면 타겟은 test.out이고, 디펜더시스는 main.o, A.o, B.o이고, 커맨더는 gcc -o test.out main.o A.o B.o 입니다.

 

 

Main.o는 자신의 소스파일인 main.c와 A.h, B.h에 의존하고 있고, A.o는 A.h, A.c, B.o는 B.h, B.c에 의존하고 있다는 것을 알 수 있습니다.

 

만약 main.c를 수정했다면 main.o가 컴파일에서 다시 생기게 됩니다.

 

 

 

  1.4 Makefile - 매크로

  앞에서 작성한 makefile를 매크로를 활용해 더 간단하게 작성할 수 있습니다.

  먼저 매크로는 반드시 사용되기 전에 정의되어야 합니다.

  그리고 매크로 참여시 $()로 묶어주어야 합니다.

 

$(매크로)
CC=<컴파일러>
TARGET=<빌드 대상 이름>
OBJS=<Object 파일 목록>

 

  여기서 사용할 변수는 CC, TARGET, OBJS입니다. 공통적으로 쓰는 makefile의 내부변수입니다.

  이에 맞게 코드를 바꿔보면 아래와 같습니다.

 

CC=gcc
TARGET=test.out
OBJS=main.o A.o B.o

$(TARGET): $(OBJS)
	$(CC) -o $(TARGET) $(OBJS)

main.o: A.h B.h main.c
	$(CC) – c main.c

A.o: A.h A.c
	$(CC) -c A.c

B.o: B.h B.c
	$(CC) -c B.c

 

     • 내부 매크로

        $@ : 현재 Target 이름

        $^ : 현재 Target이 의존하는 대상들의 전체 목록(objs)

        $? : 현재 Target보다 최신인 의존파일 참조

        $< : 의존 파일 목록 중 첫 번째 파일 참조

 

     • 확장자 규칙

        .c.o : .c소스파일을 컴파일해서 .o목적파일로 만듦

CC=gcc
TARGET=test.out
OBJS=main.o A.o B.o
CFLAGS = -g

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) -o $@ $^

.c.o:
	$(CC) -c -o $@ $<

clean:
	rm *.o

        all: $(TARGET) : 최종으로 만들 파일

        clean : 현재 디렉토리의 모든 object 파일을 제거하는 역할

     • 정해져 있는 매크로

        CFLAGS: 컴파일 옵션 -g(디버그 정보 표시)

        LDFLAGS: ld옵션

        LDLIBS: 링크 라이브러리

 

make makefile을 순서대로 읽어서 가장 처음에 나오는 target파일을 수행하게 됩니다. 따라서 최종으로 만들 파일이 무엇인지 명시해주는 것이 좋습니다. all:$(TARGET)을 통해 최종으로 만들 파일을 명시해주었고, clean은 더미 타겟으로 볼 수 있는데, 더미파일은 더미 타겟은 의존 파일이 있고 파일이 생성되지 않는 target입니다. 여기서 clean은 현재 디렉토리의 모든 오브젝트 파일을 제거하는 역할을 합니다. Make clean이라는 명령을 통해 사용할 수 있습니다.

 

또 정해져있는 매크로가 있는데 CFLAGS, LDFLAGS, LDLIBS 등 다양한 매크로가 있습니다.

 

     • Makefile 해석

1. GCC 컴파일러 사용

2. Target 파일은 test.out

3. OBJS main.o A.o B.o로 정의

4. 디버그 정보 표시

5. 최종으로 생성할 파일 명시

6. 타겟 파일을 만들기 위해 OBJS를 새용하여 다음 명령 실행

7. .c파일을 목적파일로 컴파일

8. 필요시 clean 명령 사용

 

 

 

  1.5 Make 옵션

     • -h : 옵션에 관한 도움말 출력

     • -p : make 내부에 셋팅되어 있는 값 출력

     • -k : 에러가 나도 계속 실행

     • -t : 파일의 생성날짜를 현재 시간으로 갱신

     • -f file : file 에 해당하는 파일을 Makefile로 취급

     • -r : 내장된 규칙을 없는 것으로 간주, 사용자 정의 규칙 필요

     • -d : Makefile을 수행하면서 모든 정보 출력

 

 

 

  1.6 make 실생시 에러

Makefile:17: *** missing separator. Stop.

     • command 앞에 Tap을 사용하지 않았을 경우

 

make: *** No rule to make target ‘A.h’, needed by ‘A.o’. Stop

     • A.o A.h에 의존하는데 A.h를 찾을 수 없을 경우

     • 의존 관계 문제

 

 

  2. CMake

 

  2.1 CMake란

    Make의 빌드관리 시스템을 만들기 위한 오픈소스 프로젝트

    Make처럼 Build를 직접 하는 Tool이 아니라, Makefile을 자동으로 생성할 수 있는 Build System입니다.

 

 

  2.2 CMake 설치

sudo apt install g++ cmake -y

https://cmake.org/download

 

 

  2.3 CMake 단계

  먼저 CmakeLists.txt cmake Makefile을 만들기 위해서 사용하는 파일입니다. 따라서 CmakeLists.txt cmake하면 Makefile이 생성되고, 이를 make하면 실행파일이 생성됩니다. 또한 모든 프로젝트 최상의 디렉토리에는 CmakeLists.txt파일이 있어야 합니다.

 

 

 

  2.4 간단한 CMakeLists.txt 작성 예시

   - 대문자 / 소문자 유의

CC=gcc
TARGET=test.out
OBJS=main.o A.o B.o
CFLAGS = -g

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) -o $@ $^

.c.o:
	$(CC) -c -o $@ $<

clean:
	rm *.o

위의 makefile을 간단하게 CMakeLists.txt로 작성해 보면

1. ADD_EXECUTABLE(test.out A.c B.c Main.c)

ADD_EXECUTABEL(최종실행파일이름 소스파일 소스파일 소스파일)

 

여기서 헤더파일은 명시하지 않아도 CMake에서 자동으로 호출합니다. 또한 CMakeLists의 이름의 대소문자를 주의하여 작성해야 합니다.

 

2. cmake CmakeLists.txt

 Makefile

CmakeCache.txt

cmake)install.cmake

CmakeFiles (디렉토리)

이렇게 작성된 파일을 cmake CmakeList.txt라는 명령어를 실행하면, 위의 4가지가 만들어집니다. 이 때 CMakeFiles라는 디렉토리에 A.o B.o Main.o와 같은 목적파일이 생성됩니다.

 

3. make

그 다음에 make를 실행하면, 각각 필요한 파일들이 빌드되고, 최종 target파일인 test.out이 생성됩니다.

 

※ cmake 명령은 소스파일을 추가하지 않는 이상 최초로 한 번만 실행하므로, 소스파일이 수정되었는 경우 make명령으로 빌드만 하면 됩니다.

 

 

 

 

  2.4 CMake-CMakeLists.txt 작성

 

1. PROJECT ( )

PROJECT(
	DESCRIPTION ”FOSS”
	LANGUAGES C
)

  PROJECT : 프로젝트는 프로젝트 정보를 담을 수 있는 명령어

 

 

2. CMAKE_MINIMUM_REQUIRED( VERSION <버전> )

CMAKE_MINIMUM_REQUIRED( VERSION 3.11)

CMAKE_MINIMUM_REQUIRED : 빌드에 필요한 CMake의 최소한의 버전을 명시할 수 있는 명령어

 

※ 이 두 가지는 최상위 CMakeLists에 들어가는 것이 좋습니다.

 

 

3. SET( <변수 이름><> )

    SET( <목록 변수 이름 ><항목><항목>…)

SET( SRC_FILES main.c A.c B.c)

  명령을 통해 변수를 정의할 수 있으며, 목록 변수도 정할 수 있습니다. Main.c A.c B.c와 같은 소스파일의 목록을 SRC_FILES라는 변수로 정해둘 수 있습니다. 이는 프로젝트를 진행하면서 소스파일을 추가하거나 삭제할 떄 이 목록을 수정하면 됩니다.

 

 

4. ADD_COMPILE_OPTIONS( 옵션 1, 옵션 2 …) : 소스파일 컴파일시 추가할 옵션

ADD_COMPILE_OPTIONS (-g -Wall)

 

  ADD_COMPILE_OPTIONS는 소스파일 컴파일시 추가할 옵션입니다. -g는 컴파일 시 디버깅 목적의 심볼 테이블을 포함한다는 의미, -Wall은 모든 경고메시지를 표기한다는 의미입니다.

 

 

5. ADD_EXECUTABLE( <실행 파일 이름><소스 파일>…) : 빌드 최종 결과물 생성

ADD_EXECUTABLE( test.out main.c A.c B.c )
ADD_EXECUTABLE( test.out ${SRC_FILES} )

 

  ADD_EXECUTABLE은 빌드에 최종 결과물을 생성하는 것입니다. 그래서 예시를 들면, ADD_EXECUTABLE( test.out main.c A.c B.c ) test.out이라는 실행파일을 생성할 수 있습니다.

 

 

6. CMAKE_C_COMPILER : 컴파일 및 링크 과정에서 사용할 컴파일러의 경로 지정

SET(CMAKE_C_COMPILER “gcc”)

  CMAKE_C_COMPILER는 컴파일 및 링크 과정에서 사용할 컴파일러의 경로를 지정하는 것입니다. 여기서는 gcc를 사용할 것이기 때문에 “gcc” 라 하였고, CMAKE_C_COMPILER는 변수이기 때문에 SET으로 지정하였습니다.

 

 

  2.5 CMakeLists.txt 예시

 

CMAKE_MINIMUM_REQUIRED( VERSION 3.11)

PROJECT(
	DESCRIPTION ”FOSS” 
	LANGUAGES C
)

SET ( SRC_FILES
	main.c
	A.c
	B.c
	)

SET ( CMAKE_C_COMPILER “gcc” )

ADD_COMPILE_OPTIONS ( -g -Wall )

ADD_EXECUTABLE ( test.out A.c B.c main.c )

 

     • CMAKE_MINIMUM_REQUIRED로 최소버전 명시

     • PROJECT로 프로젝트 정보 나열

     • SET ( SRC_FILES를 통해 소스 파일 목록 정의

 

이 밖에도 더 많은 변수와 명령어들이 있습니다.

 

     • MESSAGE() : 콘솔에 메시지 출력

     • ADD_EXECUTABLE(output, code1, code2…) : 빌드 최종 결과물로 바이너리 생성

     • ADD_LIBRARY(<라이브러리 이름> [STATIC|SHARED|MODULE] <소스파일> ) : 빌드 최종 결과물로 라이브러리 생성

     • INSTALL() : make install 실행 시 수행할 동작 지정

     • INCLUDE_DIRECTORIES ( 디렉토리 1, 디렉토리 2, …) : 헤더 디렉토리 목록 추가

     • SET ( RUNTIME_OUTPUT_DIRECTORY 디렉토리 1) : 빌드된 실행 바이너리를 저장할 디렉토리 지정