layer 25 리눅스 시스템 프로그래밍 - 컴파일 &라이브러리 생성

2019. 11. 17. 18:08layer7

시스템 콜 및 라이브러리 함수를 사용하여 소스 프로그램을 작성하면

컴파일 과정을 통해 실행 파일을 만들어야 합니다.

 

또한, 이 실행 코드를 재사용하기 위해서 라이브러리를 생성하여 사용하기도 합니다.

 

살펴볼 것

1. 리눅스에서 컴파일 하는 방법과 과정

2. 정적 or 공유 라이브러리를 작성하는 방법

3. 해당 라이브러리를 사용하기 위해서 설정해야 할 사항

 

 

컴파일

: 컴퓨터는 모든 명령을 cpu가 처리하고 cpu는 모든 명령을 0과 1로 이해하고

 실행합니다. "cpu는 0과 1, 오직 2가지 경우 밖에 모르는 바보다"라는 말을 들은 적 있나요?

 우리의 언어(c, python, ruby ..etc)는 컴퓨터가 이해하지 못 하므로 컴퓨터가 이해할 수 있는

 통역사(compiler)가 필요하다. 여기서 말하는 통역이 컴파일(compile) 입니다.

 

 

리눅스

: 컴파일 방법은 gcc 소스파일명 -o 실행 파일명

또는

gcc -o 실행 파일명 소스파일명

 

출처: http://blog.naver.com/PostView.nhn?blogId=fjrzlgnlwns&logNo=206040966&parentCategoryNo=&categoryNo=45&viewDate=&isShowPopularPosts=true&from=search

ex. gcc -v --save-temps -o sample sample1

다음과 같은 형식으로 rjsdnr.c라는 파일을 컴파일 해보았습니다.

 

다음과 같은 옵션으로 컴파일을 하였을 때는 아래와 같이 나옵니다.

-v 옵션: 컴파일 과정을 화면으로 출력

--save-temps 옵션은 컴파일 과정에서 발생되는 중간 파일을 지우지 않고 저장합니다.

이 명령어를 실행하면 실행파일 말고도 rjsdnr.i, rjsdnr.o, rjsdnr.s 파일이 더 생성됩니다.

 

잘 모르겠는 글씨들이 나옵니다.

실행 파일과 나머지 컴파일 과정에서 만들어진 중간 파일들이 함께 저장되어 있는

모습을 볼 수 있습니다.

 

 

 

이제 진짜 하나씩 살펴보자

컴파일 과정

1. 전처리 과정

전처리 과정은 크게 두 부분으로 나눌 수 있습니다.

  - 헤더 파일 삽입

  - 매크로 치환 및 적용

 

C 소스 내에 헤더파일을 include 하는 것은 C언어의 문법적 특성과 관련된 것으로, C언어에서는

함수를 사용하기 전에 함수의 원형을 먼저 선언 해야 합니다.

 

따라서 어떠한 함수를 사용하려면 해당 함수가 정의되어 있는 헤더파일을 include 해야 합니다.

전처리기는 #include 구문을 만나면 해당하는 헤더파일을 찾아서 그 파일의 내용을 순차적으로 삽입합니다.

 

헤더파일을 다 삽입하면 이제 매크로 치환 작업이 들어갑니다. #define 된 부분은, 심볼 테이블에

저장되고, 심볼 테이블에 들어있는 문자열과 같은 문자열을 만나면 #define 된 내용으로 치환합니다.

이때 #ifdef와 같은 다른 전처리기 매크로들도 같이 처리됩니다.

 

전처리가 끝나면 filename.i 파일이 생성됩니다.

확인해보니 이런식으로 생성 되네요.

 

2. 컴파일 과정

전처리 과정이 끝나면  이제 컴파일 과정으로 들어갑니다.

 

컴파일 과정은 크게 전단부, 중단부, 후단부로 나눌 수 있습니다.

전처리가 끝난 .i 파일을 컴파일 하면

filename.s(확장자 .s) 어셈블리 코드로 이루어진 파일이 만들어집니다.

3. 어셈블러(Assembler)

이제 완전히 기계어로 바꿔주는 역할을 합니다. 우리가 읽을 수 없거든요.

 

cat 명령어를 통해 읽어보면 알아볼 수 없는 글들이 나옵니다.

 

xxd 명령어를 통해서 한 번 읽어볼까요?

xxd 명령어는 주어진 파일이나 input으로 들어온 문자들에 대해서 hex dump(컴퓨터 데이터의 16진법적인 보임새)를

만들어 준다. Endian에 관계 없이 파일에 존재하는 순서대로 나옵니다.

hexdump라는 명령어도 있는데 출력하면 다음과 같이 나옵니다.

개인적으로 xxd 명령어가 더 마음에 듭니다.

 

이렇게 어셈블러로 기계만 해석할 수 있는 기계어가 만들어집니다.

참고로, 기계어를 어셈블리 언어로 바꿔주는 프로그램을 disassembler(디스 어셈블러)라고 합니다.

 

링커(Linker)

링커는 이름이 말해주듯이 연결해주는 역할을 합니다. 여러개의 오브젝트 파일을 하나로 합치거나

라이브러리, 사용자 라이브러리를 합칠 때 링커가 필요합니다.

 

printf() 함수나 scanf() 등의 표준 c 라이브러리 함수들은 여러분이 직접 구현하지 않아도

미리 컴파일이 되어 있기 때문에 링크하는 과정만 거치면 사용할 수 있습니다.

 

이렇게 링킹 과정이 끝나면 드디어 실행 가능한 실행 파일이 만들어지게 됩니다.

 

 

호기심

위에서 보면 #include를 통해서 stdio.h의 내용이 그대로 들어오게 된다는데

링킹 과정에서 그러면 표준 c 라이브러리,  사용자 라이브러리들을 링크하는 이유는 무엇인가?

 

- 헤더파일이랑 라이브러리랑은 다른 것이다.

  음.. 헤더파일은 그냥 인터페이스고 구현은 라이브러리로 한 것이다.

  혹시 파일 분할을 하여 c언어 프로그래밍을 해본 적이 있는가?

 

헤더파일에서는 정의만 가져오는 것이고,  실제 구현은 라이브러리에 되어 있어

헤더파일을 보고 링커가 라이브러리를 쓱쓱 찾아서 링킹해준다.

 

참고로 라이브러리는 코드가 아니라 binary 파일이다.

 

 

정적 라이브러리와 공유 라이브러리

리눅스에서 프로그램을 개발하다 보면 여러 사람들이 함께 개발하는 경우가 종종 있다.

그 때 같은 기능을 여러 사람이 공동으로 사용하게 되는 경우가 있는데,

 

그러한 기능을 각각의 소스 프로그램에 함수로 넣어 사용하는 방법 보다는

라이브러리를 만들어 함께 사용하면

- 사용하기가 훨씬 편리

- 같은 소스코드를 중복 기술하지 않아 효율적인 프로그램 작성 가능

- 다른 프로젝트에도 손쉽게 활용 가능

해진다.

 

라이브러리의 개요

기본적으로 제공되는 표준 라이브러리는 /usr/lib에 존재한다.

c 컴파일러는 프로그램 컴파일 시 기본적으로 이 디렉토리(/usr/lib)에서 라이브러리 함수를

찾아 링크 수행

 

gcc 의 -L 과 -l 옵션을 이용하면 다른 디렉토리에 있는 라이브러리도 이용할수 있음

 

라이브러리의 이름은 lib로 시작되며 라이브러리의 의미를 나타내는 이름 다음에 파일의

확장자 .a .so로 구성된다.

ex. libm.a livm.so

 

라이브러리의 종류

라이브러리에는 정적(static) 라이브러리와 공유(shared)  라이브러리 두 종류가 있다.

그 차이점과 생성 방법을 알아보도록 하자.

 

-정적 라이브러리

: 정적 라이브러리란 미리 만들어 놓은 라이브러리 함수의 오브젝트 코드를

  ar 도구를 이용하여 모아놓은 것이다.

컴파일 과정 중 링크 단계에서 프로그램의 오브젝트 코드와 지정한 라이브러리의

오브젝트 코드를 결합하여 하나의 실행 가능한 파일을 생성한다.

 

 

공유 라이브러리

정적 라이브러리를 이용하여 실행 파일을 생성하면 실행 파일에 라이브러리 함수의 코드가 포함된다.

따라서 이 라이브러리 함수를 여러 프로그램에서 사용하면 각각의 실행 파일에 라이브러리 함수의

코드가 포함된다.

따라서 이 라이브러리 함수를 여러 프로그램에서 사용하면 각각의 실행 파일에 라이브러리 함수의

코드가 포함되고, 동일한 함수의 코드가 메모리 여러 군대에 존재하게 된다.

 

이는 메모리나 하드 디스크 공간이 그만큼 낭비된다는 의미이다.

 

이와 같은 정적 라이브러리 함수의 단점을 극복한 것이 공유 라이브러리이다.

 

프로그램이 공유 라이브러리를 사용하면 실행 파일에 라이브러리 함수

코드가 포함되는 것이 아니라 실행 시 사용 가능한 공유 코드를 참조하는 방식으로 링크된다.

 

따라서 이 공유 라이브러리 함수를 이용하는 프로그램이 여러개 일지라도 하나의 라이브러리를 함께

이용하므로, 메모리나 하드 디스크를 효과적으로 사용할 수 있다.

 

리눅스에서 공유 라이브러리를 로드하고 참조 함수를 확인하는 프로그램을 동적로더(ld.so)라고 한다.

이 로더는 /etc/ld.so.conf에 설정된 경로를 이용하여 공유 라이브러리를 검색한다.

 

이 구성 파일에 경로를 추가한 후 ldconfig 명령을 이용하면 캐쉬를 업데이트 한다.

 

 

참고한 블로그

https://reakwon.tistory.com/52

 

[C언어] 컴파일 과정(Compile Process) 4단계 자세한 설명

컴파일 과정 Visual Studio에서 우리는 실행할때 F5(또는 Ctrl+F5)를 눌러서 우리가 만든 소스코드를 실행시켜봤죠? 우리는 너무 쉽게 프로그램을 실행시킨다고 생각할 수 있지만 의외로 몇몇 단계를 거치고 있습..

reakwon.tistory.com

https://gracefulprograming.tistory.com/16

 

[C언어 강좌-2] C언어 컴파일 과정

안녕하세요 피터입니다. 오늘은 지난시간에 이어 C언어의 컴파일 과정에 대해 설명드리겠습니다. 앞서 여러분이 작성했던 Hello world 코드가 컴퓨터에서 실행이 되려면 우선 컴파일(Compile) 과정을 거쳐야 합니..

gracefulprograming.tistory.com

https://babuzzzy.tistory.com/entry/4-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%83%9D%EC%84%B1

 

4. 리눅스 시스템 프로그래밍 - 컴파일 & 라이브러리 생성

시스템 콜 및 라이브러리 함수를 이용하여 소스 프로그램을 작성하면 컴파일 과정을 통해 실행 파일을 만들어야 한다 또한 이 실행 코드를 재사용하기 위해서 라이브러리를 생성하여 사용하기도 한다. 이 절에서는..

babuzzzy.tistory.com

 

긴 글 읽어주셔서 감사합니다.