Binary Journey

프로그램은 어떻게 실행될까 - 1 본문

weekly/컴퓨터 밑바닥의 비밀

프로그램은 어떻게 실행될까 - 1

binaryJournalist 2025. 4. 13. 10:51
반응형

참고도서: 컴퓨터 밑바닥의 비밀

 

프로그램의 실행 원리

 

프로그램 카운터(PC)

  • 다음 실행할 명령어의 메모리 주소를 저장하는 레지스터
  • 초기 PC 값은 프로그램 시작 시 운영체제가 설정함

 

레지스터

  • CPU 내부의 임시 저장소
  • CPU가 처리할 데이터를 일시적으로 저장

 

프로그램 실행 과정

  1. 프로그램의 첫 번째 명령어(main 함수)가 메모리에 저장됨
  2. 해당 명령어의 주소를 PC 레지스터에 기록
  3. CPU가 PC의 주소를 참조하여 명령어를 실행
  4. 명령어 실행 후 PC가 다음 명령어 주소로 업데이트됨

 

CPU와 코어의 개념

 

CPU(중앙 처리 장치)

  • 컴퓨터에서 모든 연산과 명령을 수행하는 핵심 장치
  • 하나의 CPU 안에는 보통 여러 개의 코어가 존재할 수 있음

 

코어(Core)

  • CPU 내부의 독립적인 명령어 실행 장치
  • 각 코어는 독립적으로 명령을 처리할 수 있는 연산 장치
  • 코어가 많을수록 여러 작업을 동시에 병렬 처리 가능하여 성능이 향상됨
  • 코어가 많다고 CPU가 여러 개인 것은 아니며, 일반적으로 CPU는 하나이고 그 내부에 여러 개의 코어를 포함하는 형태임

 

운영체제(OS)의 필요성

  • 여러 프로그램을 관리하고 실행 순서를 정해줌
  • CPU는 한 번에 하나의 작업만 수행할 수 있으며, OS는 작업 간 빠른 전환을 통해 동시 실행을 가능하게 함(멀티태스킹)
  • 프로그램의 실행 상태(Context)를 관리함

 

프로세스와 스레드

 

프로세스

  • OS가 리소스를 할당하는 기본 단위
  • 프로세스마다 독립된 메모리 공간이 존재함

 

스레드

  • 프로세스 내부에서 동작하는 독립적 실행 흐름
  • 프로세스의 자원을 공유함(코드, 데이터, 힙 공유 가능)
  • 각 스레드는 독립된 스택을 가짐(지역 변수 관리)

 

스레드 풀(Thread Pool)

  • 스레드를 미리 생성해두고 요청 시 재사용하여 성능을 최적화함
  • 개수는 일반적으로 시스템의 코어 개수에 따라 적절히 결정됨
CPU는 코어의 수만큼의 작업을 병렬로 동시에 처리할 수 있다. 예를 들어 CPU가 4개의 코어를 가지고 있다면 4개의 작업을 동시에 처리할 수 있다.

따라서 스레드를 너무 적게 만들면, CPU가 가진 코어의 병렬 처리 능력을 충분히 활용하지 못하게 되고 반대로 너무 많은 스레드를 만들면, 스레드 간 전환(Context Switching)으로 인해 오히려 성능이 떨어지게 된다.

결국 최적의 성능을 위해서 일반적으로 스레드 풀의 개수는 CPU의 코어 수와 비슷하게 맞추거나, 코어 수의 몇 배 정도로 설정해서 CPU의 병렬 처리 능력을 효율적으로 활용하도록 결정하는 것이다.

 

스케줄링(Scheduling)

  • 운영체제가 여러 스레드를 관리하고 CPU 자원을 할당하는 과정
  • 스레드 간 전환 부담을 고려하여 적정 수의 스레드를 유지해야 함

 

메모리 영역

메모리를 코드, 데이터, 힙, 스택 영역으로 나눈 이유는 메모리의 사용 목적과 접근 특성에 따라 효율적으로 관리하기 위함이다.

각 영역별로 접근 권한과 데이터의 생명주기가 달라, 이를 구분하여 관리하면 성능 향상과 안정성 확보에 도움이 된다.

특히 힙과 스택은 각각 메모리 관리 방식을 효율화하기 위해 만들어진 구조로, 스택은 데이터를 차곡차곡 쌓아 LIFO(Last In First Out) 방식으로 관리하고, 힙은 프로그램 실행 중 필요한 만큼 메모리 공간을 실시간으로 할당하거나 해제할 수 있도록 지원한다. 즉, 실행 전 미리 정해진 크기가 아니라, 필요에 따라 유연하게 크기를 조정하여 데이터를 저장할 수 있게 한다.

힙은 무작위로 데이터를 쌓는다는 의미의 무더기(heap)에서 이름을 따왔고, 스택은 데이터를 쌓아 올린다는 개념에서 이름이 붙여졌다. 스택 영역은 주로 LIFO 방식으로 관리되나, 배열이나 포인터를 이용해 스택 내부의 특정 위치에 직접 접근할 수도 있다. 즉, 스택에 저장된 모든 데이터가 반드시 LIFO 방식으로만 접근되는 것은 아니다.

  • 코드 영역: 실행 코드 저장(읽기 전용, 스레드 간 공유)
  • 데이터 영역: 전역 변수 저장(스레드 간 공유 가능)
  • 힙 영역: 동적 할당(malloc 등)을 통한 메모리 관리(공유 가능)
  • 스택 영역: 함수 호출 시 지역 변수 저장(각 스레드 독립적)

 

메모리 관리와 malloc

  • malloc은 메모리를 힙 영역에서 동적으로 할당하고 그 포인터를 반환
  • malloc의 이름은 memory allocation(메모리 할당)의 줄임말로, 힙 영역에서 동적으로 메모리를 할당하는 함수의 역할을 나타냄

 

가상 메모리

  • 물리적 메모리보다 큰 가상 주소 공간을 제공함
  • 프로그램 간 메모리 보호와 격리를 제공함

 

동적/정적 링크

  • 정적 링크: 모든 종속 라이브러리가 실행파일에 포함됨
  • 동적 링크: 프로그램 실행 시 필요한 라이브러리를 로드하여 공유함

 

스레드 안전성(Thread Safety)

  • 여러 스레드가 동시에 호출해도 정상적으로 동작하는 코드 특성
  • 지역 변수(Call by Value)는 스레드 안전
  • 포인터 등 공유자원은 별도의 동기화(잠금) 필요

 

동기화와 잠금 기법

  • 뮤텍스(Mutex): 한 번에 하나의 스레드만 접근 가능, 자원을 기다리는 동안 스레드를 대기 상태로 전환하여 CPU를 효율적으로 사용
  • 스핀락(Spin Lock): 짧은 시간 자원을 기다릴 때 반복적으로 상태 확인(CPU를 계속 사용하여 다른 작업을 처리하지 않고 반복적으로 상태를 확인하는 방식)
    • 스핀락은 뮤텍스처럼 한 번에 오직 하나의 스레드만 공유 리소스에 접근 가능
    • 짧은 대기 시간에서는 효율적이지만 긴 대기 시간이 발생하면 CPU 자원 낭비가 큼
  • 세마포어(Semaphore): 제한된 수의 스레드가 공유 리소스 접근 가능

 

코루틴(Coroutine)

  • 실행을 일시 중지하고 재개 가능
  • 상태 저장하여 중단된 지점에서 계속 실행 가능(yield)
  • 코루틴이 일시 중지될 때의 상태는 자체적으로 관리되는 별도의 메모리 공간(주로 별도의 스택이나 힙 영역)에 저장되어, 다시 실행될 때 정확히 중단된 시점부터 이어서 실행할 수 있게 됨

 

커널 스레드와 유저 모드

  • 커널 스레드: 운영체제가 관리하는 스레드
  • 유저 모드(User mode): 일반 프로그램이 제한된 권한으로 OS 자원에 접근하는 모드
    • 프로그램이 OS 커널의 핵심 자원을 직접 접근하지 않고 제한된 방식으로만 접근
    • OS는 커널 모드와 유저 모드로 권한을 나눠 프로그램 오류나 악성 코드로부터 시스템 보호

 

원자성 작업(Atomic Operation)

  • 나눠질 수 없는 최소 단위 작업
  • 동시에 접근해도 문제가 발생하지 않도록 보장됨

 

무상태 함수(Stateless Function)

  • 실행 시 외부 상태 변경 없이 항상 동일한 출력 보장
  • 스레드 간 동기화 필요 없음
반응형