본문 바로가기
카테고리 없음

OS - 프로세스와 스레드

by kik328288 2026. 5. 18.

프로세스 스레드 (PCB, 상태, 컨텍스트)

운영체제를 배우면서 가장 먼저 명확히 구분해야 할 두 개념이 프로세스(Process)와 스레드(Thread)이다. 두 단어가 일상 대화에서도 혼용되어 쓰이지만, 실제로는 메모리 격리·생성 비용·통신 방식·확장 전략이 모두 다른 별개의 실행 단위이다. 이 차이를 정확히 이해해야 멀티프로세스 vs 멀티스레드 설계, 컨테이너의 동작 원리, 동시성 프로그래밍의 트레이드오프까지 자연스럽게 손에 잡힌다. 본 글은 프로세스와 스레드의 정의·구성·상태·컨텍스트 스위칭을 세심하게 정리한다(출처: 위키백과 — Process (computing)). 제가 학교 OS 수업에서 가장 인상 깊었던 실습이 ps -eftop 명령으로 시스템에 떠 있는 수백 개의 프로세스를 직접 본 일이었는데, 평소 보이지 않던 시스템 데몬·세션·셸이 모두 독립된 프로세스로 동작하는 모습을 본 후로는 운영체제의 실체가 비로소 손끝에 잡혔다.

 

프로세스의 정의와 PCB·메모리 구조

프로세스(Process)란 메모리에 적재되어 실제로 실행 중인 프로그램의 인스턴스를 의미한다. 디스크에 저장된 실행 파일(program)이 정적인 코드 묶음이라면, 그 파일을 운영체제가 메모리에 올려 CPU 시간과 자원을 할당한 결과가 프로세스이다. 같은 프로그램을 두 번 실행하면 두 개의 별개 프로세스가 생기며, 각각이 독립적인 메모리 공간과 자원을 가진다. 여기서 인스턴스란 같은 설계도(프로그램)로부터 만들어진 각각의 실행 단위를 의미하며, 객체지향에서 클래스로부터 만들어진 객체와 정확히 같은 관계이다.

운영체제는 각 프로세스를 관리하기 위해 PCB(Process Control Block)라는 자료구조를 유지한다. 시험에서 매회 출제되는 핵심 용어로, 프로세스의 모든 메타 정보가 담긴 운영체제 내부의 표준 자료구조이다. PCB가 담는 정보를 한 표로 정리하면 다음과 같다.

PCB 항목 의미
PID (Process ID) 프로세스의 고유 식별자
프로세스 상태 new·ready·running·waiting·terminated
프로그램 카운터(PC) 다음에 실행할 명령어의 주소
CPU 레지스터 일반·상태·스택·플래그 레지스터의 값
메모리 정보 코드/데이터/힙/스택 영역의 경계
열린 파일 목록 파일 디스크립터 테이블
우선순위·소유자 스케줄링 우선순위와 사용자 ID
부모·자식 관계 프로세스 트리 구조

프로세스의 메모리 공간은 일반적으로 네 영역으로 구분된다. 코드(Text) 영역은 실행 가능한 명령어가 저장되는 읽기 전용 영역이며, 데이터(Data) 영역은 전역 변수와 정적 변수가 저장된다. 힙(Heap) 영역malloc 등으로 동적으로 할당되는 메모리가 위로 자라며, 스택(Stack) 영역은 함수 호출 시의 지역 변수와 매개변수가 아래로 자란다. 힙과 스택이 서로 반대 방향으로 자라면서 가운데 공간을 동적으로 공유하는 구조이며, 두 영역이 충돌하면 스택 오버플로 또는 메모리 부족이 발생한다.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
    pid_t pid = fork();           // 자식 프로세스 생성
    if (pid == 0) {
        printf("자식: PID=%d, 부모 PID=%d\n", getpid(), getppid());
    } else if (pid > 0) {
        printf("부모: PID=%d, 자식 PID=%d\n", getpid(), pid);
    }
    return 0;
}

여기서 fork()란 유닉스 계열 운영체제에서 호출한 프로세스의 완전한 복제본을 새 자식 프로세스로 만드는 시스템 콜을 가리킨다. fork 호출 한 번에 두 개의 프로세스가 동작하기 시작하며, 자식은 부모와 별개의 메모리 공간을 가진다는 점이 멀티스레드와의 결정적 차이이다.

프로세스의 상태와 컨텍스트 스위칭

프로세스는 생명주기 동안 다섯 가지 상태를 오간다. 시험과 실무 모두에서 자주 출제되는 핵심 개념이다.

상태 의미 전이 트리거
New (생성) 막 생성되어 메모리에 올라오기 전 fork·exec
Ready (준비) CPU 할당을 기다리며 준비 큐에 대기 스케줄러 선택 대기
Running (실행) CPU를 점유해 명령어를 실행 중 인터럽트·종료·I/O 요청
Waiting (대기·Blocked) I/O 완료 또는 이벤트 대기 I/O 완료 시 ready로
Terminated (종료) 실행이 끝나 자원 해제 대기 exit·kill

새로 만들어진 프로세스는 new → ready로 진입하고, 스케줄러가 선택하면 ready → running으로 전이된다. 실행 중인 프로세스는 I/O를 요청하면 running → waiting으로 빠지고, I/O가 끝나면 waiting → ready로 되돌아온다. 정해진 시간 할당량(time quantum)을 소진하면 running → ready로 떨어져 다시 줄을 서며, 마지막에 exit를 호출하면 terminated 상태가 된다. 여기서 time quantum이란 한 프로세스가 한 번에 점유할 수 있는 최대 CPU 시간을 의미하며, 라운드 로빈 스케줄링의 핵심 파라미터이다.

운영체제가 한 프로세스에서 다른 프로세스로 CPU를 넘기는 작업이 컨텍스트 스위칭(Context Switching)이다. 현재 실행 중인 프로세스의 PCB에 모든 레지스터·PC·상태를 저장하고, 다음 프로세스의 PCB에서 그 값을 복원하는 작업이 정확히 컨텍스트 스위칭이며, 이 과정에서 CPU는 사용자 작업을 하지 못한다. 컨텍스트 스위칭 비용은 일반적으로 마이크로초 단위지만, 초당 수천 번씩 일어나면 누적되어 CPU 시간의 일부가 운영체제 오버헤드로 소모된다(출처: 위키백과 — Context switch).

# Linux에서 컨텍스트 스위칭 빈도 확인
vmstat 1   # cs 컬럼 = 초당 컨텍스트 스위치 횟수

솔직히 제 경험상 학교 실습에서 처음 vmstat의 cs 값이 1초에 5000~10000을 찍는 모습을 보고 "내가 단순히 셸 하나 띄워 놓은 상태에서도 운영체제는 이렇게 일하는구나"라는 인상을 받은 후로는 컨텍스트 스위칭이 단순한 학문적 개념이 아니라 운영의 핵심 비용 항목이라는 점을 받아들였다.

스레드의 정의와 프로세스와의 차이

스레드(Thread)는 한 프로세스 안에서 동작하는 더 작은 실행 단위이다. 한 프로세스가 여러 스레드를 가질 수 있으며, 같은 프로세스의 스레드들은 코드·데이터·힙·열린 파일을 공유하고 각자 자기만의 스택과 레지스터를 가진다. 이 단순한 차이가 멀티스레드 프로그래밍의 모든 강점과 함정을 만들어 낸다.

비교 항목 프로세스 스레드
메모리 공간 완전히 독립 같은 프로세스 내 공유
생성 비용 큰 비용 (fork) 작은 비용 (pthread_create)
컨텍스트 스위치 비용 큼 (TLB 비움 포함) 작음 (같은 주소 공간)
통신 방식 IPC (파이프·소켓·공유 메모리) 공유 변수 (단, 동기화 필요)
격리 강도 강함 (한 쪽 죽어도 다른 쪽 안전) 약함 (한 스레드 충돌이 전체 프로세스 영향)
확장 전략 멀티프로세스 (예: Nginx, Postgres) 멀티스레드 (예: JVM, Go)

여기서 IPC(Inter-Process Communication)란 메모리 공간이 분리된 서로 다른 프로세스들이 데이터를 주고받기 위한 통신 메커니즘을 의미하며, 파이프·메시지 큐·공유 메모리·시그널·소켓 다섯 가지가 표준이다. 스레드는 같은 메모리를 공유하므로 별도의 IPC 없이 변수를 그대로 읽고 쓰면 되지만, 그 대가로 경쟁 조건(race condition)과 동기화 문제가 따라온다. 경쟁 조건이란 두 스레드가 같은 데이터에 동시에 접근해 결과가 실행 순서에 따라 달라지는 현상을 가리킨다.

// POSIX 스레드 — pthread 한 줄 예시
#include <pthread.h>
#include <stdio.h>

void* worker(void* arg) {
    int id = *(int*)arg;
    printf("스레드 %d 실행 중\n", id);
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;
    pthread_create(&t1, NULL, worker, &id1);
    pthread_create(&t2, NULL, worker, &id2);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

실무에서 멀티프로세스와 멀티스레드 중 어느 쪽을 택할지는 두 가지 기준으로 결정된다. 첫째 격리가 중요한가(브라우저 탭·웹 서버 프로세스 분리 등), 둘째 메모리 공유로 인한 성능 이점이 중요한가(JVM·게임 엔진·과학 계산 등)이다. 솔직히 이건 예상 밖이었는데, 제가 학교 동아리 프로젝트에서 처음 멀티스레드 카운터를 두 스레드로 1만 번씩 증가시켰을 때 결과가 2만이 아니라 17500~19000 사이에서 매번 다른 값이 나오는 모습을 본 후로는 동기화의 필요성을 손끝으로 받아들였고, 그 자연스러운 다음 글이 바로 세마포어·뮤텍스이다. 다음 글에서는 이 경쟁 조건을 푸는 동기화 도구들을 이어서 다룬다.


메타 디스크립션: 프로세스와 스레드의 정의·차이·PCB 구성·5가지 상태·컨텍스트 스위칭, 그리고 멀티프로세스 vs 멀티스레드 선택 기준을 코드 예시·비교표와 함께 OS 입문자 관점에서 세심하게 정리합니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름