[ComputerScience] 프로세스와 스레드
프로세스와 스레드
프로세스
프로세스(Process)는 실행 중인 프로그램을 의미합니다.
일반적으로 메모리에 적재된 프로세스들은 한정된 시간 동안 번갈아 가며 실행합니다.
다양한 프로세스들이 한정된 시간 동안 운영체제로부터 CPU의 자원을 번갈아 가며 할당받아서 이용합니다.
프로세스의 CPU 사용 시간은 타이머 인터럽트(Timer Interrupt, Timeout Interrupt)에 의해 제한됩니다.
커널 영역에서 프로세스 제어 블록(PCB)이 생성됩니다.
사용자 영역에서는 실행 중인 프로세스가 코드 영역, 데이터 영역, 힙 영역, 스택 영역으로 나뉘어 저장됩니다.
프로세스 제어 블록
운영체제가 다수의 프로세스를 관리하려면 프로세스를 식별할 수 있는 정보가 필요합니다.
이를 위해 커널 영역에 프로세스 제어 블록(PCB, Process Control Block)이 생성됩니다.
새로운 프로세스가 생성될 경우 PCB가 만들어지고, 실행이 끝날 경우 PCB는 폐기됩니다.
PCB는 운영체제에 따라 구성 요소가 다르지만 일반적으로 다음 정보를 담습니다.
- ID(PID): 프로세스 식별 번호
- 레지스터 값: 프로그램 카운터 등 CPU 문맥정보
- 프로세스 상태: New, Ready, Running, Blocked, Terminated
- 스케줄링 정보: 우선순위, CPU 할당 순서 등
- 메모리 관리 정보: 메모리 적재 위치
- 입출력/파일 정보: 사용 중인 파일, 장치
실행 중인 프로세스의 상태 종류는 다음과 같습니다.
- 생성(New): PCB를 할당 받고 메모리에 적재된 상태
- 준비(Ready): CPU를 기다리고 있는 상태
- 디스패치(Dispatch): 준비 상태에서 실행 상태로 전환
- 실행(Running): CPU를 할당받아 실행 중인 상태
- 정해진 시간 동안만 CPU를 사용할 수 있습니다.
- 대기(Blocked): 입출력 작업이나 자원 대기로 인해 실행 불가능한 상태
- 입출력 작업이 완료되는 등 실행 가능한 상태가 되면 다시 준비 상태로 전이
- 종료(Terminated): 프로세스가 종료된 상태
- 운영체제는 PCB와 프로세스가 사용한 메모리를 정리
대기 상태는 블로킹 입출력(Blocking I/O)에 의해 발생합니다.
반대로 논블로킹 입출력(Non-Blocking I/O)은 입출력을 요청한 뒤 실행을 멈추지 않고 명령을 이어갑니다.
문맥 교환
문맥 교환(Context Switching)은 프로세스가 실행되다 타이머 인터럽트나 자원 요청으로 실행을 양보하는 경우 수행되는 절차를 의미합니다.
문맥 교환 과정은 다음과 같습니다.
- 실행 중인 프로세스의 문맥(레지스터 값, 프로그램 카운터, 메모리/입출력 정보 등)을 PCB에 저장
- 새로 실행될 프로세스의 PCB에서 문맥을 복구
- 복구된 문맥으로 프로세스 실행
즉, 문맥 교환은 기존 프로세스의 문맥을 PCB에 백업하고, 다른 PCB에서 문맥을 복구하여 프로세스를 실행합니다.
문맥 교환은 필수적이지만, 오버헤드가 발생하기 때문에 지나치게 자주 발생하면 성능이 저하됩니다.
프로세스 메모리 구조
정적 할당 영역
- 코드 영역(Code Segment)
- 실행 가능한 명령어가 저장되는 공간, 텍스트 영역(text segment)
- CPU가 읽고 실행할 명령어가 담겨 있기 때문에 쓰기가 금지되어 있는 읽기 전용(Read-Only) 공간
- 데이터 영역(Data Segment)
- 프로그램이 실행되는 동안 유지할 데이터가 저장되는 공간
- 데이터 영역에 저장되는 데이터는 정적 변수나 전역 변수가 대표적
동적 할당 영역
- 힙 영역(Heap Segment)
- 프로그램을 만드는 사용자(개발자)가 직접 할당 가능한 저장 공간
- 힙 영역에 메모리 공간을 할당했다면 언젠가는 해당 공간을 반환해야 메모리 누수(Memory Leak) 문제를 초래하지 않습니다.
- 일부 프로그래밍 언어에서 자체적으로 사용되지 않는 힙 메모리를 해제하는 가비지 컬렉션(Garbage Collection)이 있습니다.
- 스택 영역(Stack Segment)
- 일시적으로 사용할 값들이 저장되는 공간
- 함수의 실행이 끝나면 사라지는 매개변수, 지역 변수, 함수 복귀 주소등이 스택 영역에 저장
- 스택 트레이스(Stack Trace)로 특정 시점에 스택 영역에 저장된 함수 호출 정보를 받을 수 있어 디버깅에 유용할 수 있습니다.
프로세스 종류
포그라운드 프로세스
포그라운드 프로세스(Foreground Process)는 상호자가 보는 공간에서 사용자가 상호작용하는 프로세스입니다.
백그라운드 프로세스
백그라운드 프로세스(Background Process)는 사용자와 직접 상호작용하지 않고 백그라운드에서 실행되는 프로세스입니다.
리눅스/유닉슥 계열에서는 데몬(Daemon)이라고 합니다.
윈도우에서는 서비스(Service)라고 합니다.
프로세스 관리
운영체제는 여러 PCB를 프로세스 테이블(Process Table)형태로 관리합니다.
새롭게 실행되는 프로세스가 있다면 해당 프로세스의 PCB를 프로세스 테이블에 추가하고, 필요한 자원을 할당합니다.
종료되는 프로세스가 있다면 사용 중이던 자원을 해제하고 PCB도 프로세스 테이블에서 삭제합니다.
만약, 프로세스가 종료된 후 프로세스 테이블에 남아있는 상태를 좀비 프로세스(Zombie Process)라고 합니다.
스레드
스레드(Thread)는 프로세스 내에서 실행되는 흐름의 단위입니다.
한 프로세스는 최소 하나 이상의 스레드를 가지고 있으며, 이를 메인 스레드라고 합니다.
스레드가 독립적으로 가지는 요소는 다음과 같습니다.
- 스레드ID
- 프로그램 카운터
- 레지스터 값
- 스택
스레드가 프로세스 내에서 공유하는 요소는 다음과 같습니다.
- 코드 영역
- 데이터 영역
- 힙 영역
즉, 스레드는 같은 프로세스 내 자원을 공유하면서 독립적인 실행 흐름을 가집니다.
멀티 프로세스와 멀티 스레드
멀티 프로세스
멀티 프로세스(Multi-Process)는 하나의 프로그램을 여러 프로세스로 실행하는 방식입니다.
각 프로세스는 독립적인 자원을 가지므로 안정성이 높지만 메모리 사용량과 문맥 교환 비용이 큽니다.
각각의 PID(프로세스 ID)값이 다르고, 프로세스별로 파일과 입출력장치 등의 자원이 독립적으로 할당됩니다.
한 프로세스에 문제가 생겨도 다른 프로세스에는 지장이 없거나 적습니다.
프로세스 간 통신
프로세스는 기본적으로 독립적인 메모리 공간을 가지기 때문에 직접적으로 다른 프로세스의 자원에 접근할 수 없습니다.
이런 한계를 극복하고 프로세스 간에 데이터를 주고받기 위해 프로세스 간 통신(IPC, Inter-Process Communication) 기법이 사용됩니다.
공유 메모리
공유 메모리(Shared Memory)는 프로세스 간에 특별한 메모리 공간을 공유하여 데이터를 주고받는 방식입니다.
프로세스가 공유하는 메모리 영역을 확보하는 시스템 콜을 기반으로 수행합니다.
프로세스가 공유하는 변수나 파일을 활용하기도합니다.
각 프로세스는 공유 메모리를 자신의 주소 공간처럼 읽고 쓸 수 있게 됩니다.
커널 개입이 최소화 되므로 가장 빠른 IPC 방식입니다.
동기화 문제(레이스 컨디션)를 반드시 고려해야합니다.
메시지 전달
메시지 전달(Message Passing)은 프로세스 간에 주고받을 데이터가 커널을 통해 메시지를 송수신되는 방식입니다.
공유 메모리보다 속도는 느리지만 동기화 문제를 커널이 관리해줍니다.
프로세스 간 송신자와 수신자가 명확하게 구분됩니다.
메시지 전달 기반 IPC를 위해 사용되는 대표적인 수단은 다음과 같습니다.
- 파이프(Pipe)
- 익명 파이프(Unnamed Pipe)를 활용한 부모 프로세스와 자식 프로세스 간 단방향 통신
- 지명 파이프(FIFO)를 활용한 양방향 통신과 임의 프로세스 간 통신
- 시그널(Signal)
- 이벤트(Event)가 발생했음을 알리는 비동기적인 신호
- 소켓
- 네트워크 기반 양방향 통신
- 원격 프로시저 호출(RPC)
- 다른 프로세스의 함수를 실행하는 방식
- 주로 서버간 통신에 사용됩니다.
멀티 스레드
멀티 스레드(Multi-Thread)는 하나의 프로세스 안에서 여러 스레드를 이용하는 방법입니다.
하나의 스레드는 스레드를 식별할 수 있는 스레드 ID와 프로그램 카운터, 레지스터 값, 스택 등으로 구성됩니다.
스레드마다 각각의 프로그램 카운터 값과 스택을 가지고 있기 때문에 스레드마다 다음에 실행할 주소를 가질 수 있고, 연산 과정의 임시 저장 값을 가집니다.
같은 프로세스 내 스레드들은 메모리 자원을 공유하기 때문에 빠른 협력과 통신이 가능합니다.
하지만, 한 스레드의 오류가 전체 프로세스에 영향을 줄 수 있기 때문에 안정성에 취약합니다.
댓글남기기