신입 게임 개발자의 프로그래밍 일기

[C++ 입문자에서 벗어나기]Chapter_3: 빌드(Build)#2 본문

Language/C++

[C++ 입문자에서 벗어나기]Chapter_3: 빌드(Build)#2

KFGD 2017. 12. 1. 14:10

[C++ 입문자에서 벗어나기]Chapter_3: 빌드(Build)#2


원래 계획대로는 링크 단계의 세부과정인 심볼 해석(Symbol Resolution)과 재배치(Relocation)에 대해 이야기해보려 했으나 이 이야기는 추후 시스템 프로그래밍이라는 다른 카테고리에서 해야할 것 같습니다. 이것들에 대한 세부 이야기보다는 아래와 같은 정보를 이야기하는 것이 보다 

C++ 입문자에서 벗아나기 포스팅에 적합하다고 판단하였습니다.


-정적 링킹(Static Linking)과 정적 라이브러리(Static Library)

-동적 링킹(Dynamic Linking)과 공유 라이브러리(Shared Library)

-묵시적인 로드타임 링킹(Implicit load-time linking)과 명시적인 런타임 링킹(Explicit run-time linking)


[정적 링킹(Static Linking)과 정적 라이브러리(Static Library)]


Static Linking은 재배치 가능 목적 파일(Relocatable Object File)들을 입력으로 수행되는 링크 작업으로 Object File의 코드를 그대로 복사하여 실행 바이너리에 포함시키는 작업을 말합니다.

정적라이브러리(.lib)는 일련의 .obj파일들을 모아놓은 파일 포맷입니다.

Unix에서 사용되는 링커인 GNU ld의 처리 과정을 살펴보면 아래와 같습니다.


E: 실행 파일을 구성하기 위해 합쳐질 재배치 가능 목적 파일들의 집합

U: 미해석된 심볼 집합(정의되지 않은 심볼을 참조하는 집합)

D: 이전 입력 파일들에서 정의된 심볼 집합


 



* 재배치 목적 파일들에도 다른 목적 파일에 정의된 심볼에 대한 참조가 있을 수 있음을 유의하자!




[동적 링킹(Dynamic Linking)과 공유 라이브러리(Shared Library)]


*DLL(Dynamic Link Library)은 Shared Library의 일종입니다.


"여러 프로그램에서 공통으로 사용하는 라이브러리를 한번만 메모리에 적재하여 프로그램의 크기를 줄여보자"가 탄생 배경입니다.

윈도우 운영체제는 공통 라이브러리들을 DLL 형식으로 제공하고 있으며 WIN32 API들을 살펴보면 

라이브러리 포맷이 DLL임을 확인 할 수 있습니다.(ex. KERNEL32.DLL, GDI32.DLL 등)

Static Linking의 경우 정적 라이브러리의 코드를 실행 바이너리 내부에 포함시키는 형태로 작업이 수행된다면

Dynamic Linking은 메모리에 라이브러리를 적재하고 이것을 맵핑(Mapping)하는 형태로 수행됩니다.

아래 그림을 참고해주세요.

 


(좀 더 알아보기!)

-만약 프로세스 A가 DLL 페이지(코드의 일부)에 쓰기 작업을 수행할 경우, Copy-On-Write라는 메모리 보호 정책에 따라 해당 페이지를 복사하여 제공한다.

=>https://msdn.microsoft.com/en-us/library/windows/desktop/aa366785(v=vs.85).aspx


메모리의 관점에서 DLL의 작동 원리를 살펴보았다면 이번에는 운영체제 관점에서 간단하게 살펴보겠습니다.

Windows 운영체제는 DLL을 참조 계수(Reference Count)를 이용하여 관리하는데 특정 스레드가 DLL을 로드(LoadLibrary API 함수)하면 참조 계수를 1증가시키고 스레드가 FreeLibrary API 함수를 호출하여 더이상 DLL을 사용하지 않을 시, 참조 계수를 1 감소시킵니다.

최종적으로 참조 계수가 0이 되거나 프로세스가 종료되면 Windows 운영체제는 물리적 메모리에서 DLL을 해제합니다.



[묵시적인 로드타임 링킹(Implicit load-time linking)과 명시적인 런타임 링킹(Explicit run-time linking)]


동적 링킹에는 두가지 종류가 있습니다.

그것들의 구분 기준은 동적 링킹이 수행되는 시점에 따라 달라집니다.

이 포스팅에서는 Explicit run-time linking에 대해서는 간단히 언급만하겠습니다. 그 이유는 상대적으로 덜 보편적으로 사용되는 방식이기 때문입니다. 

(중요하지 않아서가 아니에요... 다른 포스팅에서 다뤄볼 예정입니다.)

Implicit load-time linking을 하기 위해서는 총 3개의 파일들이 필요합니다.

그것들은 각각 헤더파일(.h), 라이브러리(.lib), 동적 라이브러리(.dll)로 이것들이 왜 필요한지에 대해 설명하면서 Implicit load-time linking을 설명해보겠습니다.

(여기서 .lib는 정적 라이브러리와 쓰임이 다릅니다.) 


헤더파일이 필요한 이유는 소스코드의 컴파일이 가능하기 위해서입니다.

이전에도 말했지만 컴파일러는 cpp단위로 컴파일을 수행하기 때문에 동적 라이브러리가 제공하는 Symbol(변수, 함수, 클래스)들의 선언부를 제공해야만 라이브러리의 Symbol들을 이용이 가능하며 헤더파일은 이 Symbol들의 선언부를 가지고 있습니다.

헤더파일을 추가함으로써 컴파일 단계는 통과했습니다.

이제는 링크 단계입니다!


linker: 헤더파일에 있는 Symbol이 어디에 있는거야?

.lib: 내가 가지고 있어!


linker는 헤더파일에 있는 Symbol 어느 dll에 있는 지를 확인하기 위해 .lib를 분석합니다.

.lib에는 dll에서 export하고 있는 Symbol들이 어느 dll에 있는 지에 대한 정보가 들어있습니다.

이제 링커는 .lib를 통해 dll에 접근하여 Symbol들의 정의 정보를 확인하고 실행 모듈(실행 바이너리)의 Import Address Table(IAT)섹션에 dll 정보를 기록합니다.

이제 실행 모듈이 완성되었습니다. 이 실행 모듈을 실행 시키면 loader는 실행 모듈의 IAT를 분석하여 섹션 내에 포함되어 있는 모든 DLL을 시스템으로부터 찾아내어 프로세스의 주소 공간에 맵핑을 합니다. 


Explicit run-time linking은 프로그램이 실행 중에 Kernel32.dll에 포함되어 있는 LoadLibrary를 비롯한 API 함수들로 동적 링킹을 수행하는 작업입니다.

이 작업의 특징은 아래와 같습니다.

=>MSDN의 Determining Which Linking Method to Use라는 게시글을 번역 및 축약시킨 것에 불가합니다.(오역이 있을 수 있습니다.)

=>https://msdn.microsoft.com/en-us/library/253b8k2c.aspx


- Implicit load-time linking과는 다르게 .lib파일이 필요하지 않습니다.

- Implicit load-time linking은 프로세스 실행 시에 필요한 dll이 확인되지 않을 시 운영체제에 의해 프로세스가 종료되지만 Explicit run-time linking은 이런 상황에서도 강제 종료되지 않고 다른 방안을 시도해볼 수 있습니다.(ex. dll 경로 재조정)

- DLL들은 개별적으로 DllMain 함수라는 진입점 함수가 존재하는데 Implicit load-time linking은 필요한 dll중 하나라도 DllMain 함수가 실패하면 프로세스가 종료되지만 Explicit load-time linking은 그렇지 않습니다.

- Implicit load-time linking은 프로세스 로드 시에 모든 DLL들을 로드하는 작업을 수행합니다. DLL 로드는 시간을 요하는 작업임으로 필연적으로 프로그램의 로드는 느려질 수 있는데 Explicit run-time linking을 활용하여 프로그램 실행 시 곧장 필요한 DLL만을 Implicit load-time linking을 수행하고 나머지는 추후에 Explici run-time linking방식으로 수행함으로써 실행 성능을 향상시킬 수 있습니다.

- Implicit load-time linking은 DLL이 변경될 시에 재링크가 필요하지만 Explicit run-time linking은 DLL이 변경되더라도 프로그램을 재실행 및 재링크 할 필요가 없습니다. GetProcAddress와 같은 함수를 통하여 해결이 가능합니다.


Explicit run-time linking을 수행 할 시 주의해야 할 사항:

- DllMain 함수를 가지는 DLL의 경우, 이미 해당 DLL이 메모리에 적재되어있는 상태에서는 LoadLibrary을 호출하더라도 DllMain이 호출되지 않습니다.

- (두번째 주의사항은 무슨 말인지 이해를 못해서 안쓸거에요. 나중에 이해되면 적을게요.)



Chapter_3: 빌드(Build) 포스팅은 이것으로 마치겠습니다.

계획이 약간 틀어졌지만 이 포스팅에서 이야기하지 못한 것(Symbol Resoultion, Relocation 등)들은 나중에 다른 포스팅에서 이야기하겠습니다.


*직접 Visual Studio에서 코딩을 하면서 lib와 dll을 만들어보면 좋겠으나 개인적인 사정상 현재는 불가능해서 아쉽다.

Comments