프로세스

정의

프로세스에 대해서 알아보기 전에 프로그램의 정의를 짚고 넘어가야한다. 프로그램은 명령어의 집합체이고 HDD, SSD와 같은 보조기억장치에 저장되어 있다.

프로세스는 실행 중인 프로그램을 뜻한다. 유저가 프로그램을 실행하면, 프로그램은 메모리에 올라가게 되고 운영체제로부터 자원을 할당받게 된다. CPU는 프로세스로부터 명령어를 가져오고 이를 실행한다.

프로세스는 운영체제로부터 자원을 할당받는 단위이며, 대표적으로 CPU 실행 시간, 메모리, 파일, I/O 장치 등이 있다.

상태

프로세스는 5개의 상태 중 하나의 상태에 있게 된다.

  • New 프로세스가 생성 중인 상태이다.

  • Ready 프로세스가 CPU를 할당받기 위해 대기하는 상태이다.

  • Running 프로세스가 CPU를 할당받아 명령어들이 실행되고 있는 상태이다.

  • Waiting 프로세스가 I/O 완료 또는 별도의 이벤트 완료를 기다리는 상태이다.

  • Terminated 프로세스가 종료된 상태이다.

상태 전이

프로세스의 상태들이 연결되는 과정은 다음과 같다.

state-transition.png

  • New → Ready (Admit) : 프로세스가 생성되어 Ready 큐에 등록된다.
  • Ready → Running (Scheduler Dispatch) : 프로세스가 CPU를 할당받고 명령어를 실행한다.
  • Running → Ready (Preemption): 타이머 인터럽트 등으로 인해 CPU를 빼앗기고 Ready 큐에 등록된다.
  • Running → Waiting (I/O or Event Wait) : 프로세스가 I/O 작업 혹은 이벤트 완료를 기다리기 위해 Waiting 큐에 등록된다.
  • Waiting → Ready (I/O or Event Completion) : I/O 작업 혹은 이벤트가 완료되어 Ready 큐에 등록된다.
  • Running → Terminated (Exit) : 프로세스 실행이 완료됐다.

PCB

프로세스 제어 블록이라고 불리는 PCB는 프로세스 상태, 프로그램 카운터 그리고 CPU 레지스터등과 같은 프로세스와 관련된 정보를 가지고 있다.

pcb.png

생성

하나의 프로세스는 여러 개의 새로운 프로세스들을 생성할 수 있다. 이때 프로세스를 생성하는 주체를 부모 프로세스, 부모 프로세스에 의해 생성된 프로세스를 자식 프로세스라고 한다. 이렇게 생성된 프로세스들은 또 다른 프로세스를 생성하게 되고 결과적으로 프로세스의 트리를 형성하게 된다.

부모 프로세스와 자식 프로세스를 실행시키는 데 두 가지 방법이 존재한다.

proc-create.png

  1. 부모 프로세스와 자식 프로세스를 병행해서 실행한다.
  2. 부모 프로세스는 자식 프로세스의 작업이 종료될 때까지 대기한다.

메모리 주소 공간 측면에서는 두 가지 방법이 존재한다.

  1. 부모 프로세스와 자식 프로세스가 똑같은 프로그램과 데이터를 가진다.
  2. 자식 프로세스에는 부모 프로세스와 다른 새로운 프로그램을 가지고 있다.

fork() 시스템 콜을 통해 자식 프로세스를 생성했다고 가정하자. 자식 프로세스의 메모리는 부모 프로세스의 메모리 주소 공간을 복사하여 가진다. 자식 프로세스는 부모 프로세스와 동일한 작업을 수행하며, exec() 계열 시스템 콜을 호출하게 된다면 자식 프로세스의 메모리 주소 공간에는 새로운 프로그램이 적재되어 부모 프로세스와 다른 작업을 수행하게 된다.

종료

자식 프로세스가 실행을 마쳤지만, 부모 프로세스가 wait() 시스템 콜을 호출하지 않아 PCB가 회수되지 못한 채 남아 있는 프로세스를 좀비 프로세스라고 한다.

부모 프로세스가 wait() 시스템 콜을 통해 자식 프로세스를 기다리지 않고 먼저 종료하는 경우가 있다. 이때 자식 프로세스를 고아 프로세스라고 한다. UNIX에서는 고아 프로세스의 부모 프로세스를 init(pid = 1)으로 지정하고 고아 프로세스 자원을 회수한다.

스레드

정의

스레드는 CPU 이용의 기본 단위이며, 스레드 ID, 프로그램 카운터, 레지스터 집합 그리고 스택으로 구성된다. 하나의 프로세스 내에 존재하는 스레드들은 프로세스의 힙, 코드, 데이터 그리고 열린 파일 등 운영체제 자원들을 공유한다.

thread.png

장점

다중 스레딩으로 통해 얻을 수 있는 이점은 크게 4가지가 있다.

  • 응답성 단일 스레드 환경에서는 오래 걸리는 작업이 실행되는 동안 다른 작업이 모두 블로킹된다. 다중 스레드 환경에서는 무거운 작업을 별도의 스레드로 처리할 수 있어, 메인 스레드가 멈추지 않고 사용자 요청에 계속 응답할 수 있다. 예를 들어 파일 다운로드를 백그라운드 스레드로 수행하면, 사용자는 다운로드 중에도 다른 버튼을 자유롭게 조작할 수 있다.

  • 자원 공유 같은 프로세스 내의 스레드들은 코드, 데이터, 힙 영역을 공유하기 때문에 별도의 프로세스 간 통신 메커니즘 없이도 데이터를 주고받을 수 있다.

  • 경제성 스레드 컨텍스트 스위칭에서 발생하는 오버헤드가 프로세스 컨텍스트 스위치 오버헤드보다 저렴하다. 프로세스 컨텍스트 스위칭 과정에서는 CPU의 상태를 저장하는 등 많은 작업이 수행되지만, 스레드의 경우 프로세스의 자원을 공유하고 있기 때문에 컨텍스트를 저장하는 과정에서 비용이 많이 발생하지 않는다.

  • 규모 적응성 멀티 코어 환경에서 큰 이점을 발휘한다. 단일 스레드 프로세스는 코어가 여러 개여도 하나의 코어만 사용할 수 있지만, 멀티 스레드 프로세스는 스레드들을 여러 코어에 분산하여 동시에 실행할 수 있다. 이를 통해 병렬 처리가 가능해진다.

다중 스레드 모델

스레드에는 사용자 스레드와 커널 스레드가 존재한다. 사용자 스레드는 커널 위에서 지원되며 커널의 지원 없이 관리된다. 커널 스레드는 운영체제에 의해 직접 지원되고 관리된다. 사용자 스레드가 실제로 CPU에서 실행되려면 커널 스레드에 매핑되어야 한다. 이 매핑 방식에 따라 다대일, 일대일, 다대다 모델로 나뉜다.

  • 다대일 모델

one-to-many.png

스레드 관리가 사용자 수준 라이브러리에 의해 이루어진다. 모든 사용자 스레드가 하나의 커널 스레드에 매핑되므로, 한 스레드가 블로킹 시스템 콜을 호출하면 같은 프로세스의 다른 모든 스레드까지 함께 멈춘다. 또한 커널은 한 번에 하나의 스레드만 스케줄링할 수 있기 때문에, 다중 코어 환경에서도 병렬 실행이 불가능하다.

  • 일대일 모델

one-to-one.png

각 사용자 스레드 각각 하나의 커널 스레드 매핑된다. 한 스레드가 블로킹 시스템 콜을 호출해도 다른 스레드는 계속 실행될 수 있으며, 다중 코어 환경에서 여러 스레드를 병렬로 실행할 수 있다. 사용자 스레드를 생성할 때마다 커널 스레드도 함께 생성되어야하고, 많은 수의 커널 스레드가 시스템 성능에 부담을 줄 수 있다는 것이 단점이다.

  • 다대다 모델

many-to-many.png

다대다 모델은 여러 사용자 스레드를 그보다 적거나 같은 수의 커널 스레드에 매핑하는 모델이다. 사용자 스레드는 자유롭게 생성할 수 있으면서도, 여러 커널 스레드를 사용하기 때문에 다중 코어 환경에서 병렬 실행이 가능하다. 또한 한 스레드가 블로킹 시스템 콜을 호출해도 커널이 다른 커널 스레드를 통해 다른 사용자 스레드를 계속 실행할 수 있다.

다대일 모델의 병렬 실행 불가 문제와 일대일 모델의 커널 스레드 생성 오버헤드 문제를 동시에 해결했지만, 사용자 스레드와 커널 스레드 간 매핑을 동적으로 관리해야 하므로 구현이 복잡하다.

주소 공간 구조

프로그램이 메모리에 적재될 때, 주소 공간은 크게 네 영역으로 나뉜다.

many-to-many.png

  • Stack

함수가 실행되는 과정에서 발생하는 함수 파라미터, 함수 종료 후 리턴 주소 그리고 지역 변수를 포함하는 활성화 레코드가 저장된다. 활성화 레코드는 높은 주소에서 낮은 주소로 메모리를 할당받게 된다.

ex) int i;

  • Heap 프로그램이 실행되는 동안 동적으로 할당받는 영역이다. 동적으로 할당받게 되면 낮은 주소에서 높은 주소로 메모리를 할당받게 된다.

ex) values = (int )malloc(sizeof(int)5);

  • Data 프로그램의 초기화된 전역변수들이 올라가는 영역이다.

ex) global y = 15;

초기화되지 않은 전역변수는 BSS(Block Started bySymbol) 영역에 올라간다.

  • Text 컴파일된 기계어 명령어들이 올라가는 영역이다.

멀티 프로세스

정의

하나의 작업을 여러 개의 독립된 프로세스로 나누어 처리하는 방식이다. 각 프로세스는 자신만의 주소 공간을 가진다.

장점

  • 메모리가 분리되어 있어 한 프로세스의 오류나 충돌이 다른 프로세스에 영향을 미치지 않는다.
  • 프로세스 간 메모리 접근이 차단되어 한 프로세스가 다른 프로세스의 데이터를 침범할 수 없다.

단점

  • 프로세스 생성과 컨텍스트 스위칭 비용이 크다.
  • 프로세스 간 데이터를 주고받으려면 IPC 메커니즘이 필요하다.
  • 각 프로세스가 별도의 자원을 차지하므로 메모리 사용량이 많다.

멀티 스레드

정의

하나의 프로세스 안에서 여러 스레드를 생성하여 작업을 나누어 처리하는 방식이다.

장점

  • 스레드 생성과 컨텍스트 스위칭이 가볍다
  • 같은 주소 공간을 공유하므로 데이터 교환이 빠르고 단순하다.

단점

  • 같은 메모리를 공유하기 때문에 동시성 문제가 발생할 수 있다.
  • 하나의 스레드에서 발생한 오류가 프로세스 전체에 영향을 미칠 수 있다.

컨텍스트 스위칭

인터럽트 혹은 시스템 콜이 발생하여 Running 상태인 프로세스 A가 Ready 상태로 돌아가고 프로세스 B가 CPU를 할당받아 명령어를 실행중이라고 해보자. 프로세스 B도 작업이 완료되지 않으면 다시 Ready 상태로 돌아가게 되고, Ready 상태인 프로세스 A가 CPU를 할당받게 될 것이다.

이때, 프로세스 A에서 명령어를 어디까지 실행했는지 모르게 된다면 문제가 발생할 것이다. 이를 방지하기 위해 프로세스를 변경하기 전 CPU의 상태(컨텍스트)를 어딘가에 저장해야한다. 운영체제는 이 정보를 현재 점유 중인 프로세스의 PCB에 저장한다. 컨텍스트에는 프로그램 카운터, 레지스터 값 그리고 스택 포인터 등이 포함된다.

즉, 새로운 프로세스로 교체하기 전 CPU의 현재 상태를 저장하고 이후에 작업을 재개하기 위해 저장한 내용을 복구한다. 이러한 과정을 컨텍스트 스위칭이라고 한다.

컨텍스트 스위칭하는 과정에서 어떤 유저 프로세스의 작업을 하지 못하기 때문에 이 과정에서 손실(오버헤드)가 발생한다.

context-switching.png