[CSAPP] hello.c 빌드 및 실행 과정 분석: 소스 코드에서 프로세스까지

1. 문제 제기 (Introduction & Problem Statement)

C언어로 작성된 hello.c 파일은 gcc hello.c -o hello 명령어 하나로 간단히 실행 파일이 된다. 개발자에게 이 과정은 익숙하지만, 내부적으로 소스 코드가 어떻게 기계가 이해하는 형태로 변환되며, 이 파일이 어떻게 메모리에 적재되어 CPU에 의해 실행되는지에 대한 이해는 시스템의 동작 원리를 파악하는 데 필수적이다.

본 아티클에서는 hello.c 프로그램을 예시로, 하나의 텍스트 파일이 실행 가능한 프로세스가 되기까지의 전체 파이프라인(전처리, 컴파일, 어셈블, 링킹)과 이를 처리하는 하드웨어(CPU, 메모리, 버스)의 상호작용을 CSAPP의 관점에서 단계별로 분석한다.

2. 기술 분석 및 해결 과정 (Technical Deep Dive)

2-1. 빌드 파이프라인: C 코드가 기계어가 되기까지

hello.chello 실행 파일로 변환되는 과정은 컴파일 시스템에 의해 4단계로 수행된다.

  • 1) 전처리 (Preprocessing)

    • 입력: hello.c (C 소스 파일)
    • 역할: 전처리기(cpp)가 소스 코드의 #으로 시작하는 지시문(e.g., #include, #define)을 처리한다. 예를 들어 #include <stdio.h>stdio.h 파일의 내용을 소스 코드에 삽입한다.
    • 결과: hello.i (수정된 C 소스 파일)
  • 2) 컴파일 (Compilation)

    • 입력: hello.i
    • 역할: 컴파일러(cc1)가 C 코드를 저수준의 기계 독립적인 언어인 어셈블리어로 번역한다.
    • 결과: hello.s (어셈블리 코드 파일)
  • 3) 어셈블 (Assembly)

    • 입력: hello.s
    • 역할: 어셈블러(as)가 어셈블리 코드를 기계어(0과 1의 조합)로 변환하고, 이를 '재배치 가능한 목적 파일(relocatable object file)' 포맷으로 패키징한다.
    • 결과: hello.o (바이너리 목적 파일)
  • 4) 링킹 (Linking)

    • 입력: hello.o 및 라이브러리 파일(e.g., printf 함수가 포함된 libc.so)
    • 역할: 링커(ld)가 hello.o와 필요한 라이브러리 목적 파일들을 하나로 합쳐 최종 실행 파일을 생성한다. 이 과정에서 각 목적 파일에 흩어져 있는 코드와 데이터의 주소가 결정된다.
    • 결과: hello (실행 가능한 목적 파일)

2-2. 실행: 디스크 파일에서 메모리 프로세스로

생성된 hello 파일을 셸에서 ./hello로 실행하면 OS와 하드웨어는 다음과 같이 동작한다.

  1. 로딩(Loading): 셸은 hello 파일을 디스크에서 읽어 주 메모리(DRAM)에 복사(적재)한다. 이 데이터 전송은 CPU를 거치지 않고 디스크 컨트롤러가 메모리에 직접 쓰는 DMA(Direct Memory Access) 방식을 사용하여 CPU의 부담을 줄인다.
  2. 실행(Execution): 데이터 로드가 완료되면 CPU가 프로그램의 main 함수부터 명령어를 실행한다.
    • CPU 내 PC(Program Counter) 레지스터는 다음에 실행할 명령어의 메모리 주소를 저장한다.
    • CPU는 PC가 가리키는 주소의 명령어를 메모리에서 읽고(Load), 해석한 뒤, ALU(산술논리연산장치)를 통해 연산을 수행(Operate)하고, 결과를 다시 레지스터나 메모리에 저장(Store)하는 사이클을 반복한다.

2-3. 성능의 핵심, 메모리 계층 구조 (Memory Hierarchy)

시스템 설계의 근본적인 문제 중 하나는 CPU의 연산 속도와 주 메모리의 접근 속도 간의 큰 격차, 즉 '프로세서-메모리 갭'이다. CPU가 매번 느린 주 메모리를 기다린다면 시스템 전체의 성능이 저하될 수밖에 없다.

이 문제를 해결하기 위해 컴퓨터는 '속도, 용량, 비용'을 고려한 저장장치 계층(Memory Hierarchy) 구조를 채택했다.

계층 장치 종류 특징
L0 레지스터 CPU 내부에 위치하며 가장 빠르지만 용량이 극히 작음 (수 KB)
L1/L2/L3 캐시 (SRAM) CPU 칩 내/외부에 위치. 레지스터보다 느리지만 메모리보다 훨씬 빠름 (수 MB)
L4 주 메모리 (DRAM) 실행에 필요한 코드/데이터를 저장. 캐시보다 느리지만 용량이 큼 (수 GB)
L5 디스크 (SSD/HDD) 영구 저장을 위한 공간. 가장 느리지만 용량이 매우 큼 (수 TB)

CPU는 데이터 요청 시, 가장 빠른 L1 캐시부터 확인한다. 데이터가 있으면(Cache Hit) 즉시 사용하고, 없으면(Cache Miss) 다음 계층(L2, L3, 주 메모리)으로 순차적으로 확인한다. 하위 계층에서 발견된 데이터는 상위 캐시로 복사되어 다음 접근 속도를 높인다.

이 구조는 프로그램이 한 번 접근한 데이터나 그 주변 데이터를 다시 접근할 확률이 높다는 지역성(Locality)의 원리에 기반하기에 효율적으로 동작할 수 있다.

3. 결론 및 고찰 (Conclusion & Takeaways)

  • 핵심 요약: hello.c의 실행은 단순 변환이 아닌, 컴파일 시스템에 의한 번역 파이프라인과 하드웨어의 실행 사이클, 그리고 성능 최적화를 위한 메모리 계층 구조가 상호작용한 결과다.
  • 향후 과제: 이 분석은 단일 프로그램의 실행에 초점을 맞췄다. 실제 OS 환경에서는 다수의 프로세스가 동시에 실행되는데, 이때 OS는 어떻게 CPU 자원을 분배하고 프로세스 간 전환(Context Switching)을 수행하는지, 그리고 프로세스 내의 실행 단위인 스레드는 메모리를 어떻게 공유하고 동시성 문제를 야기하는지에 대한 추가적인 탐구가 필요하다.