프로그램 컴파일부터 메모리까지

 

 

위에서 보는대로,

컴파일러를 통해 고수준 언어의 코드를 기계어로 변환하고, 어셈블러를 통해 기계어 명령어를 생성한다.

이 때, 컴파일러는 각 번역 단계에서 필요한 메타데이터를 포함하여, 실행 파일을(exe) 만들기 위한 정보를 제공한다.

 

 

여기에서 정적 링크란, 프로그램의 실행 파일을 생성할 때, 모든 외부 라이브러리를 프로그램 코드에 직접 포함시키는 방식을 말한다. 

여기에서는 프로그램 실행 시 추가적인 링크 작업이 필요하지 않으며, 프로그램의 모든 필요한 코드가 하나의 실행 파일에 포함된다.

 

 

 

 

 

컴파일러는 어셈블러에게 기계어를 넘겨주고, 어셈블러는 명령어로 변환하여 링커에게 넘겨주는데

이 과정에서 오브젝트 모듈이 생성된다.

오브젝트 모듈은 어셈블러 또는 컴파일러에 의해 생성된 기계어 명령어의 집합으로, 실행 가능한 프로그램을 빌드하기 위해 필요한 정보를 포함하고 있다.

 

 

  • 오브젝트 모듈의 주요 구성 요소:
    • 헤더(Header): 오브젝트 모듈의 내용(섹션 크기, 시작 주소 등)을 설명한다.
    • 텍스트 세그먼트: 기계어 명령어로 변환된 프로그램 코드가 포함된다.
    • 정적 데이터 세그먼트: 프로그램 전체에서 사용되는 고정된 데이터가 포함된다.
    • 재배치 정보(Relocation Information): 프로그램이 로드될 때 절대 주소를 설정할 수 있도록 하는 정보이다.
    • 심볼 테이블(Symbol Table): 전역 변수와 함수의 이름과 주소를 나타내는 정보이다.
    • 디버그 정보(Debug Information): 원시 코드와 기계어 코드 간의 매핑 정보를 제공한다.

 

즉, 오브젝트 모듈은 프로그램을 실행 파일로 빌드하기 위한 정보를 포함하는 기계어 코드의 집합이다.

 

 

 

 

 

이와 같은 오브젝트 모듈을 Linker로 넘겨주는데

여기에서 링크(Linking)는 여러 오브젝트 모듈을 결합하여 하나의 실행 가능한 프로그램을 만드는 과정을 말한다.

이 과정에서 프로그램의 각 부분을 통합하고, 모든 외부 참조를 해결한다.

 

 

링크 과정:

  • 세그먼트 병합 (Merging Segments): 여러 오브젝트 파일에 포함된 텍스트, 데이터, 힙, 스택 세그먼트를 하나의 실행 파일로 결합한다.
  • 라벨 해석 (Resolve Labels): 심볼 테이블을 참조하여 함수 및 변수의 주소를 할당한다.
  • 위치 의존 코드 수정 (Patch Location-Dependent Code): 위치 의존적인 주소를 재배치한다.
    예를 들어, 함수 호출의 대상 주소나 전역 변수의 주소를 실행 파일의 최종 메모리 레이아웃에 맞게 변경한다.
    이를 통해 프로그램이 실행될 수 있는 올바른 메모리 주소로 설정된다.

 

그런데 현대의 운영체제에서는 프로그램이 가상 메모리를 사용하기 때문에 실행 파일이 반드시 특정 메모리 주소에 로드될 필요가 없다. 그래서 프로그램을 가상 주소 공간에 적재하고, 주소 변환을 통해 실제 메모리 위치에 접근한다.

 

가상 주소 공간에서 프로그램의 각 부분이 독립된 주소를 가지기 때문에, 링커는 실행 파일을 만들 때 이 가상 주소에 맞춰 재배치를 수행하게 된다.

 

 

 

 

 

이제 마지막으로 프로그램을 로딩한다.

 

  1. 프로그램 로딩의 단계:
    • 디스크에서 이미지 파일을 메모리로 로드: 프로그램이 실행되기 위해 디스크에 있는 실행 파일 이미지를 메모리에 적재한다.
    • 헤더 읽기: 파일의 헤더 정보를 읽어 각 세그먼트의 크기를 결정한다.
    • 가상 주소 공간 생성: 프로그램이 실행될 가상 메모리 주소 공간을 만든다.
    • 텍스트와 초기화된 데이터 복사: 프로그램 코드(텍스트)와 초기화된 데이터를 메모리로 복사한다.
    • 스택 설정: 스택을 초기화하고, 프로그램의 초기 인자를 스택에 설정한다.
    • 레지스터 초기화: 모든 레지스터를 초기화한다. $sp(스택 포인터), $fp(프레임 포인터), $gp(글로벌 포인터) 등이 설정된다.
    • 시작 루틴으로 점프: 메인 루틴을 호출하기 전에 프로그램의 초기화를 수행하는 시작 루틴으로 점프한다.

 

 

시작 루틴은 프로그램 실행 전에 초기화 작업을 수행하며, 주로 프로그램의 메인 함수(main())를 호출한다. 메인 함수가 반환되면 종료 루틴이 호출된다.

 

 

간단하게 말하면 프로그램 로딩은 실행 파일을 메모리에 적재하고, 가상 주소 공간을 설정하며, 프로그램을 실행하기 위해 필요한 초기화 작업을 수행하는 단계이다.

 

 

 

 

추가)

위에서 설명한 정적 링크가 아닌 동적 링크도 있다.

동적 링크(Dynamic Linking)는 프로그램 실행 중에 필요한 라이브러리를 메모리에 로드하고, 해당 라이브러리의 코드와 데이터를 프로그램에 연결하는 방식이다.

이 방식은 정적 링크와 달리, 실행 파일 크기를 줄이고 메모리 사용량을 최적화할 수 있다.

 

 

  1. 동적 링크의 장점:
    • 메모리 절약: 동일한 라이브러리가 여러 프로그램에서 사용될 때, 메모리에 한 번만 로드되어 여러 프로그램이 공유할 수 있다.
    • 업데이트 용이성: 라이브러리가 업데이트되면, 동적 링크를 사용하는 프로그램은 다시 컴파일하지 않아도 자동으로 새로운 라이브러리를 사용할 수 있다.
    • 실행 시 링크: 라이브러리는 프로그램 실행 중에 필요한 시점에서 로드되며, 이를 통해 프로그램 시작 시점의 메모리 사용량을 줄일 수 있다.
  2. 동적 링크의 단점:
    • 실행 시 오버헤드: 라이브러리를 로드하고 링크하는 과정에서 실행 시간 오버헤드가 발생할 수 있다.
    • 의존성 문제: 프로그램이 특정 라이브러리에 의존하고 있는 경우, 해당 라이브러리가 변경되거나 삭제되면 프로그램 실행에 문제가 생길 수 있다.