링커·로더·JIT가 프로그램 실행을 완성하는 방식

컴파일 결과가 실행 파일과 프로세스로 이어지는 과정에서 링커, 로더, JIT 컴파일러가 맡는 역할과 OS 연계 지점을 정리한다.

2026-08-14 · 최초 발행 2026-01-16

소스 코드가 프로세스가 되기까지

컴파일러와 운영체제의 접점은 소스 코드가 실행 가능한 프로그램이 되는 데서 끝나지 않는다. 오브젝트 파일을 묶고, 필요한 심볼을 연결하며, 실행 파일을 메모리에 배치한 뒤 실행 중의 코드까지 최적화하는 과정이 함께 이어진다.

소스 코드(.c)전처리기컴파일러어셈블러오브젝트 파일(.o)링커실행 파일로더메모리 적재프로세스 실행

전처리기는 매크로를 확장하고 헤더를 포함한다. 컴파일러는 고수준 언어를 어셈블리로 옮기며, 어셈블러는 이를 기계어 형태의 오브젝트 파일로 바꾼다. 이후 링커가 여러 오브젝트 파일을 결합하고, 로더가 생성된 실행 파일을 메모리에 적재한다.

이 경로가 OS와 만나는 기준점은 ABI(Application Binary Interface), 시스템 콜 인터페이스, 호출 규약(Calling Convention), 실행 파일 포맷이다. ABI는 바이너리 수준의 호환성 규약을 정하고, 호출 규약은 함수 호출 시 파라미터 전달 방식을 정의한다. 실행 파일은 ELF, PE, Mach-O 같은 형식으로 운영체제에 전달된다.

링커가 모듈을 하나의 실행 단위로 묶는 과정

링커는 여러 오브젝트 파일을 하나의 실행 파일로 결합한다. 함수와 변수 참조를 연결하는 심볼 해석, 주소를 맞추는 재배치(Relocation), 정적·동적 라이브러리 통합이 이 단계의 핵심이다.

정적 링킹에서는 링크 시점에 필요한 코드를 실행 파일에 결합한다.

오브젝트 파일들정적 링커정적 라이브러리(.a)모든 코드 복사단일 실행 파일 파일 크기외부 의존성 없음

이 방식은 외부 의존성이 없어 배포가 간편하지만 실행 파일 크기가 증가한다. 라이브러리가 갱신되면 재링킹이 필요하고, 메모리도 중복해서 사용할 수 있다.

동적 링킹은 실행 시점에 공유 라이브러리(.so, .dll)를 불러온다. 여러 프로세스가 같은 라이브러리를 공유할 수 있어 메모리 효율을 얻고, 라이브러리를 독립적으로 업데이트할 수 있다. 반면 런타임 의존성을 관리해야 한다.

심볼 해석은 각 오브젝트 파일의 심볼 테이블을 모은 뒤 정의되지 않은 심볼을 찾아 연결하고, 중복 정의를 검사하는 절차다.

YesNo미해석 심볼심볼 테이블 검색심볼 발견?주소 계산링크 오류재배치 엔트리 생성주소 패치

주소가 정해지면 재배치가 수행된다. 상대 주소는 절대 주소에 맞춰 조정되며, GOT(Global Offset Table)는 전역 변수 주소를, PLT(Procedure Linkage Table)는 함수 주소를 저장한다. PIC(Position Independent Code)는 주소에 독립적인 코드 생성을 위한 방식이다.

로더가 만드는 실행 환경

로더는 실행 파일을 메모리에 적재하고 가상 주소 공간을 설정한다. 동적 라이브러리 로딩, 초기 스택과 힙 구성, 프로그램 카운터 설정 뒤 실행 시작도 로더가 준비하는 범위에 속한다.

실행 파일 열기ELF 헤더 파싱프로그램 헤더 읽기메모리 영역 할당세그먼트 로딩동적 링커 호출재배치 수행초기화 함수 실행main() 실행

실행 파일 포맷을 검증한 뒤 코드, 데이터, BSS, 스택, 힙을 위한 메모리 맵을 생성한다. 세그먼트를 메모리로 복사하고 읽기·쓰기·실행 권한을 설정한 다음, 엔트리 포인트로 제어를 넘긴다.

동적 링커/로더인 ld.so는 동적 라이브러리를 검색하고 로딩하며 런타임 심볼을 해석한다. 지연 바인딩(Lazy Binding)은 심볼이 필요한 시점에 해석하는 방식이다. LD_LIBRARY_PATH 환경 변수를 사용할 수 있고, RPATH와 RUNPATH 제한은 보안 관점에서 함께 다뤄진다.

프로세스의 가상 주소 공간은 일반적으로 다음과 같은 세그먼트로 구성된다.

높은 주소커널 영역스택(아래로 성장)힙(위로 성장)BSS 세그먼트데이터 세그먼트텍스트 세그먼트낮은 주소

텍스트 세그먼트는 읽기 전용 실행 코드를 담는다. 데이터 세그먼트에는 초기화된 전역 변수가, BSS 세그먼트에는 초기화되지 않은 전역 변수가 들어간다. 힙은 동적 메모리 할당에, 스택은 지역 변수와 함수 호출 정보에 사용된다.

ASLR(Address Space Layout Randomization)은 메모리 주소를 무작위화해 보안을 강화하는 기법이다. 스택, 힙, 라이브러리 주소를 랜덤화하여 버퍼 오버플로우 공격 방어에 활용하며, PIE(Position Independent Executable)가 필요하다.

실행 중에 코드를 바꾸는 JIT 컴파일

JIT(Just-In-Time) 컴파일러는 실행 시점에 바이트코드나 중간 코드를 네이티브 코드로 컴파일한다. 인터프리터 실행과 AOT(Ahead-of-Time) 컴파일 사이의 특성을 가지며, 자주 실행되는 핫스팟을 찾아 최적화하면서 플랫폼 독립성과 성능 향상을 함께 추구한다.

자주 실행가끔 실행바이트코드/중간 코드인터프리터 실행핫스팟 탐지JIT 컴파일네이티브 코드캐싱빠른 실행

실행 프로파일을 활용하는 적응형 최적화, 점진적으로 최적화 수준을 높이는 티어드 컴파일(Tiered Compilation), 동적 타입 정보를 저장하는 인라인 캐싱이 JIT 전략에 포함된다. 가정이 깨졌을 때 인터프리터로 돌아가는 탈최적화(Deoptimization)도 필요하다.

최적화 단계에서는 메서드 인라이닝으로 함수 호출 오버헤드를 줄이고, 루프 언롤링으로 반복 오버헤드를 낮춘다. 공통 부분식 제거(CSE)는 중복 계산을 없애며, 데드 코드 제거는 실행되지 않는 코드를 삭제한다. 레지스터 할당 최적화는 메모리 접근을 최소화한다.

JVM(Java Virtual Machine)은 Java 소스를 javac로 컴파일해 바이트코드를 만들고, 클래스 로더와 바이트코드 검증을 거쳐 인터프리터와 JIT 컴파일을 함께 사용한다.

YesNoJava 소스(.java)javac 컴파일바이트코드(.class)클래스 로더바이트코드 검증인터프리터 실행프로파일링핫스팟?C1/C2 컴파일러네이티브 코드

C1 컴파일러는 빠른 컴파일과 기본 최적화를, C2 컴파일러는 느린 컴파일과 고급 최적화를 담당한다. Graal 컴파일러는 최신 고성능 JIT 컴파일러이며, Java 9+에서는 AOT 모드로 사전 컴파일을 지원한다.

V8 JavaScript 엔진은 빠른 시작을 위한 Ignition 인터프리터와 고성능 최적화를 위한 TurboFan JIT 컴파일러를 사용한다. 히든 클래스는 JavaScript 객체 구조를 최적화하고, 인라인 캐싱은 속성 접근을 가속한다.

.NET CoreCLR에서는 RyuJIT가 JIT 컴파일러 역할을 한다. 티어드 컴파일은 빠른 시작과 최적화 사이의 균형을 제공하며, ReadyToRun은 사전 컴파일된 코드를 포함한다. Native AOT는 완전한 네이티브 컴파일을 지원한다.

JIT와 운영체제가 만나는 지점

JIT 컴파일러는 생성한 코드를 저장할 메모리를 운영체제에서 받아야 한다. 코드 캐시 메모리를 할당하고 코드 페이지의 실행 권한을 설정하며, 메모리 압박이 발생하면 코드 캐시를 해제한다.

JIT 컴파일코드 메모리 할당mmap 시스템실행 가능 페이지W^X 정책쓰기 또는 실행

이 과정에서는 W^X(Write XOR Execute) 정책을 준수한다. JIT 컴파일은 백그라운드 스레드에서 수행될 수 있어 컴파일 중에도 인터프리터 실행을 계속할 수 있다. 코드 패치에는 동기화가 필요하며, 멀티코어에서는 병렬 컴파일을 활용한다.

운영체제 기능은 mmap을 통한 코드 메모리 할당, mprotect를 통한 메모리 권한 변경, perf_event_open을 통한 성능 카운터 접근, futex를 통한 빠른 사용자 공간 뮤텍스에 활용된다.

AOT·WebAssembly·PGO가 넓히는 선택지

AOT(Ahead-of-Time) 컴파일은 배포 전에 코드를 네이티브 코드로 컴파일한다. 빠른 시작 시간과 예측 가능한 성능을 제공하며, 클라우드 네이티브 환경에 적합하다. GraalVM Native Image와 .NET Native AOT가 여기에 해당한다.

WebAssembly(Wasm)는 웹과 비웹 환경 모두를 대상으로 한다.

C/C++/Rust 소스Wasm 컴파일Wasm 바이너리브라우저/런타임Wasm VMJIT 컴파일네이티브 실행

Wasm은 샌드박스 보안 모델을 제공하고 거의 네이티브 성능을 목표로 한다. WASI(WebAssembly System Interface)를 통해 OS 기능에 접근할 수 있다.

Profile-Guided Optimization (PGO)은 실행 프로파일을 수집한 뒤 이를 바탕으로 컴파일을 최적화한다. 핫 경로를 우선 대상으로 삼고 분기 예측 정보를 활용한다.

컴파일러링커로더JIT 컴파일러운영체제