1. 컴퓨터 시스템 구조 (큰 관점)

컴퓨터 시스템은 크게 하드웨어 구조운영체제 관점의 구조로 나눠서 볼 수 있다.

  • 컴퓨터 구조 (Hardware 중심)

    • CPU ↔ Cache ↔ Main Memory
    • 연산 성능과 데이터 접근 속도 최적화에 초점
  • 운영체제 관점

    • CPU ↔ Main Memory ↔ Storage (HDD/SSD)
    • 프로세스 실행과 자원 관리에 초점

2. 컴퓨터 하드웨어의 구성

2.1 CPU (Central Processing Unit)

프로그램을 실제로 실행하는 핵심 장치

구성 요소

  • ALU (Arithmetic Logic Unit)

    • 산술 연산: 덧셈, 뺄셈 등
    • 논리 연산: AND, OR, NOT 등
    • 즉, 모든 계산의 실질적인 수행 주체
  • Control Unit (제어 장치)

    • 명령어를 해석하고 각 구성 요소에 작업 지시
    • “무엇을 할지” 결정하는 역할
  • Register Set (레지스터)

    • CPU 내부의 초고속 메모리
    • 연산에 필요한 데이터와 중간 결과를 임시 저장
  • Bus Interface

    • CPU가 외부 버스와 통신하기 위한 인터페이스
    • 메모리 및 I/O 장치와 연결되는 통로 역할

2.2 메인 메모리 (RAM)

  • 실행 중인 프로그램의 코드와 데이터를 저장
  • CPU가 직접 접근하여 명령어를 가져오고 실행
  • 휘발성 메모리 (전원 꺼지면 데이터 사라짐)

2.3 보조 기억 장치 (HDD / SSD)

  • 프로그램과 데이터를 영구 저장
  • 실행 시 → 메인 메모리로 로드됨

2.4 입출력 버스 (Bus System)

컴퓨터 내부 구성 요소 간 데이터를 주고받는 통로

버스 종류

  • Address Bus

    • 데이터가 위치한 주소 전달
    • “어디에 접근할 것인가”
  • Data Bus

    • 실제 데이터 전달
    • “무엇을 전달할 것인가”
  • Control Bus

    • 읽기/쓰기 신호 등 제어 정보 전달
    • “어떻게 동작할 것인가”

역할 요약

  • CPU ↔ Memory ↔ Storage 간 데이터 이동
  • 모든 하드웨어 통신의 기반

3. 클럭(Clock)

  • CPU 내부 구성 요소는 아니지만 매우 중요한 기준 신호
  • 모든 연산과 동작의 타이밍을 동기화
  • 주파수(Hz)로 표현되며, 성능과 직결됨
  • 게임에서 사용하는 델타 타임을 계산하는 주체

예:

  • 3GHz → 1초에 30억 번의 사이클

프로그램의 실행 과정 정리

프로그램은 작성한 소스 코드가 바로 실행되는 것이 아니라, 여러 단계를 거쳐 실행 가능한 파일로 변환된다.

기본 흐름은 다음과 같다.

전체 과정

  1. 전처리기
  2. 컴파일러
  3. 어셈블러
  4. 링커

즉,

소스 코드 → 전처리 → 컴파일 → 어셈블 → 링크 → 실행 파일 생성


1. 전처리기 (Preprocessor)

컴파일이 시작되기 전에 소스 코드에 대해 사전 처리를 수행하는 단계

전처리 후에는 대략적으로:

  • #include로 가져온 파일 내용이 삽입되고
  • SIZE는 숫자 10으로 치환된다

즉, 전처리기는
컴파일러가 실제로 읽을 수 있도록 소스 코드를 정리해주는 역할이라고 볼 수 있다.


2. 컴파일러 (Compiler)

전처리가 끝난 코드를 분석해서 어셈블리 코드 또는 그에 준하는 저수준 표현으로 변환하는 단계

하는 일

  • 문법 검사
  • 타입 검사
  • 최적화
  • 고급 언어 코드를 저급 언어 형태로 변환

핵심

우리가 작성한 C/C++ 코드를 CPU가 더 이해하기 쉬운 형태로 바꾸는 과정이다.

예를 들어:

int sum = a + b;

이런 코드는 컴파일 과정을 거치며 레지스터 사용, 메모리 접근, 덧셈 명령 등으로 풀어진 형태가 된다.

즉, 컴파일러는
사람이 작성한 고급 언어를 기계 친화적인 형태로 바꾸는 역할을 한다.


3. 어셈블러 (Assembler)

컴파일러가 만든 어셈블리 코드를 기계어(바이너리 코드) 로 변환하는 단계

하는 일

  • 어셈블리 명령어를 실제 CPU 명령 코드로 변환
  • 목적 파일(Object File) 생성

예를 들어:

MOV AX, 1
ADD AX, 2

이런 어셈블리 명령은 CPU가 이해할 수 있는 0과 1의 형태로 변환된다.

결과물

이 단계가 끝나면 보통 .obj 또는 .o 같은 목적 파일이 생성된다.

주의할 점은, 이 목적 파일은 아직 완전한 실행 파일이 아니다.

  • 외부 함수 위치가 확정되지 않았을 수 있고
  • 다른 파일의 코드와 합쳐지지 않았을 수 있다

4. 링커 (Linker)

여러 목적 파일과 라이브러리를 연결해서 최종 실행 파일을 만드는 단계

하는 일

  • 여러 소스 파일에서 생성된 목적 파일들을 하나로 결합
  • 라이브러리 연결
  • 외부 심볼 주소 해결
  • 실행 파일 생성

예시

어떤 파일에서:

void Func();

만 선언하고 실제 구현이 없으면, 컴파일 단계에서는 문법상 문제가 없을 수 있다.
하지만 링크 단계에서 실제 Func의 정의를 찾지 못하면 에러가 발생한다.

즉, 흔히 말하는 링커 에러
”이름은 참조했는데 실제 구현을 못 찾았다”는 뜻이다.

참고 )

  • 동적 링킹 (Dynamic Linking) - DLL
  • 실행 시점에 라이브러리 연결

5. 왜 함수 구현이 없으면 링커 에러가 나는가

이 부분은 아주 중요하다.

컴파일러가 보는 것

컴파일러는 우선 문법과 타입이 맞는지만 본다.

예:

void Print();  
int main()  
{  
    Print();  
}

여기서 Print()는 선언되어 있으므로
컴파일러는 “이 함수는 어딘가에 있겠지”라고 생각하고 넘어갈 수 있다.

링커가 보는 것

링커는 실제로
그 함수의 구현이 어디 있는지를 찾아야 한다.

  • 다른 .cpp 파일에 있는가?
  • 라이브러리에 있는가?

그런데 끝까지 못 찾으면 링크 실패가 난다.

즉:

컴파일 에러 = 코드 문법/형식이 잘못됨
링커 에러 = 선언은 있었지만 실제 구현을 못 찾음


프로그램 실행 과정 정리

프로그램은 단순히 실행 파일이 만들어졌다고 끝나는 것이 아니라,
운영체제에 의해 메모리에 적재되고 CPU에 의해 실행되는 과정을 거친다. *적재되는 과정에서 로더(Loader)를 거침


전체 흐름

  • 실행 파일 생성 (링커 단계 완료)
  • 운영체제가 실행 파일을 메모리에 적재
  • CPU가 명령어를 반복적으로 처리

실행 준비 (운영체제 역할)

운영체제는 프로그램 실행을 위해 다음과 같은 작업을 수행한다.

  • 실행 파일을 가상 메모리에 적재
  • 코드 영역, 데이터 영역, 스택, 힙 구성(프로세스의 가상 주소 공간 안에 있는 논리적 구역)
  • 필요한 라이브러리 연결
  • 프로그램 시작 주소(entry point) 설정

CPU 명령어 처리 사이클

CPU는 다음 3단계를 반복하며 프로그램을 실행한다.


1. Fetch (명령어 인출)

메모리에 있는 명령어를 CPU로 가져오는 단계

  • 프로그램 카운터(PC)가 현재 실행할 주소를 가리킴
  • 해당 주소의 명령어를 메모리에서 읽어옴
  • 명령어를 명령어 레지스터(IR)에 저장

흐름:

메모리 → 버스 → CPU → 명령어 레지스터

참고 요즘 대부분의 명령어 Fetch는 RAM이 아니라 캐시에서 일어남


2. Decode (명령어 해석)

가져온 명령어의 의미를 해석하는 단계

  • Control Unit이 명령어 분석
  • 필요한 연산 종류 판단
  • 사용할 레지스터 및 데이터 위치 결정

흐름:

명령어 레지스터 → Control Unit → 해석


3. Execute (명령 실행)

해석된 명령어를 실제로 수행하는 단계

  • 산술/논리 연산 → ALU 수행
  • 데이터 이동 → 레지스터 / 메모리 접근
  • 분기 명령 → 프로그램 카운터 변경

흐름:

Control Unit → ALU / 레지스터 / 메모리


옛날과 좀 다른 것 보완

  • 프로그램은 가상 메모리 기반으로 페이지 단위로 로드됨
  • CPU는 캐시 계층을 통해 메모리에 접근
  • 실행은 단순 순차가 아니라 파이프라인/병렬 처리
  • 실행 유닛은 ALU 외에도 다양하게 존재
  • 링킹은 정적 + 동적 두 단계로 이루어짐
  • OS 스케줄러가 멀티코어에서 실행을 관리함

컴퓨터 시스템 / 실행 과정 면접 질문

  1. 전체 흐름 이해
  • 프로그램이 실행되기까지 과정을 설명해보세요.
  • 소스 코드가 실행 파일이 되는 과정을 설명해보세요.
  • 실행 파일이 실행될 때 내부적으로 어떤 일이 일어나나요? 실행 파일이 실행되면 먼저 운영체제가 로더를 통해 프로그램의 코드와 데이터를 메모리에 적재하고, 실행에 필요한 스택, 힙, 라이브러리 연결 등을 준비합니다. 그 다음 CPU가 엔트리 포인트부터 명령어를 하나씩 가져와 Fetch, Decode, Execute 사이클을 반복하면서 프로그램을 실행합니다.
  1. 컴파일 / 링킹 관련
  • 컴파일러와 링커의 차이는 무엇인가요?
  • 컴파일 에러와 링커 에러의 차이를 설명해보세요.
  • 선언과 정의의 차이를 설명해보세요.
  • 왜 함수 구현이 없으면 컴파일이 아니라 링크 에러가 발생하나요?
  • 정적 링킹과 동적 링킹의 차이는 무엇인가요?
  1. 메모리 구조 / 운영체제
  • 프로그램이 메모리에 올라간다는 것은 무슨 의미인가요?
  • 코드 영역, 데이터 영역, 스택, 힙의 차이를 설명해보세요.
  • 가상 메모리는 왜 사용하는가요?
  • 페이징과 세그멘테이션의 차이는 무엇인가요?
  • 스택과 힙의 차이와 각각의 사용 목적은 무엇인가요?
  1. CPU 동작
  • CPU의 Fetch / Decode / Execute 과정 설명해보세요.
  • 프로그램 카운터(PC)의 역할은 무엇인가요?
  • 레지스터는 왜 필요한가요?
  • ALU와 Control Unit의 역할 차이는 무엇인가요?
  1. 성능 관련
  • 캐시는 왜 필요한가요?
  • 캐시 히트와 미스란 무엇인가요?
  • CPU 성능에서 병목은 주로 어디서 발생하나요? (데이터 접근)
  • 메모리 접근이 느린 이유는 무엇인가요?
  1. 현대 CPU
  • 파이프라인이 무엇인가요?
  • 분기 예측이란 무엇인가요?
  • Out-of-Order Execution이란 무엇인가요?
  • 멀티코어와 멀티스레드의 차이는 무엇인가요?
  1. 시간 / 클럭 / 델타타임
  • CPU 클럭이란 무엇인가요?
  • 클럭 속도가 높으면 무조건 빠른가요?
  • 델타 타임은 어떻게 계산하나요?
  • 게임에서 델타 타임을 사용하는 이유는 무엇인가요?
  1. 버스 / 하드웨어 연결
  • Address Bus / Data Bus / Control Bus의 역할을 설명해보세요.
  • CPU와 메모리는 어떻게 통신하나요?

추가 사항 가상 메모리와 페이징

  1. 가상 메모리란?

가상 메모리는 각 프로세스가 자기만의 연속된 메모리 공간을 가지고 있는 것처럼 보이게 하는 방식이다.

즉 프로그램은

  • “나는 0번지부터 큰 메모리를 쓰고 있다”

고 생각하지만, 실제 물리 메모리(RAM)는

  • 여러 프로세스가 함께 사용하고
  • 연속되어 있지 않을 수도 있으며
  • 일부는 아직 디스크에 있을 수도 있다

운영체제와 하드웨어는 이 차이를 숨겨준다.


  1. 왜 가상 메모리를 사용하는가?

가상 메모리를 사용하는 이유는 크게 네 가지다.

  • 메모리 보호

    • 한 프로세스가 다른 프로세스의 메모리를 함부로 건드리지 못하게 함
  • 메모리 사용 효율

    • 실제로 필요한 부분만 RAM에 올릴 수 있음
  • 큰 주소 공간 제공

    • 실제 RAM보다 더 큰 공간을 쓰는 것처럼 보이게 할 수 있음
  • 프로그램 작성 단순화

    • 프로그램은 물리 주소를 직접 신경 쓰지 않고 가상 주소 기준으로 동작 가능

  1. 가상 주소와 물리 주소

가상 메모리에서 가장 중요한 구분은 다음 두 가지다.

  • 가상 주소

    • 프로그램이 사용하는 주소
  • 물리 주소

    • 실제 RAM 상의 주소

CPU는 보통 가상 주소를 기준으로 동작하고, MMU(Memory Management Unit)가 이를 물리 주소로 변환한다.

즉 흐름은 다음과 같다.

프로그램 → 가상 주소 사용 → MMU가 주소 변환 → 실제 RAM 접근


  1. 페이징이란?

페이징은 가상 메모리를 관리하는 대표적인 방식이다.

핵심 아이디어는 메모리를 고정 크기 블록으로 나누는 것이다.

  • 가상 메모리 쪽 블록: Page
  • 물리 메모리 쪽 블록: Frame

즉,

  • 가상 주소 공간을 페이지 단위로 나누고
  • 실제 RAM을 프레임 단위로 나눈 뒤
  • 페이지를 프레임에 매핑한다

이렇게 하면 가상 주소 공간은 연속적으로 보여도, 실제 RAM에서는 흩어져 저장될 수 있다.


  1. 왜 페이징이 필요한가?

만약 프로그램 메모리를 큰 덩어리째 연속 공간으로만 배치해야 한다면 문제가 많다.

예를 들어 메모리가 이렇게 비어 있다고 하자.

[10KB 빈 공간] [5KB 사용중] [8KB 빈 공간]

총 18KB가 비어 있어도, 20KB짜리 연속 공간이 필요하면 배치할 수 없다.

이게 외부 단편화 문제다.

페이징은 메모리를 고정 크기 조각으로 잘게 나누기 때문에 연속 공간이 아니어도 여러 프레임에 나눠 담을 수 있다.

그래서 외부 단편화를 크게 줄일 수 있다.


  1. 페이지 테이블

페이지가 실제 어느 프레임에 들어 있는지 기록하는 표가 필요하다. 이게 페이지 테이블이다.

예를 들면:

  • 가상 페이지 0 → 물리 프레임 5
  • 가상 페이지 1 → 물리 프레임 12
  • 가상 페이지 2 → 물리 프레임 3

프로그램은 연속된 주소를 쓴다고 생각하지만, 운영체제와 MMU는 페이지 테이블을 보고 실제 위치를 찾는다.


  1. 주소 변환은 어떻게 하나?

가상 주소는 보통 두 부분으로 나뉜다.

  • 페이지 번호
  • 오프셋(offset)

예를 들어 어떤 가상 주소가 있다면:

가상 주소 = [페이지 번호 | 오프셋]

여기서

  • 페이지 번호로 페이지 테이블을 조회해서 프레임 번호를 찾고
  • 오프셋은 그대로 유지해서
  • 최종 물리 주소를 만든다

즉:

물리 주소 = [프레임 번호 | 오프셋]

오프셋이 그대로인 이유는 페이지와 프레임의 크기가 같기 때문이다.


  1. 페이지 폴트(Page Fault)

프로그램이 어떤 페이지에 접근했는데, 그 페이지가 현재 RAM에 없을 수 있다.

이 경우 페이지 폴트가 발생한다.

흐름은 대략 이렇다.

  • CPU가 해당 가상 주소 접근
  • 페이지 테이블 확인
  • 현재 RAM에 없음
  • 운영체제가 디스크에서 해당 페이지를 가져옴
  • RAM의 빈 프레임 또는 교체 대상 프레임에 적재
  • 다시 명령 실행

즉 페이지 폴트는 에러라기보다, 필요한 페이지를 그때그때 가져오는 과정에서 발생하는 정상적인 메커니즘일 수도 있다.

물론 너무 자주 발생하면 성능이 크게 떨어진다.


  1. Demand Paging

현대 시스템은 보통 프로그램 전체를 한 번에 RAM에 올리지 않는다.

대신 실제로 접근할 때만 페이지를 올린다. 이걸 Demand Paging이라고 한다.

장점:

  • 메모리 절약
  • 시작 속도 개선
  • 큰 프로그램도 효율적으로 실행 가능

즉 “실행 파일이 메모리에 올라간다”는 말은 현대 기준으로는 “필요한 페이지부터 점진적으로 적재된다”에 더 가깝다.


  1. TLB

페이지 테이블을 매번 메모리에서 찾으면 느리다. 그래서 CPU 안에는 주소 변환 결과를 캐시하는 작은 장치가 있다. 이게 TLB(Translation Lookaside Buffer)다.

흐름:

  • 가상 주소 접근
  • 먼저 TLB 확인
  • 있으면 바로 물리 주소 변환
  • 없으면 페이지 테이블 조회 후 TLB 갱신

즉 TLB는 “주소 변환용 캐시”라고 보면 된다.


  1. 페이징의 장단점

장점

  • 외부 단편화 완화
  • 가상 메모리 구현 용이
  • 메모리 보호와 공유에 유리
  • 필요한 부분만 로드 가능

단점

  • 주소 변환 비용 발생
  • 페이지 테이블 관리 필요
  • 내부 단편화 가능
  • 페이지 폴트가 많으면 매우 느려짐

  1. 내부 단편화와 외부 단편화

페이징은 외부 단편화를 줄이는 대신, 내부 단편화는 생길 수 있다.

예를 들어 페이지 크기가 4KB인데 어떤 데이터가 6KB 필요하면

  • 2페이지 사용 = 8KB 확보
  • 실제 사용 = 6KB
  • 남는 2KB는 내부 단편화

즉:

  • 외부 단편화: 빈 공간이 흩어져서 큰 연속 공간 확보 어려움
  • 내부 단편화: 할당받은 블록 내부에서 남는 공간 발생