June 09, 2020
참고도서: Operating System Concepts (10/E) Abraham Silberschatz, Peter B. Galvin, Greg Gagne
우리가 작성한 소스코드는 한 번의 버튼이나 커맨드를 통해서 실행되곤 하지만 그 내부는 생각보다 복잡하다. 컴파일부터 실행파일이 되어 프로세스로 메모리에 적재되기까지 소스코드가 변화되는 과정을 알아보자.
컴파일러를 통해 소스코드를 컴파일하게 되면 가장 처음 일어나는 작업이 주어진 소스코드를 새로운 소스코드로 번역하는 작업이다. 여기서 새로운 소드코드
는 우리가 import 하거나 include 한 외부의 라이브러리들, 매크로 등, 기존의 소스코드를 대체하는 코드들이 모두 합쳐진 코드를 말한다. 예를 들어, C언어의 printf()
함수는 우리가 직접 정의한 것이 아니라 stdio.h 헤더 파일안에 정의되어 있다. 그리고 이 헤더안에 정의된 코드들이 소스코드를 변환화는 과정에서 기존 소스코드를 대체하게 된다.
우리가 잘 아는 컴파일 과정을 거치면 소스코드가 바이너리코드와 데이터로 해석되어 오브젝트 파일이 된다. 컴파일러는 가장 먼저 소스코드를 어셈블리 언어로 이루어진 명령어들로 해독하고 이 명령어들이 다시 이진 코드로 해석해서 오브젝트 파일을 생성하게 된다. 컴파일 단계에서 가장 먼저 생성되는 오브젝트 파일은 재배치 가능한 오브젝트 파일(Relocatable object file)
이다. 재배치 가능한 오브젝트 파일은 단일한 하나의 소스코드에 대해서 하나의 파일로 만들어진다. 그리고 여러 소스코드들에서 생성된 이 오브젝트 파일들이 하나로 묶여서 실행가능한 오브젝트 파일(Executable object file)
이 된다. 이렇게 여러 오브젝트파일을 하나로 모아 실행가능한 파일을 만드는 작업을 링킹(Linking)
이라고 한다.
링커는 링킹을 하도록 도와주는 역할을 한다. 여러파일에 조각조각 흩어진 변수들이나 함수 등을 하나의 오브젝트 파일로 만들 수 있도록 도와준다. 링킹은 또, 두 가지 경우로 나누어지는데 하나는 정적 링킹(Static linking)
다른 하나는 동적 링킹(Dynamic Linking)
이라고 한다. 정적 링킹은 우리가 일반적으로 생각하는 그 링킹이다. 실행가능한 오브젝트 파일을 만들면서 각 오브젝트에 포함된 라이브러리의 코드들이 실행파일에 복사된다. 그런데 한정된 컴퓨터 공간 속에서 printf()
처럼 매우 빈번하게 여러 프로세스들이 모두 가지고 있는 라이브러리의 함수들의 코드를 실행파일에 담는 것은 엄청난 자원의 낭비이다. 그래서 우리의 척척이 박사님들은 동적 링킹이라는 기법을 고안해낸다.
동적링킹은 라이브러리가 실행파일에 연결되는 시점을 실행파일이 만들어질 때가 아니라, 이 프로그램이 메모리에 적재되어 실행되는 단계에 이를때까지 연결을 미루는 것이다. 대신 이 라이브러리에 포함된 함수의 원형이나 심볼화된 스니펫을 코드에 끼워넣어서 컴파일 과정에서는 전혀 문제가 없이 컴파일 되도록 한다. printf()
와 같은 함수가 포함된 라이브러리는 아마 모든 프로세스가 빠짐없이 다 사용할 것이다. 따라서 이 라이브러리 코드의 사본을 모두에게 각각 주는 것이 아니라, 필요할때만 이 라이브러리를 연결해서 사용하도록 하면, 우리는 좁은 메모리 공간에 하나의 라이브러리를 두는 것 만으로도 여러 프로세스들을 먹여사릴 수 있다. 이렇게 모두에게 동적으로 연결되어 공유되는 라이브러리를 동적 연결 라이브러리(Dynamic Linking Library)
, 혹은 공유 라이브러리라고 한다.
링커에 의해 만들어진 실행파일은 이제 디스크에 만들어졌다. 우리가 컴파일 이후에 만나게되는 a.out
과 같은 녀석이 바로 이 녀석이다. 그럼 만약 우리가 이 실행파을 실행하게 되면 내부에서는 어떤 일이 일어날까? 이때 로더가 등장한다. 로더는 실행파일을 메모리에 적재시키는 것이 주 임무이다. 메모리에 올라간 파일은 실행과 함께 프로세스가 되고, 스케줄링을 기다리게된다. 컴퓨터 시스템에서 일어나는 모든 작업은 프로세서(CPU)에 의해 진행되는데, 프로세서가 접근할 수 있는 유일한 메모리 공간이 바로 메인메모리이다. 하지만 실행파일은 메인 메모리가 아닌 디스크에 있으니, 이 녀석을 프로세서가 사용할 수 있는 공간으로 넣어주는 것이다.
이 이후의 과정들은 스케줄러를 통해 CPU를 선점하고 CPU에 의해 프로세스의 명령어들이 실행된다.