본문 바로가기

Computer Science/Operating System

프로세스와 쓰레드

1. 개요

운영체제 과목 수업을 들을 때 전체 내용에서 빠짐없이 등장하는 중요한 키워드를 골라보라고 한다면 프로세스와 쓰레드를 고를 수 있을 것 같다. 이번 포스트에서는 프로그램 실행을 제어하는 기본 개념임과 동시에 프로그램이 여러 작업을 동시에 실행할 수 있도록 허용하는 역할을 담당하는 프로세스와 쓰레드에 대해서 정리해보려고 한다.

2. 프로세스

노션이나 카카오톡과 같이 우리가 컴퓨터에 어떠한 프로그램을 설치할 때를 떠올려 보면 .exe라는 확장자가 붙은 파일을 항상 마주하게 된다. 이처럼 프로그램은 .exe와 같은 확장자를 가진 하나의 실행파일로 컴퓨터의 저장매체(하드디스크, SSD 등)에 존재하고 있다. 이 프로그램이 메모리에 올려지고, CPU에 의해 실행되는 것이 바로 프로세스이다.

즉, 프로그램이 저장매체에 담긴 실행코드라면, 프로세스는 이 프로그램이 메모리 상에서 실행될 때의 작업 단위를 뜻한다고 보면 된다.

또한 쓰레드에 대해서도 잠깐 언급을 하자면, 하나의 프로세스에는 여러 개의 스레드들이 동시에 병행하여 실행하는 기능을 가지고 있다. 현대의 운영체제 대부분은 하나의 프로세스가 다중 스레드를 지원하지만, 유닉스 시스템과 같이 과거의 고전적인 프로세스에서는 하나의 프로세스가 단일 스레드로만 구성되어 있었다.

3. 프로세스의 생애주기

앞서 프로세스는 메모리 상에서 실행될 때의 작업 단위라고 설명했다. 여기서 메모리 상에 올라갔다는 것은 언젠가는 프로세스가 KILL되는 순간이 찾아온다는 것을 예상할 수 있다.

메모리는 유한한 공간이고, 하나의 프로세스가 계속해서 메모리를 점유하고 있다면 많은 메모리 공간을 필요로 하는 어떤 작업은 실행이 불가능한 상태를 맞이할 수 있다. 따라서 스케줄러는 스케줄링 알고리즘에 따라서 적절하게 프로세스들을 배치하게 되는데 이 때 프로세스의 상태를 참고하게 되고, 이 상태들을 모두 합쳤을 때 하나의 생애주기가 그려진다.

3.1. 메모리에 적재된 프로세스

먼저 프로세스가 메모리에 어떻게 적재되는지 살펴보자.

프로세스에게 할당되는 메모리 공간은 Code 영역(text 영역), Data 영역, Heap 영역, Stack 영역으로 나누어진다. 그리고 각각의 프로세스는 독립적으로 이 메모리 공간을 할당받는다.

Code 영역

코드 영역은 프로그램이 시작될 때 비휘발성 저장소(e.g., HDD, SSD)에서 읽어온 컴파일된 프로그램 코드로 구성되는 메모리 영역이다.

Data 영역

데이터 영역은 프로그램의 전역 변수와 정적 변수가 저장되고, main함수를 실행하기 전에 할당되고 초기화되는 메모리 영역이다.

Heap 영역

힙 영역은 동적 메모리 할당에 사용되고 new, delete, malloc, free 등에 대한 호출을 통해서 관리된다. 그리고, runtime에 따라서 메모리 영역의 크기가 결정된다.

이 때, 낮은 주소에서부터 높은 주소 방향으로 힙 영역이 사용된다.

Stack 영역

스택 영역은 지역 변수를 저장하기 위해 사용된다. 스택의 공간은 지역 변수가 선언될 때 예약되고 지역 변수가 범위를 벗어나면 공간이 해제된다. 또한, 함수의 반환 값에도 활용되며, 스택 관리의 정확한 메커니즘은 언어 별로 상이할 수 있다.

스택과 힙은 프로세스가 할당 받은 메모리 공간 중 코드와 데이터 영역을 제외한 여유 공간에서 서로를 향해서 영역을 확대하는 모습을 띄게 된다.

만약 이 두 개의 영역이 서로 교차하게 되면 stack overflow가 발생하거나 사용가능한 메모리의 부족으로 newmalloc에 대한 호출을 실패하게 된다.

3.2. 프로세스의 상태

프로세스의 생애주기는 다음과 같이 5가지 상태로 구성된다.

New

프로그램을 실행하기 위해 OS에게 요청할 때 프로세스를 New상태로 생성한다.

Ready

현재 프로세스를 실행하기 위한 모든 리소스가 준비된 상태이지만 아직 스케줄러에 의해서 선택되지 않은 상태이다. 스케줄러는 Ready상태의 프로세스들 중 하나를 선택해서 Running상태로 전환한다.

Running

Running은 말 그대로 CPU에 의해 현재 실행되고 있는 프로세스의 상태를 의미한다. Running 상태에서 Waiting상태로 변경되는 경우는 여러 가지가 존재할 수 있다.

파일 읽기 작업의 경우 어느 정도 시간이 소요되는 작업인 만큼 CPU가 해당 시간 동안 다른 프로세스를 선택해서 실행할 수 있도록 읽기 작업을 위한 프로세스를 Waiting상태로 잠시 변경해둔다. 이후, 읽기 작업이 완료되는 시점에 맞춰 Ready상태로 변경한다.

키보드 입력도 마찬가지이다. 입력장치에 입력을 요청했을 실제 입력이 될 때까지 Waiting상태를 유지한다. 이 외에도 자식 프로세스가 종료될 때까지 대기하도록 요청하는 wait 시스템 콜 등 다양한 경우가 존재한다.

Waiting

Waiting 상태의 프로세스는 특정 자원을 사용할 수 있게 되기를 기다리거나, 특정 이벤트가 발생하기를 기다리고 있는 상태이기 때문에 해당 프로세스는 현재 실행할 수 없는 상태이다.

이 때의 프로세스 상태는 Running상태에서도 설명한 것처럼 키보드의 입력, 저장장치의 액세스 요청, 프로세스 간의 메시지, 자식 프로세스의 종료 등을 기다리고 있다.

Terminated

현재 실행 중인 프로그램이 완전히 종료되면 Terminated상태가 되고, 운영체제에 의해서 해당 프로세스가 점유했던 자원들을 모두 회수한다.

3.3. 프로세스의 득과 실

프로세스는 독립적으로 메모리 공간을 할당받기 때문에 하나의 프로세스가 갑자기 강제 종료되더라도 다른 프로세스가 영향을 받지 않는다는 장점이 있다.

반면, 시스템 자원을 많이 소모한다는 단점도 있다. 이 부분은 프로세스가 생성되는 과정을 살펴보면 좋을 것 같다.

사용자가 shell 프로세스에게 실행할 파일을 지정해서 실행하도록 입력하면 shell 프로세스는 운영체제에게 파일명을 지정하고 프로세스를 생성하는 시스템 콜 함수를 호출한다.

shell이 아닌 임의의 프로세스에서도 프로세스를 생성하는 시스템 콜 함수를 통해 프로세스를 생성할 수도 있다.

이렇게 생성된 프로세스를 자식 프로세스, 그리고 생성을 요청한 프로세스를 부모 프로세스라고 부르는데, 자식 프로세스는 부모 프로세스의 전체 메모리 영역을 복제하게 된다. 이 과정에서 시간과 메모리를 많이 소모하게 될 수도 있다.

또한, 각 프로세스는 독립된 상태이기 때문에, 메모리를 서로 직접 공유하는 상태가 아니다. 따라서 프로세스 간에 데이터 전송을 위해서 통신 메커니즘(e.g., IPC기법)을 사용하게 되는데 이는 상대적으로 메모리를 공유하는 것보다 느리고 복잡할 수 있다.

4. 컨텍스트(Context)와 프로세스 제어블록(PCB: Process Control Block)

하나의 프로세스에는 많은 정보가 담겨 있다. 특히, 시분할 시스템에서는 매우 짧은 시간 동안 프로세스들이 서로 차례를 바꿔가며 CPU에서 일정부분의 명령을 수행하는데, 자신의 차례가 다시 돌아왔을 때는 이전에 진행하던 작업을 이어서 진행해야 한다. 따라서 이전에 어떤 명령까지 수행했고, 사용했던 레지스터는 어떤 레지스터이며 해당 레지스터에 어떤 값을 저장했는지에 대한 정보를 모두 기억하고 있어야 한다.

이처럼 프로세스의 작업 정보 전반을 컨텍스트(Context) 라고 하며, 컨텍스트는 PCB에 담겨 있다. 그리고 한 프로세스에서 다른 프로세스로 CPU의 제어권을 넘겨주는 것을 컨텍스트 스위치(Context switch) 라고 한다.

PCB는 운영체제가 현재 생성된 프로세스들에 대한 목록을 정리한 자료구조이다. 운영체제는 생성되는 프로세스마다 PCB를 할당하여 스케줄링에 필요한 정보를 기록하고 있다. PCB에 기록되는 정보는 대표적으로 다음과 같은 정보들이 있다.

  • 프로세스의 현재 상태: Running, Waiting, ...
  • 프로세스 ID와 부모 프로세스 ID
  • 프로그램 카운터(PC): 해당 프로세스가 다음에 실행할 명령어의 주소
  • 레지스터: 컴퓨터 구조에 따라서 다양한 수와 유형을 가진 레지스터 값들이 담긴다.
  • 메모리 정보: 페이지 테이블, 세그먼트 테이블 등
  • CPU 스케줄링 정보: 우선순위, 실행 시간 등

세부적인 내용들은 사용하는 시스템에 따라서 조금씩 상이할 수 있다.

5. 멀티 프로세스(Multi process)

"멀티"라는 단어가 의미하는 바와 같이 멀티 프로세스는 2개 이상의 프로세스가 동시에 실행되는 것을 의미한다. 이 때 "동시"라는 단어는 동시성(concurrency)병렬성(parallelism) 2가지의 의미를 담고 있다.

이 때, 동시성은 하나의 CPU 코어가 있을 때 여러 프로세스를 짧은 시간 동안 번갈아 가면서 수행하는 시분할 시스템을 생각하면 된다. 굉장히 짧은 시간 안에 컨택스트 스위칭이 발생하기 때문에 사용자는 마치 동시에 실행되는 것과 같은 느낌을 받게 된다. 한편, 병렬성은 동시성과 달리 물리적으로 동시에 다수의 작업이 처리되는 것을 말한다.

예를 들어 어떤 CPU의 코어가 듀얼코어이고 현재 처리해야할 프로세스가 2개일 때 각 코어가 프로세스 하나씩 담당해서 처리를 하게 되면 병렬성의 특징을 가진다고 할 수 있다.

물론, 실제 CPU에서는 병렬성만 유지하거나 동시성만 유지하는 것이 아니라 둘 다 고려하는 상황일 것이다. 현재 본인의 컴퓨터의 작업관리자를 들어가서 실행 중인 프로세스를 확인해보면 절대 PC의 CPU 코어 수만큼 있지 않을 것이다.

현재 사용하는 PC의 CPU는 듀얼코어이다. 그리고 위의 이미지에서도 확인할 수 있듯이 현재 실행 중인 프로세스는 코어의 수보다 훨씬 많은 상황임에도 동시에 실행되고 있는 것처럼 느낄 수 있는 것은 동시성도 같이 유지하고 있기 때문이다. 그리고 당연히 병렬성이나 동시성과 같은 개념뿐만 아니라, 후술할 스레드나 캐시 등 여러 장치들과 유기적인 관계를 통해서 현대의 PC는 물리적인 코어가 적더라도 과거의 PC들보다 높은 사용자 경험을 제공할 수 있다.

5.1. 멀티프로세싱(Multi-processing)과 멀티 프로세스

멀티 프로세스와 유사한 단어 중에 멀티 프로세싱이 있다. 개인적으로 멀티 프로세싱과 멀티 프로세스의 차이를 명확히 알지 못했는데 이번 기회에 정리하면서 확실해졌다. 그리고 이 둘은 동일선 상에서 비교할 개념은 아닌 것 같다.

우선 멀티프로세싱은 단일 프로세서가 아닌 다수의 프로세서가 서로 협력하여 일을 처리하는 방식을 말한다. 그리고 이 때 주의해야 할 사항은 프로세서와 CPU를 동일시하면 안된다는 점이다. 개인적으로 두 개념을 동일시해서 생각했다가 멀티프로세싱과 멀티 프로세스를 초반에 잘못 이해했다.

프로세서는 읽기/쓰기 명령을 처리하는 "칩(chip)"을 지칭하는 것이지 CPU 그 자체를 의미하는 것은 아니다. 따라서 프로세서는 GPU, 하드드라이브, 키보드 등 컴퓨터가 작동하기 위한 모든 장비에는 프로세서가 달려 있다고 생각해도 좋다. 다만, CPU가 컴퓨터의 중앙 처리 장치이기 때문에 CPU의 명령을 따르는 것이다.

이처럼 다수의 프로세서가 작업을 병렬처리하는 것이 멀티프로세싱이다.

정리해보자면, 멀티프로세싱은 컴퓨터가 job을 처리하는 방식이고, 멀티 프로세스는 컴퓨터가 job을 처리하는 상태라고 정리할 수 있을 것 같다.

6. 스레드(Thread)와 멀티스레드(Multithread)

스레드는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 뜻한다. 고전적인 프로세스는 하나의 스레드만을 가지고 있었지만, 오늘날의 운영체제에서는 멀티스레드 기능을 제공하고 있어 하나의 프로세스에 여러 개의 스레드를 동시에 실행할 수 있도록 구성되어 있다.

6.1. 스레드와 메모리

위의 이미지와 같이 동일한 프로세스에 포함된 스레드들 간에는 코드 영역, 데이터 영역, 힙 영역을 공유하고, 프로세스로부터 스택 영역은 쓰레드 별로 할당받는다.

그리고 스레드는 프로세스 내에서 독립적으로 함수를 호출하는데 이 때 스택 영역은 함수 호출 시 전달되는 인자, 함수의 return address, 함수 내 지역 변수 등을 저장하기 위한 공간으로 사용된다.

또한, 멀티 스레드 환경에서는 코드 영역에 각 스레드 별 PC(Program Counter) 레지스터를 가지고 있어야 한다. 이렇게 구성되어 있어야 PC 레지스터에 담긴 주소를 바탕으로 프로세스 내의 스레드끼리 context switch를 진행할 수 있다.

6.2. 멀티스레드의 장점

6.2.1. 높은 응답성

'워드'라는 하나의 응용프로그램을 위해 프로세스가 할당되었다고 생각해보자. 사용자가 워드를 통해서 글을 작성하는 동안 자동저장 기능이 작동하고, 맞춤법 검사 기능도 동시에 작동한다. 즉, 하나의 프로세스 내에서 다수의 스레드가 동시에 작동 중인 것이다.

이것을 통해 사용자는 해당 프로그램에서 높은 응답성을 경험할 수 있게 된다.

6.2.2. 자원 공유

스레드는 자신이 속한 프로세스의 자원과 메모리를 공유하기 때문에 하나의 주소 공간 내에서 다수의 작업이 동시에 수행될 수 있도록 만들어준다.

6.2.3. 경제성

프로세스를 생성하고 여기에 메모리와 자원을 할당하는 것은 비용이 큰 작업이다. 하지만, 스레드를 사용한다면 자신이 속한 프로세스의 자원을 공유하기 때문에 상대적으로 스레드를 생성하고 이들 사이에 문맥교환하는데 적은 비용으로 수행할 수 있게 된다.

6.2.4. 멀티프로세서 환경에서의 이점

멀티프로세서 환경에서는 하나의 프로세스에 속한 스레드들을 여러 개의 프로세서에서 병렬로 실행할 수 있고, 이러한 환경에서는 프로세스의 완료시점을 단축할 수 있다.

6.3. 멀티스레드의 단점

하나의 프로세스 내에서 스레드를 너무 많이 분할해서 사용하면 스레드들 간의 스케줄링 오버헤드 및 문맥교환 오버헤드가 늘어나게 되며, 스레드들 간의 동기화를 위한 오버헤드도 늘어날 수 있다.

그리고 동일한 프로세스에 속한 스레드 간에는 스택 메모리 영역의 보호가 되지 않는 것이 일반적다. 따라서, 만약 스레드들 간에 스택 영역을 침범하는 일이 발생할 경우 프로세스가 예측하지 못한 동작을 수행할 수도 있다.

7. 멀티프로세스 v.s. 멀티스레드

  • 멀티스레드는 멀티프로세스보다 적은 메모리 공간을 차지하고, 멀티프로세스와 달리 문맥 교환 시 캐시 메모리를 초기화할 필요가 없기 때문에 상대적으로 문맥교환이 빠르게 이루어진다.
  • 반면, 멀티프로세스는 멀티스레드보다 많은 메모리 공간과 cpu시간을 차지한다.
  • 멀티스레드는 서로 다른 스레드가 메모리 영역을 공유하기 때문에 여러 스레드가 동일한 자원에 동시에 접근하여 엉뚱한 값을 읽거나 수정하는 동기화 문제가 발생할 수 있다.
  • 멀티스레드는 하나의 스레드 장애로 전체 스레드가 종료될 수도 있지만, 멀티 프로세스는 하나의 프로세스가 죽어도 다른 프로세스에 영향을 주지 않기 때문에 높은 안정성을 지닌다.

Reference