프로세스와 스레드
웹이든 앱이든 관계없이 개발을 해본 사람이라면 스레드에 대해 배우거나 들었을 것이다. 오늘은 CS에 대한 이해를 위해 운영체제에 관련된 내용 중 프로세스와 스레드에 대해 정리하려고 한다. 스레드에 관한 내용은 이전에도 올렸지만 이번에는 총체적으로 담아보려고 한다!
아래는 내가 이전에 정리한 스레드 관련 글이다.
2024.07.08 - [CS/OS] - 스레드 Thread (1) - 하드웨어와 소프트웨어
프로세스?
프로세스는 실행 중인 프로그램을 의미한다.
응용 프로그램은 보조 기억 장치(하드 디스크, SSD 등)에 저장되어 있다가, 사용자가 해당 프로그램을 실행하는 순간 메모리에 적재되면서 '프로세스'가 된다. 컴퓨터는 수많은 프로세스들을 동시에 생성하고, 사용이 끝나면 메모리에서 삭제함으로써 메모리 공간을 관리한다.
프로세스는 크게 사용자가 직접 상호작용할 수 있는지 여부에 따라 두 가지로 나뉜다.
- 포그라운드 프로세스(Foreground Process): 사용자와 직접 상호작용하며 화면에 나타나는 프로세스를 의미한다. 웹 브라우저, 워드 프로세서, 게임 등이 대표적이다.
- 백그라운드 프로세스(Background Process): 사용자 눈에 직접 보이지는 않지만, 시스템의 특정 기능을 수행하거나 다른 프로그램을 지원하는 프로세스다. 백그라운드 프로세스 중에서도 사용자와의 상호작용 없이 묵묵히 정해진 일만 수행하는 것을 데몬(Daemon)이라고 부른다. 윈도우즈 운영체제에서는 이를 서비스(Service)라고 지칭한다.
프로세스 관리에는 아래와 같은 여러 중요한 개념들도 연결되어 있다.
- CPU 스케줄링: CPU가 어떤 프로세스를 언제, 얼마나 실행할지 결정하는 메커니즘이다.
- 메모리 관리: 프로세스가 메모리에 어떻게 할당되고 사용될지를 결정한다.
- 입출력 장치 (하드웨어 인터럽트): 프로세스가 데이터를 읽거나 쓰는 등의 입출력 작업을 수행할 때 발생하는 인터럽트와 관련된 개념이다.
인터럽트 신호
CPU는 일반적으로 한 번에 하나의 프로세스만 실행할 수 있다. 하지만 현실에서 우리가 컴퓨터를 사용할 때 느끼기에는 여러 프로그램이 동시에 실행되는 것처럼 보인다. 우리는 브라우저를 켜놓고, 카톡을 하며, 한쪽으로 게임을 켜놓기도 한다. 이 3가지를 한 화면으로 보면 동시에 돌아가고 있는 것으로 보인다. 그러나 이는 CPU가 여러 프로세스들을 매우 빠른 속도로 번갈아 가며 실행하기 때문이다.
정확히 말하자면 프로세스는 타이머 인터럽트(Timer Interrupt)에 의해 CPU 할당 시간이 끝나면, 다음 프로세스에게 CPU 제어권을 넘겨주고 자신의 다음 차례를 기다린다.
한 프로세스가 실행 중에 인터럽트 신호를 받으면 진행 중인 내용을 백업해놓고 CPU는 다음 프로세스를 실행한다. 이러한 방식은 마치 착시 현상처럼 느껴질 정도로 빠르다.
프로세스 제어 블록(PCB)은 프로세스의 신분증이다
모든 프로세스는 운영체제가 프로세스를 관리하고 식별하기 위한 고유한 정보 묶음을 가지고 있는데, 이것이 바로 프로세스 제어 블록(Process Control Block, PCB)이다. PCB는 프로세스와 관련된 모든 중요한 정보를 저장한 자료구조이며, 시스템의 가장 핵심적인 영역인 커널(Kernel) 영역에 저장된다. 운영체제는 이 PCB를 통해 특정 프로세스를 식별하고 제어한다.
프로세스가 실행을 마치거나 비정상적으로 종료될 경우, 해당 프로세스의 PCB는 운영체제에 의해 메모리에서 삭제된다.
PCB에 담기는 정보
- 프로세스 ID (PID): 각 프로세스를 고유하게 식별하는 번호다. 주민등록번호와 같다고 생각하면 이해하기 쉽다.
- 레지스터 값: CPU 내부의 레지스터(연산에 사용되는 임시 저장 공간)에 저장되어 있던 값들이다. 프로세스가 자신의 CPU 차례를 다시 받으면, 이전에 사용했던 중간 값을 복원하여 이어서 작업을 수행할 수 있도록 한다.
- 프로세스 상태: 현재 프로세스가 어떤 상태에 있는지(예: 실행 중, 대기 중, 준비 상태 등)를 기록한다.
- CPU 스케줄링 정보: 프로세스의 우선순위, 남은 CPU 할당 시간 등 CPU 스케줄링에 필요한 정보가 담긴다.
- 메모리 관리 정보: 프로세스가 사용하는 메모리 영역의 시작 주소(베이스 레지스터), 크기(한계 레지스터) 등 메모리 관리에 필요한 정보가 저장된다.
- 사용한 파일과 입출력 장치 목록: 현재 프로세스가 열어둔 파일이나 사용 중인 입출력 장치 목록을 기록한다.
이처럼 프로세스 제어 블록은 언제든 프로세스를 다시 실행해도 이전에 중단되었던 작업의 중간 지점부터 시작할 수 있도록, 프로세스의 현재 상태와 관련된 모든 문맥(Context)을 저장한다.
현재 실행 중인 프로세스의 문맥을 저장하고, 다음에 실행될 프로세스의 문맥을 가져와 CPU에 적재하는 과정을 문맥 교환(Context Switching)이라고 한다. 문맥 교환은 매우 빈번하게 발생하며, 시스템 성능에 중요한 영향을 미친다.
프로세스 메모리 영역
프로세스가 생성될 때 커널 영역에 PCB가 생성된다면, 사용자 영역에는 프로세스가 사용하는 실제 코드와 데이터들이 저장되는 공간이 필요하다. 이 사용자 영역의 메모리는 일반적으로 네 가지 주요 영역인 코드(Code) 영역, 데이터(Data) 영역, 힙(Heap) 영역, 스택(Stack) 영역으로 나뉘어 저장된다.
- 코드 영역 (Code Segment): 텍스트 영역(Text Segment)이라고도 불린다. CPU가 실행할 명령어가 기계어 형태로 저장되어 있는 공간이다. 프로그램은 정해진 명령어의 집합체이므로 실행되는 동안 명령어는 변경되어서는 안 된다. 따라서 쓰기가 불가능한 읽기 전용(Read-Only) 공간으로 보호된다.
- 데이터 영역 (Data Segment): 프로그램이 실행될 때부터 종료될 때까지 유지되는 데이터가 저장되는 공간이다. 주로 전역 변수(Global Variable)나
static
변수 등이 이곳에 저장된다. 코드 영역과 데이터 영역은 프로그램이 실행되는 순간 이미 그 크기가 정해지거나, 프로그램 실행 내내 유지되는 불변적인 특성을 가진다. 따라서 이 두 영역을 정적 할당 영역(Static Allocation Area)이라고 한다. - 힙 영역 (Heap Area): 프로그래머(사용자)가 프로그램 실행 중에 필요한 데이터를 직접 동적으로 할당하거나 해제해야 하는 저장 공간이다. C/C++에서는
malloc
/free
, Java에서는new
키워드 등을 통해 이 영역을 관리한다. 만약 할당된 공간을 사용 후 반환하지 않으면, 더 이상 사용하지 않는 데이터가 메모리 공간을 차지하게 되어 메모리 누수(Memory Leak)가 발생할 수 있다. - 스택 영역 (Stack Area): 데이터를 일시적으로 저장해두는 공간이다. 함수 호출 시 지역 변수(Local Variable), 매개 변수(Parameter), 반환 주소 등이 여기에 저장된다. 스택 자료구조의 특성(LIFO: Last-In, First-Out)을 따르므로, 데이터를 저장할 때는 PUSH 연산으로 쌓이고, 사용이 끝나면 POP 연산으로 제거되어 스택 영역에서 사라진다.
재미있는 점은 스택 영역은 메모리의 높은 주소에서 낮은 주소 방향으로 할당되고, 힙 영역은 낮은 주소에서 높은 주소 방향으로 할당된다는 것이다. 이처럼 서로 반대 방향으로 공간을 할당받는 구조 덕분에 스택과 힙이 서로 겹쳐서 충돌할 위험을 줄여준다.
스레드
스레드(Thread)는 실행 흐름의 단위를 의미한다. 초기 컴퓨터 시스템에서는 하나의 프로세스가 오직 하나의 작업만 수행하는 단일 스레드 프로세스(Single-threaded Process)가 일반적이었다. 하지만 현대의 응용 프로그램들은 훨씬 복잡하고 다양한 작업을 동시에 처리해야 할 필요성이 커졌다.
'스레드'가 도입되면서, 하나의 프로세스는 여러 가지 작업을 동시에 수행할 수 있게 되었다. 예를 들어, 워드 프로세서 프로그램은 사용자의 입력을 받는 동시에, 맞춤법을 검사하거나 문서를 저장하는 작업을 병렬로 처리할 수 있다. 이런 의미에서 스레드는 프로세스를 구성하는 실행 단위가 된다.
가장 중요한 스레드의 특징은 프로세스 내에서 코드(Code), 데이터(Data), 힙(Heap) 영역을 공유한다는 점이다. 즉, 같은 프로세스에 속한 스레드들은 프로그램의 명령어, 전역 변수, 동적 할당된 메모리를 함께 사용한다.
각 스레드가 갖는 독립적인 영역
하지만 모든 것을 공유하는 것은 아니다. 각 스레드는 고유한 자신만의 영역을 갖는다.
- 스택 영역: 각 스레드는 프로세스에서 필요한 만큼의 스택 영역을 할당받는다. 여기에 자신만의 지역 변수와 함수 호출 정보 등을 저장한다.
- 프로그램 카운터(Program Counter, PC): 다음에 실행할 명령어의 주소를 가리키는 레지스터로, 각 스레드마다 독립적으로 유지되어 자신이 어디까지 실행했는지를 기억한다.
- 레지스터 값: CPU의 레지스터에 저장되는 값들로, 각 스레드마다 독립적으로 관리된다.
- 열린 파일 목록: 정확히는 프로세스가 열어둔 파일을 공유하지만, 각 스레드는 파일의 특정 위치에 대한 포인터 등 자신만의 파일 접근 정보를 가질 수 있다.
멀티 프로세스와 멀티 스레드는 무엇이 다를까?
두 가지 모두 여러 작업을 동시에 처리하는 병렬성을 구현하는 방법이지만, 그 구현 방식과 장단점에 차이가 있다.
멀티 프로세스 (Multi-Process)
멀티 프로세스는 코드, 데이터, 힙, 스택 영역을 모두 독립적으로 갖는 여러 개의 프로세스를 동시에 실행하는 방식이다. 예를 들어, fork()
시스템 호출을 사용하여 기존 프로세스와 완전히 동일한 복사본(자식 프로세스)을 하나 더 생성하면, 부모 프로세스와 자식 프로세스는 독립적인 메모리 공간을 갖는다.
- 장점:
- 독립적인 자원: 각 프로세스는 독립적인 메모리 공간을 가지므로, 하나의 프로세스에서 문제가 발생해도 다른 프로세스에 영향을 주지 않는다. 즉, 안정성과 견고성(Robustness)이 높다.
- 쉬운 자원 관리: 메모리 관리 측면에서 서로 간섭할 위험이 적다.
- 단점:
- 높은 메모리 사용량: 각 프로세스가 독립적인 코드, 데이터, 힙 영역을 모두 복사하여 가지므로, 동일한 작업을 여러 프로세스로 처리할 경우 메모리 낭비가 심하다.
- 느린 문맥 교환: 프로세스 간 문맥 교환 시에는 모든 레지스터 값과 캐시 메모리 정보 등을 새로 로드해야 하므로, 스레드에 비해 오버헤드가 크고 속도가 느리다.
- 복잡한 통신: 프로세스 간 통신(IPC: Inter-Process Communication)을 위해서는 파이프, 소켓, 공유 메모리 등 별도의 통신 메커니즘이 필요하여 구현이 복잡하다.
멀티 스레드 (Multi-Thread)
멀티 스레드는 하나의 프로세스 내에서 여러 스레드를 생성하여 실행하는 방식이다. 스레드들은 코드, 데이터, 힙 영역을 공유하고 각자 독립적인 스택, 프로그램 카운터, 레지스터 값 등을 가진다.
- 장점:
- 효율적인 자원 사용: 프로세스 내부의 코드, 데이터, 힙 영역을 공유하기 때문에, 프로세스를 통째로 복사하는 멀티 프로세스에 비해 메모리 공간을 훨씬 적게 차지한다. 이는 더 많은 작업을 동시에 효율적으로 처리할 수 있게 한다.
- 빠른 문맥 교환: 스레드 간 문맥 교환 시에는 스택, 프로그램 카운터, 레지스터 값 등 적은 양의 정보만 교환하면 되므로, 멀티 프로세스에 비해 오버헤드가 적고 속도가 빠르다.
- 쉬운 통신과 협력: 스레드들은 같은 프로세스 내의 메모리 공간을 공유하므로, 별도의 통신 메커니즘 없이도 전역 변수나 공유 메모리를 통해 데이터를 쉽게 주고받을 수 있어 협력과 통신에 매우 유리하다.
- 단점:
- 안정성 문제: 하나의 스레드에 문제가 발생하여 프로세스의 공유 자원(코드, 데이터, 힙)을 손상시키면, 해당 프로세스 내의 다른 모든 스레드와 나아가 프로세스 전체에 치명적인 영향을 미칠 수 있다. 즉, 동기화 문제와 안정성 취약점이 존재한다.
- 동기화 문제: 여러 스레드가 동시에 공유 자원에 접근할 경우, 데이터의 일관성이 깨지거나 예측 불가능한 결과가 발생할 수 있다. 이를 해결하기 위해 락(Lock), 세마포어(Semaphore), 뮤텍스(Mutex) 등 복잡한 동기화 메커니즘이 필요하다.
- 디버깅의 어려움: 스레드 간 상호작용이 복잡해질수록 문제 발생 시 원인을 파악하고 디버깅하기가 어렵다.
구분 | 멀티 프로세스 (Multi-Process) | 멀티 스레드 (Multi-Thread) |
---|---|---|
자원 공유 | 코드, 데이터, 힙, 스택 모두 독립적 (별도의 메모리 공간) | 코드, 데이터, 힙 공유, 스택/PC/레지스터 독립 (동일 프로세스 내) |
메모리 사용 | 높음 (각 프로세스마다 자원 중복) | 효율적 (공유 자원 활용) |
문맥 교환 | 느림 (오버헤드 큼) | 빠름 (오버헤드 적음) |
안정성 | 높음 (한 프로세스 문제 시 다른 프로세스 영향 없음) | 낮음 (한 스레드 문제 시 프로세스 전체 영향 가능) |
통신/협력 | 복잡함 (IPC 필요) | 용이함 (공유 메모리 직접 접근) |
복잡도 | 구현 비교적 간단 (독립적) | 동기화, 디버깅 등 구현 복잡도 높음 |
'CS > OS' 카테고리의 다른 글
동기화 (2) | 2025.06.10 |
---|---|
커널 Kernel (0) | 2025.06.04 |
운영체제란? 커널, 프로세스, 교착 상태까지 (0) | 2025.03.14 |
스레드 Thread (1) - 하드웨어와 소프트웨어 (1) | 2024.07.08 |
운영체제(9) - 입출력 시스템과 디스크 관리 (0) | 2022.12.07 |