놀라운 운영체제 #2 – 프로세스와 스레드

전하윤·2025년 7월 14일
0

OS(operating system)

목록 보기
2/6
post-thumbnail

목차


개요

앞선 글에서 운영체제의 핵심 역할로 자원 관리, 자원 보호, 하드웨어/사용자 인터페이스 제공 등을 살펴봤다.
이 중에서도 CPU와 메모리 같은 컴퓨터의 핵심 자원을 여러 프로그램이 안전하고 효율적으로 사용하도록 관리하는 것이 운영체제의 가장 중요한 일 중 하나다.

여기서 등장하는 것이 바로 프로세스와 스레드다.

운영체제는 우리가 실행하는 각각의 프로그램(예: 웹브라우저, 음악플레이어, 에디터 등)을 ‘프로세스’라는 단위로 관리한다.
또한, 현대 소프트웨어에서는 한 프로그램 안에서도 여러 작업을 동시에 처리할 필요가 있어,
운영체제는 ‘스레드’라는 더 작은 실행 단위를 제공한다.

이 글에서는

  • 프로세스와 스레드가 무엇인지

  • 운영체제가 왜 이 두 가지 단위로 자원을 관리하는지

  • CPU 스케줄링, 메모리 관리, 자원 보호와 프로세스/스레드가 어떻게 연결되는지
    차근차근 풀어볼 예정이다.


프로세스란?

프로세스란 간단히 말하면 실행중인 프로그램이다.
하지만 그냥 프로그램이 아니라 운영체제로부터 PCB(프로세스 제어 블록)을 얻은 프로그램이다.

그렇다면 PCB란 뭘까?


프로세서 제어 블록(PCB)

프로세스 제어 블록에는 대표적으로 3가지 정보가 있다.
1. PID(프로세스 구분자) : 각 프로세스를 구분하는 구분자
2. 메모리 관련 정보: 프로세스의 메모리 위치 정보, 메모리 보호를 위한 경계 레지스터와 한계 레지스터
3. 각종 중간값(프로그램 카운터, 레지스터 값, 메모리 정보 등): 프로세스의 진행 도중의 상태 정보(다시 시작될때 이어가기 위함)

그 외에도 프로세스 제어 블록에는 더 많은 정보가 있지만, 모두 다루진 않겠다.

경계 레지스터와 한계 레지스터?


경계 레지스터와 한계 레지스터는 운영체제가 프로세스의 메모리 보호를 위해 메모리의 시작주소와 메모리의 공간의 크기를 저장하는 레지스터이다.
운영체제는 두 레지스터를 함께 써서, 프로세스가 자기 메모리 범위만 안전하게 접근하도록 운영체제가 감시한다.


프로세스의 상태

그렇게 PCB블록을 얻고 프로세스가 되면 프로세스는 어떤 과정을 거치면서 완료하는 걸까?
프로세스의 상태를 크게 다섯가지 상태로 나눌수 있다.

프로세스의 다섯 가지 상태

프로세스의 다섯 가지 상태를 보기전에 이해를 돕기 위해 단계를 나눠보겠다.

  • 프로세스의 네 가지 상태

각 단계는 CPU 스케줄러에 의해 통제 받는다.
1. CPU 스케줄러가 준비 상태에 있는 여러 프로세스 중 다음에 실행할 프로세스를 선정한다.
2. 준비 상태의 프로세스 중 하나를 골라 실행 상태로 바꾼다.
3. 프로세스가 자신에게 주어진 하나의 타임 슬라이스 동안 작업을 끝내지 못하면 다시 준비 상태로 돌아간다.

  • 프로세스의 다섯 가지 상태

프로세스의 네 가지 상태에 추가로 대기상태가 추가 됐다.
대기 상태란 실행 상태의 프로세스가 입출력을 요청하면 입출력이 완료될 때까지 기다리는 상태다. 이는 작업의 효율을 높이기 위해 만들어진 것이다.

표로 보는 프로세스 작업 상태

기본적으로는 준비(Ready), 실행(Running), 대기(Waiting), 종료(Terminated)의 네 가지 상태, 그리고 대기(입출력 등)로 인한 “다섯 가지 상태”가 핵심이다.
하지만 실제 운영체제에서는,
특정 프로세스가 외부 요인(예: 메모리 부족, 관리자가 일시 중지 등)으로 인해 “일시적으로 완전히 중단”되는 상황도 존재한다.
이런 경우, 프로세스는 “보류(Suspended, 휴식) 상태”로 추가 관리된다.
참고로 활성 상태와 보류 상태의 차이는 메모리 위에 올라왔는가 아닌가로 구분하면 된다.

그렇다면 여러 프로세스가 번갈아가며 실행되는 환경에서,
운영체제는 어떻게 각 프로세스의 작업을 중단했다가 다시 이어서 실행할 수 있을까?
여기서 사용되는 개념이 바로 문맥 교환이다.


문맥교환

문맥교환이란 운영체제가 현재 실행 중인 프로세스의 상태(문맥)를 PCB에 저장하고,
다른 프로세스의 PCB에 기록된 정보를 다시 CPU에 복원해서
새로운 프로세스가 끊김 없이 실행을 이어가도록 하는 과정을 말한다.

위에서 PCB의 주요 역할중에 각종 중간값(프로그램 카운터, 레지스터 값, 메모리 정보 등)을 저장하는 역할에 대해 언급 했는데, 그 각종 중간값의 개념이 이때 사용된다.
각종 프로세스의 마지막 값을 기억하고, 다시 실행될 때 그 정보를 불러와서 그 지점부터 작업을 계속 이어간다.

  • 문맥교환

타임 슬라이스의 크기의 기준으로 문맥 교환을 진행하기 때문에 타임 슬라이스의 크기를 적절하게 설정하는것이 중요하다.

  • 타임 슬라이스 크기 설정

프로세스의 구조

그렇다면 프로세스의 구조는 어떻게 나뉘어져 있을까?

코드 영역

  • 프로그램의 본문이 기술된 곳
  • 프로그래머가 작성한 코드가 탑재되며 탑재된 코드는 읽기 전용으로 처리됨

데이터 영역

  • 코드가 실행되면서 사용하는 변수나 파일 등의 각종 데이터를 모아놓은 곳
  • 데이터는 변하는 값이므로 이곳의 내용은 기본적으로 읽기와 쓰기가 가능

힙 영역

  • 실행 도중에 동적으로 할당되는 데이터(객체, 배열, 포인터 등)를 저장하는 공간
  • 개발자가 명시적으로 메모리를 할당(malloc, new 등)하거나 해제해야 함
  • 크기가 실행 중에 자유롭게 변하며, 프로그램의 런타임에 필요한 데이터를 유연하게 관리할 수 있음

스택 영역

  • 운영체제가 프로세스를 실행하기 위해 필요한 데이터를 모아놓은 곳
  • 프로세스 내에서 함수를 호출하면 함수를 수행하고 원래 프로그램으로 되돌아
    올 위치를 저장하는 곳
  • 운영체제가 사용자 프로세스를 작동하기 위해 유지하는 영역으로 사용자에게
    는 보이지 않음

이렇게 프로세스는 각각의 영역을 나눠서 프로그램 실행에 필요한 다양한 정보를 저장한다.


프로세스의 생성과 fork(), exec()

운영체제에서 새로운 프로세스를 생성하는 대표적인 방식이 바로 fork()exec() 시스템 콜이다.

fork()

  • fork()현재 실행 중인 프로세스를 “복제”해서
    똑같은 PCB와 메모리 구조(코드, 데이터, 스택 등)를 가진 자식 프로세스를 만들어낸다.
  • 복제된 자식 프로세스는 부모 프로세스와 거의 동일하지만,
    PID(프로세스 식별자) 등 일부 값만 다르고
    코드 실행 지점도 부모와 같다.
  • 이렇게 만들어진 두 프로세스(부모와 자식)는
    각자 독립적으로 실행을 계속한다.

exec()

  • exec()는 실행 중인 프로세스의 메모리(코드, 데이터, 스택 등)를
    새로운 프로그램으로 “완전히 덮어쓰는” 시스템 콜이다.
  • 보통 fork()로 자식 프로세스를 만든 뒤,
    자식 프로세스가 exec()를 호출해서
    새로운 프로그램으로 자신의 내용을 교체하는 방식으로 많이 사용된다.
  • 이 과정에서 PID는 그대로 유지되지만,
    실행 중이던 프로그램의 내용이 새 프로그램으로 바뀌고
    기존 코드와 데이터 영역은 모두 사라진다.

이 두 가지 시스템 콜을 조합하면
운영체제는 동시에 여러 프로그램을 독립적으로 실행할 수 있게
프로세스를 효과적으로 생성·관리할 수 있다.


스레드란?

스레드는 프로세스 내에서 실제로 작업을 수행하는 실행의 흐름(작업의 최소 단위)이다.

즉 정리하면
• 운영체제 입장에서의 작업 단위는 프로세스
• CPU 입장에서의 작업 단위는 스레드

스레드의 기본 정의

스레드는 프로세스 내에서 실제로 작업을 수행하는 실행의 흐름(작업의 최소 단위)입니다.
CPU가 처리할 실제 "일"을 의미하며, 한 프로세스 안에 여러 개의 스레드가 존재할 수 있습니다.

프로세스와의 관계

  • 프로세스: 실행 중인 프로그램 전체.
    • 자원(메모리, 파일, 입출력 등)을 독립적으로 소유
    • 운영체제 입장에서 "작업"의 단위
  • 스레드: 프로세스 내에서 실제로 코드를 실행하는 "흐름".
    • 여러 개의 스레드가 하나의 프로세스 자원을 공유
    • CPU 입장에서는 "스레드"가 실제 실행 단위

"작업의 최소 단위"란?

  • 운영체제는 프로세스를 관리하지만,
    실제로 CPU에서 돌아가는 실행 흐름은 스레드 단위로 관리됨
  • 한 프로세스가 여러 스레드를 가지면,
    하나의 프로그램이 여러 작업을 동시에 처리할 수 있음

운영체제와 CPU 입장에서의 스레드

  • 운영체제(OS): 프로세스 단위로 자원을 배분하고, 각 프로세스 내에서 여러 스레드를 관리
  • CPU: 실제로는 "스레드" 단위로 작업을 받아서 실행
    (즉, 프로세스가 여러 개의 스레드를 만들고, CPU는 그 스레드들을 번갈아가며 처리)

스레드와 프로세스의 차이

기본적으로 스레드와 프로세스는 모두 운영체제에서 작업을 수행하는 실행 단위 이지만, 구조와 자원 관리 방식, 실제 동작 방식에서 차이가 있다.

구분프로세스(Process)스레드(Thread)
정의실행 중인 프로그램, 자원을 독립적으로 가지는 단위프로세스 내에서 실행되는 더 작은 실행 단위
자원각각 별도의 메모리(코드, 데이터, 힙, 스택)와 PCB 보유코드, 데이터, 힙 등은 프로세스와 공유, 스택만 따로 가짐
독립성완전히 독립적 (다른 프로세스에 영향 X)같은 프로세스 내 스레드끼리는 서로 영향을 줄 수 있음
통신프로세스 간 통신(IPC)은 복잡하고 비용이 큼스레드 간 통신은 매우 빠르고 쉽다 (공유 자원 이용)
오버헤드생성/종료/문맥교환에 많은 오버헤드(부하)가 발생스레드끼리의 전환은 오버헤드가 작음
에러 발생 시한 프로세스가 죽어도 다른 프로세스에 영향 없음한 스레드가 에러로 죽으면 같은 프로세스의 다른 스레드도 영향 받을 수 있음

혼동 가능한 용어 정리

멀티태스킹: 운영체제가 CPU에 작업을 줄 대 시간을 잘게 나누어 배분하는 기법
멀티스레드: 프로세스 내 작업을 여러 개의 스레드를 분할해 작업 부담을 줄이는 프로세스 운영 기법
멀티 프로세싱: 여러 개 CPU로 여러 개 스레드를 동시에 처리하는 작업 환경
CPU 멀티스레드: 한 번에 하나씩 처리해야 하는 스레드를 잘게 쪼개어 동시에 처리하는 병렬 처리 기법


스레드를 왜 사용할까?

프로세스만 사용했을 때의 한계

단일 프로세스만으로 여러 작업을 처리하면 하나의 작업이 오래 걸릴때, 전체 프로그램의 반응성이 떨어진다. 또한 I/O 작업이나 네트워크 요청으로 인해 프로세스가 블록되면 전체 기능이 멈출 수도 있다.


멀티스레드

멀티스레드란 하나의 프로세스 안에서 여러 개의 스레드가 동시에 실행되는 구조를 의미한다. 즉, 하나의 프로그램(프로세스)이 여러 작업(스레드)을 동시에 처리할 수 있도록 만드는 기술이다.

위 사진을 보면 쓰레드와 프로세스가 공유하는 자원에 대해 다른걸 확인 할 수 있다. 자원 공유의 차이에 대해 한번 다뤄 보겠다.


프로세스의 자원 관리

  • 프로세스는 각각 독립적인 메모리 공간(코드, 데이터, 힙, 스택 등)을 가짐
  • 프로세스 간에는 직접적으로 자원을 공유하지 않음
    • 각 프로세스는 다른 프로세스의 변수나 데이터에 접근할 수 없음
  • 장점
    • 한 프로세스의 오류나 문제가 다른 프로세스에 영향을 주지 않음 → 안정성, 보안성이 높음
    • 운영체제가 각 프로세스를 분리해 관리하므로 시스템 전체가 더 안전함
  • 단점
    • 서로 다른 프로세스 간에 데이터를 주고받으려면
    1. 복잡한 통신(IPC: Inter-Process Communication) 기법,
    2. LPC(Local inter-Process Communication),
    3. 별도로 공유 메모리를 만들어서 정보를 주고받도록 설정하는 방법등 필요

그러나 프로세스 자원 공유는 RAM과 CPU 사이의 캐시 메모리까지 초기화 되기때문에 자원 부담이 크다는 단점이 있다. 그렇기에 다중 작업이 필요한 경우 스레드를 이용하는것이 일반적이다.


스레드의 자원 관리

  • 스레드같은 프로세스 내부에서 실행되는 여러 작업의 흐름이다.
  • 같은 프로세스 내의 스레드들은 코드, 데이터, 힙 등 대부분의 자원(메모리, 파일, 전역 변수 등)을 서로 공유한다.
    단, 스택 영역만은 스레드마다 별도로 가지고 있어 함수 호출/지역 변수는 분리된다.
  • 장점
    • 빠르고 효율적인 데이터 공유가 가능해 통신이 매우 간단하다.
    • 자원 복사가 필요 없어 메모리와 CPU 오버헤드가 적다.
    • 스레드 간 전환이 빠르고, 멀티코어 환경에서 병렬 처리가 쉽다.
  • 단점
    • 공유 데이터에 여러 스레드가 동시에 접근하면 동기화 문제(Race Condition, Deadlock 등)가 발생할 수 있음 → 동기화(락, 세마포어 등) 필요
    • 한 스레드의 오류나 예외가 전체 프로세스(그리고 모든 스레드)에 영향을 줄 수 있다.

병행 수행(Concurrency)와 병렬 수행(Parallelism)

병렬 수행(Parallelism)

  • 여러 CPU(코어)를 활용해 실제로 여러 작업을 동시에 물리적으로 실행하는 방식

병행 수행(Concurrency)

  • 여러 개의 작업(스레드/프로세스)이 논리적으로 동시에 실행되는 것처럼 보이도록 운영체제가 빠르게 작업을 전환하는 방식

병행 수행(Concurrency)의 필요성

병렬 수행의 경우 직관적으로 좋은 방식이라는게 느껴진다. 하지만 병행 수행의 경우 의문이 생길 수 있다. 병행 수행은 하나의 작업을 잘게 나누어서 조금씩 작업을 진행하고, 다음 작업으로 넘어가는 방식을 빠르게 반복하는건데, 그렇다면 결국 최종 작업이 걸리는 시간은 차이가 없다. 그렇다면 왜 번거롭게 작업을 나눠서 스위칭하며 처리하는 것일까?

1. 하드웨어적 한계

가장 근본적인 이유는 CPU(코어) 개수의 한계 때문이다.
실제 대부분의 시스템은 프로세서(코어) 수보다 훨씬 많은 프로그램과 작업을 동시에 처리해야 한다.
모든 작업을 병렬로 처리하고 싶어도,
물리적으로 사용 가능한 코어가 적다면
하나의 코어에 여러 작업을 순차적으로 몰아넣을 수밖에 없다.

실제로 사용자는 여러 앱(코어보다 많은 갯수)을 실행하게 될 때, 운영체제는 병행 수행의 타임슬라이스를 적절하게 조정하여 마치 모든 프로그램이 동시에 동작하는 것처럼 느끼게 해준다.

2. 논리적인 효율

병행 수행의 또 다른 목적은 시스템의 반응성과 자원 활용 효율을 높이기 위함이다.
만약 현재 4코어 8스레드로 환경에 16개의 작업이 있는데, 이때 8개의 작업은 오래 걸리는 작업, 나머지 8개의 작업은 빠르게 끝나는 작업이라고 가정할때, 만일 8개의 오래걸리는 작업이 먼저 수행되면, 나머지 8개는 금방 끝나는 작업임에도 불구하고 나머지 8개의 오래 걸리는 작업이 끝날때까지 기다려야 한다. 하지만 병행 수행을 통해 작업을 하게되면 이러한 일이 사전에 방지 된다.


3. 스레드 구현 모델

스레드가 운영체제에서 어떻게 관리되는지에 따라
실제 시스템에서는 여러 방식이 사용된다.
아래는 각 모델의 실제 동작 예시와 함께 설명한다.


1. 사용자 스레드 (User-Level Thread, 1:N 모델)

  • 정의
    한 프로세스 내 여러 개의 사용자 스레드가 커널 스레드 하나와 연결되어 실행되는 구조
  • 동작
    스레드 생성/전환/종료 등 모든 관리가 언어 런타리브러리(사용자 수준)에서 직접 이루어짐
    운영체제(커널)는 프로세스 하나만 인식, 내부 스레드는 모름
  • 장점
    • 스레드 생성·전환이 매우 빠르고, 오버헤드 거의 없음
  • 단점
    • 커널 스레드가 I/O 등으로 블록되면, 모든 사용자 스레드가 함께 멈춤
    • 여러 CPU(코어)를 동시 활용 불가(병렬성 부족)
  • 실제 예시
    • 구버전 자바의 Green Thread

      5개의 크롤링 스레드 중 한 스레드가 네트워크 I/O로 블록되면,
      나머지 4개 스레드도 모두 같이 대기 → 전체 작업 지연
      멀티코어 CPU여도 한 코어만 사용

    • 일부 경량 스레드 라이브러리 등

2. 커널 스레드 (Kernel-Level Thread, 1:1 모델)

  • 정의
    하나의 사용자 스레드가 하나의 커널 스레드와 직접 연결되어 운영체제가 관리하는 구조
  • 동작
    각 스레드는 운영체제 커널에 의해 독립적으로 인식, 스케줄링
    (운영체제가 스레드를 직접 관리)
  • 장점
    • 한 스레드가 I/O 등으로 블록되어도, 다른 스레드는 계속 동작
    • 멀티코어 환경에서 진짜 병렬 실행 가능
    • 현대 OS/프로그램에서 가장 널리 쓰임
  • 단점
    • 스레드 생성·전환 시 시스템 콜 필요 → 오버헤드(성능 부하)
    • 많은 스레드가 생기면 자원 소모가 큼
  • 실제 예시
    • 리눅스, 윈도우, 현대 JVM, Python, POSIX Thread

      예) 웹 서버에서 100개 요청을 각각 스레드로 처리할 때
      일부 스레드가 DB I/O로 대기해도,
      나머지 스레드는 그대로 요청을 계속 처리 (진짜 병렬성)


3. 멀티레벨 스레드 (Multi-level Thread, M:N 모델)

  • 정의
    여러 개의 사용자 스레드(M)가 여러 개의 커널 스레드(N)에 유연하게 매핑되는 구조
  • 동작
    언어 런타임/스케줄러가 수많은 사용자 스레드를 소수의 커널 스레드에 매핑
    커널 스레드가 I/O 등으로 블록되어도, 다른 커널 스레드에서 남은 사용자 스레드를 계속 실행
    빠르게 움직여야 하는 작업(짧은 계산 등)은 사용자 스레드에서,
    안정적이어야 하는 작업은 커널 스레드에서 처리
  • 장점
    • 사용자 스레드의 빠른 전환/생성,
      커널 스레드의 병렬 처리 및 I/O 대기 문제 해결
    • 자원 효율적, 초대규모 동시성에 유리
  • 단점
    • 내부 스케줄러 구현이 복잡, OS/런타임의 지원이 필요
    • 커널 스레드와 연동 시 여전히 약간의 오버헤드 발생
  • 실제 예시
    • Go 언어(Goroutine + worker thread), Solaris, 일부 UNIX

      예) Go 서버에서 10만 개 Goroutine을 수천 개 커널 스레드에 자동 분산
      일부 Goroutine이 I/O로 멈춰도,
      나머지는 계속 다른 커널 스레드에서 실행(진짜 대규모 동시성)

    • 최신 런타임 환경

커널 스레드(1:1)와 멀티레벨(M:N)의 차이, 실전에서 어떤 차이가 있을까?

  • 커널 스레드(1:1)

    • 애플리케이션에서 만든 사용자 스레드 수 = 커널 스레드 수
    • 운영체제가 직접 각 스레드를 "독립적"으로 스케줄링
    • I/O 블록에도 나머지 스레드는 그대로 실행
    • 단, 스레드 수가 많아지면 운영체제 커널에서 직접 관리해야 하므로
      메모리 사용량, 컨텍스트 스위칭 오버헤드가 매우 커질 수 있음
    • 대규모 동시접속이 필요한 환경에서는 비효율적
  • 멀티레벨(M:N)

    • 애플리케이션에서 10만 개 스레드를 만들어도
      실제 운영체제(커널)에는 수백~수천 개 스레드만 등록됨
    • 언어 런타임/스케줄러가 내부적으로 사용자 스레드와 커널 스레드 간 매핑 관리
    • I/O로 일부 커널 스레드가 대기 상태가 되어도,
      다른 커널 스레드가 남은 사용자 스레드를 계속 실행
    • 운영체제의 오버헤드는 적고, 대규모 동시성에 특화
    • 대표적으로 Go(Goroutine), Erlang, Solaris Thread 등에서 사용

실전 요약 표

모델대표 기술/언어I/O 블록 영향멀티코어 활용초대규모 동시성
사용자 스레드(1:N)Green Thread(구자바)전체 멈춤××
커널 스레드(1:1)Linux, Java, Python영향 없음(독립적)△(자원부담)
멀티레벨(M:N)Go, Solaris, Erlang영향 적음(자동분산)

[Reference]

profile
개발에 대한 고민과 성장의 기록을 일기장처럼 성찰하며 남기는 공간

2개의 댓글

comment-user-thumbnail
2025년 7월 16일

글 잘보고 갑니다.
스레드를 왜 사용할까?에서 하고자하는 말이
멀티스레드를 사용하면 병목 현상이 줄어든다 인가요?

1개의 답글