프로세스 & 스레드
참고 자료
1. 프로세스 & 스레드의 메모리
1.1 프로세스의 자원 구조
프로그램이 실행되어 프로세스가 만들어지면 다음 4가지의 메모리 영역으로 구성되어 할당 받게 된다.

코드 영역 : 프로그래머가 작성한 프로그램 함수들의 코드가 CPU 가 해석 가능한 기계어 형태로 저장되어 있다.
데이터 영역 : 코드가 실행되면서 사용하는 전역 변수나 각종 데이터들이 모여있다. 데이터영역은 .data, .rodata, .bss 영역으로 세분화 된다.
.data : 전역 변수 또는 static 변수 등 프로그램이 사용하는 데이터를 저장
.bss : 초기값 없는 전역 변수, static 변수가 저장
.rodata : const 같은 상수 키워드 선언 된 변수나 문자열 상수가 저장
스택 영역 : 스택은 함수 단위로 스택프레임이 할당되며, 함수 호출이 완료되면 소멸한다. 만일 stack 영역을 초과하면 stack overflow 에러가 발생한다.
힙 영역 : 생성자, 인스턴스와 같은 동적으로 할당되는 데이터들을 위해 존재하는 공간이다. 사용자에 의해 메모리 공간이 동적으로 할당되고 해제된다.
코드 영역과 데이터 영역은 선언할 때 그 크기가 결정되는 정적 영역이지만, 스택 영역과 힙 영역은 프로세스가 실행되는 동안 크기가 늘어났다 줄어들기도 하는 동적 영역이다.
1.2 스레드의 자원 구조
스레드는 프로세스가 할당 받은 자원을 이용하는 실행의 단위로서, 스레드가 여러개 있으면 우리가 파일을 다운 받으며 동시에 웹 서핑을 할 수 있게 해준다. 스레드끼리 프로세스의 자원을 공유하면서 프로세스 실행 흐름의 일부가 되기 때문에 동시 작업이 가능한 것이다. 그래서 아래 사진과 같이 하나의 프로세스 내에 여러개의 스레드가 들어있는 상태인 것이다.

이때 프로세스의 4가지 메모리 영역(코드, 데이터, 힙, 스택) 중 스레드는 스택만 할당받아 복사하고 나머지 영역은 다른 스레드들과 공유된다. 따라서 각각의 스레드는 별도의 스택을 가지고 있지만 힙 메모리는 고유하기 때문에, 서로 다른 스레드에서 가져와 읽고 쓸 수 있게 된다.
스레드마다 스택을 가지고 있다는 말은 스레드 마다 독립적인 함수 호출이 가능하다는 의미이다. 그리고 독립적인 함수 호출은 독립적인 실행 흐름을 가지고 있다는 뜻이다.
반면에 프로세스는 기본적으로 프로세스 끼리 다른 프로세스의 메모리에 직접 접근할 수는 없다.
이렇게 구성한 이유는 하나의 프로세스를 다수의 실행 단위인 스레드로 구분하여 자원을 공유하고, 자원의 생성과 관리의 중복성을 최소화하여 수행 능력을 올리기 위해서이다.
1.3 프로세스의 자원 공유
기본적으로 각 프로세스는 메모리에 별도의 주소 공간에서 실행되기 때문에, 한 프로세스는 다른 프로세스의 변수나 자료구조에 접근할 수 없다. 그렇다면 프로세스는 영원히 다른 프로세스 정보에 접근할 수 없을까?
현재 우리가 사용하는 대부분의 컴퓨터 프로그램은 다른 프로그램의 있는 정보를 가져오는 경우가 심심치 않게 볼 수 있을 것이다. 이처럼 특별한 방법을 통해 프로세스가 다른 프로세스의 정보에 접근하는 것이 가능하다.
프로세스 간 자원을 공유하는 것을 IPC(Inter Process Communication) 이라고 부르고, 자원을 공유하는 방법은 2가지 방법이 있다.
Message Passing
Shared Memory

Message Passing프로세스간에 데이터 전송 및 읽기와 같은 통신을 위해 메시지 전송이라는 방법이 존재한다. 메시지 전송 방법을 구현하기 위한 방법은 다음과 같다.
메시지 큐(Message Queue)
파이프(Pipe)
소켓(Socket)
원격 프로시저 콜(Remote Procedure Call) 방법이 존재한다.
운영체제는 메시지 전송 기능을 통해서 각각의 프로세스들이 서로 통신하기 위한 API 들을 제공한다. 메시지 전송 API 는 다음과 같다.
send(message, destination)orsend(message)
receive(message, host)orreceive(message)위와 같은 API를 기반으로 메시지 큐, 파이프, 소켓, 원격 프로시저 콜과 같은 구현체가 존재할 수 있습니다. 각 구현체는 각자의 방법으로 프로세스간 통신을 수행하게 합니다.
예를 들어, 메시지 큐를 이용하여 메시지를 전송하는 방식은 다음과 같은 순서로 이루어진다.
메시지 생성
send()시스템 콜 호출커널 개입 :
send()시스템 콜이 호출되면, 운영체제의 커널이 개입하여 메시지를 메시지 큐에 저장한다. (이 과정에서 복사가 발생한다)컨텍스트 스위칭 : 메시지를 보낸 프로세스는 다시 자신의 작업을 수행한다. 이때 받는 프로세스는 메시지가 도착했는지 알지 못해도 된다.
receive()시스템 콜 호출커널 개입 :
receive()시스템 콜이 호출되면, 커널은 메시지 큐에 해당 메시지가 있는지 확인한다.메시지 수신 (이 과정에서 복사가 발생한다)
Shared Memory주고 받는 프로세스가 모두 읽고 쓸 수 있는 메모리를 만들어 메모리 안에서 서로 자원을 공유하도록 하는 것이다.
이 방법을 통해서는 따로 커널 프로세스와 컨텍스트 스위칭 과정을 거치지 않으니 Message Passing 보다 오버헤드가 적다.
물론 이렇게 Shared Memory 를 이용해도, 처음에는 커널한태 "우리 Shared Memory 를 이용할께" 라고 알려주어야 한다.
이후의 모든 통신의 커널의 문제 없이 진행된다.
하지만 동기화 문제가 발생할 수 있다.
이러한 문제를 해결하기 위해서 뮤텍스, 세마포어 같은 동기화 도구를 사용해야 한다.
개발자가 직접 동기화 문제를 처리해야 하는 책임이 따른다.
그러나 프로세스 자원 공유(Message Passing)는 커널 프로세스와 컨텍스트 스위칭(Message Passing 방식)을 해야하기 때문에, 자원 부담이 크다는 단점이 있다. 그래서 다중 작업이 필요한경우 스레드를 사용하는 것이 효율적이라, 현대 컴퓨터의 운영체제에선 다중 프로세싱을 지원하고 있지만 다중 스레딩을 기본으로 하고 있다.
2. 프로레스 & 스레드 동시 실행 원리
2.1 멀티 코어와 스레드
한번 컴퓨터 견적을 맞춰본 경험이 있는 사람들은 4코어 8스레드 CPU 에 대한 단어를 본 적이 있을 것이다.

CPU 한 개는 여러개의 코어를 가질수 있다. 코어는 말그대로 CPU 코어 유닛이다. 즉, 명령어를 메모리에서 뽑아 해석하고 실행하는 반도체 유닛 4개가 있는 것이다. 4코어가 물리적 코어 갯수면, 8스레드는 논리적 코어 갯수를 말한다. 이 경우 물리적 코어 하나가 스레드 두개 이상을 동시에 실행 가능하다는 것을 의미한다.
즉, 운영체제가 8개의 작업을 동시에 처리할 수 있다는 뜻이다. 이를 하이퍼스레딩 기술이라고 한다.
단, 여기서 말하는 CPU 스레드는 우리가 배운 프로세스의 스레드와는 조금 다른 개념이다.
엄밀히 말하자면 CPU 스레드는 하드웨어적 스레드이고, 프로세스의 스레드는 소프트웨어적 스레드로 구분할 수 있다.
그런데 우리는 컴퓨터를 이용할 때 프로그램을 수십, 수백개를 켜 놓고 이용한다. 그럼 그 수십수백개의 프로세스를 고작 8개의 논리적인 스레드로 어떻게 처리하는 것일까?
이 원리를 알기 위해서는 "병렬성(Parallelism)" 과 "동시성(Concurrency)" 이라는 개념을 알고 있어야 한다. 이 개념은 운영체제의 프로세스, 스레드를 이해하는데 있어 가장 핵심 골자가 되는 녀석들이다.
2.2 CPU 의 작업 처리 방식
2.2.1 병렬성(Parallelism)
병렬성은 직관적으로 명령어를 메모리에서 뽑아 해석하고 실행하는 반도체 유닛이 여러개인 코어에 맞춰 여러개의 프로세스, 스레드를 돌려 병렬로 작업들을 동시에 수행하는 것을 의미한다.
듀얼코어, 쿼드코어, 옥타코어 등등 이런 명칭이 붙는 멀티코어 프로세스가 달린 컴퓨터에서 할 수 있는 방식이다.

2.2.2 동시성(Concurrent)
동시성은 둘 이상의 작업이 동시에 실행되는 것을 의미한다. 이 '동시' 라는 의미에서 병렬성과 동시성의 한글 의미가 헷갈릴 수 있다. Parallelism 이 물리적으로 정말로 동시에 실행되는 것이라고 하면, Concurrent 는 동시에 실행하는 것처럼 보이게 하는 것으로 이해하면 된다. (like 멀티태스킹)
즉, 1개의 코어가 있고 4개의 작업이 있다고 가정하면, 프로세스들을 계속 번갈아가면서 조금씩 처리함으로써 마치 프로그램이 동시에 실행되는 것 처럼 보이는 것이다. 이때, 프로세스들을 번갈아가면서 매우 빠르게 처리하기 때문에 컴퓨터를 모르는 사람들이 보면 마치 동시에 돌아가는 것처럼 보이게 된다.
단, 이 때 작업들을 번갈아가면서 실행할 때, 작업들을 아주 잘게 나누어 아주 조금씩만 작업을 수행하고 다음 작업으로 넘어가는 식으로 동작한다. 이렇게 하는 이유는 여러 작업을 동시에 처리하는 것처럼 보이게 만들어, 사용자에게 더 빠른 반응성을 제공하게 위해서다. 그리고 이렇게 진행중인 작업들을 바꾸는 것을 Context Switching 이라고 한다.

2.2.3 동시성이 필요한 이유
그런데 상식적으로 생각해보면 Concurrent 는 '동시에 돌아가는 것 처럼' 보이는 것이지, 정말 실제로 동시에 돌아가는 것이 아니기 때문에, 최종 작업이 걸리는 시간은 거의 차이가 없을 것이다. 병렬성은 실제 하드웨어적으로 각 코어에 프로세스를 나누어 실행하는 것이기 때문에 최종 작업에 걸리는 시간에 영향을 줄 것이다.
그렇다면 왜 이렇게 번거롭게 작업들을 스위칭하며 처리하는 것일까?
하드웨어적 한계 때문이라고 할 수 있다. CPU 발열 때문에 깡 클럭으로 성능을 올리기에는 한계가 있다. 때문에 코어의 성능을 올리는 대신 코어를 여러개 탑재하여 쿼도 코어, 옥타 코어 CPU 들을 출시하고 있다. 하지만 아무리 코어를 많이 넣어도 수십개의 코어를 넣을수는 없으니 결국 하드웨어적 제한이 걸리게 된다.
이 상황에서 수십수백개의 프로세스가 동시에 돌아가는 것처럼 보이기 위해서는 결국 동시성(논리적인 효율)이 필요한다.
4코어 8스레드 CPU 환경에서 현재 총 16개의 작업이 있다고 가정해보자. 그중 8개는 오래 걸리는 작업이고 나머지 8개는 짧은 시간을 필요로 하는 작업이라고 한다. 논리적인 코어는 8개이기 때문에 최대 8개까지 동시에 실행할 수 있을텐데, 만일 최악의 경우 8개의 오래 걸리는 작업이 먼저 동시에 처리되기 시작했다고 한다면, 이 경우 나머지 가벼운 작업은 처리하는데 짧은 시간이 걸리는 데에도 불구하고 현재 처리중인 8개의 작업이 다 끝날때까지 기다려야 할 것이다. 따라서 이러한 비효율적인 면을 극복하기 위해서 작업을 아주 잘게 나눠 번갈아가면서 처리하는 동시성 개념을 채택한 것이다.
따라서 최대 8개의 작업에 대해서 8개의 논리적인 스레드가 병렬적으로 아주 빠르게 동시적으로 작업하면서, 그보다 많은 수십개의 소프트웨어적 스레드가 있다면 적절히 병렬성과 동시성을 섞어 동시에 돌리게 된다.
3. 프로세스 & 스레드 생명 주기
프로세스와 스레드는 각각의 생명 주기를 가지고 있으며, 운영체제는 이러한 생명 주기를 관리하고, 프로세스와 스레드를 조정하여 시스템 자원을 효율적으로 사용할 수 있게 된다.
3.1 프로세스 스케줄링
프로세스 스케줄링은 운영체제에서 CPU 를 사용할 수 있는 프로세스를 선택하고, CPU 를 할당하는 작업을 말한다. 프로세스 스케줄링은 프로세스의 우선순위, 작업량 등을 고려하여 효율적으로 배치하여, 이를 통해 운영체제는 CPU 를 효율적으로 사용하며 시스템 전반적인 성능을 향상시킨다. 그래서 스케줄링은 멀티태스킹 작업을 만들어내는 데에 있어 핵심적인 역할을 한다.
스케줄링은 운영체제의 특징과 시스템 요구사항에 따라서 다양한 알고리즘 방식으로 동작되며, 대표적인 알고리즘으로 Multi Level Queue 방식이 있다.
3.2 프로세스 상태
프로세스의 상태는 프로세스가 실행되는 동안 변경되는 고유의 상태를 의미한다. 프로세스가 생성되어 실행하기까지 프로세스는 여러가지의 상태를 갖게 되고, 상태의 변화에 따라서 프로세스가 동작하는 것이다. 프로세스는 일반적으로 다음과 같은 5가지 상태를 갖는다.

3.2.1 프로세스 상태 전이
프로세스 상태 전이란 프로세스가 실행되는 동안 상태가 OS 에 의해 변경되는 것을 의미한다. 운영체제는 프로세스의 상태를 감시하고, 프로세스 상태를 기반으로 프로세스 스케줄링을 통해 프로세스를 관리하고 제어한다.
예를 들어, READY 상태에 있는 여러 프로세스 중 어떤 프로세스를 RUNNING 상태로 바꿀지, TERMINATED 상태에 있는 프로세스를 제거하고 READY 상태에 있는 다른 프로세스를 선택할지 스케줄링 알고리즘에 의해서 동작된다.
3.3 프로세스 컨텍스트 스위칭
컨텍스트 스위칭(Context Switching) 은 CPU 가 한 프로세스에서 다른 프로세스로 전환할 때 발생하는 일련의 과정을 의미한다. 위의 동시성(Concurrent) 파트에서 다뤘듯이 CPU 는 한번에 하나의 프로세스만 실행할 수 있으므로, 여러 개의 프로세스를 번갈아가면서 실행하여 CPU 활용률을 높이기 위해서 컨텍스트 스위칭이 필요한 것이다.
컨텍스트 스위칭을 좀더 구체적으로 말하자면, 동작 중인 프로세스가 대기를 하면서 해당 프로세스의 상태(Context : PCB) 를 보관하고, 대기하고 있던 다음 순서의 프로세스가 동작하면서 이전에 보관했던 프로세스의 상태를 복구하는 작업을 말한다. 이러한 컨텍스트 스위칭이 일어날 때 다음번 프로세스는 스케줄러가 결정하게 된다.
즉, 컨텍스트 스위칭을 하는 주체는 스케줄러이다.
3.3.1 PCB(Process Control Block)
PCB(프로세스 제어 블록) 는 운영체제에서 프로세스를 관리하기 위해 해당 프로세스의 상태 정보를 담고 있는 자료구조를 말한다.
프로세스를 컨텍스트 스위칭을 할 때 기존 프로세스의 상태를 어딘가에 저장해 둬야 다음에 똑같은 작업을 이어서 할 수 있을 것이고, 새로 해야 할 작업의 상태 또한 알아야 어디서부터 다시 작업을 시작할지 결정할 수 있다.
즉, PCB 는 프로세스 스케줄링을 위해 프로세스에 관한 모든 정보를 저장하는 임시 저장소 인 것이다.
프로세스가 생성되면 메모리에 해당 프로세스의 PCB 가 함께 생성되고, 종료 시 삭제된다.
따라서 운영체제는 PCB 에 담긴 프로세스 고유 정보를 통해 프로세스를 관리하며, 프로세스의 실행 상태를 파악하고, 우선순위를 조정하며, 스케줄링을 수행하고, 다른 프로세스와의 동기화를 제어한다.
3.3.2 Context Switching 과정
두 프로세스가 컨텍스트 스위칭을 그림으로 표현한 것이다.

CPU 는 P1 을 실행한다.
일정 시간이 지나서 인터럽트 혹은 시스템 콜이 발생한다.
현재 실행중인 P1 의 상태를 PCB1 에 저장한다.
다음으로 실행할 P2 를 선택한다.
P2 의 상태를 PCB2 에서 불러온다.
CPU 는 P2 를 실행한다.
일정 시간이 지나서 인터럽트 혹은 시스템 콜이 발생한다.
현재 실행중인 P2 의 상태를 PCB2 에 저장한다.
다시 P1 을 실행할 차례가 된다.
P1 의 상태를 PCB1 에서 불러온다.
CPU 는 P1 을 중간 지점부터 실행한다.
3.3.3 Context Switching Overhead
이러한 컨텍스트 스위칭 과정은 사용자로 하여금 빠른 반응성과 동시성을 제공하지만, 실행되는 프로세스의 변경 과정에서 PCB 값이 저장되고 불러오는 과정들을 수행하기 때문에, 시스템에 많은 부담을 주게 된다.
이를 컨텍스트 스위칭 오버헤드라고 부른다.
컨텍스트 스위칭 오버헤드는 대표적으로 다음과 같은 행위에 의해 발생한다.
PCB 저장 및 복원 비용
CPU 캐시 메모리 무효화에 따른 비용
프로세스 자체가 교체되는 것이니, CPU 캐시 메모리에 저장된 데이터가 무효화된다.
이 과정에서 메모리 접근 시간이 늘어나고, 성능 저하가 발생할 수 있다.
프로세스 스케줄링 비용
컨텍스트 스위칭은 꼭 프로세스 뿐만 아니라, 여러개의 스레드들 끼리도 스위칭이 일어난다. 보통 멀티 스레드라고 하면 여러개의 스레드가 동시에 돌아가니 프로그램 성능이 무조건 상승할 것이라 생각하고, 이는 정확하지 않다. 컨텍스트 스위칭 오버헤드라는 변수 때문에 스레드 교체 과정에서 과하게 오버헤드가 발생하면 오히려 멀티 스레드가 싱글 스레드보다 성능이 떨어지는 현상이 나타날 수 있기 때문이다.
3.4 스레드 스케줄링
프로세스 스케줄링과 마찬가지로, 스레드 스케줄링(Thread Scheduling) 은 운영체제에서 다중 스레드를 관리하며, CPU 를 사용할 수 있는 스레드를 선택하고, CPU 를 할당하는 작업을 말한다.
스레드의 우선순위, 실행시간, 입출령 요청 등의 정보를 고려하여 CPU 를 사용할 수 있는 스레드를 선택하는, 스레드 스케줄링 알고리즘은 프로세스 스케줄링 알고리즘과 유사하게 동작한다. 다양한 알고리즘이 있으며 대표적으로 Multi Level Queue 등이 있다.
다만 스레드 스케줄링은 프로세스 스케줄링과 다르게, 하나의 프로세스 내에서 다수의 스레드가 동작하는 형태이기 때문에, 스레드 간의 상호작용과 동기화 문제를 고려해야 한다는 차이점이 존재한다.
3.5 스레드 상태
프로세스와 마찬가지로, 스레드에도 상태가 있다. 다음과 같은 4가지 상태를 가진다.

3.6 스레드 컨텍스트 스위칭
스레드 컨텍스트 스위칭(Thread Context Switching) 은 멀티 스레딩 환경에서 스레드 간의 실행을 전환하는 기술이다. 프로세스 컨텍스트 스위칭과 다른 점은 스레드 컨텍스트 스위칭은 하나의 프로세스 내의 스레드들을 교환한다는 점이다.

3.6.1 TCB(Thread Control Block)
PCB 처럼, TCB(스레드 제어 블록) 는 각 스레드마다 운영 체제에서 유지하는 스레드에 대한 정보를 담고 있는 자료구조이다. 그림에서 볼 수 있듯이 TCB 는 PCB 안에 들어가 있다. 스레드가 프로세스 내에 위치한 것처럼 말이다.

역시 스레드의 상태 정보, 스레드 ID, 스레드 우선순위, 스케줄링 정보 등 다양한 정보를 저장한다. TCB 도 스레드가 생성될 때 운영 체제에 의해 생성되며, 스레드가 실행을 마치고 소멸될 때 함께 소멸된다.
또한 스레드 간의 자원 공유와 동기화도 TCB 를 사용하여 관리된다. 예를 들어 뮤텍스 나 세마포어와 같은 동기화 기법을 사용할 때, TCB 에서 해당 스레드의 뮤텍스나 세마포어 정보를 관리하고, 스레드가 해당 자원에 대한 접근 권한을 획득하거나 반납할 때 TCB 의 정보를 업데이트하게 된다.
4. 프로세스 컨텍스트 스위칭 vs 스레드 컨텍스트 스위칭
프로세스 컨텍스트 스위칭과 스레드 컨텍스트 스취칭은 모두 멀티태스킹 환경에서 여러 프로세스 또는 스레드를 동시에 실행하기 위한 기술이다. 그러나 두 기술은 몇가지 차이점이 있다.
1. TCB 가 PCB 보다 가볍다.
결론부터 말하자면, 스레드 컨텍스트 스위칭이 프로세스 컨텍스트 스위칭보다 더 빠르다.
위에서 프로세스와 스레드의 메모리 섹션에서 다뤘듯이 프로세스 내의 스레드들은 text, data, heap 영역 메모리를 공유하기 때문에, TCB 에는 stack 및 간단한 레지스터 포인터 정보만을 저장하기 때문에, PCB 보다 TCB 가 가벼워 더 빨리 읽고 쓸 수 있다.
2. 캐시 메모리 초기화 여부
CPU 캐시 메모리는 CPU 와 메인 메모리 사이에 위치하며, CPU 에서 한번 이상 읽어들인 메모리의 데이터를 저장하고 있다가, CPU 가 다시 그 메모리에 저장된 데이터를 요구할 때, 메인 메모리를 통하지 않고 곧바로 데이터를 전달해 주는 용도이다.
그런데 프로세스 컨텍스트 스위칭이 발생할 경우, 다른 프로세스의 실행으로 인해 CPU 가 새로운 명령어와 데이터를 로드해야 하기 때문에, CPU 캐시 메모리를 초기화하여야 한다. 이것이 프로세스 컨텍스트 스위칭에 부담이 되는 요소이다.
스레드 컨텍스트 스위칭일 경우, 프로세스 내 스레드 간에 스택과 레지스터 값 등 일부 컨텍스트 정보만 변경되므로 CPU 캐시 메모리는 초기화되지 않는다. 다만 스레드가 다른 CPU 코어에서 실행될 때는 해당 코어의 캐시 메모리에 스레드 컨텍스트 정보가 로드되어야 하므로 초기화 될 수 있다.
3. 자원 동기화 문제
스레드 컨텍스트 스위칭이 발생해 다른 스레드가 heap 영역의 공유 데이터에 접근할 때, 이전 스레드가 이미 공유 자원을 사용하고 있는 경우 동기화 문제가 발생할 수 있다.
예를 들어, 두 개의 스레드가 동시에 하나의 변수를 수정하려 할 때, 스레드 컨텍스트 스위칭이 발생하면 변수의 값을 잘못된 값으로 업데이트 할 수 있는 것이다. 이것을 스레드 간 경쟁조건(race condition) 이라고 한다.
프로세스는 기본적으로 독립된 공간이지만, IPC 와 같은 공유 자원을 사용하는 경우에 똑같이 경쟁 조건이 발생 할 수 있다.
예를 들어, 여러 개의 프로세스가 동시에 파일 시스템에 접근하여 파일을 수정하려 할 때, 컨텍스트 스위칭이 발생할 때 다른 프로세스가 그 파일에 접근할 수 있기 때문에, 파일 내용이 손상될 수 있다.
따라서 이들을 해결하기 위해서 각 상황에 적절한 공유 자원에 대한 동기화 메커니즘이 필요하다.
Last updated